package co.codewizards.cloudstore.core.util;
import static co.codewizards.cloudstore.core.util.AssertUtil.*;
import static co.codewizards.cloudstore.core.util.Util.*;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ReflectionUtil {
private static final Logger logger = LoggerFactory.getLogger(ReflectionUtil.class);
private ReflectionUtil() { }
public static <T> T invokeConstructor(final Class<T> clazz, final Object ... args) {
assertNotNull(clazz, "clazz");
final Class<?>[] argTypes = getArgumentTypes(args);
final List<Constructor<?>> compatibleConstructors = new ArrayList<>();
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
if (isConstructorCompatible(constructor, argTypes))
compatibleConstructors.add(constructor);
}
if (compatibleConstructors.isEmpty()) {
final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(clazz.getSimpleName(), argTypes);
throw new IllegalArgumentException(new NoSuchMethodException(String.format("None of the constructors of %s matches %s (or an equivalent using super-types of these parameter-types)!", clazz.getName(), methodNameWithParameterTypes)));
}
if (compatibleConstructors.size() > 1 && logger.isDebugEnabled()) {
// TODO find + invoke the *most* *suitable* one - instead of logging this warning (and simply invoking the first).
final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(clazz.getSimpleName(), argTypes);
final String msg = String.format("%s declares multiple constructors matching %s (or an equivalent using super-types of these parameter-types)!", clazz.getName(), methodNameWithParameterTypes);
// throw new IllegalArgumentException(new NoSuchMethodException(msg));
logger.warn("invokeConstructor: {}", msg);
}
return cast(invoke(compatibleConstructors.get(0), args));
}
public static <T> T invokeConstructor(final Class<T> clazz, Class<?>[] parameterTypes, final Object ... args) {
final Constructor<T> constructor = getDeclaredConstructorOrFail(clazz, parameterTypes);
return invoke(constructor, args);
}
public static <T> T invokeStatic(final Class<?> clazz, final String methodName, final Object ... args) {
assertNotNull(clazz, "clazz");
assertNotNull(methodName, "methodName");
return invoke(clazz, (Object)null, methodName, args);
}
public static <T> T invokeStatic(final Class<?> clazz, final String methodName, Class<?>[] parameterTypes, final Object ... args) {
final Method method = getDeclaredMethodOrFail(clazz, methodName, parameterTypes);
return invoke((Object)null, method, args);
}
public static <T> T invoke(final Object object, final String methodName, final Object ... args) {
assertNotNull(object, "object");
assertNotNull(methodName, "methodName");
return invoke(object.getClass(), object, methodName, args);
}
private static <T> T invoke(final Class<?> clazz, final Object object, final String methodName, final Object ... args) {
assertNotNull(clazz, "clazz");
// object may be null
assertNotNull(methodName, "methodName");
// args may be null
final Class<?>[] argTypes = getArgumentTypes(args);
// TODO cache methods - don't search for them again and again
final List<Method> methods = getDeclaredMethods(clazz, methodName);
final List<Method> compatibleMethods = new ArrayList<>(Math.min(5, methods.size()));
for (final Method method : methods) {
if (isMethodCompatible(method, argTypes))
compatibleMethods.add(method);
}
if (compatibleMethods.isEmpty()) {
final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(methodName, argTypes);
throw new IllegalArgumentException(new NoSuchMethodException(String.format("Neither %s nor one of its super-classes declares the method %s (or an equivalent using super-types of these parameter-types)!", clazz.getName(), methodNameWithParameterTypes)));
}
if (compatibleMethods.size() > 1 && logger.isDebugEnabled()) {
// TODO find + invoke the *most* *suitable* one - instead of logging this warning (and simply invoking the first).
final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(methodName, argTypes);
final String msg = String.format("%s and its super-classes declare multiple methods matching %s (or an equivalent using super-types of these parameter-types)!", clazz.getName(), methodNameWithParameterTypes);
// final Exception x = new NoSuchMethodException(msg);
logger.warn("invoke: {}", msg);
}
return invoke(object, compatibleMethods.get(0), args);
}
private static Class<?>[] getArgumentTypes(final Object... args) {
final Class<?>[] argTypes = args == null ? new Class<?>[0] : new Class<?>[args.length];
for (int i = 0; i < argTypes.length; i++)
argTypes[i] = args[i] == null ? null : args[i].getClass();
return argTypes;
}
public static <T> T invoke(final Object object, final String methodName, Class<?>[] parameterTypes, final Object ... args) {
assertNotNull(object, "object");
assertNotNull(methodName, "methodName");
if (parameterTypes == null)
return invoke(object, methodName, args);
final Method method = getDeclaredMethodOrFail(object.getClass(), methodName, parameterTypes);
return invoke(object, method, args);
}
private static <T> T invoke(final Constructor<T> constructor, final Object ... args) {
try {
constructor.setAccessible(true);
final Object result = constructor.newInstance(args);
return cast(result);
} catch (IllegalArgumentException e) {
throw e;
} catch (IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
}
private static <T> T invoke(final Object object, Method method, final Object ... args) {
try {
method.setAccessible(true);
final Object result = method.invoke(object, args);
return cast(result);
} catch (final IllegalArgumentException e) {
throw e;
} catch (final IllegalAccessException e) {
throw new RuntimeException(e);
} catch (final InvocationTargetException e) {
final Throwable cause = e.getCause();
if (cause instanceof RuntimeException)
throw (RuntimeException) cause;
else if (cause instanceof Error)
throw (Error) cause;
else
throw new RuntimeException(cause);
}
}
private static boolean isConstructorCompatible(final Constructor constructor, final Class<?>[] argTypes) {
final Class<?>[] parameterTypes = constructor.getParameterTypes();
return areMethodParametersCompatible(parameterTypes, argTypes);
}
private static boolean isMethodCompatible(final Method method, final Class<?>[] argTypes) {
final Class<?>[] parameterTypes = method.getParameterTypes();
return areMethodParametersCompatible(parameterTypes, argTypes);
}
private static boolean areMethodParametersCompatible(final Class<?>[] parameterTypes, final Class<?>[] argTypes) {
if (argTypes.length != parameterTypes.length)
return false;
for (int i = 0; i < parameterTypes.length; i++) {
if (argTypes[i] != null
&& ! parameterTypes[i].isAssignableFrom(argTypes[i])
&& ! isSimpleTypeAssignmentPossible(parameterTypes[i], argTypes[i]))
return false;
}
return true;
}
private static boolean isSimpleTypeAssignmentPossible(Class<?> parameterType, Class<?> argType) {
if (parameterType == boolean.class && argType == Boolean.class)
return true;
if (parameterType == byte.class && argType == Byte.class)
return true;
if (parameterType == char.class && argType == Character.class)
return true;
if (parameterType == double.class && argType == Double.class)
return true;
if (parameterType == float.class && argType == Float.class)
return true;
if (parameterType == int.class && argType == Integer.class)
return true;
if (parameterType == long.class && argType == Long.class)
return true;
if (parameterType == short.class && argType == Short.class)
return true;
return false;
}
public static List<Method> getDeclaredMethods(final Class<?> clazz, final String name) {
final List<Method> result = new ArrayList<>();
Class<?> c = clazz;
while (c != null) {
final Method[] methods = c.getDeclaredMethods();
for (final Method method : methods) {
if (name.equals(method.getName()))
result.add(method);
}
c = c.getSuperclass();
}
return result;
}
public static <T> Constructor<T> getDeclaredConstructorOrFail(final Class<T> clazz, final Class<?>[] parameterTypes) {
final Constructor<T> constructor;
try {
constructor = clazz.getDeclaredConstructor(parameterTypes);
} catch (NoSuchMethodException | SecurityException e) {
final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(clazz.getName(), parameterTypes);
throw new IllegalArgumentException(new NoSuchMethodException(String.format("%s does not declare the method %s!", clazz.getName(), methodNameWithParameterTypes)).initCause(e));
}
return constructor;
}
public static Method getDeclaredMethodOrFail(final Class<?> clazz, final String name, final Class<?>[] parameterTypes) {
final Method method = getDeclaredMethod(clazz, name, parameterTypes);
if (method == null) {
final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(name, parameterTypes);
throw new IllegalArgumentException(new NoSuchMethodException(String.format("Neither %s nor one of its super-classes declares the method %s!", clazz.getName(), methodNameWithParameterTypes)));
}
return method;
}
private static String createMethodNameWithParameterTypes(final String name, final Class<?>[] parameterTypes) {
final StringBuilder sb = new StringBuilder();
if (parameterTypes == null)
return name + "(...)";
for (Class<?> parameterType : parameterTypes) {
if (sb.length() > 0)
sb.append(", ");
sb.append(parameterType.getName());
}
return name + '(' + sb.toString() + ')';
}
public static Method getDeclaredMethod(final Class<?> clazz, final String name, final Class<?>[] parameterTypes) {
Class<?> c = clazz;
while (c != null) {
try {
final Method declaredMethod = c.getDeclaredMethod(name, parameterTypes);
return declaredMethod;
} catch (NoSuchMethodException x) {
doNothing(); // expected in many cases ;-)
}
c = c.getSuperclass();
}
return null;
}
private static final Map<Class<?>, Reference<List<Field>>> class2AllDeclaredFields = Collections.synchronizedMap(
new WeakHashMap<Class<?>, Reference<List<Field>>>());
/**
* Gets all fields declared by the given {@code clazz} including its super-classes; starting from the most concrete sub-class
* (i.e. the given {@code clazz}).
* <p>
* Please note that the order of the fields declared by one single class is unspecified according to the Java specification.
* The only guaranteed order is that between fields declared by one class and fields declared by its super-classes: The result
* of this method begins with fields of the most concrete class, followed by its super-class's fields, followed by the
* fields of the super-class' super-class and so on.
* <p>
* For example, consider the following classes: {@code Roadster} <i>extends</i> {@code Car} <i>extends</i> {@code Vehicle}. This
* method would thus return all fields declared by all three classes, starting with the fields of {@code Roadster},
* followed by the fields of {@code Car} and finally followed by the fields of {@code Vehicle}.
*
* @param clazz the class whose fields to obtain.
* @return the list of fields declared by the given class and all its super-classes. Never <code>null</code>, but maybe
* empty.
*/
public static List<Field> getAllDeclaredFields(final Class<?> clazz) {
assertNotNull(clazz, "clazz");
synchronized(clazz) {
final Reference<List<Field>> resultRef = class2AllDeclaredFields.get(clazz);
List<Field> result = resultRef == null ? null : resultRef.get();
if (result == null) {
result = new ArrayList<>();
Class<?> c = clazz;
while (c != null) {
final Field[] declaredFields = c.getDeclaredFields();
for (Field field : declaredFields)
result.add(field);
c = c.getSuperclass();
}
((ArrayList<?>)result).trimToSize();
class2AllDeclaredFields.put(clazz, new WeakReference<List<Field>>(result));
}
return result;
}
}
public static Map<Field, Object> getAllDeclaredFieldValues(final Object object) {
assertNotNull(object, "object");
final List<Field> allDeclaredFields = getAllDeclaredFields(object.getClass());
final Map<Field, Object> result = new HashMap<>(allDeclaredFields.size());
for (Field field : allDeclaredFields) {
field.setAccessible(true);
try {
final Object fieldValue = field.get(object);
result.put(field, fieldValue);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
return result;
}
public static <V> V getFieldValue(final Object object, final String fieldName) {
assertNotNull(object, "object");
assertNotNull(fieldName, "fieldName");
// TODO pretty inefficient implementation - make better!
String className = null;
String simpleFieldName = fieldName;
final int lastDotIndex = fieldName.lastIndexOf('.');
if (lastDotIndex >= 0) {
className = fieldName.substring(0, lastDotIndex);
simpleFieldName = fieldName.substring(lastDotIndex + 1);
}
final List<Field> declaredFields = getAllDeclaredFields(object.getClass());
for (final Field field : declaredFields) {
if (className != null && !className.equals(field.getDeclaringClass().getName()))
continue;
if (!simpleFieldName.equals(field.getName()))
continue;
field.setAccessible(true);
try {
@SuppressWarnings("unchecked")
final V value = (V) field.get(object);
return value;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
throw new IllegalArgumentException(String.format("object's class %s does not have this field: %s", object.getClass(), fieldName));
}
/**
* Sets the {@code object}'s field – identified by {@code fieldName} – to the given {@code value}.
* <p>
* The {@code fieldName} can be simple (e.g. "firstName") or fully qualified (e.g. "co.codewizards.bla.Person.firstName").
* If it is simple, the most concrete sub-class' matching field is used.
*
* @param object the object whose field to manipulate. Must not be <code>null</code>.
* @param fieldName the simple or fully qualified field name (fully qualified means prefixed by the class name). Must not be <code>null</code>.
* @param value the value to be assigned. May be <code>null</code>.
*/
public static void setFieldValue(final Object object, final String fieldName, final Object value) {
assertNotNull(object, "object");
assertNotNull(fieldName, "fieldName");
// TODO pretty inefficient implementation - make better!
String className = null;
String simpleFieldName = fieldName;
final int lastDotIndex = fieldName.lastIndexOf('.');
if (lastDotIndex >= 0) {
className = fieldName.substring(0, lastDotIndex);
simpleFieldName = fieldName.substring(lastDotIndex + 1);
}
final List<Field> declaredFields = getAllDeclaredFields(object.getClass());
for (final Field field : declaredFields) {
if (className != null && !className.equals(field.getDeclaringClass().getName()))
continue;
if (!simpleFieldName.equals(field.getName()))
continue;
field.setAccessible(true);
try {
field.set(object, value);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return;
}
throw new IllegalArgumentException(String.format("object's class %s does not have this field: %s", object.getClass(), fieldName));
}
public static Set<Class<?>> getAllInterfaces(final Class<?> clazz) {
assertNotNull(clazz, "clazz");
final Set<Class<?>> interfaces = new LinkedHashSet<>();
Class<?> c = clazz;
while (c != null) {
populateInterfaces(interfaces, c);
c = c.getSuperclass();
}
return interfaces;
}
private static void populateInterfaces(Collection<Class<?>> interfaces, Class<?> clazz) {
for (final Class<?> iface : clazz.getInterfaces())
interfaces.add(iface);
for (final Class<?> iface : clazz.getInterfaces())
populateInterfaces(interfaces, iface);
}
/**
* Resolves the actual type arguments of a base-class declared in a concrete sub-class.
* <p>
* This is a convenience method delegating to {@link #resolveActualTypeArguments(Class, Class)}
* passing {@code concreteObject.getClass()} as {@code concreteClass}.
* @param baseClass the base class. Must not be <code>null</code>.
* @param concreteObject an instance of a sub-class of the generic {@code baseClass}.
* @return the resolved type arguments. Never <code>null</code> (empty array for a non-generic base-class).
*/
public static final <T> Type[] resolveActualTypeArguments(final Class<T> baseClass, final T concreteObject) {
assertNotNull(baseClass, "baseClass");
assertNotNull(concreteObject, "concreteObject");
@SuppressWarnings("unchecked")
final Class<? extends T> concreteClass = (Class<? extends T>) concreteObject.getClass();
return resolveActualTypeArguments(baseClass, concreteClass);
}
/**
* Resolves the actual type arguments of a base-class declared in a concrete sub-class.
* <p>
* The length as well as the order of the resolved type arguments matches the declaration order
* in the base-class. If a type argument could successfully be resolved, it is usually an instance of
* {@link Class}. If it could not be resolved (because the sub-class does not specify the generic type info
* - directly or indirectly), it is an instance of {@link TypeVariable}.
* <p>
* A typical use-case is this:
* <pre>
* public abstract class MyBase<A, B, C> {
* final Class<A> actualTypeArgumentA;
* final Class<B> actualTypeArgumentB;
* final Class<C> actualTypeArgumentC;
*
* public MyBase() {
* final Type[] actualTypeArguments = resolveActualTypeArguments(MyBase.class, this);
*
* // The following assignments fail - of course -, if the concrete class lacks
* // generic type info - like the example class "MyFail" below.
* actualTypeArgumentA = (Class<A>) actualTypeArguments[0];
* actualTypeArgumentB = (Class<B>) actualTypeArguments[1];
* actualTypeArgumentC = (Class<C>) actualTypeArguments[2];
* }
* }
*
* public class MyConcrete extends MyBase<Long, Boolean, String> {
* }
*
* public class MyFail extends MyBase {
* }
* </pre>
*
* @param baseClass the base class. Must not be <code>null</code>.
* @param concreteClass a sub-class of the generic {@code baseClass}.
* @return the resolved type arguments. Never <code>null</code> (empty array for a non-generic base-class).
*/
public static final <T> Type[] resolveActualTypeArguments(final Class<T> baseClass, final Class<? extends T> concreteClass) {
return _resolveActualTypeArgs(baseClass, concreteClass);
}
private static final <T> Type[] _resolveActualTypeArgs(final Class<T> baseClass, final Class<? extends T> concreteClass, final Type... actualArgs) {
assertNotNull(baseClass, "baseClass");
assertNotNull(concreteClass, "concreteClass");
assertNotNull(actualArgs, "actualArgs");
if (actualArgs.length != 0 && actualArgs.length != concreteClass.getTypeParameters().length)
throw new IllegalArgumentException("actualArgs.length != 0 && actualArgs.length != concreteClass.typeParameters.length");
final Type[] _actualArgs = actualArgs.length == 0 ? concreteClass.getTypeParameters() : actualArgs;
// map type parameters into the actual types
Map<String, Type> typeVariables = new HashMap<String, Type>();
for (int i = 0; i < _actualArgs.length; i++) {
TypeVariable<?> typeVariable = concreteClass.getTypeParameters()[i];
typeVariables.put(typeVariable.getName(), _actualArgs[i]);
}
// Find direct ancestors (superclass, interfaces)
List<Type> ancestors = new LinkedList<Type>();
if (concreteClass.getGenericSuperclass() != null) {
ancestors.add(concreteClass.getGenericSuperclass());
}
for (Type t : concreteClass.getGenericInterfaces()) {
ancestors.add(t);
}
// Recurse into ancestors (superclass, interfaces)
for (Type type : ancestors) {
if (type instanceof Class<?>) {
// ancestor is non-parameterized. Recurse only if it matches the base class.
Class<?> ancestorClass = (Class<?>) type;
if (baseClass.isAssignableFrom(ancestorClass)) {
Type[] result = _resolveActualTypeArgs(baseClass, (Class<? extends T>) ancestorClass);
if (result != null) {
return result;
}
}
}
if (type instanceof ParameterizedType) {
// ancestor is parameterized. Recurse only if the raw type matches the base class.
ParameterizedType parameterizedType = (ParameterizedType) type;
Type rawType = parameterizedType.getRawType();
if (rawType instanceof Class<?>) {
Class<?> rawTypeClass = (Class<?>) rawType;
if (baseClass.isAssignableFrom(rawTypeClass)) {
// loop through all type arguments and replace type variables with the actually known types
List<Type> resolvedTypes = new LinkedList<Type>();
for (Type t : parameterizedType.getActualTypeArguments()) {
if (t instanceof TypeVariable<?>) {
Type resolvedType = typeVariables.get(((TypeVariable<?>) t).getName());
resolvedTypes.add(resolvedType != null ? resolvedType : t);
} else {
resolvedTypes.add(t);
}
}
Type[] result = _resolveActualTypeArgs(baseClass, (Class<? extends T>) rawTypeClass, resolvedTypes.toArray(new Type[] {}));
if (result != null) {
return result;
}
}
}
}
}
// we have a result if we reached the base class.
return concreteClass.equals(baseClass) ? _actualArgs : null;
}
}