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 }