View Javadoc
1   package org.djutils.reflection;
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.lang.reflect.Modifier;
8   import java.util.ArrayList;
9   import java.util.Arrays;
10  import java.util.Collections;
11  import java.util.HashMap;
12  import java.util.HashSet;
13  import java.util.List;
14  import java.util.Map;
15  import java.util.Set;
16  
17  import org.djutils.primitives.Primitive;
18  
19  /**
20   * ClassUtil is a utility class providing assistance for Java Classes.
21   * <p>
22   * Copyright (c) 2002-2019 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
23   * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
24   * distributed under a three-clause BSD-style license, which can be found at
25   * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>.
26   * </p>
27   * @author Peter Jacobs, Niels Lang, Alexander Verbraeck
28   */
29  public final class ClassUtil
30  {
31      /** CACHE reflects the internal repository CACHE. */
32      private static final Map<String, Object> CACHE = Collections.synchronizedMap(new HashMap<String, Object>());
33  
34      /**
35       * constructs a new ClassUtil.
36       */
37      private ClassUtil()
38      {
39          super();
40          // unreachable code
41      }
42  
43      /** ************ CONSTRUCTOR UTILITIES *********** */
44      /**
45       * gets all the constructors of a class and adds the result to result.
46       * @param clazz Class&lt;?&gt;; the class
47       * @param result Constructor&lt;?&gt;[]; the resulting set
48       * @return result
49       */
50      public static Constructor<?>[] getAllConstructors(final Class<?> clazz, final Constructor<?>[] result)
51      {
52          List<Constructor<?>> list = new ArrayList<Constructor<?>>(Arrays.asList(result));
53          list.addAll(Arrays.asList(clazz.getDeclaredConstructors()));
54          if (clazz.getSuperclass() != null)
55          {
56              return ClassUtil.getAllConstructors(clazz.getSuperclass(), list.toArray(new Constructor[list.size()]));
57          }
58          return list.toArray(new Constructor[list.size()]);
59      }
60  
61      /**
62       * returns the interface method.
63       * @param clazz Class&lt;?&gt;; the class to start with
64       * @param callerClass Class&lt;?&gt;; the calling class
65       * @param parameterTypes Class&lt;?&gt;[]; the parameterTypes
66       * @return Constructor
67       * @throws NoSuchMethodException if the method cannot be resolved
68       */
69      public static Constructor<?> resolveConstructor(final Class<?> clazz, final Class<?> callerClass,
70              final Class<?>[] parameterTypes) throws NoSuchMethodException
71      {
72          Constructor<?> constructor = ClassUtil.resolveConstructor(clazz, parameterTypes);
73          if (ClassUtil.isVisible(constructor, callerClass.getClass()))
74          {
75              return constructor;
76          }
77          throw new NoSuchMethodException("constructor resolved but not visible");
78      }
79  
80      /**
81       * returns the interface method.
82       * @param clazz Class&lt;?&gt;; the class to start with
83       * @param parameterTypes Class&lt;?&gt;[]; the parameterTypes
84       * @return Constructor
85       * @throws NoSuchMethodException if the method cannot be resolved
86       */
87      public static Constructor<?> resolveConstructor(final Class<?> clazz, final Class<?>[] parameterTypes)
88              throws NoSuchMethodException
89      {
90          try
91          {
92              return resolveConstructorSuper(clazz, (Class<?>[]) ClassUtil.checkInput(parameterTypes, Class.class));
93          }
94          catch (Exception exception)
95          {
96              String className = clazz.getName();
97              if (className.indexOf("$") >= 0)
98              {
99                  Class<?> parentClass = null;
100                 try
101                 {
102                     parentClass = Class.forName(className.substring(0, className.lastIndexOf("$")));
103                 }
104                 catch (Exception e2)
105                 {
106                     throw new NoSuchMethodException("class " + parentClass + " not found to resolve constructor");
107                 }
108                 return ClassUtil.resolveConstructor(parentClass,
109                         (Class<?>[]) ClassUtil.checkInput(parameterTypes, Class.class));
110             }
111             throw new NoSuchMethodException("class " + clazz + " does not contain constructor");
112         }
113     }
114 
115     /**
116      * returns the constructor.
117      * @param clazz Class&lt;?&gt;; the clazz to start with
118      * @param arguments Object[]; the arguments
119      * @return Constructor
120      * @throws NoSuchMethodException on lookup failure
121      */
122     public static Constructor<?> resolveConstructor(final Class<?> clazz, final Object[] arguments)
123             throws NoSuchMethodException
124     {
125         Class<?>[] parameterTypes = ClassUtil.getClass(arguments);
126         String key = "CONSTRUCTOR:" + clazz + "@" + FieldSignature.toDescriptor(parameterTypes);
127         if (CACHE.containsKey(key))
128         {
129             return (Constructor<?>) CACHE.get(key);
130         }
131         try
132         {
133             return ClassUtil.resolveConstructor(clazz, parameterTypes);
134         }
135         catch (NoSuchMethodException noSuchMethodException)
136         {
137             // We get all constructors
138             Constructor<?>[] constructors = ClassUtil.getAllConstructors(clazz, new Constructor[0]);
139             // now we match the signatures
140             constructors = ClassUtil.matchSignature(constructors, parameterTypes);
141             // Now we find the most specific
142             Constructor<?> result = ClassUtil.getSpecificConstructor(constructors);
143             CACHE.put(key, result);
144             return result;
145         }
146     }
147 
148     /* ************ FIELD UTILITIES *********** */
149 
150     /**
151      * gets all the fields of a class (public, protected, package, and private) and adds the result to the return value.
152      * @param clazz Class&lt;?&gt;; the class
153      * @param result Set&lt;Field&gt;; the resulting set
154      * @return the set of fields including all fields of the field clazz
155      */
156     public static Set<Field> getAllFields(final Class<?> clazz, final Set<Field> result)
157     {
158         Field[] fields = clazz.getDeclaredFields();
159         for (int i = 0; i < fields.length; i++)
160         {
161             result.add(fields[i]);
162         }
163         if (clazz.getSuperclass() != null)
164         {
165             return ClassUtil.getAllFields(clazz.getSuperclass(), result);
166         }
167         return result;
168     }
169 
170     /**
171      * gets all the fields of a class (public, protected, package, and private).
172      * @param clazz Class&lt;?&gt;; the class
173      * @return all fields of the class
174      */
175     public static Set<Field> getAllFields(final Class<?> clazz)
176     {
177         Set<Field> fieldSet = new HashSet<Field>();
178         return ClassUtil.getAllFields(clazz, fieldSet);
179     }
180 
181     /**
182      * resolves the field for a class, taking into account inner classes.
183      * @param clazz the class to resolve the field for, including inner classes
184      * @param fieldName name of the field
185      * @return Field the field
186      * @throws NoSuchFieldException on no such field
187      */
188 
189     public static Field resolveField(final Class<?> clazz, final String fieldName) throws NoSuchFieldException
190     {
191         try
192         {
193             return resolveFieldSuper(clazz, fieldName);
194         }
195         catch (NoSuchFieldException noSuchFieldException)
196         {
197             String className = clazz.getName();
198             if (className.indexOf("$") >= 0)
199             {
200                 Class<?> clazz2 = null;
201                 try
202                 {
203                     clazz2 = Class.forName(className.substring(0, className.lastIndexOf("$")));
204                 }
205                 catch (ClassNotFoundException classNotFoundException)
206                 {
207                     throw new NoSuchFieldException("class " + clazz + " not found to resolve field " + fieldName);
208                 }
209                 return ClassUtil.resolveField(clazz2, fieldName);
210             }
211             throw new NoSuchFieldException("class " + clazz + " does not contain field " + fieldName);
212         }
213     }
214 
215     /**
216      * returns the field.
217      * @param clazz Class&lt;?&gt;; the class to start with
218      * @param callerClass Class&lt;?&gt;; the calling class
219      * @param name String; the fieldName
220      * @return Constructor
221      * @throws NoSuchFieldException if the method cannot be resolved
222      */
223     public static Field resolveField(final Class<?> clazz, final Class<?> callerClass, final String name)
224             throws NoSuchFieldException
225     {
226         Field field = ClassUtil.resolveField(clazz, name);
227         if (ClassUtil.isVisible(field, callerClass.getClass()))
228         {
229             return field;
230         }
231         throw new NoSuchFieldException("field resolved but not visible");
232     }
233 
234     /**
235      * resolves the field for a given object instance.
236      * @param object Object; the object to resolve the field for
237      * @param fieldName String; name of the field to resolve
238      * @return the field (if found)
239      * @throws NoSuchFieldException if the field cannot be resolved
240      */
241     public static Field resolveField(final Object object, final String fieldName) throws NoSuchFieldException
242     {
243         if (object == null)
244         {
245             throw new NoSuchFieldException("resolveField: object is null for field " + fieldName);
246         }
247         return resolveField(object.getClass(), fieldName);
248     }
249 
250     /** ************ METHOD UTILITIES *********** */
251     /**
252      * gets all the methods of a class and adds the result to result.
253      * @param clazz Class&lt;?&gt;; the class
254      * @param name String; the name of the method
255      * @param result Method[]; the resulting set
256      * @return result
257      */
258     public static Method[] getAllMethods(final Class<?> clazz, final String name, final Method[] result)
259     {
260         List<Method> list = new ArrayList<Method>(Arrays.asList(result));
261         Method[] methods = clazz.getDeclaredMethods();
262         for (int i = 0; i < methods.length; i++)
263         {
264             if (methods[i].getName().equals(name))
265             {
266                 list.add(methods[i]);
267             }
268         }
269         if (clazz.getSuperclass() != null)
270         {
271             return ClassUtil.getAllMethods(clazz.getSuperclass(), name, list.toArray(new Method[list.size()]));
272         }
273         return list.toArray(new Method[list.size()]);
274     }
275 
276     /**
277      * returns the interface method.
278      * @param clazz Class&lt;?&gt;; the class to start with
279      * @param callerClass Class&lt;?&gt;; the caller class
280      * @param name String; the name of the method
281      * @param parameterTypes Class&lt;?&gt;[]; the parameterTypes
282      * @return Method
283      * @throws NoSuchMethodException on lookup failure
284      */
285     public static Method resolveMethod(final Class<?> clazz, final Class<?> callerClass, final String name,
286             final Class<?>[] parameterTypes) throws NoSuchMethodException
287     {
288         Method method = ClassUtil.resolveMethod(clazz, name, parameterTypes);
289         if (ClassUtil.isVisible(method, callerClass))
290         {
291             return method;
292         }
293         throw new NoSuchMethodException("method found but not visible");
294     }
295 
296     /**
297      * returns the interface method.
298      * @param clazz Class&lt;?&gt;; the class to start with
299      * @param name String; the name of the method
300      * @param parameterTypes Class&lt;?&gt;[]; the parameterTypes
301      * @return Method
302      * @throws NoSuchMethodException on lookup failure
303      */
304     public static Method resolveMethod(final Class<?> clazz, final String name, final Class<?>[] parameterTypes)
305             throws NoSuchMethodException
306     {
307         try
308         {
309             return resolveMethodSuper(clazz, name, (Class<?>[]) ClassUtil.checkInput(parameterTypes, Class.class));
310         }
311         catch (Exception exception)
312         {
313             String className = clazz.getName();
314             if (className.indexOf("$") >= 0)
315             {
316                 Class<?> parentClass = null;
317                 try
318                 {
319                     parentClass = Class.forName(className.substring(0, className.lastIndexOf("$")));
320                 }
321                 catch (Exception e2)
322                 {
323                     throw new NoSuchMethodException("class " + parentClass + " not found to resolve method " + name);
324                 }
325                 return ClassUtil.resolveMethod(parentClass, name,
326                         (Class<?>[]) ClassUtil.checkInput(parameterTypes, Class.class));
327             }
328             throw new NoSuchMethodException("class " + clazz + " does not contain method " + name);
329         }
330     }
331 
332     /**
333      * resolves a method the method.
334      * @param object Object; the object to start with
335      * @param name String; the name of the method
336      * @param parameterTypes Class&lt;?&gt;[]; the parameterTypes
337      * @return Method
338      * @throws NoSuchMethodException on lookup failure
339      */
340     public static Method resolveMethod(final Object object, final String name, final Class<?>[] parameterTypes)
341             throws NoSuchMethodException
342     {
343         if (object == null)
344         {
345             throw new NoSuchMethodException("resolveField: object is null for method " + name);
346         }
347         return resolveMethod(object.getClass(), name, parameterTypes);
348     }
349 
350     /**
351      * returns the method.
352      * @param object Object; the object to start with
353      * @param name String; the name of the method
354      * @param arguments Object[]; the arguments
355      * @return Method
356      * @throws NoSuchMethodException on lookup failure
357      */
358     public static Method resolveMethod(final Object object, final String name, final Object[] arguments)
359             throws NoSuchMethodException
360     {
361         Class<?>[] parameterTypes = ClassUtil.getClass(arguments);
362         String key = "METHOD:" + object.getClass() + "@" + name + "@" + FieldSignature.toDescriptor(parameterTypes);
363         if (CACHE.containsKey(key))
364         {
365             return (Method) CACHE.get(key);
366         }
367         try
368         {
369             return ClassUtil.resolveMethod(object, name, parameterTypes);
370         }
371         catch (NoSuchMethodException noSuchMethodException)
372         {
373             // We get all methods
374             Method[] methods = ClassUtil.getAllMethods(object.getClass(), name, new Method[0]);
375             if (methods.length == 0)
376             {
377                 throw new NoSuchMethodException("No such method: " + name + " for object " + object);
378             }
379             // now we match the signatures
380             methods = ClassUtil.matchSignature(methods, name, parameterTypes);
381             if (methods.length == 0)
382             {
383                 throw new NoSuchMethodException("No method with right signature: " + name + " for object " + object);
384             }
385             // Now we find the most specific
386             Method result = ClassUtil.getSpecificMethod(methods);
387             CACHE.put(key, result);
388             return result;
389         }
390     }
391 
392     /**
393      * Returns whether a declaringClass is accessible according to the modifiers.
394      * @param modifiers int; the modifiers
395      * @param declaringClass Class&lt;?&gt;; the declaringClass
396      * @param caller Class&lt;?&gt;; the caller
397      * @return boolean isVisible
398      */
399     public static boolean isVisible(final int modifiers, final Class<?> declaringClass, final Class<?> caller)
400     {
401         if (Modifier.isPublic(modifiers))
402         {
403             return true;
404         }
405         if (Modifier.isProtected(modifiers))
406         {
407             if (declaringClass.isAssignableFrom(caller))
408             {
409                 return true;
410             }
411             if (declaringClass.getPackage().equals(caller.getPackage()))
412             {
413                 return true;
414             }
415             return false;
416         }
417         if (declaringClass.equals(caller))
418         {
419             return true;
420         }
421         return false;
422     }
423 
424     /**
425      * Determines &amp; returns whether constructor 'a' is more specific than constructor 'b', as defined in the Java
426      * Language Specification ???15.12.
427      * @return true if 'a' is more specific than b, false otherwise. 'false' is also returned when constructors are
428      *         incompatible, e.g. have different names or a different number of parameters.
429      * @param a Class&lt;?&gt;[]; reflects the first constructor
430      * @param b Class&lt;?&gt;[]; reflects the second constructor
431      */
432     public static boolean isMoreSpecific(final Class<?>[] a, final Class<?>[] b)
433     {
434         if (a.length != b.length)
435         {
436             return false;
437         }
438         int i = 0;
439         while (i < a.length)
440         {
441             if (!b[i].isAssignableFrom(a[i]))
442             {
443                 return false;
444             }
445             i++;
446         }
447         return true;
448     }
449 
450     /**
451      * Determines &amp; returns whether constructor 'a' is more specific than constructor 'b', as defined in the Java
452      * Language Specification ???15.12.
453      * @return true if 'a' is more specific than b, false otherwise. 'false' is also returned when constructors are
454      *         incompatible, e.g. have different names or a different number of parameters.
455      * @param a Constructor&lt;?&gt;; reflects the first constructor
456      * @param b Constructor&lt;?&gt;; reflects the second constructor
457      */
458     public static boolean isMoreSpecific(final Constructor<?> a, final Constructor<?> b)
459     {
460         if (a.getParameterTypes().equals(b.getParameterTypes()))
461         {
462             if (b.getDeclaringClass().isAssignableFrom(a.getDeclaringClass()))
463             {
464                 return true;
465             }
466         }
467         return ClassUtil.isMoreSpecific(a.getParameterTypes(), b.getParameterTypes());
468     }
469 
470     /**
471      * Determines &amp; returns whether constructor 'a' is more specific than constructor 'b', as defined in the Java
472      * Language Specification ???15.12.
473      * @return true if 'a' is more specific than b, false otherwise. 'false' is also returned when constructors are
474      *         incompatible, e.g. have different names or a different number of parameters.
475      * @param a Method; reflects the first method
476      * @param b Method; reflects the second method
477      */
478     public static boolean isMoreSpecific(final Method a, final Method b)
479     {
480         if (!a.getName().equals(b.getName()))
481         {
482             return false;
483         }
484         return ClassUtil.isMoreSpecific(a.getParameterTypes(), b.getParameterTypes());
485     }
486 
487     /**
488      * Returns whether a field is visible for a caller.
489      * @param field Field; The field
490      * @param caller Class&lt;?&gt;; The class of the caller for whom invocation visibility is checked.
491      * @return boolean yes or no
492      */
493     public static boolean isVisible(final Field field, final Class<?> caller)
494     {
495         return ClassUtil.isVisible(field.getModifiers(), field.getDeclaringClass(), caller);
496     }
497 
498     /**
499      * Returns whether a constructor is visible for a caller.
500      * @param constructor Constructor&lt;?&gt;; The constructor
501      * @param caller Class&lt;?&gt;; The class of the caller for whom invocation visibility is checked.
502      * @return boolean yes or no
503      */
504     public static boolean isVisible(final Constructor<?> constructor, final Class<?> caller)
505     {
506         return ClassUtil.isVisible(constructor.getModifiers(), constructor.getDeclaringClass(), caller);
507     }
508 
509     /**
510      * Returns whether a method is visible for a caller.
511      * @param method Method; The method
512      * @param caller Class&lt;?&gt;; The class of the caller for whom invocation visibility is checked.
513      * @return boolean yes or no
514      */
515     public static boolean isVisible(final Method method, final Class<?> caller)
516     {
517         return ClassUtil.isVisible(method.getModifiers(), method.getDeclaringClass(), caller);
518     }
519 
520     /**
521      * Filters an array methods for signatures that are compatible with a given signature.
522      * @param methods Method[]; which are methods to be filtered.
523      * @param name String; reflects the method's name, part of the signature
524      * @param argTypes Class&lt;?&gt;[]; are the method's argument types
525      * @return Method[] An unordered Method-array consisting of the elements of 'methods' that match with the given
526      *         signature. An array with 0 elements is returned when no matching Method objects are found.
527      */
528     public static Method[] matchSignature(final Method[] methods, final String name, final Class<?>[] argTypes)
529     {
530         List<Method> results = new ArrayList<Method>();
531         for (int i = 0; i < methods.length; i++)
532         {
533             if (ClassUtil.matchSignature(methods[i], name, argTypes))
534             {
535                 results.add(methods[i]);
536             }
537         }
538         return results.toArray(new Method[results.size()]);
539     }
540 
541     /**
542      * Filters an array methods for signatures that are compatible with a given signature.
543      * @param method Method; The method to be filtered.
544      * @param name String; reflects the method's name, part of the signature
545      * @param argTypes Class&lt;?&gt;[]; are the method's argument types
546      * @return boolean if methodParameters assignable from argTypes
547      */
548     public static boolean matchSignature(final Method method, final String name, final Class<?>[] argTypes)
549     {
550         if (!method.getName().equals(name))
551         {
552             return false;
553         }
554         if (method.getParameterTypes().length != argTypes.length)
555         {
556             return false;
557         }
558         Class<?>[] types = method.getParameterTypes();
559         for (int i = 0; i < method.getParameterTypes().length; i++)
560         {
561             if (!(types[i].isAssignableFrom(argTypes[i]) || types[i].equals(Primitive.getPrimitive(argTypes[i]))))
562             {
563                 return false;
564             }
565         }
566         return true;
567     }
568 
569     /**
570      * Filters an array methods for signatures that are compatible with a given signature.
571      * @param constructor Constructor&lt;?&gt;; which are constructors to be filtered.
572      * @param argTypes Class&lt;?&gt;[]; are the constructor's argument types
573      * @return boolean if methodParameters assignable from argTypes
574      */
575     public static boolean matchSignature(final Constructor<?> constructor, final Class<?>[] argTypes)
576     {
577         if (constructor.getParameterTypes().length != argTypes.length)
578         {
579             return false;
580         }
581         Class<?>[] types = constructor.getParameterTypes();
582         for (int i = 0; i < constructor.getParameterTypes().length; i++)
583         {
584             if (!(types[i].isAssignableFrom(argTypes[i]) || types[i].equals(Primitive.getPrimitive(argTypes[i]))))
585             {
586                 return false;
587             }
588         }
589         return true;
590     }
591 
592     /**
593      * Filters an array methods for signatures that are compatible with a given signature.
594      * @param constructors Constructor&lt;?&gt;[]; which are constructors to be filtered.
595      * @param argTypes Class&lt;?&gt;[]; are the constructor's argument types
596      * @return Constructor&lt;?&gt;[] An unordered Constructor-array consisting of the elements of 'constructors' that
597      *         match with the given signature. An array with 0 elements is returned when no matching Method objects are
598      *         found.
599      */
600     public static Constructor<?>[] matchSignature(final Constructor<?>[] constructors, final Class<?>[] argTypes)
601     {
602         List<Constructor<?>> results = new ArrayList<Constructor<?>>();
603         for (int i = 0; i < constructors.length; i++)
604         {
605             if (ClassUtil.matchSignature(constructors[i], argTypes))
606             {
607                 results.add(constructors[i]);
608             }
609         }
610         return results.toArray(new Constructor[results.size()]);
611     }
612 
613     /**
614      * converts an array of objects to their corresponding classes.
615      * @param array Object[]; the array to invoke
616      * @return Class&lt;?&gt;[] the result;
617      */
618     public static Class<?>[] getClass(final Object[] array)
619     {
620         if (array == null)
621         {
622             return new Class[0];
623         }
624         Class<?>[] result = new Class[array.length];
625         for (int i = 0; i < result.length; i++)
626         {
627             if (array[i] == null)
628             {
629                 result[i] = null;
630             }
631             else
632             {
633                 result[i] = array[i].getClass();
634             }
635         }
636         return result;
637     }
638 
639     /** ************** PRIVATE METHODS ********* */
640 
641     /**
642      * checks the input of an array.
643      * @param array Object[]; the array
644      * @param myClass Class&lt;?&gt;; the class of the result
645      * @return Returns array if array!=null else returns myClass[0]
646      */
647     private static Object checkInput(final Object[] array, final Class<?> myClass)
648     {
649         if (array != null)
650         {
651             return array;
652         }
653         return Array.newInstance(myClass, 0);
654     }
655 
656     /**
657      * Determines & returns the most specific constructor as defined in the Java Language Specification par 15.12. The
658      * current algorithm is simple and reliable, but probably slow.
659      * @param methods Constructor&lt;?&gt;[]; are the constructors to be searched. They are assumed to have the same
660      *            name and number of parameters, as determined by the constructor matchSignature.
661      * @return Constructor which is the most specific constructor.
662      * @throws NoSuchMethodException when no constructor is found that's more specific than the others.
663      */
664     private static Constructor<?> getSpecificConstructor(final Constructor<?>[] methods) throws NoSuchMethodException
665     {
666         if (methods.length == 0)
667         {
668             throw new NoSuchMethodException();
669         }
670         if (methods.length == 1)
671         {
672             return methods[0];
673         }
674         // Apply generic algorithm
675         int resultID = 0; // Assume first method to be most specific
676         while (resultID < methods.length)
677         {
678             // Verify assumption
679             boolean success = true;
680             for (int i = 0; i < methods.length; i++)
681             {
682                 if (resultID == i)
683                 {
684                     continue;
685                 }
686                 if (!isMoreSpecific(methods[resultID], methods[i]))
687                 {
688                     success = false;
689                 }
690             }
691             // Assumption verified
692             if (success)
693             {
694                 return methods[resultID];
695             }
696             resultID++;
697         }
698         // No method is most specific, thus:
699         throw new NoSuchMethodException();
700     }
701 
702     /**
703      * Determines & returns the most specific method as defined in the Java Language Specification par 15.12. The
704      * current algorithm is simple and reliable, but probably slow.
705      * @param methods Method[]; which are the methods to be searched. They are assumed to have the same name and number
706      *            of parameters, as determined by the method matchSignature.
707      * @return The most specific method.
708      * @throws NoSuchMethodException when no method is found that's more specific than the others.
709      */
710     private static Method getSpecificMethod(final Method[] methods) throws NoSuchMethodException
711     {
712         // Check for evident cases
713         if (methods.length == 0)
714         {
715             throw new NoSuchMethodException();
716         }
717         if (methods.length == 1)
718         {
719             return methods[0];
720         }
721         // Apply generic algorithm
722         int resultID = 0; // Assume first method to be most specific
723         while (resultID < methods.length)
724         {
725             // Verify assumption
726             boolean success = true;
727             for (int i = 0; i < methods.length; i++)
728             {
729                 if (resultID == i)
730                 {
731                     continue;
732                 }
733                 if (!isMoreSpecific(methods[resultID], methods[i]))
734                 {
735                     success = false;
736                 }
737             }
738             // Assumption verified
739             if (success)
740             {
741                 return methods[resultID];
742             }
743             resultID++;
744         }
745         // No method is most specific, thus:
746         throw new NoSuchMethodException();
747     }
748 
749     /**
750      * returns the constructor.
751      * @param clazz Class&lt;?&gt;; the class to start with
752      * @param parameterTypes Class&lt;?&gt;[]; the parameterTypes
753      * @return Method
754      * @throws NoSuchMethodException if the method cannot be resolved
755      */
756     private static Constructor<?> resolveConstructorSuper(final Class<?> clazz, final Class<?>[] parameterTypes)
757             throws NoSuchMethodException
758     {
759         String key = "CONSTRUCTOR:" + clazz + "@" + FieldSignature.toDescriptor(parameterTypes);
760         try
761         {
762             if (CACHE.containsKey(key))
763             {
764                 return (Constructor<?>) CACHE.get(key);
765             }
766             Constructor<?> constructor = clazz.getDeclaredConstructor(parameterTypes);
767             CACHE.put(key, constructor);
768             return constructor;
769         }
770         catch (Exception exception)
771         {
772             if (clazz.getSuperclass() != null)
773             {
774                 Constructor<?> constructor = ClassUtil.resolveConstructorSuper(clazz.getSuperclass(), parameterTypes);
775                 CACHE.put(key, constructor);
776                 return constructor;
777             }
778             throw new NoSuchMethodException(exception.getMessage());
779         }
780     }
781 
782     /**
783      * returns the interface method.
784      * @param clazz Class&lt;?&gt;; the class to start with
785      * @param name String; the name of the method
786      * @param parameterTypes Class&lt;?&gt;[]; the parameterTypes
787      * @return Method
788      * @throws NoSuchMethodException on lookup failure
789      */
790     private static Method resolveMethodSuper(final Class<?> clazz, final String name, final Class<?>[] parameterTypes)
791             throws NoSuchMethodException
792     {
793         String key = "METHOD:" + clazz + "@" + name + "@" + FieldSignature.toDescriptor(parameterTypes);
794         try
795         {
796             if (CACHE.containsKey(key))
797             {
798                 return (Method) CACHE.get(key);
799             }
800             Method method = clazz.getDeclaredMethod(name, parameterTypes);
801             CACHE.put(key, method);
802             return method;
803         }
804         catch (Exception exception)
805         {
806             if (clazz.getSuperclass() != null)
807             {
808                 Method method = ClassUtil.resolveMethodSuper(clazz.getSuperclass(), name, parameterTypes);
809                 CACHE.put(key, method);
810                 return method;
811             }
812             throw new NoSuchMethodException(exception.getMessage());
813         }
814     }
815 
816     /**
817      * resolves the field for a class, taking into account superclasses.
818      * @param clazz Class&lt;?&gt;; the class for which superclasses will be probed
819      * @param fieldName String; the name of the field to resolve
820      * @return the field (if found)
821      * @throws NoSuchFieldException if the field cannot be resolved
822      */
823     private static Field resolveFieldSuper(final Class<?> clazz, final String fieldName) throws NoSuchFieldException
824     {
825         String key = "FIELD:" + clazz + "@" + fieldName;
826         try
827         {
828             if (CACHE.containsKey(key))
829             {
830                 return (Field) CACHE.get(key);
831             }
832             Field result = clazz.getDeclaredField(fieldName);
833             CACHE.put(key, result);
834             return result;
835         }
836         catch (Exception exception)
837         {
838             if (clazz.getSuperclass() != null)
839             {
840                 Field result = ClassUtil.resolveFieldSuper(clazz.getSuperclass(), fieldName);
841                 CACHE.put(key, result);
842                 return result;
843             }
844             throw new NoSuchFieldException(exception.getMessage());
845         }
846     }
847 }