ClassUtil.java
package org.djutils.reflection;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.djutils.primitives.Primitive;
/**
* ClassUtil is a utility class providing assistance for Java Classes.
* <p>
* Copyright (c) 2002-2019 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
* distributed under a three-clause BSD-style license, which can be found at
* <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>.
* </p>
* @author Peter Jacobs, Niels Lang, Alexander Verbraeck
*/
public final class ClassUtil
{
/** CACHE reflects the internal repository CACHE. */
private static final Map<String, Object> CACHE = Collections.synchronizedMap(new HashMap<String, Object>());
/**
* constructs a new ClassUtil.
*/
private ClassUtil()
{
super();
// unreachable code
}
/** ************ CONSTRUCTOR UTILITIES *********** */
/**
* gets all the constructors of a class and adds the result to result.
* @param clazz Class<?>; the class
* @param result Constructor<?>[]; the resulting set
* @return result
*/
public static Constructor<?>[] getAllConstructors(final Class<?> clazz, final Constructor<?>[] result)
{
List<Constructor<?>> list = new ArrayList<Constructor<?>>(Arrays.asList(result));
list.addAll(Arrays.asList(clazz.getDeclaredConstructors()));
if (clazz.getSuperclass() != null)
{
return ClassUtil.getAllConstructors(clazz.getSuperclass(), list.toArray(new Constructor[list.size()]));
}
return list.toArray(new Constructor[list.size()]);
}
/**
* returns the interface method.
* @param clazz Class<?>; the class to start with
* @param callerClass Class<?>; the calling class
* @param parameterTypes Class<?>[]; the parameterTypes
* @return Constructor
* @throws NoSuchMethodException if the method cannot be resolved
*/
public static Constructor<?> resolveConstructor(final Class<?> clazz, final Class<?> callerClass,
final Class<?>[] parameterTypes) throws NoSuchMethodException
{
Constructor<?> constructor = ClassUtil.resolveConstructor(clazz, parameterTypes);
if (ClassUtil.isVisible(constructor, callerClass.getClass()))
{
return constructor;
}
throw new NoSuchMethodException("constructor resolved but not visible");
}
/**
* returns the interface method.
* @param clazz Class<?>; the class to start with
* @param parameterTypes Class<?>[]; the parameterTypes
* @return Constructor
* @throws NoSuchMethodException if the method cannot be resolved
*/
public static Constructor<?> resolveConstructor(final Class<?> clazz, final Class<?>[] parameterTypes)
throws NoSuchMethodException
{
try
{
return resolveConstructorSuper(clazz, (Class<?>[]) ClassUtil.checkInput(parameterTypes, Class.class));
}
catch (Exception exception)
{
String className = clazz.getName();
if (className.indexOf("$") >= 0)
{
Class<?> parentClass = null;
try
{
parentClass = Class.forName(className.substring(0, className.lastIndexOf("$")));
}
catch (Exception e2)
{
throw new NoSuchMethodException("class " + parentClass + " not found to resolve constructor");
}
return ClassUtil.resolveConstructor(parentClass,
(Class<?>[]) ClassUtil.checkInput(parameterTypes, Class.class));
}
throw new NoSuchMethodException("class " + clazz + " does not contain constructor");
}
}
/**
* returns the constructor.
* @param clazz Class<?>; the clazz to start with
* @param arguments Object[]; the arguments
* @return Constructor
* @throws NoSuchMethodException on lookup failure
*/
public static Constructor<?> resolveConstructor(final Class<?> clazz, final Object[] arguments)
throws NoSuchMethodException
{
Class<?>[] parameterTypes = ClassUtil.getClass(arguments);
String key = "CONSTRUCTOR:" + clazz + "@" + FieldSignature.toDescriptor(parameterTypes);
if (CACHE.containsKey(key))
{
return (Constructor<?>) CACHE.get(key);
}
try
{
return ClassUtil.resolveConstructor(clazz, parameterTypes);
}
catch (NoSuchMethodException noSuchMethodException)
{
// We get all constructors
Constructor<?>[] constructors = ClassUtil.getAllConstructors(clazz, new Constructor[0]);
// now we match the signatures
constructors = ClassUtil.matchSignature(constructors, parameterTypes);
// Now we find the most specific
Constructor<?> result = ClassUtil.getSpecificConstructor(constructors);
CACHE.put(key, result);
return result;
}
}
/* ************ FIELD UTILITIES *********** */
/**
* gets all the fields of a class (public, protected, package, and private) and adds the result to the return value.
* @param clazz Class<?>; the class
* @param result Set<Field>; the resulting set
* @return the set of fields including all fields of the field clazz
*/
public static Set<Field> getAllFields(final Class<?> clazz, final Set<Field> result)
{
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++)
{
result.add(fields[i]);
}
if (clazz.getSuperclass() != null)
{
return ClassUtil.getAllFields(clazz.getSuperclass(), result);
}
return result;
}
/**
* gets all the fields of a class (public, protected, package, and private).
* @param clazz Class<?>; the class
* @return all fields of the class
*/
public static Set<Field> getAllFields(final Class<?> clazz)
{
Set<Field> fieldSet = new HashSet<Field>();
return ClassUtil.getAllFields(clazz, fieldSet);
}
/**
* resolves the field for a class, taking into account inner classes.
* @param clazz the class to resolve the field for, including inner classes
* @param fieldName name of the field
* @return Field the field
* @throws NoSuchFieldException on no such field
*/
public static Field resolveField(final Class<?> clazz, final String fieldName) throws NoSuchFieldException
{
try
{
return resolveFieldSuper(clazz, fieldName);
}
catch (NoSuchFieldException noSuchFieldException)
{
String className = clazz.getName();
if (className.indexOf("$") >= 0)
{
Class<?> clazz2 = null;
try
{
clazz2 = Class.forName(className.substring(0, className.lastIndexOf("$")));
}
catch (ClassNotFoundException classNotFoundException)
{
throw new NoSuchFieldException("class " + clazz + " not found to resolve field " + fieldName);
}
return ClassUtil.resolveField(clazz2, fieldName);
}
throw new NoSuchFieldException("class " + clazz + " does not contain field " + fieldName);
}
}
/**
* returns the field.
* @param clazz Class<?>; the class to start with
* @param callerClass Class<?>; the calling class
* @param name String; the fieldName
* @return Constructor
* @throws NoSuchFieldException if the method cannot be resolved
*/
public static Field resolveField(final Class<?> clazz, final Class<?> callerClass, final String name)
throws NoSuchFieldException
{
Field field = ClassUtil.resolveField(clazz, name);
if (ClassUtil.isVisible(field, callerClass.getClass()))
{
return field;
}
throw new NoSuchFieldException("field resolved but not visible");
}
/**
* resolves the field for a given object instance.
* @param object Object; the object to resolve the field for
* @param fieldName String; name of the field to resolve
* @return the field (if found)
* @throws NoSuchFieldException if the field cannot be resolved
*/
public static Field resolveField(final Object object, final String fieldName) throws NoSuchFieldException
{
if (object == null)
{
throw new NoSuchFieldException("resolveField: object is null for field " + fieldName);
}
return resolveField(object.getClass(), fieldName);
}
/** ************ METHOD UTILITIES *********** */
/**
* gets all the methods of a class and adds the result to result.
* @param clazz Class<?>; the class
* @param name String; the name of the method
* @param result Method[]; the resulting set
* @return result
*/
public static Method[] getAllMethods(final Class<?> clazz, final String name, final Method[] result)
{
List<Method> list = new ArrayList<Method>(Arrays.asList(result));
Method[] methods = clazz.getDeclaredMethods();
for (int i = 0; i < methods.length; i++)
{
if (methods[i].getName().equals(name))
{
list.add(methods[i]);
}
}
if (clazz.getSuperclass() != null)
{
return ClassUtil.getAllMethods(clazz.getSuperclass(), name, list.toArray(new Method[list.size()]));
}
return list.toArray(new Method[list.size()]);
}
/**
* returns the interface method.
* @param clazz Class<?>; the class to start with
* @param callerClass Class<?>; the caller class
* @param name String; the name of the method
* @param parameterTypes Class<?>[]; the parameterTypes
* @return Method
* @throws NoSuchMethodException on lookup failure
*/
public static Method resolveMethod(final Class<?> clazz, final Class<?> callerClass, final String name,
final Class<?>[] parameterTypes) throws NoSuchMethodException
{
Method method = ClassUtil.resolveMethod(clazz, name, parameterTypes);
if (ClassUtil.isVisible(method, callerClass))
{
return method;
}
throw new NoSuchMethodException("method found but not visible");
}
/**
* returns the interface method.
* @param clazz Class<?>; the class to start with
* @param name String; the name of the method
* @param parameterTypes Class<?>[]; the parameterTypes
* @return Method
* @throws NoSuchMethodException on lookup failure
*/
public static Method resolveMethod(final Class<?> clazz, final String name, final Class<?>[] parameterTypes)
throws NoSuchMethodException
{
try
{
return resolveMethodSuper(clazz, name, (Class<?>[]) ClassUtil.checkInput(parameterTypes, Class.class));
}
catch (Exception exception)
{
String className = clazz.getName();
if (className.indexOf("$") >= 0)
{
Class<?> parentClass = null;
try
{
parentClass = Class.forName(className.substring(0, className.lastIndexOf("$")));
}
catch (Exception e2)
{
throw new NoSuchMethodException("class " + parentClass + " not found to resolve method " + name);
}
return ClassUtil.resolveMethod(parentClass, name,
(Class<?>[]) ClassUtil.checkInput(parameterTypes, Class.class));
}
throw new NoSuchMethodException("class " + clazz + " does not contain method " + name);
}
}
/**
* resolves a method the method.
* @param object Object; the object to start with
* @param name String; the name of the method
* @param parameterTypes Class<?>[]; the parameterTypes
* @return Method
* @throws NoSuchMethodException on lookup failure
*/
public static Method resolveMethod(final Object object, final String name, final Class<?>[] parameterTypes)
throws NoSuchMethodException
{
if (object == null)
{
throw new NoSuchMethodException("resolveField: object is null for method " + name);
}
return resolveMethod(object.getClass(), name, parameterTypes);
}
/**
* returns the method.
* @param object Object; the object to start with
* @param name String; the name of the method
* @param arguments Object[]; the arguments
* @return Method
* @throws NoSuchMethodException on lookup failure
*/
public static Method resolveMethod(final Object object, final String name, final Object[] arguments)
throws NoSuchMethodException
{
Class<?>[] parameterTypes = ClassUtil.getClass(arguments);
String key = "METHOD:" + object.getClass() + "@" + name + "@" + FieldSignature.toDescriptor(parameterTypes);
if (CACHE.containsKey(key))
{
return (Method) CACHE.get(key);
}
try
{
return ClassUtil.resolveMethod(object, name, parameterTypes);
}
catch (NoSuchMethodException noSuchMethodException)
{
// We get all methods
Method[] methods = ClassUtil.getAllMethods(object.getClass(), name, new Method[0]);
if (methods.length == 0)
{
throw new NoSuchMethodException("No such method: " + name + " for object " + object);
}
// now we match the signatures
methods = ClassUtil.matchSignature(methods, name, parameterTypes);
if (methods.length == 0)
{
throw new NoSuchMethodException("No method with right signature: " + name + " for object " + object);
}
// Now we find the most specific
Method result = ClassUtil.getSpecificMethod(methods);
CACHE.put(key, result);
return result;
}
}
/**
* Returns whether a declaringClass is accessible according to the modifiers.
* @param modifiers int; the modifiers
* @param declaringClass Class<?>; the declaringClass
* @param caller Class<?>; the caller
* @return boolean isVisible
*/
public static boolean isVisible(final int modifiers, final Class<?> declaringClass, final Class<?> caller)
{
if (Modifier.isPublic(modifiers))
{
return true;
}
if (Modifier.isProtected(modifiers))
{
if (declaringClass.isAssignableFrom(caller))
{
return true;
}
if (declaringClass.getPackage().equals(caller.getPackage()))
{
return true;
}
return false;
}
if (declaringClass.equals(caller))
{
return true;
}
return false;
}
/**
* Determines & returns whether constructor 'a' is more specific than constructor 'b', as defined in the Java
* Language Specification ???15.12.
* @return true if 'a' is more specific than b, false otherwise. 'false' is also returned when constructors are
* incompatible, e.g. have different names or a different number of parameters.
* @param a Class<?>[]; reflects the first constructor
* @param b Class<?>[]; reflects the second constructor
*/
public static boolean isMoreSpecific(final Class<?>[] a, final Class<?>[] b)
{
if (a.length != b.length)
{
return false;
}
int i = 0;
while (i < a.length)
{
if (!b[i].isAssignableFrom(a[i]))
{
return false;
}
i++;
}
return true;
}
/**
* Determines & returns whether constructor 'a' is more specific than constructor 'b', as defined in the Java
* Language Specification ???15.12.
* @return true if 'a' is more specific than b, false otherwise. 'false' is also returned when constructors are
* incompatible, e.g. have different names or a different number of parameters.
* @param a Constructor<?>; reflects the first constructor
* @param b Constructor<?>; reflects the second constructor
*/
public static boolean isMoreSpecific(final Constructor<?> a, final Constructor<?> b)
{
if (a.getParameterTypes().equals(b.getParameterTypes()))
{
if (b.getDeclaringClass().isAssignableFrom(a.getDeclaringClass()))
{
return true;
}
}
return ClassUtil.isMoreSpecific(a.getParameterTypes(), b.getParameterTypes());
}
/**
* Determines & returns whether constructor 'a' is more specific than constructor 'b', as defined in the Java
* Language Specification ???15.12.
* @return true if 'a' is more specific than b, false otherwise. 'false' is also returned when constructors are
* incompatible, e.g. have different names or a different number of parameters.
* @param a Method; reflects the first method
* @param b Method; reflects the second method
*/
public static boolean isMoreSpecific(final Method a, final Method b)
{
if (!a.getName().equals(b.getName()))
{
return false;
}
return ClassUtil.isMoreSpecific(a.getParameterTypes(), b.getParameterTypes());
}
/**
* Returns whether a field is visible for a caller.
* @param field Field; The field
* @param caller Class<?>; The class of the caller for whom invocation visibility is checked.
* @return boolean yes or no
*/
public static boolean isVisible(final Field field, final Class<?> caller)
{
return ClassUtil.isVisible(field.getModifiers(), field.getDeclaringClass(), caller);
}
/**
* Returns whether a constructor is visible for a caller.
* @param constructor Constructor<?>; The constructor
* @param caller Class<?>; The class of the caller for whom invocation visibility is checked.
* @return boolean yes or no
*/
public static boolean isVisible(final Constructor<?> constructor, final Class<?> caller)
{
return ClassUtil.isVisible(constructor.getModifiers(), constructor.getDeclaringClass(), caller);
}
/**
* Returns whether a method is visible for a caller.
* @param method Method; The method
* @param caller Class<?>; The class of the caller for whom invocation visibility is checked.
* @return boolean yes or no
*/
public static boolean isVisible(final Method method, final Class<?> caller)
{
return ClassUtil.isVisible(method.getModifiers(), method.getDeclaringClass(), caller);
}
/**
* Filters an array methods for signatures that are compatible with a given signature.
* @param methods Method[]; which are methods to be filtered.
* @param name String; reflects the method's name, part of the signature
* @param argTypes Class<?>[]; are the method's argument types
* @return Method[] An unordered Method-array consisting of the elements of 'methods' that match with the given
* signature. An array with 0 elements is returned when no matching Method objects are found.
*/
public static Method[] matchSignature(final Method[] methods, final String name, final Class<?>[] argTypes)
{
List<Method> results = new ArrayList<Method>();
for (int i = 0; i < methods.length; i++)
{
if (ClassUtil.matchSignature(methods[i], name, argTypes))
{
results.add(methods[i]);
}
}
return results.toArray(new Method[results.size()]);
}
/**
* Filters an array methods for signatures that are compatible with a given signature.
* @param method Method; The method to be filtered.
* @param name String; reflects the method's name, part of the signature
* @param argTypes Class<?>[]; are the method's argument types
* @return boolean if methodParameters assignable from argTypes
*/
public static boolean matchSignature(final Method method, final String name, final Class<?>[] argTypes)
{
if (!method.getName().equals(name))
{
return false;
}
if (method.getParameterTypes().length != argTypes.length)
{
return false;
}
Class<?>[] types = method.getParameterTypes();
for (int i = 0; i < method.getParameterTypes().length; i++)
{
if (!(types[i].isAssignableFrom(argTypes[i]) || types[i].equals(Primitive.getPrimitive(argTypes[i]))))
{
return false;
}
}
return true;
}
/**
* Filters an array methods for signatures that are compatible with a given signature.
* @param constructor Constructor<?>; which are constructors to be filtered.
* @param argTypes Class<?>[]; are the constructor's argument types
* @return boolean if methodParameters assignable from argTypes
*/
public static boolean matchSignature(final Constructor<?> constructor, final Class<?>[] argTypes)
{
if (constructor.getParameterTypes().length != argTypes.length)
{
return false;
}
Class<?>[] types = constructor.getParameterTypes();
for (int i = 0; i < constructor.getParameterTypes().length; i++)
{
if (!(types[i].isAssignableFrom(argTypes[i]) || types[i].equals(Primitive.getPrimitive(argTypes[i]))))
{
return false;
}
}
return true;
}
/**
* Filters an array methods for signatures that are compatible with a given signature.
* @param constructors Constructor<?>[]; which are constructors to be filtered.
* @param argTypes Class<?>[]; are the constructor's argument types
* @return Constructor<?>[] An unordered Constructor-array consisting of the elements of 'constructors' that
* match with the given signature. An array with 0 elements is returned when no matching Method objects are
* found.
*/
public static Constructor<?>[] matchSignature(final Constructor<?>[] constructors, final Class<?>[] argTypes)
{
List<Constructor<?>> results = new ArrayList<Constructor<?>>();
for (int i = 0; i < constructors.length; i++)
{
if (ClassUtil.matchSignature(constructors[i], argTypes))
{
results.add(constructors[i]);
}
}
return results.toArray(new Constructor[results.size()]);
}
/**
* converts an array of objects to their corresponding classes.
* @param array Object[]; the array to invoke
* @return Class<?>[] the result;
*/
public static Class<?>[] getClass(final Object[] array)
{
if (array == null)
{
return new Class[0];
}
Class<?>[] result = new Class[array.length];
for (int i = 0; i < result.length; i++)
{
if (array[i] == null)
{
result[i] = null;
}
else
{
result[i] = array[i].getClass();
}
}
return result;
}
/** ************** PRIVATE METHODS ********* */
/**
* checks the input of an array.
* @param array Object[]; the array
* @param myClass Class<?>; the class of the result
* @return Returns array if array!=null else returns myClass[0]
*/
private static Object checkInput(final Object[] array, final Class<?> myClass)
{
if (array != null)
{
return array;
}
return Array.newInstance(myClass, 0);
}
/**
* Determines & returns the most specific constructor as defined in the Java Language Specification par 15.12. The
* current algorithm is simple and reliable, but probably slow.
* @param methods Constructor<?>[]; are the constructors to be searched. They are assumed to have the same
* name and number of parameters, as determined by the constructor matchSignature.
* @return Constructor which is the most specific constructor.
* @throws NoSuchMethodException when no constructor is found that's more specific than the others.
*/
private static Constructor<?> getSpecificConstructor(final Constructor<?>[] methods) throws NoSuchMethodException
{
if (methods.length == 0)
{
throw new NoSuchMethodException();
}
if (methods.length == 1)
{
return methods[0];
}
// Apply generic algorithm
int resultID = 0; // Assume first method to be most specific
while (resultID < methods.length)
{
// Verify assumption
boolean success = true;
for (int i = 0; i < methods.length; i++)
{
if (resultID == i)
{
continue;
}
if (!isMoreSpecific(methods[resultID], methods[i]))
{
success = false;
}
}
// Assumption verified
if (success)
{
return methods[resultID];
}
resultID++;
}
// No method is most specific, thus:
throw new NoSuchMethodException();
}
/**
* Determines & returns the most specific method as defined in the Java Language Specification par 15.12. The
* current algorithm is simple and reliable, but probably slow.
* @param methods Method[]; which are the methods to be searched. They are assumed to have the same name and number
* of parameters, as determined by the method matchSignature.
* @return The most specific method.
* @throws NoSuchMethodException when no method is found that's more specific than the others.
*/
private static Method getSpecificMethod(final Method[] methods) throws NoSuchMethodException
{
// Check for evident cases
if (methods.length == 0)
{
throw new NoSuchMethodException();
}
if (methods.length == 1)
{
return methods[0];
}
// Apply generic algorithm
int resultID = 0; // Assume first method to be most specific
while (resultID < methods.length)
{
// Verify assumption
boolean success = true;
for (int i = 0; i < methods.length; i++)
{
if (resultID == i)
{
continue;
}
if (!isMoreSpecific(methods[resultID], methods[i]))
{
success = false;
}
}
// Assumption verified
if (success)
{
return methods[resultID];
}
resultID++;
}
// No method is most specific, thus:
throw new NoSuchMethodException();
}
/**
* returns the constructor.
* @param clazz Class<?>; the class to start with
* @param parameterTypes Class<?>[]; the parameterTypes
* @return Method
* @throws NoSuchMethodException if the method cannot be resolved
*/
private static Constructor<?> resolveConstructorSuper(final Class<?> clazz, final Class<?>[] parameterTypes)
throws NoSuchMethodException
{
String key = "CONSTRUCTOR:" + clazz + "@" + FieldSignature.toDescriptor(parameterTypes);
try
{
if (CACHE.containsKey(key))
{
return (Constructor<?>) CACHE.get(key);
}
Constructor<?> constructor = clazz.getDeclaredConstructor(parameterTypes);
CACHE.put(key, constructor);
return constructor;
}
catch (Exception exception)
{
if (clazz.getSuperclass() != null)
{
Constructor<?> constructor = ClassUtil.resolveConstructorSuper(clazz.getSuperclass(), parameterTypes);
CACHE.put(key, constructor);
return constructor;
}
throw new NoSuchMethodException(exception.getMessage());
}
}
/**
* returns the interface method.
* @param clazz Class<?>; the class to start with
* @param name String; the name of the method
* @param parameterTypes Class<?>[]; the parameterTypes
* @return Method
* @throws NoSuchMethodException on lookup failure
*/
private static Method resolveMethodSuper(final Class<?> clazz, final String name, final Class<?>[] parameterTypes)
throws NoSuchMethodException
{
String key = "METHOD:" + clazz + "@" + name + "@" + FieldSignature.toDescriptor(parameterTypes);
try
{
if (CACHE.containsKey(key))
{
return (Method) CACHE.get(key);
}
Method method = clazz.getDeclaredMethod(name, parameterTypes);
CACHE.put(key, method);
return method;
}
catch (Exception exception)
{
if (clazz.getSuperclass() != null)
{
Method method = ClassUtil.resolveMethodSuper(clazz.getSuperclass(), name, parameterTypes);
CACHE.put(key, method);
return method;
}
throw new NoSuchMethodException(exception.getMessage());
}
}
/**
* resolves the field for a class, taking into account superclasses.
* @param clazz Class<?>; the class for which superclasses will be probed
* @param fieldName String; the name of the field to resolve
* @return the field (if found)
* @throws NoSuchFieldException if the field cannot be resolved
*/
private static Field resolveFieldSuper(final Class<?> clazz, final String fieldName) throws NoSuchFieldException
{
String key = "FIELD:" + clazz + "@" + fieldName;
try
{
if (CACHE.containsKey(key))
{
return (Field) CACHE.get(key);
}
Field result = clazz.getDeclaredField(fieldName);
CACHE.put(key, result);
return result;
}
catch (Exception exception)
{
if (clazz.getSuperclass() != null)
{
Field result = ClassUtil.resolveFieldSuper(clazz.getSuperclass(), fieldName);
CACHE.put(key, result);
return result;
}
throw new NoSuchFieldException(exception.getMessage());
}
}
}