1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.onemind.commons.java.lang.reflect;
22
23 import java.lang.reflect.*;
24 import java.util.*;
25 import java.util.logging.Level;
26 import java.util.logging.Logger;
27 import org.onemind.commons.java.datastructure.Scoreable;
28 import org.onemind.commons.java.util.StringUtils;
29 /***
30 * Reflection related utilities
31 * @author TiongHiang Lee (thlee@onemindsoft.org)
32 * @version $Id: ReflectUtils.java,v 1.12 2006/08/01 23:57:03 thlee Exp $ $Name: $
33 */
34 public final class ReflectUtils
35 {
36
37 /*** the logger * */
38 private static final Logger _logger = Logger.getLogger(ReflectUtils.class.getName());
39
40 /*** the lookup cache * */
41 private static final Map _classCache = new HashMap();
42
43 /*** the method cache **/
44 private static final Map _methodCache = new HashMap();
45
46 /*** class caching setting **/
47 private static boolean _classCaching = true;
48
49 /*** method caching setting **/
50 private static boolean _methodCaching = true;
51
52 /*** keep a primitive class and their compatible types **/
53 private static final Map WIDENABLES = new HashMap();
54 static
55 {
56 Object[][] primitiveWideningMap = new Object[][]{
57 {Boolean.TYPE, new Object[]{Boolean.TYPE, Boolean.class}},
58 {Boolean.class, new Object[]{Boolean.TYPE, Boolean.class}},
59 {Byte.TYPE, new Object[]{Byte.TYPE, Byte.class, Short.class, Short.TYPE, Integer.class, Integer.TYPE, Long.class, Long.TYPE, Float.class, Float.TYPE, Double.class, Double.TYPE}},
60 {Byte.class, new Object[]{Byte.TYPE, Byte.class, Short.class, Short.TYPE, Integer.class, Integer.TYPE, Long.class, Long.TYPE, Float.class, Float.TYPE, Double.class, Double.TYPE}},
61 {Short.TYPE, new Object[]{Short.TYPE, Short.class, Integer.class, Integer.TYPE, Long.class, Long.TYPE, Float.class, Float.TYPE, Double.class, Double.TYPE}},
62 {Short.class, new Object[]{Short.TYPE, Short.class, Integer.class, Integer.TYPE, Long.class, Long.TYPE, Float.class, Float.TYPE, Double.class, Double.TYPE}},
63 {Character.TYPE, new Object[]{Character.TYPE, Character.class, Integer.class, Integer.TYPE, Long.class, Long.TYPE, Float.class, Float.TYPE, Double.class, Double.TYPE}},
64 {Character.class, new Object[]{Character.TYPE, Character.class, Integer.class, Integer.TYPE, Long.class, Long.TYPE, Float.class, Float.TYPE, Double.class, Double.TYPE}},
65 {Integer.TYPE, new Object[]{Integer.TYPE, Integer.class, Long.class, Long.TYPE, Float.class, Float.TYPE, Double.class, Double.TYPE}},
66 {Integer.class, new Object[]{Integer.TYPE, Integer.class, Long.class, Long.TYPE, Float.class, Float.TYPE, Double.class, Double.TYPE}},
67 {Long.TYPE, new Object[]{Long.TYPE, Long.class, Float.class, Float.TYPE, Double.class, Double.TYPE}},
68 {Long.class, new Object[]{Long.TYPE, Long.class, Float.class, Float.TYPE, Double.class, Double.TYPE}},
69 {Float.TYPE, new Object[]{Float.TYPE, Float.class, Double.class, Double.TYPE}},
70 {Float.class, new Object[]{Float.TYPE, Float.class, Double.class, Double.TYPE}},
71 {Double.TYPE, new Object[]{Double.TYPE, Double.class}},
72 {Double.class, new Object[]{Double.TYPE, Double.class}}};
73
74 for (int i = 0; i < primitiveWideningMap.length; i++)
75 {
76 WIDENABLES.put(primitiveWideningMap[i][0], Arrays.asList((Object[]) primitiveWideningMap[i][1]));
77 }
78 }
79
80 /***
81 * The method key
82 */
83 private static class MethodKey
84 {
85
86 /*** the name **/
87 private String _name;
88
89 /*** the class **/
90 private Class _clazz;
91
92 /*** the arguments **/
93 private Class[] _args;
94
95 /*** the hash code **/
96 private int _hashCode;
97
98 /***
99 * Constructor
100 * @param clazz the class
101 * @param name the name
102 * @param args the arguments
103 */
104 public MethodKey(Class clazz, String name, Class[] args)
105 {
106 _clazz = clazz;
107 _name = name;
108 _args = args;
109 _hashCode = _clazz.hashCode() + _name.hashCode();
110 }
111
112 /***
113 * {@inheritDoc}
114 */
115 public int hashCode()
116 {
117 return _hashCode;
118 }
119
120 /***
121 * {@inheritDoc}
122 */
123 public boolean equals(Object obj)
124 {
125 if (obj instanceof MethodKey)
126 {
127 MethodKey key = (MethodKey) obj;
128 return _clazz.equals(key._clazz) && _name.equals(key._name) && Arrays.equals(_args, key._args);
129 } else
130 {
131 throw new IllegalArgumentException("Cannot compare " + this + " to " + obj);
132 }
133 }
134 }
135
136 /***
137 * {@inheritDoc}
138 */
139 private ReflectUtils()
140 {
141 };
142
143 /***
144 * Construct the argument type class array from a list of arg objects
145 * @param args the arguments
146 * @return the class array
147 * @todo decide what to do with null value arguments and what to do with isCompatibleCheck
148 * TODO decide what to do with null value arguments and what to do with isCompatibleCheck
149 */
150 public static final Class[] toArgTypes(Object[] args)
151 {
152 if (args == null)
153 {
154 args = new Object[0];
155 }
156 Class[] argTypes = new Class[args.length];
157 for (int i = 0; i < args.length; i++)
158 {
159 if (args[i] != null)
160 {
161 argTypes[i] = args[i].getClass();
162 }
163 }
164 return argTypes;
165 }
166
167 /***
168 * Get the class
169 * @param name the name of the class
170 * @return the class
171 * @throws ClassNotFoundException if the class cannot be found
172 */
173 public static final Class getClass(String name) throws ClassNotFoundException
174 {
175 if (_classCaching && _classCache.containsKey(name))
176 {
177 Class c = (Class) _classCache.get(name);
178 if (c == null)
179 {
180 throw new ClassNotFoundException("Class " + name + " not found");
181 } else
182 {
183 return c;
184 }
185 } else
186 {
187 Class c = null;
188 try
189 {
190 c = Class.forName(name);
191 return c;
192 } finally
193 {
194 if (_classCaching)
195 {
196 _classCache.put(name, c);
197 }
198 }
199 }
200 }
201
202 /***
203 * Get the constructor of the type given the arguments to the constructor
204 * @param type the type
205 * @param args the arguments
206 * @return the constructor
207 * @throws NoSuchMethodException if the constructor cannot be found
208 */
209 public static final Constructor getConstructor(Class type, Object[] args) throws NoSuchMethodException
210 {
211 if (args == null)
212 {
213 args = new Object[0];
214 }
215 Class[] argTypes = toArgTypes(args);
216 Constructor c = null;
217 if (_methodCaching)
218 {
219 c = (Constructor) _methodCache.get(new MethodKey(type, "$Constructor", argTypes));
220 if (c != null)
221 {
222 return c;
223 }
224 }
225 try
226 {
227
228 if (_logger.isLoggable(Level.FINEST))
229 {
230 _logger.finest("Looking for constructor for " + type.getName() + "(" + StringUtils.concat(argTypes, ",") + ")");
231 }
232 c = type.getConstructor(argTypes);
233 } catch (NoSuchMethodException e)
234 {
235 c = searchConstructor(type, argTypes);
236 }
237 if (c == null)
238 {
239 throw new NoSuchMethodException("Constructor not found for class " + toMethodString(type.getName(), args));
240 } else if (_methodCaching)
241 {
242 _methodCache.put(new MethodKey(type, "$Constructor", argTypes), c);
243 }
244 return c;
245 }
246
247 /***
248 * To the method representation string e.g. toString()
249 * @param methodName the method
250 * @param args the arguments
251 * @return the method representation string
252 */
253 public static final String toMethodString(String methodName, Object[] args)
254 {
255 StringBuffer sb = new StringBuffer(methodName);
256 sb.append("(");
257 if (args != null)
258 {
259 sb.append(StringUtils.concat(args, ","));
260 }
261 sb.append(")");
262 return sb.toString();
263 }
264
265 /***
266 * Search for a particular constructor based on arg types classes
267 * @param type the type
268 * @param argTypes the argument types
269 * @return the constructor
270 */
271 public static final Constructor searchConstructor(Class type, Class[] argTypes)
272 {
273 if (_logger.isLoggable(Level.FINEST))
274 {
275 _logger.finest("Searching for constructor for " + type.getName());
276 }
277 Constructor[] constructors = type.getConstructors();
278 TreeSet scoreboard = new TreeSet();
279 for (int i = 0; i < constructors.length; i++)
280 {
281 Class[] types = constructors[i].getParameterTypes();
282 if (_logger.isLoggable(Level.FINEST))
283 {
284 _logger.finest("trying arg types " + StringUtils.concat(types, ","));
285 }
286 int score = computeCompatibalityScore(types, argTypes);
287 if (score > 0)
288 {
289 scoreboard.add(new Scoreable(score, constructors[i]));
290 }
291 }
292 if (scoreboard.size() > 0)
293 {
294 return (Constructor) ((Scoreable) scoreboard.last()).getObject();
295 } else
296 {
297 return null;
298 }
299 }
300
301 /***
302 * Return whether the argument objects is compatible with the argument types specification
303 * @param types the argument types
304 * @param args the arguments
305 * @return true if compatible
306 */
307 public static final boolean isCompatible(Class[] types, Object[] args)
308 {
309 return computeCompatibalityScore(types, toArgTypes(args)) > 0;
310 }
311
312 public static final boolean isCompatible(Class[] types, Class[] argTypes)
313 {
314 return computeCompatibalityScore(types, argTypes) > 0;
315 }
316
317 /***
318 * Return whether the types of arguments is compatible with the argument type spec of a method
319 * @param methodTypes the argument type spec of a method
320 * @param argTypes the argument type
321 * @return true if compatible
322 */
323 public static final int computeCompatibalityScore(Class[] methodTypes, Class[] argTypes)
324 {
325 int score = 0;
326 if ((methodTypes == null) || (methodTypes.length == 0))
327 {
328 if ((argTypes == null) || (argTypes.length == 0))
329 {
330 score = 1;
331 }
332 } else if (argTypes != null && methodTypes.length == argTypes.length)
333 {
334 for (int i = 0; i < methodTypes.length; i++)
335 {
336 if (_logger.isLoggable(Level.FINEST))
337 {
338 _logger.finest("Comparing " + methodTypes[i] + " to " + argTypes[i]);
339 }
340 if (methodTypes[i] == argTypes[i])
341 {
342 score += 2;
343 }else if (argTypes[i] == null)
344 {
345 if (methodTypes[i].isPrimitive()){
346 score = 0;
347 break;
348 } else {
349 score += 1;
350 }
351 } else if (WIDENABLES.containsKey(argTypes[i]))
352 {
353
354 int thisScore = computeWideningScore(methodTypes[i], argTypes[i]);
355 if (thisScore == 0)
356 {
357 score = 0;
358 break;
359 } else
360 {
361 score += thisScore;
362 }
363 } else if (methodTypes[i].isAssignableFrom(argTypes[i]))
364 {
365 score += 1;
366 } else {
367 score = 0;
368 break;
369 }
370 }
371 }
372 return score;
373 }
374
375 /***
376 * Create a new instance of the class type with the arguments to constructor
377 * @param type the type
378 * @param args the argument
379 * @return the new instance
380 * @throws IllegalAccessException if there's access problem
381 * @throws InstantiationException if there's instantiation problem
382 * @throws InvocationTargetException if there's target exception
383 * @throws NoSuchMethodException if there's no such constructor
384 */
385 public static final Object newInstance(Class type, Object[] args) throws IllegalAccessException, InstantiationException,
386 InvocationTargetException, NoSuchMethodException
387 {
388 if (args == null)
389 {
390 args = new Object[0];
391 }
392 Constructor c = getConstructor(type, args);
393 if (c != null)
394 {
395 return c.newInstance(args);
396 } else
397 {
398 throw new NoSuchMethodException("Constructor not found for " + type);
399 }
400 }
401
402 /***
403 * Invoke a named method on the object using the arguments
404 * @param o the object
405 * @param methodName the name of the method
406 * @param args the arguments
407 * @return the object return by the invocation
408 * @throws NoSuchMethodException if there's no such method
409 * @throws IllegalAccessException if there's access problem
410 * @throws InvocationTargetException if there's target problem
411 * @todo decide if is necessary to check for declaring class before invoke
412 */
413 public static final Object invoke(Object o, String methodName, Object[] args) throws NoSuchMethodException,
414 IllegalAccessException, InvocationTargetException
415 {
416 if (args == null)
417 {
418 args = new Object[0];
419 }
420 Method m = null;
421 if (o instanceof Class)
422 {
423 try
424 {
425
426 m = getMethod((Class) o, methodName, args);
427 } catch (NoSuchMethodException e)
428 {
429
430 m = getMethod(o.getClass(), methodName, args);
431 }
432 } else
433 {
434 m = getMethod(o.getClass(), methodName, args);
435 }
436 if (m != null)
437 {
438 if (_logger.isLoggable(Level.FINEST))
439 {
440 _logger.finest("Invoking " + m + " on " + o);
441 }
442 return m.invoke(o, args);
443 } else
444 {
445 throw new NoSuchMethodException("There's no method " + toMethodString(methodName, args) + " for " + m);
446 }
447 }
448
449 /***
450 * Resolve the method from the interfaces
451 * @param c the class
452 * @param methodName the method
453 * @param argTypes the arg types
454 * @return the method or null
455 * @todo decide if this method is needed
456 */
457 public static final Method getInterfaceMethod(Class[] c, String methodName, Class[] argTypes)
458 {
459
460 return null;
461 }
462
463 /***
464 * Get a named method of class type with the argument type compatible with the argument passed in.
465 *
466 * @param type the class
467 * @param methodName the method name
468 * @param args the arguments
469 * @return the method
470 * @throws NoSuchMethodException if the method cannot be found
471 */
472 public static final Method getMethod(Class type, String methodName, Object[] args) throws NoSuchMethodException
473 {
474 Method m = null;
475 if (_logger.isLoggable(Level.FINEST))
476 {
477 _logger.finest("Finding method " + toMethodString(methodName, args) + " of " + type);
478 }
479 if (args == null)
480 {
481 args = new Object[0];
482 }
483 Class[] argTypes = toArgTypes(args);
484 return getMethod(type, methodName, argTypes);
485 }
486
487 /***
488 * Get a named method of class type with the argument type compatible with the argument passed in.
489 *
490 * @param type the class
491 * @param methodName the method name
492 * @param args the arguments
493 * @return the method
494 * @throws NoSuchMethodException if the method cannot be found
495 */
496 public static final Method getMethod(Class type, String methodName, Class[] argTypes) throws NoSuchMethodException
497 {
498 Method m;
499 if (_methodCaching)
500 {
501 m = (Method) _methodCache.get(new MethodKey(type, methodName, argTypes));
502 if (m != null)
503 {
504 return m;
505 }
506 }
507 try
508 {
509
510 m = type.getMethod(methodName, argTypes);
511 if (_logger.isLoggable(Level.FINEST))
512 {
513 _logger.finest("Found using reflection");
514 }
515 } catch (NoSuchMethodException nme)
516 {
517 if (_logger.isLoggable(Level.FINEST))
518 {
519 _logger.finest("Failed using reflection: " + nme.getMessage() + ". Search for method.");
520 }
521 m = searchMethod(type, methodName, argTypes);
522 }
523 if (m != null)
524 {
525 if (_methodCaching)
526 {
527 _methodCache.put(new MethodKey(type, methodName, argTypes), m);
528 }
529 if (!m.isAccessible())
530 {
531 m.setAccessible(true);
532 }
533 } else
534 {
535 throw new NoSuchMethodException("Method " + type.getName() + "." + toMethodString(methodName, argTypes) + " not found.");
536 }
537 return m;
538 }
539
540 /***
541 * Search a named method of class type through the class's hierachy
542 * @param type the class
543 * @param methodName the method name
544 * @param argTypes the argument types
545 * @return the method
546 */
547 private static final Method searchMethod(Class type, String methodName, Class[] argTypes)
548 {
549 TreeSet scoreboard = new TreeSet();
550 Method[] methods = type.getMethods();
551 for (int i = 0; i < methods.length; i++)
552 {
553 Method m = methods[i];
554 if (_logger.isLoggable(Level.FINEST))
555 {
556 _logger.finest("Checking compatibility with " + m);
557 }
558 if (m.getName().equals(methodName))
559 {
560 int score = computeCompatibalityScore(m.getParameterTypes(), argTypes);
561 if (score > 0)
562 {
563 scoreboard.add(new Scoreable(score, methods[i]));
564 }
565 }
566 }
567 if (scoreboard.size() > 0)
568 {
569 return (Method) ((Scoreable) scoreboard.last()).getObject();
570 }
571 return null;
572 }
573
574 /***
575 * Set the classCaching
576 * @param caching true to turn on class caching
577 */
578 protected static final void setClassCaching(boolean caching)
579 {
580 _classCaching = caching;
581 }
582
583 /***
584 * Set the _methodCaching
585 * @param caching true to turn on method caching
586 */
587 protected static final void setMethodCaching(boolean caching)
588 {
589 _methodCaching = caching;
590 }
591
592 /***
593 * Return whether a given object is a primitive or compatible (through unwrapping and widening) instance of primitiveClass
594 * @param primitiveClass the primitive class
595 * @param obj the object
596 * @return true if is instance
597 */
598 public static final boolean isPrimitiveInstance(Class primitiveClass, Object obj)
599 {
600 if (!primitiveClass.isPrimitive())
601 {
602 throw new IllegalArgumentException(primitiveClass + " is not primitive type ");
603 }
604 if (obj == null)
605 {
606 return false;
607 } else
608 {
609 return isPrimitiveCompatible(primitiveClass, obj.getClass());
610 }
611 }
612
613 /***
614 * Check if class c can be widen to targetClass and return the score.
615 * Return 2 if c==primitiveClass, 1 if c can be widened, or 0 if c cannot be widened.
616 * @param primitiveClass
617 * @param c
618 * @return
619 */
620
621 private static final int computeWideningScore(Class primitiveClass, Class c)
622 {
623
624 List set = (List) WIDENABLES.get(c);
625 int i = set.indexOf(primitiveClass);
626 if (i==-1){
627 return 0;
628 } else if (i<2){
629 return 2;
630 } else {
631 return 1;
632 }
633 }
634
635 /***
636 * Return true if primitiveClass and clazz is both primitive and clazz is primitive compatible with primitiveClass
637 * using java rules (unwrapping or widening)
638 * @param primitiveClass
639 * @param clazz
640 * @return
641 */
642 public static final boolean isPrimitiveCompatible(Class primitiveClass, Class clazz)
643 {
644 return computeWideningScore(primitiveClass, clazz) > 0;
645 }
646 }