View Javadoc
1   /*
2    * #%L
3    * Nuiton Utils
4    * %%
5    * Copyright (C) 2004 - 2010 CodeLutin
6    * %%
7    * This program is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU Lesser General Public License as 
9    * published by the Free Software Foundation, either version 3 of the 
10   * License, or (at your option) any later version.
11   * 
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Lesser Public License for more details.
16   * 
17   * You should have received a copy of the GNU General Lesser Public 
18   * License along with this program.  If not, see
19   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
20   * #L%
21   */
22  
23  package org.nuiton.util;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  
28  import java.lang.annotation.Annotation;
29  import java.lang.reflect.Field;
30  import java.lang.reflect.Method;
31  import java.lang.reflect.Modifier;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.HashMap;
35  import java.util.HashSet;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Set;
39  
40  import static org.nuiton.i18n.I18n.t;
41  
42  /**
43   * Introspection sur une classe. Détermine si un attribut est constant,
44   * recherche de constantes d'un type donné, conversion d'une classe en
45   * Enum...
46   *
47   * Created: 30 décembre 2007
48   *
49   * @author Tony Chemit - chemit@codelutin.com
50   */
51  public class ReflectUtil {
52  
53  
54      /** Logger */
55      private static final Log log = LogFactory.getLog(ReflectUtil.class);
56  
57      /**
58       * Pour déterminer si un champ d'une classe est une constante
59       * (modifiers sont static, final et public)
60       *
61       * @param field le champs à tester
62       * @return {@code true} si les modifiers sont final, static et public
63       */
64      public static boolean isConstantField(Field field) {
65          int modifiers = field.getModifiers();
66          return Modifier.isPublic(modifiers) &&
67                 Modifier.isStatic(modifiers) &&
68                 Modifier.isFinal(modifiers);
69      }
70  
71      /**
72       * Recherche dans une classe donnée {@code klazz}, les constantes d'un
73       * certain type {@code searchingClass} et les retourne.
74       *
75       * L'algorithme parcourt aussi les superclasses.
76       *
77       * @param <T>            enumeration's type
78       * @param klass          la classe contenant les constantes
79       * @param searchingClass le type des champs constants à récupérer
80       * @return la liste des champs du type requis dans
81       * @throws RuntimeException si problème lors de la récupération
82       */
83      @SuppressWarnings({"unchecked"})
84      public static <T> List<T> getConstants(Class<?> klass,
85                                             Class<T> searchingClass) {
86          List<T> result = new ArrayList<T>();
87          for (Field field : klass.getDeclaredFields()) {
88              if (!field.isAccessible()) {
89                  field.setAccessible(true);
90              }
91              if (searchingClass.isAssignableFrom(field.getType()) &&
92                  isConstantField(field)) {
93                  try {
94                      result.add((T) field.get(null));
95                  } catch (IllegalAccessException e) {
96                      throw new RuntimeException(e);
97                  }
98              }
99          }
100         Class<?> superClass = klass.getSuperclass();
101         if (superClass != null) {
102             result.addAll(getConstants(superClass, searchingClass));
103         }
104         return result;
105     }
106 
107     /**
108      * @param <T>       enumeration's type
109      * @param klass     the required class
110      * @param fieldName the required constant name
111      * @return the constant value
112      */
113     @SuppressWarnings({"unchecked"})
114     public static <T> T getConstant(Class<?> klass, String fieldName) {
115         try {
116             T result = null;
117             Field f = klass.getDeclaredField(fieldName);
118             if (isConstantField(f)) {
119                 f.setAccessible(true);
120                 result = (T) f.get(null);
121             }
122             return result;
123         } catch (NoSuchFieldException e) {
124             throw new RuntimeException(e);
125         } catch (IllegalAccessException e) {
126             throw new RuntimeException(e);
127         }
128     }
129 
130     /**
131      * Convertit une classe non typée, en une classe d'enum
132      *
133      * @param <T>  enumeration's type
134      * @param type la classe a typer
135      * @return la classe typee
136      * @throws IllegalArgumentException si le type est null ou non une extension
137      *                                  de la classe Enum.
138      */
139     @SuppressWarnings({"unchecked"})
140     public static <T extends Enum<T>> Class<T> getEnumClass(Class<?> type) throws IllegalArgumentException {
141         if (type == null) {
142             throw new IllegalArgumentException(t("nuitonutil.error.null.parameter", "type"));
143         }
144         if (!type.isEnum()) {
145             throw new IllegalArgumentException(t("nuitonutil.error.not.an.enum", type));
146         }
147         return (Class<T>) type;
148     }
149 
150     /**
151      * Cherche une methode selon son nom et ses paramètres d'invocation.
152      *
153      * @param klass      la classe dans laquelle rechercher la méthode
154      * @param methodName le nom de la méthode recherchée
155      * @param strict     un drapeau pour déclancher une exception si la méthode
156      *                   n'est pas trouvée
157      * @param arguments  les arguments d'invocation de la méthode
158      * @return la méthode trouvée
159      * @throws IllegalArgumentException si la méthode n'est pas trouvée et que
160      *                                  le drapeau {@code strict} est à {@code true}
161      * @since 1.3.1
162      */
163     public static Method getDeclaredMethod(Class<?> klass,
164                                            String methodName,
165                                            boolean strict,
166                                            Object... arguments) throws IllegalArgumentException {
167         HashSet<Class<?>> classes = new HashSet<Class<?>>();
168         Method method;
169         try {
170             method = getDeclaredMethod(klass, methodName, classes, arguments);
171         } finally {
172             if (log.isDebugEnabled()) {
173                 log.debug("Inspected classes : " + classes);
174             }
175             classes.clear();
176         }
177         if (method == null && strict) {
178             throw new IllegalArgumentException(
179                     "could not find method " + methodName + " on type " +
180                     klass.getName());
181         }
182 
183         return method;
184     }
185 
186     /**
187      * Obtain the boxed type of any incoming type.
188      *
189      * If incoming type is not a primitive type, then just returns himself.
190      *
191      * @param type the type to box
192      * @return the boxed type
193      * @see Class#isPrimitive()
194      * @since 1.3.1
195      */
196     public static Class<?> boxType(Class<?> type) {
197         if (!type.isPrimitive()) {
198             return type;
199         }
200         if (boolean.class.equals(type)) {
201             return Boolean.class;
202         }
203         if (char.class.equals(type)) {
204             return Character.class;
205         }
206         if (byte.class.equals(type)) {
207             return Byte.class;
208         }
209         if (short.class.equals(type)) {
210             return Short.class;
211         }
212         if (int.class.equals(type)) {
213             return Integer.class;
214         }
215         if (long.class.equals(type)) {
216             return Long.class;
217         }
218         if (float.class.equals(type)) {
219             return Float.class;
220         }
221         if (double.class.equals(type)) {
222             return Double.class;
223         }
224         if (void.class.equals(type)) {
225             return Void.class;
226         }
227         // should never come here...
228         return type;
229 
230     }
231 
232     /**
233      * Obtain the unboxed type of any incoming type.
234      *
235      * If incoming type is a primitive type, then just returns himself.
236      *
237      * @param type the type to unbox
238      * @return the unboxed type
239      * @see Class#isPrimitive()
240      * @since 1.3.1
241      */
242     public static Class<?> unboxType(Class<?> type) {
243         if (type.isPrimitive()) {
244             return type;
245         }
246         if (Boolean.class.equals(type)) {
247             return boolean.class;
248         }
249         if (Character.class.equals(type)) {
250             return char.class;
251         }
252         if (Byte.class.equals(type)) {
253             return byte.class;
254         }
255         if (Short.class.equals(type)) {
256             return short.class;
257         }
258         if (Integer.class.equals(type)) {
259             return int.class;
260         }
261         if (Long.class.equals(type)) {
262             return long.class;
263         }
264         if (Float.class.equals(type)) {
265             return float.class;
266         }
267         if (Double.class.equals(type)) {
268             return double.class;
269         }
270         if (Void.class.equals(type)) {
271             return void.class;
272         }
273 
274         // not a primitive type
275         return type;
276     }
277 
278     /**
279      * Obtain all the declared fields of the given {@code objectClass} with a
280      * deep scan inside super classes and interfaces.
281      *
282      * <strong>Note:</strong> The type {@link Object} will not be scanned.
283      *
284      * @param objectClass the object class to scan
285      * @return the set of all declared fields found
286      * @since 2.0
287      */
288     public static Set<Field> getAllDeclaredFields(Class<?> objectClass) {
289         Set<Field> result = new HashSet<Field>();
290         Set<Class<?>> visitedClasses = new HashSet<Class<?>>();
291 
292         try {
293             getAllDeclaredFields(objectClass, visitedClasses, result);
294         } finally {
295             visitedClasses.clear();
296         }
297         return result;
298     }
299 
300     /**
301      * Obtain all the declared methods of the given {@code objectClass} with a
302      * deep scan inside super classes and interfaces.
303      *
304      * <strong>Note:</strong> The type {@link Object} will not be scanned.
305      *
306      * @param objectClass the object class to scan
307      * @return the set of all declared methods found
308      * @since 2.0
309      */
310     public static Set<Method> getAllDeclaredMethods(Class<?> objectClass) {
311         Set<Method> result = new HashSet<Method>();
312         Set<Class<?>> visitedClasses = new HashSet<Class<?>>();
313 
314         try {
315             getAllDeclaredMethods(objectClass, visitedClasses, result);
316         } finally {
317             visitedClasses.clear();
318         }
319         return result;
320     }
321 
322     /**
323      * Obtain all the fields with the given annotation type.
324      *
325      * <strong>Note:</strong> This method will not scan deeply the given type
326      * (no scan of super-classes nor interfaces).
327      *
328      * @param objectClass     the type to scan
329      * @param annotationClass the type of annotation to scan
330      * @param <A>             type of annotation to scan
331      * @return the dictionnary of fields with the given annotation
332      * @since 2.0
333      */
334     public static <A extends Annotation> Map<Field, A> getFieldAnnotation(Class<?> objectClass,
335                                                                           Class<A> annotationClass) {
336         Map<Field, A> result = getFieldAnnotation(objectClass,
337                                                   annotationClass,
338                                                   false
339         );
340         return result;
341     }
342 
343     /**
344      * Obtain all the fields with the given annotation type.
345      *
346      * <strong>Note:</strong> This method will scan deeply the given type
347      * if parameter {@code deepVisit} is setted to {@code true}.
348      *
349      * <strong>Note:</strong> The type {@link Object} will not be scanned.
350      *
351      * @param objectClass     the type to scan
352      * @param annotationClass the type of annotation to scan
353      * @param deepVisit       flag to visit deeply the class (if set to
354      *                        {@code true}, will also scan super classes
355      *                        and interfaces)
356      * @param <A>             type of annotation to scan
357      * @return the dictionnary of fields with the given annotation
358      * @since 2.0
359      */
360     public static <A extends Annotation> Map<Field, A> getFieldAnnotation(Class<?> objectClass,
361                                                                           Class<A> annotationClass,
362                                                                           boolean deepVisit) {
363 
364         Map<Field, A> result = new HashMap<Field, A>();
365         Set<Class<?>> visitedClasses = new HashSet<Class<?>>();
366 
367         try {
368             getFieldAnnotation(objectClass,
369                                annotationClass,
370                                deepVisit,
371                                visitedClasses,
372                                result
373             );
374         } finally {
375             visitedClasses.clear();
376         }
377 
378         return result;
379     }
380 
381     /**
382      * Obtain all the methods with the given annotation type.
383      *
384      * <strong>Note:</strong> This method will not scan deeply the given type
385      * (no scan of super-classes nor interfaces).
386      *
387      * @param objectClass     the type to scan
388      * @param annotationClass the type of annotation to scan
389      * @param <A>             type of annotation to scan
390      * @return the dictionnary of methods with the given annotation
391      * @since 2.0
392      */
393     public static <A extends Annotation> Map<Method, A> getMethodAnnotation(Class<?> objectClass,
394                                                                             Class<A> annotationClass) {
395         Map<Method, A> result = getMethodAnnotation(objectClass,
396                                                     annotationClass,
397                                                     false
398         );
399         return result;
400     }
401 
402     /**
403      * Obtain all the methods with the given annotation type.
404      *
405      * <strong>Note:</strong> This method will scan deeply the given type
406      * if parameter {@code deepVisit} is setted to {@code true}.
407      *
408      * <strong>Note:</strong> The type {@link Object} will not be scanned.
409      *
410      * @param objectClass     the type to scan
411      * @param annotationClass the type of annotation to scan
412      * @param <A>             type of annotation to scan
413      * @param deepVisit       flag to visit deeply the class (if set to
414      *                        {@code true}, will also scan super classes
415      *                        and interfaces)
416      * @return the dictionnary of methods with the given annotation
417      * @since 2.0
418      */
419     public static <A extends Annotation> Map<Method, A> getMethodAnnotation(Class<?> objectClass,
420                                                                             Class<A> annotationClass,
421                                                                             boolean deepVisit) {
422         Map<Method, A> result = new HashMap<Method, A>();
423         Set<Class<?>> visitedClasses = new HashSet<Class<?>>();
424 
425         try {
426             getMethodAnnotation(objectClass,
427                                 annotationClass,
428                                 deepVisit,
429                                 visitedClasses,
430                                 result
431             );
432         } finally {
433             visitedClasses.clear();
434         }
435         return result;
436     }
437 
438     protected static Method getDeclaredMethod(Class<?> klass,
439                                               String methodName,
440                                               Set<Class<?>> visitedClasses,
441                                               Object... arguments) {
442         if (visitedClasses.contains(klass)) {
443 
444             // this means class was already unsucessfull visited
445             return null;
446         }
447         visitedClasses.add(klass);
448         Method method = null;
449         for (Method m : klass.getDeclaredMethods()) {
450             if (!methodName.equals(m.getName())) {
451                 continue;
452             }
453 
454             // same method name
455 
456             Class<?>[] types = m.getParameterTypes();
457             if (arguments.length != types.length) {
458                 continue;
459             }
460 
461             // same number arguments
462 
463             Class<?>[] prototype = m.getParameterTypes();
464             if (log.isDebugEnabled()) {
465                 log.debug("Found a method with same parameters size : " +
466                           m.getName() + " : " + Arrays.toString(prototype));
467             }
468             int index = 0;
469             boolean parametersMatches = true;
470             for (Object argument : arguments) {
471                 Class<?> type = prototype[index++];
472                 if (argument == null) {
473 
474                     // can not say anything, let says it is ok...
475                     continue;
476                 }
477                 Class<?> runtimeType = argument.getClass();
478                 if (log.isDebugEnabled()) {
479                     log.debug("Test parameter [" + (index - 1) + "] : " +
480                               type + " vs  " + runtimeType);
481                 }
482 
483                 type = boxType(type);
484                 runtimeType = boxType(runtimeType);
485 
486                 if (!type.equals(runtimeType) &&
487                     !type.isAssignableFrom(runtimeType)) {
488 
489                     // not same type
490                     parametersMatches = false;
491                     if (log.isDebugEnabled()) {
492                         log.debug("Types are not matching.");
493                     }
494                     break;
495                 }
496             }
497             if (parametersMatches) {
498 
499                 // same parameters types, this is a match
500                 method = m;
501             }
502             break;
503         }
504         if (method == null) {
505 
506             // try on super class
507             if (klass.getSuperclass() != null) {
508                 method = getDeclaredMethod(klass.getSuperclass(),
509                                            methodName,
510                                            visitedClasses,
511                                            arguments
512                 );
513             }
514         }
515 
516         if (method == null) {
517 
518             // try on interfaces
519             Class<?>[] interfaces = klass.getInterfaces();
520             for (Class<?> anInterface : interfaces) {
521                 method = getDeclaredMethod(anInterface,
522                                            methodName,
523                                            visitedClasses,
524                                            arguments
525                 );
526                 if (method != null) {
527                     break;
528                 }
529             }
530         }
531         return method;
532 
533     }
534 
535     protected static void getAllDeclaredFields(Class<?> objectClass,
536                                                Set<Class<?>> visitedClasses,
537                                                Set<Field> result) {
538 
539         if (visitedClasses.contains(objectClass) ||
540             Object.class.equals(objectClass)) {
541 
542             // already scanned
543             // or arrives to Object.class
544             return;
545         }
546 
547         // mark as scanned
548         visitedClasses.add(objectClass);
549 
550         Field[] declaredFields = objectClass.getDeclaredFields();
551         result.addAll(Arrays.asList(declaredFields));
552 
553         Class<?> superclass = objectClass.getSuperclass();
554         if (superclass != null) {
555 
556             // scan also the superclass
557             getAllDeclaredFields(superclass,
558                                  visitedClasses,
559                                  result
560             );
561         }
562 
563         Class<?>[] interfaces = objectClass.getInterfaces();
564 
565         for (Class<?> anInterface : interfaces) {
566 
567             // scan also the interface
568             getAllDeclaredFields(anInterface,
569                                  visitedClasses,
570                                  result
571             );
572         }
573 
574     }
575 
576     protected static void getAllDeclaredMethods(Class<?> objectClass,
577                                                 Set<Class<?>> visitedClasses,
578                                                 Set<Method> result) {
579 
580         if (visitedClasses.contains(objectClass) ||
581             Object.class.equals(objectClass)) {
582 
583             // already scanned
584             // or arrives to Object.class
585             return;
586         }
587 
588         // mark as scanned
589         visitedClasses.add(objectClass);
590 
591         Method[] declaredFields = objectClass.getDeclaredMethods();
592         result.addAll(Arrays.asList(declaredFields));
593 
594         Class<?> superclass = objectClass.getSuperclass();
595         if (superclass != null) {
596 
597             // scan also the superclass
598             getAllDeclaredMethods(superclass,
599                                   visitedClasses,
600                                   result
601             );
602         }
603 
604         Class<?>[] interfaces = objectClass.getInterfaces();
605 
606         for (Class<?> anInterface : interfaces) {
607 
608             // scan also the interface
609             getAllDeclaredMethods(anInterface,
610                                   visitedClasses,
611                                   result
612             );
613         }
614 
615     }
616 
617     protected static <A extends Annotation> void getFieldAnnotation(Class<?> objectClass,
618                                                                     Class<A> annotationClass,
619                                                                     boolean deepVisit,
620                                                                     Set<Class<?>> visitedClasses,
621                                                                     Map<Field, A> result) {
622 
623         if (visitedClasses.contains(objectClass) ||
624             Object.class.equals(objectClass)) {
625 
626             // already scanned
627             // or arrives to Object.class
628             return;
629         }
630 
631         // mark as scanned
632         visitedClasses.add(objectClass);
633 
634         for (Field field : objectClass.getDeclaredFields()) {
635             A annotation = field.getAnnotation(annotationClass);
636             if (annotation != null) {
637                 result.put(field, annotation);
638             }
639         }
640 
641         if (deepVisit) {
642 
643             Class<?> superclass = objectClass.getSuperclass();
644             if (superclass != null) {
645 
646                 // scan also the superclass
647                 getFieldAnnotation(superclass,
648                                    annotationClass,
649                                    deepVisit,
650                                    visitedClasses,
651                                    result
652                 );
653             }
654 
655             Class<?>[] interfaces = objectClass.getInterfaces();
656 
657             for (Class<?> anInterface : interfaces) {
658 
659                 // scan also the interface
660                 getFieldAnnotation(anInterface,
661                                    annotationClass,
662                                    deepVisit,
663                                    visitedClasses,
664                                    result
665                 );
666             }
667         }
668     }
669 
670     protected static <A extends Annotation> void getMethodAnnotation(Class<?> objectClass,
671                                                                      Class<A> annotationClass,
672                                                                      boolean deepVisit,
673                                                                      Set<Class<?>> visitedClasses,
674                                                                      Map<Method, A> result) {
675 
676         if (visitedClasses.contains(objectClass) ||
677             Object.class.equals(objectClass)) {
678 
679             // already scanned
680             // or arrives to Object.class
681             return;
682         }
683 
684         // mark as scanned
685         visitedClasses.add(objectClass);
686 
687         for (Method method : objectClass.getDeclaredMethods()) {
688             A annotation = method.getAnnotation(annotationClass);
689             if (annotation != null) {
690                 result.put(method, annotation);
691             }
692         }
693 
694         if (deepVisit) {
695 
696             Class<?> superclass = objectClass.getSuperclass();
697             if (superclass != null) {
698 
699                 // scan also the superclass
700                 getMethodAnnotation(superclass,
701                                     annotationClass,
702                                     deepVisit,
703                                     visitedClasses,
704                                     result
705                 );
706             }
707 
708             Class<?>[] interfaces = objectClass.getInterfaces();
709 
710             for (Class<?> anInterface : interfaces) {
711 
712                 // scan also the interface
713                 getMethodAnnotation(anInterface,
714                                     annotationClass,
715                                     deepVisit,
716                                     visitedClasses,
717                                     result
718                 );
719             }
720         }
721     }
722 }