View Javadoc

1   /*
2    * Copyright (C) 2004 TiongHiang Lee
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 2.1 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12   * Lesser General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this library; if not,  write to the Free Software
16   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17   *
18   * Email: thlee@onemindsoft.org
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) //look in cache
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             //first trial
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; //assume underlying args is null which is allowable    
350                     }
351                 } else if (WIDENABLES.containsKey(argTypes[i]))
352                 {//maybe it can be widen
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                 //try to get static method
426                 m = getMethod((Class) o, methodName, args);
427             } catch (NoSuchMethodException e)
428             {
429                 //when user trying to get the "class instance" method
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         //TODO: decide if needed
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) //look in cache
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             //first trial
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         //check if c can be widen to primitiveClass
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; //exact match
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 }