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