se.fortnox.reactivewizard.util.ReflectionUtil Maven / Gradle / Ivy
package se.fortnox.reactivewizard.util;
import rx.Observable;
import rx.Single;
import javax.inject.Provider;
import java.lang.annotation.Annotation;
import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import static java.util.Arrays.asList;
public class ReflectionUtil {
private static final String CGLIB_CLASS_SEPARATOR = "$$";
public static Type getTypeOfObservable(Method method) {
Type type = method.getGenericReturnType();
if (!(type instanceof ParameterizedType)) {
method = getInterfaceMethod(method);
if (method == null) {
throw new RuntimeException("method does not have a generic return type");
}
type = method.getGenericReturnType();
}
ParameterizedType parameterizedType = (ParameterizedType)type;
Class> rawClass = (Class>)parameterizedType.getRawType();
if (!(rawClass.equals(Observable.class)) && !(rawClass.equals(Single.class))) {
throw new RuntimeException(type + " is not an Observable or Single");
}
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
return actualTypeArguments[0];
}
public static Class> getGenericParameter(Type type) {
if (!(type instanceof ParameterizedType)) {
throw new RuntimeException("The sent in type " + type + " is not a ParameterizedType");
}
ParameterizedType pt = (ParameterizedType)type;
Type[] actualTypeArguments = pt.getActualTypeArguments();
if (actualTypeArguments.length != 1) {
throw new RuntimeException("The sent in type " + type + " should have exactly one type argument, but had " + actualTypeArguments.length);
}
return (Class>)actualTypeArguments[0];
}
private static Method getInterfaceMethod(Method method) {
for (Class iface : method.getDeclaringClass().getInterfaces()) {
for (Method candidate : iface.getDeclaredMethods()) {
if (methodsEquals(method, candidate)) {
return candidate;
}
}
}
return null;
}
private static boolean methodsEquals(Method method, Method candidateMethod) {
return candidateMethod.getName().equals(method.getName()) && Arrays.equals(candidateMethod.getParameterTypes(), method.getParameterTypes());
}
public static Method getOverriddenMethod(Method method) {
Class> declaringClass = getUserDefinedClass(method.getDeclaringClass());
Optional found;
for (Class> iface : declaringClass.getInterfaces()) {
found = findMethodInClass(method, iface);
if (found.isPresent()) {
return found.get();
}
}
found = findMethodInClass(method, declaringClass);
found = found.filter(m -> !m.equals(method));
return found.orElse(null);
}
private static Class> getUserDefinedClass(Class> clazz) {
if (clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
Class> superclass = clazz.getSuperclass();
if (superclass != null && superclass != Object.class) {
return superclass;
}
}
return clazz;
}
public static Optional findMethodInClass(Method method, Class> cls) {
for (Method candidate : cls.getDeclaredMethods()) {
if (methodsEquals(method, candidate)) {
return Optional.of(candidate);
}
}
return Optional.empty();
}
public static T getAnnotation(Method method, Class annotationClass) {
T annotation = method.getAnnotation(annotationClass);
if (annotation == null) {
Method overriddenMethod = getOverriddenMethod(method);
if (overriddenMethod != null) {
annotation = overriddenMethod.getAnnotation(annotationClass);
}
}
return annotation;
}
public static List getAnnotations(Method method) {
List result = new LinkedList<>(asList(method.getAnnotations()));
Method overridden = getOverriddenMethod(method);
if (overridden != null) {
result.addAll(asList(overridden.getAnnotations()));
}
return result;
}
public static List> getParameterAnnotations(Method method) {
List> result = new LinkedList>();
Annotation[][] annotations = method.getParameterAnnotations();
Method overridden = getOverriddenMethod(method);
Annotation[][] inheritedAnnotations = null;
if (overridden != null) {
inheritedAnnotations = overridden.getParameterAnnotations();
}
for (int i = 0; i < annotations.length; i++) {
List merged = new LinkedList();
merged.addAll(asList(annotations[i]));
if (inheritedAnnotations != null) {
merged.addAll(asList(inheritedAnnotations[i]));
}
result.add(merged);
}
return result;
}
public static List getParameterAnnotations(Parameter parameter) {
List annotations = new ArrayList<>(asList(parameter.getAnnotations()));
Method method = (Method)parameter.getDeclaringExecutable();
Method overriddenMethod = getOverriddenMethod(method);
if (overriddenMethod != null) {
for (Parameter overriddenParameter : overriddenMethod.getParameters()) {
if (overriddenParameter.getName().equals(parameter.getName())) {
annotations.addAll(asList(overriddenParameter.getAnnotations()));
return annotations;
}
}
}
return annotations;
}
public static Class> getRawType(Type type) {
if (type == null) {
return null;
}
if (type instanceof Class) {
return (Class>)type;
}
if (type instanceof ParameterizedType) {
return (Class>)((ParameterizedType)type).getRawType();
}
throw new RuntimeException("Unexpected type: " + type);
}
/**
* Locate a getter (method or field) for a property.
*
* The class hierarchy will be traversed to find any inherited getter for the given property.
*
* @param cls The class inspected.
* @param propertyName The property to locate a getter for.
* @return a Getter instance for either a method or a field
*/
static Getter getGetter(Class> cls, String propertyName) {
return getGetter(cls, cls, propertyName);
}
/**
* Recurse through the class hierarchy to find a getter for a property.
*
* @param original The original class inspected for a getter.
* @param declaringClass The current class in the hierarchy being inspected.
* @param propertyName The property to locate a getter for.
* @return a Getter instance for either a method or a field
*/
private static Getter getGetter(Class> original, Class> declaringClass, String propertyName) {
String capitalizedPropertyName = propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
Method method = getAccessor(declaringClass, capitalizedPropertyName);
if (method == null) {
method = getBooleanAccessor(declaringClass, capitalizedPropertyName);
}
if (method == null) {
method = getMethod(declaringClass, propertyName);
}
if (method == null) {
Field field;
try {
field = declaringClass.getDeclaredField(propertyName);
} catch (NoSuchFieldException e) {
if (declaringClass.getSuperclass() != null) {
return getGetter(original, declaringClass.getSuperclass(), propertyName);
}
return null;
}
return FieldGetter.create(original, field);
}
return MethodGetter.create(original, method);
}
/**
* Locate a setter (method or field) for a property.
*
* The class hierarchy will be traversed to find any inherited setter for the given property.
*
* @param cls The class inspected.
* @param propertyName The property to locate a setter for.
* @return a Setter instance for either a method or a field
*/
static Setter getSetter(Class> cls, String propertyName) {
return getSetter(cls, cls, propertyName);
}
/**
* Recurse through the class hierarchy to find a setter for a property.
*
* @param original The original class inspected for a setter.
* @param declaringClass The current class in the hierarchy being inspected.
* @param propertyName The property to locate a setter for.
* @return a Setter instance for either a method or a field
*/
private static Setter getSetter(Class> original, Class> declaringClass, String propertyName) {
String capitalizedPropertyName = propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
try {
String methodName = "set" + capitalizedPropertyName;
Optional first = Arrays.stream(declaringClass.getMethods())
.filter(m -> !m.isSynthetic() && m.getName().equals(methodName) && m.getReturnType().equals(void.class) && m.getParameterCount() == 1)
.findFirst();
if (first.isPresent()) {
return MethodSetter.create(original, first.get());
}
} catch (Exception ignored) {
}
try {
Field field = declaringClass.getDeclaredField(propertyName);
return FieldSetter.create(original, field);
} catch (NoSuchFieldException ignored) {
if (declaringClass.getSuperclass() != null) {
return getSetter(original, declaringClass.getSuperclass(), propertyName);
}
}
return null;
}
public static Optional getPropertyResolver(Type type, String... propertyNames) {
return PropertyResolver.from(type, propertyNames);
}
/**
* If the method given is part of a proxy (e.g. for validating purposes), and that proxy implements Supplier in
* order to expose it's underlying implementation, then this will return the underlying Method definition, so that
* param factories can search the implementing class for annotations.
*/
public static Method getInstanceMethod(Method method, Object resourceInstance) {
if (!Proxy.isProxyClass(method.getDeclaringClass())) {
return method;
}
InvocationHandler invocationHandler = Proxy.getInvocationHandler(resourceInstance);
if (!(invocationHandler instanceof Provider)) {
return method;
}
resourceInstance = ((Provider)invocationHandler).get();
Optional methodInClass = ReflectionUtil.findMethodInClass(method, resourceInstance.getClass());
return methodInClass.orElse(method);
}
private static Method getAccessor(Class> cls, String propertyName) {
return getMethod(cls, "get" + propertyName);
}
private static Method getBooleanAccessor(Class> cls, String propertyName) {
Method method = getMethod(cls, "is" + propertyName);
if (method == null) {
return getMethod(cls, "has" + propertyName);
}
return method;
}
private static Method getMethod(Class> cls, String propertyName) {
try {
return cls.getDeclaredMethod(propertyName);
} catch (NoSuchMethodException e) {
if (cls.getSuperclass() != null) {
return getMethod(cls.getSuperclass(), propertyName);
}
return null;
}
}
public static Supplier instantiator(Class cls) {
try {
Constructor> constructor = Stream.of(cls.getDeclaredConstructors())
.filter(c -> c.getParameterCount() == 0)
.findFirst()
.orElseThrow(NoSuchMethodException::new);
MethodHandles.Lookup lookup = lookupFor(cls, constructor);
constructor.setAccessible(true);
MethodHandle methodHandle = lookup.unreflectConstructor(constructor);
CallSite callSite = LambdaMetafactory.metafactory(
lookup,
"get",
MethodType.methodType(Supplier.class),
MethodType.methodType(Object.class),
methodHandle,
methodHandle.type()
);
return (Supplier)callSite.getTarget().invoke();
} catch (NoSuchMethodException e) {
throw new RuntimeException("No constructor with zero parameters found on " + cls.getSimpleName(), e);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
static MethodHandles.Lookup lookupFor(Class cls, AccessibleObject accessibleObject) {
try {
final MethodHandles.Lookup original = MethodHandles.lookup();
if (accessibleObject.isAccessible()) {
return original;
}
// Change the lookup to allow private access
final Field internal = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
internal.setAccessible(true);
final MethodHandles.Lookup trusted = (MethodHandles.Lookup) internal.get(original);
return trusted.in(cls);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
static Function lambdaForFunction(MethodHandles.Lookup lookup, MethodHandle methodHandle) throws Throwable {
CallSite callSite = LambdaMetafactory.metafactory(
lookup,
"apply",
MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class),
methodHandle,
methodHandle.type()
);
return (Function) callSite.getTarget().invoke();
}
public static Optional> getter(Class instanceCls, String propertyPath) {
Optional propertyResolver = ReflectionUtil.getPropertyResolver(instanceCls, propertyPath.split("\\."));
return propertyResolver.map(PropertyResolver::getter);
}
public static Optional> setter(Class instanceCls, String propertyPath) {
Optional propertyResolver = ReflectionUtil.getPropertyResolver(instanceCls, propertyPath.split("\\."));
return propertyResolver.map(PropertyResolver::setter);
}
}