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