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