View Javadoc

1   package junitx.ddtunit.util;
2   
3   import java.lang.reflect.Array;
4   import java.lang.reflect.Constructor;
5   import java.lang.reflect.Field;
6   import java.lang.reflect.Method;
7   import java.util.HashSet;
8   import java.util.Iterator;
9   import java.util.Set;
10  import java.util.Vector;
11  
12  import org.slf4j.Logger;
13  import org.slf4j.LoggerFactory;
14  
15  /**
16   * This example is taken from Thinking in Java secd. ed. (p.680f) This class
17   * used reflection to show all the methods of a class, even if the methods are
18   * defined in the base class.
19   * 
20   * @author jgellien
21   */
22  public class ClassAnalyser {
23      /**
24       * Constant to define constructor select in method
25       * {@link #findMethodByParams(String, String, Class[])}
26       */
27      public final static String CLASS_CONSTRUCTOR = "class$";
28  
29      private final static String usage = "usage: \n"
30              + "ShowMethods qualified.class.name\n"
31              + "To show all methods in class or: \n"
32              + "ShowMethods qualified.class.name word\n"
33              + "To search for methods involving 'word'";
34  
35      private static Logger log = LoggerFactory.getLogger(ClassAnalyser.class);
36  
37      /**
38       * Constructor for the ClassAnalyser object
39       */
40      private ClassAnalyser() {
41          // no special init
42      }
43  
44      /**
45       * The main program for the ShowMethods class
46       * 
47       * @param args The command line arguments
48       */
49      public static void main(String[] args) {
50          if (args.length < 1) {
51              System.out.println(ClassAnalyser.usage);
52              System.exit(0);
53          }
54  
55          System.out.println("ClassAnalyser started with class " + args[0]);
56  
57          if (args.length == 1) {
58              ClassAnalyser.showAllMethods(args[0]);
59              ClassAnalyser.showAllFields(args[0]);
60          } else {
61              ClassAnalyser.showSelectedMethods(args[0], args[1]);
62          }
63  
64          System.out.println("ClassAnalyser end.");
65      }
66  
67      /**
68       * Extract simple class name without package information
69       * 
70       * @param obj object to analyse
71       * 
72       * @return class name
73       */
74      public static String getShortName(Object obj) {
75          String className = obj.getClass().getName();
76  
77          return getShortName(className);
78      }
79  
80      /**
81       * Extract simple class name without package information
82       * 
83       * @param className to analyse
84       * 
85       * @return name without package extension
86       */
87      public static String getShortName(Class className) {
88          String localName = className.getName();
89  
90          return getShortName(localName);
91      }
92  
93      /**
94       * Extract simple class name without package information
95       * 
96       * @param className qualified class name to analyse
97       * 
98       * @return class name
99       */
100     public static String getShortName(String className) {
101         String shortName = className.substring(className.lastIndexOf(".") + 1,
102             className.length());
103 
104         return shortName;
105     }
106 
107     /**
108      * Display all attributes/fields of class quaifiedClassName to the
109      * configured appender specified by Log4j
110      * 
111      * @param qualifiedClassName name of class under analysis
112      * 
113      * @throws RuntimeException ClassAnalyserException if an error occures
114      */
115     public static void showAllFields(String qualifiedClassName) {
116         try {
117             Class c = Class.forName(qualifiedClassName);
118             Field[] fields = c.getDeclaredFields();
119             log.debug("===Class " + qualifiedClassName + " fields:");
120 
121             for (int i = 0; i < fields.length; i++) {
122                 log.debug(fields[i].toString());
123             }
124         } catch (ClassNotFoundException ex) {
125             log.error("No such class: " + qualifiedClassName, ex);
126             throw new ClassAnalyserException("No class found of type "
127                     + qualifiedClassName, ex);
128         }
129     }
130 
131     /**
132      * Display all attributes/fields of class quaifiedClassName matching
133      * searchTerm to the configured appender specified by Log4j
134      * 
135      * @param qualifiedClassName name of class under analysis
136      * @param searchTerm match string to check against
137      * 
138      * @throws RuntimeException ClassAnalyserException if an error occures
139      */
140     public static void showSelectedFields(String qualifiedClassName,
141             String searchTerm) {
142         try {
143             Class c = Class.forName(qualifiedClassName);
144             Field[] fields = c.getDeclaredFields();
145             log.debug("===Class " + qualifiedClassName
146                     + " fields selected by '" + searchTerm + "':");
147 
148             for (int i = 0; i < fields.length; i++) {
149                 if (fields[i].toString().indexOf(searchTerm) != -1) {
150                     log.debug(fields[i].toString());
151                 }
152             }
153         } catch (ClassNotFoundException ex) {
154             log.error("No such class: " + qualifiedClassName, ex);
155             throw new ClassAnalyserException("No class found of type "
156                     + qualifiedClassName, ex);
157         }
158     }
159 
160     /**
161      * Search for exact match of searchTerm in the list of declared fields of
162      * the class qualifiedClassName.
163      * 
164      * @param qualifiedClassName Description of the Parameter
165      * @param searchTerm Description of the Parameter
166      * 
167      * @return Field that was found or null if field does not exists
168      * 
169      * @throws RuntimeException ClassAnalyserException if an error occures
170      */
171     public static Field getSelectedField(String qualifiedClassName,
172             String searchTerm) {
173         Field localField = null;
174         if (qualifiedClassName != null && searchTerm != null) {
175             try {
176                 Class c = Class.forName(qualifiedClassName);
177 
178                 while (c != null) {
179                     // this gets you only not inherited fiels ==> you have to
180                     // iterate over
181                     // all parent classes
182                     Field[] fields = c.getDeclaredFields();
183                     String className = c.getName();
184                     log.debug("===Class " + className + " fields selected by '"
185                             + searchTerm + "':");
186 
187                     boolean found = false;
188 
189                     for (int i = 0; i < fields.length; i++) {
190                         // use Field.getName() instead of Field.toString()
191                         // if field does not exists getName() returns a string
192                         // starting with
193                         // "class$<full qualified class name>"
194                         String fieldName = fields[i].getName();
195                         log.debug("check search term <" + searchTerm
196                                 + "> in field <" + fieldName + ">");
197 
198                         if (!fieldName.startsWith("class$")
199                                 && fieldName.equals(searchTerm)) {
200                             // first hit
201                             if (!found) {
202                                 localField = fields[i];
203                                 found = true;
204                                 log.debug("First hit.");
205 
206                                 break;
207                             } else {
208                                 throw new IllegalArgumentException(
209                                         "double count of field " + searchTerm);
210                             }
211                         }
212                     }
213 
214                     if (!found) {
215                         c = c.getSuperclass();
216                         log.debug("=== No Hit");
217                     } else {
218                         c = null;
219                         log.debug("=== End of Check");
220                     }
221                 }
222             } catch (ClassNotFoundException ex) {
223                 log.error("No such class: " + qualifiedClassName, ex);
224                 throw new ClassAnalyserException("No class found of type "
225                         + qualifiedClassName, ex);
226             }
227         }
228         return localField;
229     }
230 
231     /**
232      * Display all methods of class quaifiedClassName to the configured appender
233      * specified by Log4j
234      * 
235      * @param qualifiedClassName name of class under analysis
236      * 
237      * @throws RuntimeException ClassAnalyserException if an error occures
238      */
239     public static void showAllMethods(String qualifiedClassName) {
240         try {
241             Class c = Class.forName(qualifiedClassName);
242             Method[] m = c.getMethods();
243             Constructor[] ctor = c.getConstructors();
244             log.debug("===Class " + qualifiedClassName + " methods:");
245 
246             for (int i = 0; i < m.length; i++) {
247                 log.debug(m[i].toString());
248             }
249 
250             log.debug("===Class " + qualifiedClassName + " constructors:");
251 
252             for (int i = 0; i < ctor.length; i++) {
253                 log.debug(ctor[i].toString());
254             }
255         } catch (ClassNotFoundException ex) {
256             log.error("No such class: " + qualifiedClassName, ex);
257             throw new ClassAnalyserException("No class found of type "
258                     + qualifiedClassName, ex);
259         }
260     }
261 
262     /**
263      * Put all method names of the class qualifiedClassName into a String array
264      * and return it.
265      * 
266      * @param qualifiedClassName Description of the Parameter
267      * 
268      * @return array of all method names of the class to analyse
269      * 
270      * @throws RuntimeException ClassAnalyserException if an error occures
271      */
272     public static String[] getAllMethods(String qualifiedClassName) {
273         Class c;
274         Method[] m;
275 
276         try {
277             c = Class.forName(qualifiedClassName);
278             m = c.getMethods();
279             log.debug("===Class " + qualifiedClassName + " methods:");
280 
281             String[] methods = new String[m.length];
282 
283             for (int i = 0; i < m.length; i++) {
284                 log.debug(m[i].toString());
285                 methods[i] = m[i].getName();
286             }
287 
288             return methods;
289         } catch (ClassNotFoundException ex) {
290             log.error("No such class: " + qualifiedClassName, ex);
291             throw new ClassAnalyserException("No class found of type "
292                     + qualifiedClassName, ex);
293         }
294     }
295 
296     /**
297      * Display all constructors and methods of qualifiedClassName class which
298      * match with searchTerm. <br/>Output is set to Log4J Info level of this
299      * classes Logger.
300      * 
301      * @param qualifiedClassName Name of class to analyse
302      * @param searchTerm Match term for methods to display
303      * 
304      * @throws RuntimeException ClassAnalyserException if an error occures
305      */
306     public static void showSelectedMethods(String qualifiedClassName,
307             String searchTerm) {
308         try {
309             Class c = Class.forName(qualifiedClassName);
310             Method[] m = c.getMethods();
311             Constructor[] ctor = c.getConstructors();
312 
313             for (int i = 0; i < m.length; i++) {
314                 if (m[i].toString().indexOf(searchTerm) != -1) {
315                     log.info(m[i].toString());
316                 }
317             }
318 
319             for (int i = 0; i < ctor.length; i++) {
320                 if (ctor[i].toString().indexOf(searchTerm) != -1) {
321                     log.info(ctor[i].toString());
322                 }
323             }
324         } catch (ClassNotFoundException ex) {
325             log.error("No such class: " + qualifiedClassName, ex);
326             throw new ClassAnalyserException("No class found of type "
327                     + qualifiedClassName, ex);
328         }
329     }
330 
331     /**
332      * Find a Constructor/Method of class className by using the argument list
333      * args and try to vary arguments which could be of primitive type. <br/>
334      * The args list only contains classes.
335      * 
336      * @param className
337      * @param methodName
338      * @param args
339      * @return Method/Constructor object
340      */
341     public static Object findMethodByParams(String className,
342             String methodName, Class[] args) {
343         Object method = null;
344 
345         try {
346             Object[] methods;
347             Class myClass = Class.forName(className);
348             if (CLASS_CONSTRUCTOR.compareTo(methodName) == 0) {
349                 methods = myClass.getDeclaredConstructors();
350             } else {
351                 methods = myClass.getDeclaredMethods();
352             }
353 
354             Vector searchMethods = filterByNameAndParamCount(methods,
355                 methodName, args.length);
356             boolean found = false;
357             for (Iterator iter = searchMethods.iterator(); iter.hasNext();) {
358                 Object myMethod = iter.next();
359 
360                 found = filterByParam(myMethod, args, 0);
361 
362                 if (found) {
363                     // exit with first valid method
364                     method = myMethod;
365 
366                     break;
367                 }
368             }
369             // if method not found in active clazz search in superclazzes
370             if (!found) {
371                 Set superClazzes = getSuperElements(myClass);
372                 for (Iterator iter = superClazzes.iterator(); iter.hasNext();) {
373                     Class clazz = (Class) iter.next();
374                     method = findMethodByParams(clazz.getName(), methodName,
375                         args);
376                     if (method != null) {
377                         found = true;
378                         break;
379                     }
380                 }
381             }
382         } catch (Exception e) {
383             throw new ClassAnalyserException(
384                     "Could not find constructor of class " + className, e);
385         }
386 
387         return method;
388     }
389 
390     /**
391      * Display all methods of class quaifiedClassName to the configured appender
392      * specified by Log4j
393      * 
394      * @param qualifiedClassName name of class under analysis
395      * @param methodName to search for
396      * @throws RuntimeException ClassAnalyserException if an error occures
397      */
398     public static Method findMethodByName(String qualifiedClassName, String methodName) {
399         try {
400             Class c = Class.forName(qualifiedClassName);
401             Method[] m = c.getMethods();
402             Method firstMethod = null;
403             log.debug("===Class " + qualifiedClassName + " methods:");
404 
405             for (int i = 0; i < m.length; i++) {
406             	if (m[i].getName().endsWith(methodName)){
407                   firstMethod = m[i];
408                   break;
409             	}
410             }
411             return firstMethod;
412         } catch (ClassNotFoundException ex) {
413             log.error("No such class: " + qualifiedClassName, ex);
414             throw new ClassAnalyserException("No class found of type "
415                     + qualifiedClassName, ex);
416         }
417     }
418 
419 
420     /**
421      * Check if parameter type on position pos of constructor method and args
422      * are the same. <br/>If the actual position arguments are the same the
423      * method is called with (pos+1).
424      * 
425      * @param constructor
426      * @param list of class arguments to match
427      * @param pos of argument to match
428      * @return true if parameter matches
429      */
430     private static boolean filterByParam(Object method, Class[] args, int pos) {
431         boolean valid = false;
432         Class arg;
433         Class argOnPos;
434 
435         // exit criterion for recursion
436         if (pos >= args.length) {
437             valid = true;
438         } else {
439             arg = args[pos];
440 
441             if (java.lang.reflect.Constructor.class.isInstance(method)) {
442                 argOnPos = ((Constructor) method).getParameterTypes()[pos];
443             } else {
444                 argOnPos = ((Method) method).getParameterTypes()[pos];
445             }
446 
447             // check if argument class is a valid argument substitution of
448             // constructor signature argument class. E.g. check if argument
449             // is perhaps derived from a superclass of selected
450             // constructor/method argument
451             if (arg.equals(argOnPos)
452                     || getSuperElements(arg).contains(argOnPos)) {
453                 valid = filterByParam(method, args, pos + 1);
454 
455                 // This path does not work, go the primitive way
456                 if (!valid) {
457                     valid = filterByPrimitiveParam(method, args, pos + 1);
458                 }
459             } else {
460                 valid = filterByPrimitiveParam(method, args, pos);
461             }
462         }
463 
464         return valid;
465     }
466 
467     public static Set getSuperElements(Class clazz) {
468         Set clazzList = new HashSet();
469         Class[] interfaces = clazz.getInterfaces();
470         for (int count = 0; count < interfaces.length; count++) {
471             clazzList.addAll(getSuperElements(interfaces[count]));
472             clazzList.add(interfaces[count]);
473         }
474         Class superClazz = clazz.getSuperclass();
475         if (superClazz == null) {
476             return clazzList;
477         } else {
478             Set result = getSuperElements(superClazz);
479             result.add(superClazz);
480             result.addAll(clazzList);
481             return result;
482         }
483     }
484 
485     private static boolean filterByPrimitiveParam(Object method, Class[] args,
486             int pos) {
487         boolean valid = false;
488         Class arg;
489         Class argOnPos;
490 
491         // exit criterion for recursion
492         if (pos >= args.length) {
493             valid = true;
494         } else {
495             arg = args[pos];
496 
497             if (java.lang.reflect.Constructor.class.isInstance(method)) {
498                 argOnPos = ((Constructor) method).getParameterTypes()[pos];
499             } else {
500                 argOnPos = ((Method) method).getParameterTypes()[pos];
501             }
502 
503             // check if primitiv type of argOnPos exists
504             if (hasPrimitive(arg) && argOnPos.equals(getPrimitiveClass(arg))) {
505                 valid = filterByParam(method, args, pos + 1);
506             }
507         }
508 
509         return valid;
510     }
511 
512     /**
513      * Retrieve primitove type of specified clazz
514      * 
515      * @param checkClass to retrieve associated primitive type from
516      * 
517      * @return associated primitive type or null if non exists
518      */
519     public static Class getPrimitiveClass(Class checkClass) {
520         Class primitive = null;
521         boolean arrayFound = false;
522         Class verifyClazz = checkClass;
523         if (hasPrimitive(checkClass)) {
524             // check for array class
525             String name = checkClass.getName();
526             if (name.startsWith("[L")) {
527                 arrayFound = true;
528                 try {
529                     verifyClazz = Class.forName(name.substring(2,
530                         name.length() - 1));
531                 } catch (ClassNotFoundException ex) {
532                     throw new ClassAnalyserException(
533                             "Could not construct base class from array");
534                 }
535             }
536             if (verifyClazz.equals(java.lang.Integer.class)) {
537                 if (arrayFound) {
538                     primitive = getPrimitiveArrayClass("[I");
539                 } else {
540                     primitive = Integer.TYPE;
541                 }
542             } else if (verifyClazz.equals(java.lang.Long.class)) {
543                 if (arrayFound) {
544                     primitive = getPrimitiveArrayClass("[J");
545                 } else {
546                     primitive = Long.TYPE;
547                 }
548             } else if (verifyClazz.equals(java.lang.Short.class)) {
549                 if (arrayFound) {
550                     primitive = getPrimitiveArrayClass("[S");
551                 } else {
552                     primitive = Short.TYPE;
553                 }
554             } else if (verifyClazz.equals(java.lang.Double.class)) {
555                 if (arrayFound) {
556                     primitive = getPrimitiveArrayClass("[D");
557                 } else {
558                     primitive = Double.TYPE;
559                 }
560             } else if (verifyClazz.equals(java.lang.Float.class)) {
561                 if (arrayFound) {
562                     primitive = getPrimitiveArrayClass("[F");
563                 } else {
564                     primitive = Float.TYPE;
565                 }
566             } else if (verifyClazz.equals(Character.class)) {
567                 if (arrayFound) {
568                     primitive = getPrimitiveArrayClass("[C");
569                 } else {
570                     primitive = Character.TYPE;
571                 }
572             } else if (verifyClazz.equals(Byte.class)) {
573                 if (arrayFound) {
574                     primitive = getPrimitiveArrayClass("[B");
575                 } else {
576                     primitive = Byte.TYPE;
577                 }
578             } else if (verifyClazz.equals(Boolean.class)) {
579                 if (arrayFound) {
580                     primitive = getPrimitiveArrayClass("[Z");
581                 } else {
582                     primitive = Boolean.TYPE;
583                 }
584             }
585         }
586 
587         return primitive;
588     }
589 
590     /**
591      * Convert an array of object to its primitive counterpart. Values of null
592      * will be replaced by the primitive default value.
593      * 
594      * @param value - array of Object to convert
595      * @return array of primitive counterpart
596      */
597     public static Object createPrimitiveArray(Object value) {
598         int valueLength = Array.getLength(value);
599         Object valueArray;
600         valueArray = Array.newInstance(ClassAnalyser
601             .getPrimitiveArrayBaseType(value.getClass()), valueLength);
602         Object obj = null;
603         for (int count = 0; count < valueLength; count++) {
604             obj = Array.get(value, count);
605             if (obj != null) {
606                 Array.set(valueArray, count, obj);
607             }
608         }
609         return valueArray;
610     }
611 
612     /**
613      * 
614      * @param clazz
615      * @return primitive clazz
616      */
617     static public Class getPrimitiveArrayBaseType(Class clazz) {
618         String clazzName = clazz.getName();
619         Class baseClazz = null;
620         if (clazzName != null && clazzName.startsWith("[L")) {
621             clazzName = clazzName.substring(2, clazzName.length() - 1);
622             try {
623                 baseClazz = Class.forName(clazzName);
624             } catch (ClassNotFoundException ex) {
625                 throw new ClassAnalyserException(
626                         "Could not create base class of array");
627             }
628         }
629         Class primitiveClazz = getPrimitiveClass(baseClazz);
630         return primitiveClazz;
631     }
632 
633     /**
634      * @param primitive array string presentation
635      * @return array class of primitives
636      */
637     static private Class getPrimitiveArrayClass(String primitive) {
638         Class primitiveArrayClazz = null;
639         try {
640             primitiveArrayClazz = Class.forName(primitive);
641         } catch (ClassNotFoundException ex) {
642             throw new ClassAnalyserException("Could not create " + primitive
643                     + " array");
644         }
645         return primitiveArrayClazz;
646     }
647 
648     /**
649      * Verify if provided clazz has primitive type
650      * 
651      * @param checkClass to look for associated primitive type
652      * 
653      * @return true if primitive type is found
654      */
655     public static boolean hasPrimitive(Class checkClass) {
656         boolean check = false;
657         Class verifyClazz = checkClass;
658         // check for array class and process with content class
659         String name = checkClass.getName();
660         if (name.startsWith("[L")) {
661             try {
662                 verifyClazz = Class.forName(name
663                     .substring(2, name.length() - 1));
664             } catch (ClassNotFoundException ex) {
665                 throw new ClassAnalyserException(
666                         "Could not construct base class from array");
667             }
668         }
669         if (verifyClazz.equals(java.lang.Integer.class)
670                 || verifyClazz.equals(java.lang.Short.class)
671                 || verifyClazz.equals(java.lang.Long.class)
672                 || verifyClazz.equals(java.lang.Float.class)
673                 || verifyClazz.equals(java.lang.Double.class)
674                 || verifyClazz.equals(java.lang.Character.class)
675                 || verifyClazz.equals(java.lang.Byte.class)
676                 || verifyClazz.equals(java.lang.Boolean.class)) {
677             check = true;
678         }
679 
680         return check;
681     }
682 
683     /**
684      * Take array and return all constructors that have the signature count
685      * defined by parameter count.
686      * 
687      * @param set of constructors to filer
688      * @param name of method to process
689      * @param count of signature to filter
690      * @return Vector of valid constructors
691      */
692     private static Vector filterByNameAndParamCount(Object[] methods,
693             String methodName, int count) {
694         Vector selected = new Vector();
695 
696         for (int i = 0; i < methods.length; i++) {
697             int paramCount;
698             String myName;
699 
700             if (java.lang.reflect.Constructor.class.isInstance(methods[i])) {
701                 Constructor myConstr = (Constructor) methods[i];
702                 myName = CLASS_CONSTRUCTOR;
703                 paramCount = myConstr.getParameterTypes().length;
704             } else {
705                 Method myMethod = (Method) methods[i];
706                 myName = myMethod.getName();
707                 paramCount = myMethod.getParameterTypes().length;
708             }
709 
710             if ((methodName.compareTo(myName) == 0) && (paramCount == count)) {
711                 selected.add(methods[i]);
712             }
713         }
714 
715         return selected;
716     }
717 
718     /**
719      * Extract package from object instance
720      * 
721      * @return package name of object instance
722      */
723     public static String classPackage(Object obj) {
724         String packageName = "";
725         String pathSeparator = "/";
726         if (obj != null) {
727             if (obj.getClass().getPackage() != null) {
728                 packageName = obj.getClass().getPackage().getName().replaceAll(
729                     "\\.", pathSeparator);
730             }
731         }
732         return packageName;
733     }
734     
735     /**
736      * Extract package from class
737      * 
738      * @return package name of class
739      */
740     public static String classPackage(Class<?> clazz) {
741         String packageName = "";
742         String pathSeparator = "/";
743         if (clazz != null) {
744             if (clazz.getPackage() != null) {
745                 packageName = clazz.getPackage().getName().replaceAll(
746                     "\\.", pathSeparator);
747             }
748         }
749         return packageName;
750     }
751 
752 
753 }