View Javadoc
1   package org.djutils.reflection;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.lang.annotation.Annotation;
6   import java.lang.reflect.Array;
7   import java.lang.reflect.Constructor;
8   import java.lang.reflect.Field;
9   import java.lang.reflect.Method;
10  import java.lang.reflect.Modifier;
11  import java.lang.reflect.Proxy;
12  import java.net.URISyntaxException;
13  import java.net.URL;
14  import java.nio.file.Files;
15  import java.nio.file.Paths;
16  import java.nio.file.attribute.BasicFileAttributes;
17  import java.util.ArrayList;
18  import java.util.Arrays;
19  import java.util.Collections;
20  import java.util.HashMap;
21  import java.util.HashSet;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.NoSuchElementException;
25  import java.util.Set;
26  import java.util.jar.JarEntry;
27  import java.util.jar.JarFile;
28  import java.util.zip.ZipEntry;
29  
30  import org.djutils.io.URLResource;
31  import org.djutils.primitives.Primitive;
32  
33  /**
34   * ClassUtil is a utility class providing assistance for Java Classes.
35   * <p>
36   * Copyright (c) 2002-2019 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
37   * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
38   * distributed under a three-clause BSD-style license, which can be found at
39   * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>.
40   * </p>
41   * @author Peter Jacobs, Niels Lang, Alexander Verbraeck
42   */
43  public final class ClassUtil
44  {
45      /** CACHE reflects the internal repository CACHE. */
46      private static final Map<String, Object> CACHE = Collections.synchronizedMap(new HashMap<String, Object>());
47  
48      /**
49       * constructs a new ClassUtil.
50       */
51      private ClassUtil()
52      {
53          super();
54          // unreachable code
55      }
56  
57      /** ************ CONSTRUCTOR UTILITIES *********** */
58  
59      /**
60       * Returns all the constructors of a class. Public, package, protected and private constructors are returned. This method
61       * returns an array of length 0 if the class object represents an interface, a primitive type, an array class, or void. Note
62       * that the constructors of the superclass are not returned as these can never be invoked.
63       * @param clazz Class&lt;?&gt;; the class to resolve the constructors for.
64       * @return an array with all constructors
65       */
66      @SuppressWarnings("unchecked")
67      public static <T> Constructor<T>[] getAllConstructors(final Class<T> clazz)
68      {
69          return (Constructor<T>[]) clazz.getDeclaredConstructors();
70      }
71  
72      /**
73       * Returns a constructor with the given signature, possibly from the cache. When the constructor is resolved for the first
74       * time, it will be added to the cache.
75       * @param clazz Class&lt;?&gt;; the class to resolve the constructor for
76       * @param parameterTypes Class&lt;?&gt;[]; the parameter types of the signature
77       * @return the constructor with the given signature, if found
78       * @throws NoSuchMethodException if the constructor cannot be resolved
79       */
80      @SuppressWarnings("unchecked")
81      private static <T> Constructor<T> resolveConstructorCache(final Class<T> clazz, final Class<?>[] parameterTypes)
82              throws NoSuchMethodException
83      {
84          String key = "CONSTRUCTOR:" + clazz + "@" + FieldSignature.toDescriptor(parameterTypes);
85          if (CACHE.containsKey(key))
86          {
87              return (Constructor<T>) CACHE.get(key);
88          }
89          Constructor<T> constructor = clazz.getDeclaredConstructor(parameterTypes);
90          CACHE.put(key, constructor);
91          return constructor;
92      }
93  
94      /**
95       * Returns the constructor of a class with a particular signature if and only if the caller class can invoke that
96       * constructor. So a private constructor will not be invoked, unless the caller class is the class that defines the
97       * constructor. For a protected class the superclass and classes in the same package can invoke the constructor, but other
98       * classes should not be able to invoke the constructor.
99       * @param clazz Class&lt;?&gt;; the class for which the constructor needs to be
100      * @param callerClass Class&lt;?&gt;; the calling class for which the test is carried out whether the constructor is visible
101      * @param parameterTypes Class&lt;?&gt;[]; the parameter types for the constructor's signature
102      * @return the retrieved constructor
103      * @throws NoSuchMethodException if the constructor with the given signature does not exist
104      * @throws IllegalAccessException if the constructor exists but is not callable from the callerClass
105      */
106     public static <T> Constructor<T> resolveConstructor(final Class<T> clazz, final Class<?> callerClass,
107             final Class<?>[] parameterTypes) throws NoSuchMethodException, IllegalAccessException
108     {
109         Constructor<T> constructor = ClassUtil.resolveConstructor(clazz, parameterTypes);
110         if (ClassUtil.isVisible(constructor, callerClass.getClass()))
111         {
112             return constructor;
113         }
114         throw new IllegalAccessException("constructor resolved but not visible");
115     }
116 
117     /**
118      * returns the interface method.
119      * @param clazz Class&lt;?&gt;; the class to start with
120      * @param parameterTypes Class&lt;?&gt;[]; the parameterTypes
121      * @return Constructor
122      * @throws NoSuchMethodException if the method cannot be resolved
123      */
124     @SuppressWarnings("unchecked")
125     public static <T> Constructor<T> resolveConstructor(final Class<T> clazz, final Class<?>[] parameterTypes)
126             throws NoSuchMethodException
127     {
128         try
129         {
130             return resolveConstructorCache(clazz, (Class<?>[]) ClassUtil.checkInput(parameterTypes, Class.class));
131         }
132         catch (Exception exception)
133         {
134             String className = clazz.getName();
135             if (className.indexOf("$") >= 0)
136             {
137                 Class<?> parentClass = null;
138                 try
139                 {
140                     parentClass = Class.forName(className.substring(0, className.lastIndexOf("$")));
141                 }
142                 catch (Exception e2)
143                 {
144                     throw new NoSuchMethodException("class " + parentClass + " not found to resolve constructor");
145                 }
146                 return (Constructor<T>) ClassUtil.resolveConstructor(parentClass,
147                         (Class<?>[]) ClassUtil.checkInput(parameterTypes, Class.class));
148             }
149             throw new NoSuchMethodException("class " + clazz + " does not contain constructor");
150         }
151     }
152 
153     /**
154      * returns the constructor.
155      * @param clazz Class&lt;?&gt;; the clazz to start with
156      * @param arguments Object[]; the arguments
157      * @return Constructor
158      * @throws NoSuchMethodException on lookup failure
159      */
160     @SuppressWarnings("unchecked")
161     public static <T> Constructor<T> resolveConstructor(final Class<T> clazz, final Object[] arguments)
162             throws NoSuchMethodException
163     {
164         Class<?>[] parameterTypes = ClassUtil.getClass(arguments);
165         String key = "CONSTRUCTOR:" + clazz + "@" + FieldSignature.toDescriptor(parameterTypes);
166         if (CACHE.containsKey(key))
167         {
168             return (Constructor<T>) CACHE.get(key);
169         }
170         try
171         {
172             return ClassUtil.resolveConstructor(clazz, parameterTypes);
173         }
174         catch (NoSuchMethodException noSuchMethodException)
175         {
176             // We get all constructors
177             Constructor<T>[] constructors = ClassUtil.getAllConstructors(clazz);
178             // now we match the signatures
179             constructors = ClassUtil.matchSignature(constructors, parameterTypes);
180             // Now we find the most specific
181             Constructor<T> result = (Constructor<T>) ClassUtil.getSpecificConstructor(constructors);
182             CACHE.put(key, result);
183             return result;
184         }
185     }
186 
187     /**
188      * Filters an array methods for signatures that are compatible with a given signature.
189      * @param constructor Constructor&lt;?&gt;; which are constructors to be filtered.
190      * @param argTypes Class&lt;?&gt;[]; are the constructor's argument types
191      * @return boolean if methodParameters assignable from argTypes
192      */
193     public static <T> boolean matchSignature(final Constructor<T> constructor, final Class<?>[] argTypes)
194     {
195         if (constructor.getParameterTypes().length != argTypes.length)
196         {
197             return false;
198         }
199         Class<?>[] types = constructor.getParameterTypes();
200         for (int i = 0; i < constructor.getParameterTypes().length; i++)
201         {
202             if (!(types[i].isAssignableFrom(argTypes[i]) || types[i].equals(Primitive.getPrimitive(argTypes[i]))))
203             {
204                 return false;
205             }
206         }
207         return true;
208     }
209 
210     /**
211      * Filters an array methods for signatures that are compatible with a given signature.
212      * @param constructors Constructor&lt;?&gt;[]; which are constructors to be filtered.
213      * @param argTypes Class&lt;?&gt;[]; are the constructor's argument types
214      * @return Constructor&lt;?&gt;[] An unordered Constructor-array consisting of the elements of 'constructors' that match
215      *         with the given signature. An array with 0 elements is returned when no matching Method objects are found.
216      */
217     @SuppressWarnings("unchecked")
218     public static <T> Constructor<T>[] matchSignature(final Constructor<T>[] constructors, final Class<?>[] argTypes)
219     {
220         List<Constructor<T>> results = new ArrayList<Constructor<T>>();
221         for (int i = 0; i < constructors.length; i++)
222         {
223             if (ClassUtil.matchSignature(constructors[i], argTypes))
224             {
225                 results.add(constructors[i]);
226             }
227         }
228         return results.toArray(new Constructor[results.size()]);
229     }
230 
231     /* ************ FIELD UTILITIES *********** */
232 
233     /**
234      * gets all the fields of a class (public, protected, package, and private) and adds the result to the return value.
235      * @param clazz Class&lt;?&gt;; the class
236      * @param result Set&lt;Field&gt;; the resulting set
237      * @return the set of fields including all fields of the field clazz
238      */
239     public static Set<Field> getAllFields(final Class<?> clazz, final Set<Field> result)
240     {
241         Field[] fields = clazz.getDeclaredFields();
242         for (int i = 0; i < fields.length; i++)
243         {
244             result.add(fields[i]);
245         }
246         if (clazz.getSuperclass() != null)
247         {
248             return ClassUtil.getAllFields(clazz.getSuperclass(), result);
249         }
250         return result;
251     }
252 
253     /**
254      * gets all the fields of a class (public, protected, package, and private).
255      * @param clazz Class&lt;?&gt;; the class
256      * @return all fields of the class
257      */
258     public static Set<Field> getAllFields(final Class<?> clazz)
259     {
260         Set<Field> fieldSet = new HashSet<Field>();
261         return ClassUtil.getAllFields(clazz, fieldSet);
262     }
263 
264     /**
265      * resolves the field for a class, taking into account inner classes.
266      * @param clazz the class to resolve the field for, including inner classes
267      * @param fieldName name of the field
268      * @return Field the field
269      * @throws NoSuchFieldException on no such field
270      */
271 
272     public static Field resolveField(final Class<?> clazz, final String fieldName) throws NoSuchFieldException
273     {
274         try
275         {
276             return resolveFieldSuper(clazz, fieldName);
277         }
278         catch (NoSuchFieldException noSuchFieldException)
279         {
280             String className = clazz.getName();
281             if (className.indexOf("$") >= 0)
282             {
283                 Class<?> clazz2 = null;
284                 try
285                 {
286                     clazz2 = Class.forName(className.substring(0, className.lastIndexOf("$")));
287                 }
288                 catch (ClassNotFoundException classNotFoundException)
289                 {
290                     throw new NoSuchFieldException("class " + clazz + " not found to resolve field " + fieldName);
291                 }
292                 return ClassUtil.resolveField(clazz2, fieldName);
293             }
294             throw new NoSuchFieldException("class " + clazz + " does not contain field " + fieldName);
295         }
296     }
297 
298     /**
299      * returns the field.
300      * @param clazz Class&lt;?&gt;; the class to start with
301      * @param callerClass Class&lt;?&gt;; the calling class
302      * @param name String; the fieldName
303      * @return Constructor
304      * @throws NoSuchFieldException if the method cannot be resolved
305      */
306     public static Field resolveField(final Class<?> clazz, final Class<?> callerClass, final String name)
307             throws NoSuchFieldException
308     {
309         Field field = ClassUtil.resolveField(clazz, name);
310         if (ClassUtil.isVisible(field, callerClass.getClass()))
311         {
312             return field;
313         }
314         throw new NoSuchFieldException("field resolved but not visible");
315     }
316 
317     /**
318      * resolves the field for a given object instance.
319      * @param object Object; the object to resolve the field for
320      * @param fieldName String; name of the field to resolve
321      * @return the field (if found)
322      * @throws NoSuchFieldException if the field cannot be resolved
323      */
324     public static Field resolveField(final Object object, final String fieldName) throws NoSuchFieldException
325     {
326         if (object == null)
327         {
328             throw new NoSuchFieldException("resolveField: object is null for field " + fieldName);
329         }
330         return resolveField(object.getClass(), fieldName);
331     }
332 
333     /** ************ METHOD UTILITIES *********** */
334     /**
335      * gets all the methods of a class and adds the result to result.
336      * @param clazz Class&lt;?&gt;; the class
337      * @param name String; the name of the method
338      * @param result Method[]; the resulting set
339      * @return result
340      */
341     public static Method[] getAllMethods(final Class<?> clazz, final String name, final Method[] result)
342     {
343         List<Method> list = new ArrayList<Method>(Arrays.asList(result));
344         Method[] methods = clazz.getDeclaredMethods();
345         for (int i = 0; i < methods.length; i++)
346         {
347             if (methods[i].getName().equals(name))
348             {
349                 list.add(methods[i]);
350             }
351         }
352         if (clazz.getSuperclass() != null)
353         {
354             return ClassUtil.getAllMethods(clazz.getSuperclass(), name, list.toArray(new Method[list.size()]));
355         }
356         return list.toArray(new Method[list.size()]);
357     }
358 
359     /**
360      * returns the interface method.
361      * @param clazz Class&lt;?&gt;; the class to start with
362      * @param callerClass Class&lt;?&gt;; the caller class
363      * @param name String; the name of the method
364      * @param parameterTypes Class&lt;?&gt;[]; the parameterTypes
365      * @return Method
366      * @throws NoSuchMethodException on lookup failure
367      */
368     public static Method resolveMethod(final Class<?> clazz, final Class<?> callerClass, final String name,
369             final Class<?>[] parameterTypes) throws NoSuchMethodException
370     {
371         Method method = ClassUtil.resolveMethod(clazz, name, parameterTypes);
372         if (ClassUtil.isVisible(method, callerClass))
373         {
374             return method;
375         }
376         throw new NoSuchMethodException("method found but not visible");
377     }
378 
379     /**
380      * returns the interface method.
381      * @param clazz Class&lt;?&gt;; the class to start with
382      * @param name String; the name of the method
383      * @param parameterTypes Class&lt;?&gt;[]; the parameterTypes
384      * @return Method
385      * @throws NoSuchMethodException on lookup failure
386      */
387     public static Method resolveMethod(final Class<?> clazz, final String name, final Class<?>[] parameterTypes)
388             throws NoSuchMethodException
389     {
390         try
391         {
392             return resolveMethodSuper(clazz, name, (Class<?>[]) ClassUtil.checkInput(parameterTypes, Class.class));
393         }
394         catch (Exception exception)
395         {
396             String className = clazz.getName();
397             if (className.indexOf("$") >= 0)
398             {
399                 Class<?> parentClass = null;
400                 try
401                 {
402                     parentClass = Class.forName(className.substring(0, className.lastIndexOf("$")));
403                 }
404                 catch (Exception e2)
405                 {
406                     throw new NoSuchMethodException("class " + parentClass + " not found to resolve method " + name);
407                 }
408                 return ClassUtil.resolveMethod(parentClass, name,
409                         (Class<?>[]) ClassUtil.checkInput(parameterTypes, Class.class));
410             }
411             throw new NoSuchMethodException("class " + clazz + " does not contain method " + name);
412         }
413     }
414 
415     /**
416      * resolves a method the method.
417      * @param object Object; the object to start with
418      * @param name String; the name of the method
419      * @param parameterTypes Class&lt;?&gt;[]; the parameterTypes
420      * @return Method
421      * @throws NoSuchMethodException on lookup failure
422      */
423     public static Method resolveMethod(final Object object, final String name, final Class<?>[] parameterTypes)
424             throws NoSuchMethodException
425     {
426         if (object == null)
427         {
428             throw new NoSuchMethodException("resolveField: object is null for method " + name);
429         }
430         return resolveMethod(object.getClass(), name, parameterTypes);
431     }
432 
433     /**
434      * returns the method.
435      * @param object Object; the object to start with
436      * @param name String; the name of the method
437      * @param arguments Object[]; the arguments
438      * @return Method
439      * @throws NoSuchMethodException on lookup failure
440      */
441     public static Method resolveMethod(final Object object, final String name, final Object[] arguments)
442             throws NoSuchMethodException
443     {
444         Class<?>[] parameterTypes = ClassUtil.getClass(arguments);
445         String key = "METHOD:" + object.getClass() + "@" + name + "@" + FieldSignature.toDescriptor(parameterTypes);
446         if (CACHE.containsKey(key))
447         {
448             return (Method) CACHE.get(key);
449         }
450         try
451         {
452             return ClassUtil.resolveMethod(object, name, parameterTypes);
453         }
454         catch (NoSuchMethodException noSuchMethodException)
455         {
456             // We get all methods
457             Method[] methods = ClassUtil.getAllMethods(object.getClass(), name, new Method[0]);
458             if (methods.length == 0)
459             {
460                 throw new NoSuchMethodException("No such method: " + name + " for object " + object);
461             }
462             // now we match the signatures
463             methods = ClassUtil.matchSignature(methods, name, parameterTypes);
464             if (methods.length == 0)
465             {
466                 throw new NoSuchMethodException("No method with right signature: " + name + " for object " + object);
467             }
468             // Now we find the most specific
469             Method result = ClassUtil.getSpecificMethod(methods);
470             CACHE.put(key, result);
471             return result;
472         }
473     }
474 
475     /* ************ ANNOTATION UTILITIES *********** */
476 
477     /**
478      * gets all the annotations of a class (public, protected, package, and private) and adds the result to the return value.
479      * @param clazz Class&lt;?&gt;; the class
480      * @param result Set&lt;Annotation&gt;; the resulting set
481      * @return the set of annotations including all annotations of the annotation clazz
482      */
483     public static Set<Annotation> getAllAnnotations(final Class<?> clazz, final Set<Annotation> result)
484     {
485         Annotation[] annotations = clazz.getDeclaredAnnotations();
486         for (int i = 0; i < annotations.length; i++)
487         {
488             result.add(annotations[i]);
489         }
490         if (clazz.getSuperclass() != null)
491         {
492             return ClassUtil.getAllAnnotations(clazz.getSuperclass(), result);
493         }
494         return result;
495     }
496 
497     /**
498      * gets all the annotations of a class (public, protected, package, and private).
499      * @param clazz Class&lt;?&gt;; the class
500      * @return all annotations of the class
501      */
502     public static Set<Annotation> getAllAnnotations(final Class<?> clazz)
503     {
504         Set<Annotation> annotationSet = new HashSet<Annotation>();
505         return ClassUtil.getAllAnnotations(clazz, annotationSet);
506     }
507 
508     /**
509      * resolves the annotation for a class, taking into account inner classes.
510      * @param clazz the class to resolve the annotation for, including inner classes
511      * @param annotationClass class of the annotation
512      * @return Annotation the annotation
513      * @throws NoSuchElementException on no such annotation
514      */
515 
516     public static Annotation resolveAnnotation(final Class<?> clazz, final Class<? extends Annotation> annotationClass)
517             throws NoSuchElementException
518     {
519         try
520         {
521             return resolveAnnotationSuper(clazz, annotationClass);
522         }
523         catch (NoSuchElementException noSuchAnnotationException)
524         {
525             String className = clazz.getName();
526             if (className.indexOf("$") >= 0)
527             {
528                 Class<?> clazz2 = null;
529                 try
530                 {
531                     clazz2 = Class.forName(className.substring(0, className.lastIndexOf("$")));
532                 }
533                 catch (ClassNotFoundException classNotFoundException)
534                 {
535                     throw new NoSuchElementException("class " + clazz + " not found to resolve annotation " + annotationClass);
536                 }
537                 return ClassUtil.resolveAnnotation(clazz2, annotationClass);
538             }
539             throw new NoSuchElementException("class " + clazz + " does not contain annotation " + annotationClass);
540         }
541     }
542 
543     /**
544      * resolves the annotation for a given object instance.
545      * @param object Object; the object to resolve the annotation for
546      * @param annotationName String; name of the annotation to resolve
547      * @return the annotation (if found)
548      * @throws NoSuchElementException if the annotation cannot be resolved
549      */
550     public static Annotation resolveAnnotation(final Object object, final String annotationName) throws NoSuchElementException
551     {
552         if (object == null)
553         {
554             throw new NoSuchElementException("resolveAnnotation: object is null for annotation " + annotationName);
555         }
556         return resolveAnnotation(object.getClass(), annotationName);
557     }
558 
559     /* ************ OTHER UTILITIES *********** */
560 
561     /**
562      * Returns whether a declaringClass is accessible according to the modifiers.
563      * @param modifiers int; the modifiers
564      * @param declaringClass Class&lt;?&gt;; the declaringClass
565      * @param caller Class&lt;?&gt;; the caller
566      * @return boolean isVisible
567      */
568     public static boolean isVisible(final int modifiers, final Class<?> declaringClass, final Class<?> caller)
569     {
570         if (Modifier.isPublic(modifiers))
571         {
572             return true;
573         }
574         if (Modifier.isProtected(modifiers))
575         {
576             if (declaringClass.isAssignableFrom(caller))
577             {
578                 return true;
579             }
580             if (declaringClass.getPackage().equals(caller.getPackage()))
581             {
582                 return true;
583             }
584             return false;
585         }
586         if (declaringClass.equals(caller))
587         {
588             return true;
589         }
590         return false;
591     }
592 
593     /**
594      * Determines &amp; returns whether constructor 'a' is more specific than constructor 'b', as defined in the Java Language
595      * Specification ???15.12.
596      * @return true if 'a' is more specific than b, false otherwise. 'false' is also returned when constructors are
597      *         incompatible, e.g. have different names or a different number of parameters.
598      * @param a Class&lt;?&gt;[]; reflects the first constructor
599      * @param b Class&lt;?&gt;[]; reflects the second constructor
600      */
601     public static boolean isMoreSpecific(final Class<?>[] a, final Class<?>[] b)
602     {
603         if (a.length != b.length)
604         {
605             return false;
606         }
607         int i = 0;
608         while (i < a.length)
609         {
610             if (!b[i].isAssignableFrom(a[i]))
611             {
612                 return false;
613             }
614             i++;
615         }
616         return true;
617     }
618 
619     /**
620      * Determines &amp; returns whether constructor 'a' is more specific than constructor 'b', as defined in the Java Language
621      * Specification ???15.12.
622      * @return true if 'a' is more specific than b, false otherwise. 'false' is also returned when constructors are
623      *         incompatible, e.g. have different names or a different number of parameters.
624      * @param a Constructor&lt;?&gt;; reflects the first constructor
625      * @param b Constructor&lt;?&gt;; reflects the second constructor
626      */
627     public static boolean isMoreSpecific(final Constructor<?> a, final Constructor<?> b)
628     {
629         if (a.getParameterTypes().equals(b.getParameterTypes()))
630         {
631             if (b.getDeclaringClass().isAssignableFrom(a.getDeclaringClass()))
632             {
633                 return true;
634             }
635         }
636         return ClassUtil.isMoreSpecific(a.getParameterTypes(), b.getParameterTypes());
637     }
638 
639     /**
640      * Determines &amp; returns whether constructor 'a' is more specific than constructor 'b', as defined in the Java Language
641      * Specification ???15.12.
642      * @return true if 'a' is more specific than b, false otherwise. 'false' is also returned when constructors are
643      *         incompatible, e.g. have different names or a different number of parameters.
644      * @param a Method; reflects the first method
645      * @param b Method; reflects the second method
646      */
647     public static boolean isMoreSpecific(final Method a, final Method b)
648     {
649         if (!a.getName().equals(b.getName()))
650         {
651             return false;
652         }
653         return ClassUtil.isMoreSpecific(a.getParameterTypes(), b.getParameterTypes());
654     }
655 
656     /**
657      * Returns whether a field is visible for a caller.
658      * @param field Field; The field
659      * @param caller Class&lt;?&gt;; The class of the caller for whom invocation visibility is checked.
660      * @return boolean yes or no
661      */
662     public static boolean isVisible(final Field field, final Class<?> caller)
663     {
664         return ClassUtil.isVisible(field.getModifiers(), field.getDeclaringClass(), caller);
665     }
666 
667     /**
668      * Returns whether a constructor is visible for a caller.
669      * @param constructor Constructor&lt;?&gt;; The constructor
670      * @param caller Class&lt;?&gt;; The class of the caller for whom invocation visibility is checked.
671      * @return boolean yes or no
672      */
673     public static boolean isVisible(final Constructor<?> constructor, final Class<?> caller)
674     {
675         return ClassUtil.isVisible(constructor.getModifiers(), constructor.getDeclaringClass(), caller);
676     }
677 
678     /**
679      * Returns whether a method is visible for a caller.
680      * @param method Method; The method
681      * @param caller Class&lt;?&gt;; The class of the caller for whom invocation visibility is checked.
682      * @return boolean yes or no
683      */
684     public static boolean isVisible(final Method method, final Class<?> caller)
685     {
686         return ClassUtil.isVisible(method.getModifiers(), method.getDeclaringClass(), caller);
687     }
688 
689     /**
690      * Filters an array methods for signatures that are compatible with a given signature.
691      * @param methods Method[]; which are methods to be filtered.
692      * @param name String; reflects the method's name, part of the signature
693      * @param argTypes Class&lt;?&gt;[]; are the method's argument types
694      * @return Method[] An unordered Method-array consisting of the elements of 'methods' that match with the given signature.
695      *         An array with 0 elements is returned when no matching Method objects are found.
696      */
697     public static Method[] matchSignature(final Method[] methods, final String name, final Class<?>[] argTypes)
698     {
699         List<Method> results = new ArrayList<Method>();
700         for (int i = 0; i < methods.length; i++)
701         {
702             if (ClassUtil.matchSignature(methods[i], name, argTypes))
703             {
704                 results.add(methods[i]);
705             }
706         }
707         return results.toArray(new Method[results.size()]);
708     }
709 
710     /**
711      * Filters an array methods for signatures that are compatible with a given signature.
712      * @param method Method; The method to be filtered.
713      * @param name String; reflects the method's name, part of the signature
714      * @param argTypes Class&lt;?&gt;[]; are the method's argument types
715      * @return boolean if methodParameters assignable from argTypes
716      */
717     public static boolean matchSignature(final Method method, final String name, final Class<?>[] argTypes)
718     {
719         if (!method.getName().equals(name))
720         {
721             return false;
722         }
723         if (method.getParameterTypes().length != argTypes.length)
724         {
725             return false;
726         }
727         Class<?>[] types = method.getParameterTypes();
728         for (int i = 0; i < method.getParameterTypes().length; i++)
729         {
730             if (!(types[i].isAssignableFrom(argTypes[i]) || types[i].equals(Primitive.getPrimitive(argTypes[i]))))
731             {
732                 return false;
733             }
734         }
735         return true;
736     }
737 
738     /**
739      * Converts an array of objects to their corresponding classes. Note that primitive types are always autoboxed to the
740      * corresponding object types. So an int in the array will have an Integer.class at the position in the resulting Class
741      * array.
742      * @param array Object[]; the array to invoke
743      * @return Class&lt;?&gt;[] the result;
744      */
745     public static Class<?>[] getClass(final Object[] array)
746     {
747         if (array == null)
748         {
749             return new Class[0];
750         }
751         Class<?>[] result = new Class[array.length];
752         for (int i = 0; i < result.length; i++)
753         {
754             if (array[i] == null)
755             {
756                 result[i] = null;
757             }
758             else
759             {
760                 result[i] = array[i].getClass();
761             }
762         }
763         return result;
764     }
765 
766     /** ************** PRIVATE METHODS ********* */
767 
768     /**
769      * checks the input of an array.
770      * @param array Object[]; the array
771      * @param myClass Class&lt;?&gt;; the class of the result
772      * @return Returns array if array!=null else returns myClass[0]
773      */
774     private static Object checkInput(final Object[] array, final Class<?> myClass)
775     {
776         if (array != null)
777         {
778             return array;
779         }
780         return Array.newInstance(myClass, 0);
781     }
782 
783     /**
784      * Determines & returns the most specific constructor as defined in the Java Language Specification par 15.12. The current
785      * algorithm is simple and reliable, but probably slow.
786      * @param methods Constructor&lt;?&gt;[]; are the constructors to be searched. They are assumed to have the same name and
787      *            number of parameters, as determined by the constructor matchSignature.
788      * @return Constructor which is the most specific constructor.
789      * @throws NoSuchMethodException when no constructor is found that's more specific than the others.
790      */
791     private static Constructor<?> getSpecificConstructor(final Constructor<?>[] methods) throws NoSuchMethodException
792     {
793         if (methods.length == 0)
794         {
795             throw new NoSuchMethodException();
796         }
797         if (methods.length == 1)
798         {
799             return methods[0];
800         }
801         // Apply generic algorithm
802         int resultID = 0; // Assume first method to be most specific
803         while (resultID < methods.length)
804         {
805             // Verify assumption
806             boolean success = true;
807             for (int i = 0; i < methods.length; i++)
808             {
809                 if (resultID == i)
810                 {
811                     continue;
812                 }
813                 if (!isMoreSpecific(methods[resultID], methods[i]))
814                 {
815                     success = false;
816                 }
817             }
818             // Assumption verified
819             if (success)
820             {
821                 return methods[resultID];
822             }
823             resultID++;
824         }
825         // No method is most specific, thus:
826         throw new NoSuchMethodException();
827     }
828 
829     /**
830      * Determines & returns the most specific method as defined in the Java Language Specification par 15.12. The current
831      * algorithm is simple and reliable, but probably slow.
832      * @param methods Method[]; which are the methods to be searched. They are assumed to have the same name and number of
833      *            parameters, as determined by the method matchSignature.
834      * @return The most specific method.
835      * @throws NoSuchMethodException when no method is found that's more specific than the others.
836      */
837     private static Method getSpecificMethod(final Method[] methods) throws NoSuchMethodException
838     {
839         // Check for evident cases
840         if (methods.length == 0)
841         {
842             throw new NoSuchMethodException();
843         }
844         if (methods.length == 1)
845         {
846             return methods[0];
847         }
848         // Apply generic algorithm
849         int resultID = 0; // Assume first method to be most specific
850         while (resultID < methods.length)
851         {
852             // Verify assumption
853             boolean success = true;
854             for (int i = 0; i < methods.length; i++)
855             {
856                 if (resultID == i)
857                 {
858                     continue;
859                 }
860                 if (!isMoreSpecific(methods[resultID], methods[i]))
861                 {
862                     success = false;
863                 }
864             }
865             // Assumption verified
866             if (success)
867             {
868                 return methods[resultID];
869             }
870             resultID++;
871         }
872         // No method is most specific, thus:
873         throw new NoSuchMethodException();
874     }
875 
876     /**
877      * returns the interface method.
878      * @param clazz Class&lt;?&gt;; the class to start with
879      * @param name String; the name of the method
880      * @param parameterTypes Class&lt;?&gt;[]; the parameterTypes
881      * @return Method
882      * @throws NoSuchMethodException on lookup failure
883      */
884     private static Method resolveMethodSuper(final Class<?> clazz, final String name, final Class<?>[] parameterTypes)
885             throws NoSuchMethodException
886     {
887         String key = "METHOD:" + clazz + "@" + name + "@" + FieldSignature.toDescriptor(parameterTypes);
888         try
889         {
890             if (CACHE.containsKey(key))
891             {
892                 return (Method) CACHE.get(key);
893             }
894             Method method = clazz.getDeclaredMethod(name, parameterTypes);
895             CACHE.put(key, method);
896             return method;
897         }
898         catch (Exception exception)
899         {
900             if (clazz.getSuperclass() != null)
901             {
902                 Method method = ClassUtil.resolveMethodSuper(clazz.getSuperclass(), name, parameterTypes);
903                 CACHE.put(key, method);
904                 return method;
905             }
906             throw new NoSuchMethodException(exception.getMessage());
907         }
908     }
909 
910     /**
911      * resolves the field for a class, taking into account superclasses.
912      * @param clazz Class&lt;?&gt;; the class for which superclasses will be probed
913      * @param fieldName String; the name of the field to resolve
914      * @return the field (if found)
915      * @throws NoSuchFieldException if the field cannot be resolved
916      */
917     private static Field resolveFieldSuper(final Class<?> clazz, final String fieldName) throws NoSuchFieldException
918     {
919         String key = "FIELD:" + clazz + "@" + fieldName;
920         try
921         {
922             if (CACHE.containsKey(key))
923             {
924                 return (Field) CACHE.get(key);
925             }
926             Field result = clazz.getDeclaredField(fieldName);
927             CACHE.put(key, result);
928             return result;
929         }
930         catch (Exception exception)
931         {
932             if (clazz.getSuperclass() != null)
933             {
934                 Field result = ClassUtil.resolveFieldSuper(clazz.getSuperclass(), fieldName);
935                 CACHE.put(key, result);
936                 return result;
937             }
938             throw new NoSuchFieldException(exception.getMessage());
939         }
940     }
941 
942     /**
943      * resolves the annotation for a class, taking into account superclasses.
944      * @param clazz Class&lt;?&gt;; the class for which superclasses will be probed
945      * @param annotationClass Class&lt;? extends Annotation&gt;; the class of the annotation to resolve
946      * @return the annotation (if found)
947      * @throws NoSuchElementException if the annotation cannot be resolved
948      */
949     private static Annotation resolveAnnotationSuper(final Class<?> clazz, final Class<? extends Annotation> annotationClass)
950             throws NoSuchElementException
951     {
952         String key = "ANNOTATION:" + clazz + "@" + annotationClass;
953         try
954         {
955             if (CACHE.containsKey(key))
956             {
957                 return (Annotation) CACHE.get(key);
958             }
959             Annotation[] annotations = clazz.getDeclaredAnnotations();
960             Annotation result = null;
961             for (Annotation annotation : annotations)
962             {
963                 if (annotation.annotationType().equals(annotationClass))
964                 {
965                     result = annotation;
966                     break;
967                 }
968             }
969             if (result == null)
970             {
971                 throw new NoSuchElementException("Annotation " + annotationClass + " not found in class " + clazz.getName());
972             }
973             CACHE.put(key, result);
974             return result;
975         }
976         catch (Exception exception)
977         {
978             if (clazz.getSuperclass() != null)
979             {
980                 Annotation result = ClassUtil.resolveAnnotationSuper(clazz.getSuperclass(), annotationClass);
981                 CACHE.put(key, result);
982                 return result;
983             }
984             throw new NoSuchElementException(exception.getMessage());
985         }
986     }
987 
988     /**
989      * Change the value of a property of an annotation through reflection. The annotation that can be changed can be a class,
990      * field, or method annotation. Based on:
991      * https://stackoverflow.com/questions/14268981/modify-a-class-definitions-annotation-string-parameter-at-runtime
992      * @param annotation the annotation to change
993      * @param key the field to look for in the annotation
994      * @param newValue the value to set the annotation field to
995      * @throws IllegalStateException when the annotation has no member values or access to the member values is denied
996      * @throws IllegalArgumentException when the value that is changed is of a different type than the type of the newValue
997      */
998     @SuppressWarnings("unchecked")
999     public static void changeAnnotationValue(final Annotation annotation, final String key, final Object newValue)
1000     {
1001         Object handler = Proxy.getInvocationHandler(annotation);
1002         Field f;
1003         try
1004         {
1005             f = handler.getClass().getDeclaredField("memberValues");
1006         }
1007         catch (NoSuchFieldException | SecurityException e)
1008         {
1009             throw new IllegalStateException(e);
1010         }
1011         f.setAccessible(true);
1012         Map<String, Object> memberValues;
1013         try
1014         {
1015             memberValues = (Map<String, Object>) f.get(handler);
1016         }
1017         catch (IllegalArgumentException | IllegalAccessException e)
1018         {
1019             throw new IllegalStateException(e);
1020         }
1021         Object oldValue = memberValues.get(key);
1022         if (oldValue == null || oldValue.getClass() != newValue.getClass())
1023         {
1024             throw new IllegalArgumentException();
1025         }
1026         memberValues.put(key, newValue);
1027     }
1028 
1029     /**
1030      * Retrieve a file pointer of a class, e.g. to request the last compilation date.
1031      * @param object Object; the object for which the class information should be retrieved
1032      * @return a ClassFileDescriptor with some information of the .class file
1033      */
1034     public static ClassFileDescriptor classFileDescriptor(final Object object)
1035     {
1036         return classFileDescriptor(object.getClass());
1037     }
1038 
1039     /**
1040      * Retrieve a file pointer of a class, e.g. to request the last compilation date.
1041      * @param clazz Class&lt;?&gt;; the class for which a file descriptor should be retrieved
1042      * @return a ClassFileDescriptor with some information of the .class file
1043      */
1044     public static ClassFileDescriptor classFileDescriptor(final Class<?> clazz)
1045     {
1046         URL clazzUrl = URLResource.getResource("/" + clazz.getName().replaceAll("\\.", "/") + ".class");
1047         return classFileDescriptor(clazzUrl);
1048     }
1049 
1050     /**
1051      * Retrieve a file pointer of a class, e.g. to request the last compilation date.
1052      * @param clazzUrl URL; the URL to a class for which a file descriptor should be retrieved
1053      * @return a ClassFileDescriptor with some information of the .class file
1054      */
1055     public static ClassFileDescriptor classFileDescriptor(final URL clazzUrl)
1056     {
1057         if (clazzUrl.toString().startsWith("jar:file:") && clazzUrl.toString().contains("!"))
1058         {
1059             String[] parts = clazzUrl.toString().split("\\!");
1060             String jarFileName = parts[0].replace("jar:file:", "");
1061             try
1062             {
1063                 URL jarURL = new URL("file:" + jarFileName);
1064                 File jarUrlFile = new File(jarURL.toURI());
1065                 try (JarFile jarFile = new JarFile(jarUrlFile))
1066                 {
1067                     if (parts[1].startsWith("/"))
1068                     {
1069                         parts[1] = parts[1].substring(1);
1070                     }
1071                     JarEntry jarEntry = jarFile.getJarEntry(parts[1]);
1072                     return new ClassFileDescriptor(jarEntry, jarFileName + "!" + parts[1]);
1073                 }
1074                 catch (Exception exception)
1075                 {
1076                     URL jarURL2 = new URL("file:" + jarFileName);
1077                     return new ClassFileDescriptor(new File(jarURL2.toURI()));
1078                 }
1079             }
1080             catch (Exception exception)
1081             {
1082                 return new ClassFileDescriptor(new File(jarFileName));
1083             }
1084         }
1085         try
1086         {
1087             return new ClassFileDescriptor(new File(clazzUrl.toURI()));
1088         }
1089         catch (URISyntaxException exception)
1090         {
1091             return new ClassFileDescriptor(new File(clazzUrl.getPath()));
1092         }
1093     }
1094 
1095     /**
1096      * ClassFileDescriptor contains some information about a class file, either stand-alone on the classpath, or within a Jar
1097      * file.<br>
1098      * <br>
1099      * Copyright (c) 2019-2019 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved.
1100      * See for project information <a href="https://www.simulation.tudelft.nl/" target="_blank">www.simulation.tudelft.nl</a>.
1101      * The source code and binary code of this software is proprietary information of Delft University of Technology.
1102      * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank">Alexander Verbraeck</a>
1103      */
1104     public static class ClassFileDescriptor
1105     {
1106         /** the final name + extension (without path) of the file. */
1107         private final String name;
1108 
1109         /** the full path (with a ! inside if it is a Jar file descriptor). */
1110         private final String path;
1111 
1112         /** whether it is a file from a Jar container or a zip file. */
1113         private final boolean jar;
1114 
1115         /** last changed date of the file in millis, if known. Otherwise 1-1-1970, 00:00. */
1116         private long lastChangedDate;
1117 
1118         /**
1119          * Construct a ClassFileDescriptor from a File.
1120          * @param classFile File; the file to use.
1121          */
1122         public ClassFileDescriptor(final File classFile)
1123         {
1124             this.name = classFile.getName();
1125             this.path = classFile.getPath();
1126             this.jar = false;
1127             long lastModified = classFile.lastModified();
1128             if (lastModified == 0L)
1129             {
1130                 BasicFileAttributes attributes;
1131                 try
1132                 {
1133                     attributes = Files.readAttributes(Paths.get(this.path), BasicFileAttributes.class);
1134                     lastModified = attributes.lastModifiedTime().toMillis();
1135                 }
1136                 catch (IOException exception)
1137                 {
1138                     // ignore - date will be 1-1-1970
1139                 }
1140             }
1141             this.lastChangedDate = lastModified;
1142         }
1143 
1144         /**
1145          * Construct a ClassFileDescriptor from a JarEntry.
1146          * @param jarEntry JarEntry; the JarEntry to use.
1147          * @param path the path of the JarEntry
1148          */
1149         public ClassFileDescriptor(final JarEntry jarEntry, final String path)
1150         {
1151             this.name = jarEntry.getName();
1152             this.path = path;
1153             this.jar = true;
1154             this.lastChangedDate = jarEntry.getLastModifiedTime().toMillis();
1155         }
1156 
1157         /**
1158          * Construct a ClassFileDescriptor from a ZipEntry.
1159          * @param zipEntry ZipEntry; the ZipEntry to use.
1160          * @param path the path of the ZipEntry
1161          */
1162         public ClassFileDescriptor(final ZipEntry zipEntry, final String path)
1163         {
1164             this.name = zipEntry.getName();
1165             this.path = path;
1166             this.jar = true;
1167             this.lastChangedDate = zipEntry.getLastModifiedTime().toMillis();
1168         }
1169 
1170         /**
1171          * @return name
1172          */
1173         public String getName()
1174         {
1175             return this.name;
1176         }
1177 
1178         /**
1179          * @return path
1180          */
1181         public String getPath()
1182         {
1183             return this.path;
1184         }
1185 
1186         /**
1187          * @return jar
1188          */
1189         public boolean isJar()
1190         {
1191             return this.jar;
1192         }
1193 
1194         /**
1195          * @return lastChangedDate
1196          */
1197         public long getLastChangedDate()
1198         {
1199             return this.lastChangedDate;
1200         }
1201 
1202         /** {@inheritDoc} */
1203         @Override
1204         public String toString()
1205         {
1206             return "ClassFileDescriptor [name=" + this.name + ", path=" + this.path + ", jar=" + this.jar + ", lastChangedDate="
1207                     + this.lastChangedDate + "]";
1208         }
1209     }
1210 }