001: /*
002: * Created on Oct 21, 2004
003: */
004: package net.kildenpedersen.reflect;
005:
006: import java.beans.IntrospectionException;
007: import java.beans.Introspector;
008: import java.beans.PropertyDescriptor;
009: import java.lang.reflect.AccessibleObject;
010: import java.lang.reflect.Array;
011: import java.lang.reflect.Constructor;
012: import java.lang.reflect.Field;
013: import java.lang.reflect.Member;
014: import java.lang.reflect.Method;
015: import java.lang.reflect.Modifier;
016: import java.util.Collection;
017: import java.util.HashSet;
018: import java.util.List;
019: import java.util.Map;
020: import java.util.Set;
021: import java.util.StringTokenizer;
022:
023: /**
024: * @author Nils Kilden-Pedersen
025: */
026: public final class Reflector {
027:
028: private static final String DOT = ".";
029:
030: private static Set getAccessibleMethods(final Class type,
031: final String name, int parmCount, boolean staticMethods) {
032: Method[] methods = type.getDeclaredMethods();
033: HashSet methodSet = new HashSet(methods.length);
034: for (int i = 0; i < methods.length; i++) {
035: if (methods[i].getName().equals(name)
036: && methods[i].getParameterTypes().length == parmCount
037: && staticMethods == Modifier.isStatic(methods[i]
038: .getModifiers())) {
039: setAccessible(methods[i]);
040: methodSet.add(methods[i]);
041: }
042: }
043: return methodSet;
044: }
045:
046: /**
047: * @param target
048: * @param propertyName
049: * @param methodType "set" or "get"
050: * @param propertyType
051: * @return method
052: */
053: private static Method getAccessiblePropertyMethod(
054: final Class target, String propertyName, String methodType,
055: Class propertyType) {
056:
057: Method method = null;
058: String methodName = methodType
059: + propertyName.substring(0, 1).toUpperCase()
060: + (propertyName.length() > 1 ? propertyName
061: .substring(1) : "");
062: Method[] methods = target.getDeclaredMethods();
063: for (int i = 0; i < methods.length; i++) {
064: if (methods[i].getParameterTypes().length == 1
065: && methods[i].getName().equals(methodName)) {
066: method = methods[i];
067: if (propertyType != null
068: && propertyType.equals(method
069: .getParameterTypes()[0])) {
070: break;
071: }
072: }
073: }
074: setAccessible(method);
075: return method;
076: }
077:
078: /**
079: * Get named node value.
080: * @param nodeObject
081: * @param nodeName
082: * @return node value
083: */
084: private static Object getNodeValue(Object nodeObject,
085: final String nodeName) {
086:
087: String parmName = nodeName;
088: if (parmName.endsWith("]")) {
089: int pos = parmName.lastIndexOf('[');
090: if (pos + 2 == parmName.length()) {
091: throw new IllegalArgumentException("Index empty: "
092: + nodeName);
093: }
094: int idx = Integer.parseInt(parmName.substring(pos + 1,
095: parmName.length() - 1));
096: parmName = parmName.substring(0, pos);
097: nodeObject = getNodeValue(nodeObject, parmName);
098: try {
099: return getIndexObject(nodeObject, idx);
100: } catch (IllegalArgumentException e) {
101: String msg = parmName
102: + "[] is not array or java.util.List";
103: throw new IllegalArgumentException(msg);
104: }
105: }
106:
107: // Check if it's a Map
108: if (nodeObject instanceof Map) {
109: Map parameterMap = (Map) nodeObject;
110: // Must check for key. Value can be null.
111: if (!parameterMap.containsKey(parmName)) {
112: throw newException(parmName, nodeName);
113: }
114: return parameterMap.get(parmName);
115: }
116:
117: Class parmClass = nodeObject.getClass();
118: Method readMethod;
119:
120: if (parmName.endsWith("()")) {
121: parmName = parmName.substring(0, parmName.length() - 2);
122: readMethod = Reflector.getAccessibleMethod(parmClass,
123: parmName, null);
124: if (readMethod == null) {
125: String msg = "No " + parmName + " method found on "
126: + parmClass.toString();
127: throw new IllegalArgumentException(msg);
128: }
129: } else {
130: readMethod = Reflector.getAccessibleReadProperty(parmClass,
131: parmName);
132: }
133:
134: if (readMethod != null) {
135: try {
136: return readMethod.invoke(nodeObject, null);
137: } catch (Exception e) {
138: throw new RuntimeException(e);
139: }
140: }
141:
142: // If not method, try field
143: Field field = Reflector.getAccessibleField(parmClass, parmName);
144: if (field != null) {
145: try {
146: return field.get(nodeObject);
147: } catch (Exception e) {
148: throw new RuntimeException(e);
149: }
150: }
151:
152: // If all else fails
153: throw newException(parmName, nodeName);
154: }
155:
156: private static PropertyDescriptor getProperty(Class target,
157: String name) {
158: PropertyDescriptor[] properties;
159: try {
160: properties = Introspector.getBeanInfo(target, Object.class)
161: .getPropertyDescriptors();
162: } catch (IntrospectionException e) {
163: throw new RuntimeException(e);
164: }
165: for (int i = 0; i < properties.length; i++) {
166: if (name.equals(properties[i].getName())) {
167: return properties[i];
168: }
169: }
170: return null;
171: }
172:
173: private static IllegalArgumentException newException(String node,
174: String tree) {
175: String ending = (node.equals(tree) ? "'" : "' in '" + tree
176: + "'");
177: return new IllegalArgumentException("Cannot find '" + node
178: + ending);
179: }
180:
181: private static void setAccessible(AccessibleObject object) {
182: if (object != null && !object.isAccessible()) {
183: object.setAccessible(true);
184: }
185: }
186:
187: /**
188: * @param type
189: * @param parmTypes
190: * @return accessible constructor
191: */
192: public static Constructor getAccessibleConstructor(
193: final Class type, Class[] parmTypes) {
194: Constructor constructor;
195: try {
196: constructor = type.getDeclaredConstructor(parmTypes);
197: } catch (NoSuchMethodException e) {
198: return null;
199: }
200: setAccessible(constructor);
201: return constructor;
202: }
203:
204: /**
205: * Get a Set of accessible constructors with the given parameter count.
206: * @param type
207: * @param parmCount
208: * @return constructor array
209: */
210: public static Set getAccessibleConstructors(final Class type,
211: int parmCount) {
212: Constructor[] constructors = type.getDeclaredConstructors();
213: HashSet constructorSet = new HashSet(constructors.length);
214: for (int i = 0; i < constructors.length; i++) {
215: if (constructors[i].getParameterTypes().length == parmCount) {
216: setAccessible(constructors[i]);
217: constructorSet.add(constructors[i]);
218: }
219: }
220: return constructorSet;
221: }
222:
223: /**
224: * @param type
225: * @param name
226: * @return field or null.
227: */
228: public static Field getAccessibleField(final Class type,
229: final String name) {
230: Field field;
231: try {
232: field = type.getDeclaredField(name);
233: } catch (NoSuchFieldException e) {
234: return null;
235: }
236: setAccessible(field);
237: return field;
238: }
239:
240: /**
241: * Return set of accessible member methods with the given name and parameter count.
242: * @param type class type
243: * @param name method name
244: * @param parmCount parameter count
245: * @return Set of {@linkplain Method}s
246: */
247: public static Set getAccessibleMemberMethods(final Class type,
248: final String name, final int parmCount) {
249: return getAccessibleMethods(type, name, parmCount, false);
250: }
251:
252: /**
253: * @param type
254: * @param name
255: * @param parmTypes
256: * @return accessible method
257: */
258: public static Method getAccessibleMethod(final Class type,
259: final String name, final Class[] parmTypes) {
260: Method method;
261: try {
262: method = type.getDeclaredMethod(name, parmTypes);
263: } catch (NoSuchMethodException e) {
264: return null;
265: }
266: setAccessible(method);
267: return method;
268: }
269:
270: /**
271: * @param target
272: * @param name property name
273: * @return accessible read property
274: */
275: public static Method getAccessibleReadProperty(final Class target,
276: String name) {
277: Method readMethod = null;
278: Class propertyType = null;
279: PropertyDescriptor property = getProperty(target, name);
280: if (property != null) {
281: readMethod = property.getReadMethod();
282: if (readMethod != null) {
283: return readMethod;
284: }
285: propertyType = property.getPropertyType();
286: }
287:
288: readMethod = getAccessiblePropertyMethod(target, name, "get",
289: propertyType);
290: if (readMethod == null) {
291: readMethod = getAccessiblePropertyMethod(target, name,
292: "is", propertyType);
293: }
294: return readMethod;
295: }
296:
297: /**
298: * Return set of accessible static methods with the given name and parameter count.
299: * @param type class type
300: * @param name method name
301: * @param parmCount parameter count
302: * @return Set of {@linkplain Method}s
303:
304: */
305: public static Set getAccessibleStaticMethods(final Class type,
306: final String name, int parmCount) {
307: return getAccessibleMethods(type, name, parmCount, true);
308: }
309:
310: /**
311: * @param target target
312: * @param name property name
313: * @return accessible read property
314: */
315: public static Method getAccessibleWriteProperty(final Class target,
316: String name) {
317: Method writeMethod = null;
318: Class propertyType = null;
319: PropertyDescriptor property = getProperty(target, name);
320: if (property != null) {
321: writeMethod = property.getWriteMethod();
322: if (writeMethod != null) {
323: return writeMethod;
324: }
325: propertyType = property.getPropertyType();
326: }
327:
328: writeMethod = getAccessiblePropertyMethod(target, name, "set",
329: propertyType);
330: return writeMethod;
331: }
332:
333: /**
334: * Get class name, corrected for arrays
335: * @param type
336: * @return class name string
337: */
338: public static String getClassName(Class type) {
339: return type.isArray() ? type.getComponentType().getName()
340: + "[]" : type.getName();
341: }
342:
343: /**
344: * Return object at the given index.
345: * @param collection Array or List of objects
346: * @param index Index value to return
347: * @return indexed value
348: * @throws IllegalArgumentException if object is not array or collection
349: */
350: public static Object getIndexObject(Object collection, int index) {
351: if (collection.getClass().isArray()) {
352: return Array.get(collection, index);
353: }
354: if (collection instanceof List) {
355: return ((List) collection).get(index);
356: }
357: throw new IllegalArgumentException("Object not array or List.");
358: }
359:
360: /**
361: * Get leaf value on a dot separated tree.
362: * @param rootObject
363: * @param nodeTree
364: * @return object value
365: */
366: public static Object getLeafValue(final Object rootObject,
367: final String nodeTree) {
368:
369: if (rootObject == null) {
370: return null;
371: }
372:
373: if (nodeTree.indexOf(DOT) < 0) {
374: return getNodeValue(rootObject, nodeTree);
375: }
376:
377: Object node = rootObject;
378: StringTokenizer tokens = new StringTokenizer(nodeTree, DOT);
379: while (tokens.hasMoreTokens() && node != null) {
380: node = Reflector.getNodeValue(node, tokens.nextToken());
381: }
382: return node;
383:
384: }
385:
386: /**
387: * Get the parameter types for the member.
388: * @param member
389: * @return array of parameter types
390: */
391: public static Class[] getParameterTypes(Member member) {
392: /*
393: * Damn Sun for not having a supertype or
394: * interface for both constructors and methods
395: * with a getParameterTypes() method.
396: */
397: Class memberType = member.getClass();
398: if (memberType == Constructor.class) {
399: return ((Constructor) member).getParameterTypes();
400: } else if (memberType == Method.class) {
401: return ((Method) member).getParameterTypes();
402: }
403: throw new IllegalArgumentException(
404: "Member type is neither constructor nor method. This is not your fault, but the idiot who programmed this.");
405: }
406:
407: /**
408: * Get size of array or collection.
409: * @param collection Array or collection of objects
410: * @return size of collection
411: */
412: public static int getSize(Object collection) {
413: if (collection.getClass().isArray()) {
414: return Array.getLength(collection);
415: }
416: if (collection instanceof Collection) {
417: return ((Collection) collection).size();
418: }
419: throw new IllegalArgumentException(
420: "Object not array or Collection.");
421: }
422:
423: /**
424: * Set leaf value on a dot separated tree.
425: * @param rootObject Root object
426: * @param nodeTree Node tree
427: * @param value Value to set
428: */
429: public static void setLeafValue(final Object rootObject,
430: final String nodeTree, final Object value) {
431:
432: Object settableObject;
433: String settableName;
434: int lastDot = nodeTree.lastIndexOf(DOT);
435: if (lastDot > 0) {
436: settableObject = getLeafValue(rootObject, nodeTree
437: .substring(0, lastDot));
438: settableName = nodeTree.substring(lastDot + 1);
439: } else {
440: settableObject = rootObject;
441: settableName = nodeTree;
442: }
443:
444: if (settableName.endsWith("]")) {
445: int pos = settableName.lastIndexOf('[');
446: int idx = Integer.parseInt(settableName.substring(pos + 1,
447: settableName.length() - 1));
448: settableName = settableName.substring(0, pos);
449: settableObject = getNodeValue(settableObject, settableName);
450: if (settableObject.getClass().isArray()) {
451: Array.set(settableObject, idx, value);
452: return;
453: }
454: if (settableObject instanceof List) {
455: List list = (List) settableObject;
456: list.set(idx, value);
457: return;
458: }
459: String msg = settableName
460: + "[] is not array or java.util.List";
461: throw new IllegalArgumentException(msg);
462: }
463:
464: // Check if it's a Map
465: if (settableObject instanceof Map) {
466: Map map = (Map) settableObject;
467: map.put(settableName, value);
468: return;
469: }
470:
471: Class settableClass = settableObject.getClass();
472: Method writeMethod;
473:
474: if (settableName.endsWith("(*)")) {
475: settableName = settableName.substring(0, settableName
476: .length() - 3);
477: writeMethod = Reflector.getAccessibleMethod(settableClass,
478: settableName, new Class[] { value.getClass() });
479: if (writeMethod == null) {
480: String msg = "No '" + settableName
481: + "' method found on "
482: + settableClass.toString();
483: throw new IllegalArgumentException(msg);
484: }
485: } else {
486: writeMethod = Reflector.getAccessibleWriteProperty(
487: settableClass, settableName);
488: }
489:
490: if (writeMethod != null) {
491: try {
492: writeMethod.invoke(settableObject,
493: new Object[] { value });
494: return;
495: } catch (Exception e) {
496: throw new RuntimeException(e);
497: }
498: }
499:
500: // If not method, try field
501: Field field = Reflector.getAccessibleField(settableClass,
502: settableName);
503: if (field != null) {
504: try {
505: field.set(settableObject, value);
506: return;
507: } catch (Exception e) {
508: String msg;
509: Class fieldType = field.getType();
510: if (fieldType.isPrimitive() && value == null) {
511: msg = "Cannot set primitive field '"
512: + field.getName() + "' with null value.";
513: } else if (value != null
514: && !fieldType
515: .isAssignableFrom(value.getClass())) {
516: msg = "Cannot assign type "
517: + value.getClass().getName()
518: + " to field '" + field.getName()
519: + "' of type " + fieldType.getName();
520: } else {
521: msg = e.getMessage();
522: }
523: throw new RuntimeException(msg, e);
524: }
525: }
526:
527: // If all else fails
528: throw newException(settableName, nodeTree);
529: }
530:
531: /**
532: * Private constructor.
533: */
534: private Reflector() {
535: return;
536: }
537: }
|