blob: c9147a6e69021a6e210c837f99027e603d485088 [file] [log] [blame]
package org.robolectric.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Collection of helper methods for calling methods and accessing fields reflectively.
*/
@SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"})
public class ReflectionHelpers {
public static final Map<String, Object> PRIMITIVE_RETURN_VALUES =
Collections.unmodifiableMap(new HashMap<String, Object>() {{
put("boolean", Boolean.FALSE);
put("int", 0);
put("long", (long) 0);
put("float", (float) 0);
put("double", (double) 0);
put("short", (short) 0);
put("byte", (byte) 0);
}});
public static <T> T createNullProxy(Class<T> clazz) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
new Class[]{clazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return PRIMITIVE_RETURN_VALUES.get(method.getReturnType().getName());
}
});
}
public static <T> T createDelegatingProxy(Class<T> clazz, final Object delegate) {
final Class delegateClass = delegate.getClass();
return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
new Class[]{clazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Method delegateMethod = delegateClass.getMethod(method.getName(), method.getParameterTypes());
delegateMethod.setAccessible(true);
return delegateMethod.invoke(delegate, args);
} catch (NoSuchMethodException e) {
return PRIMITIVE_RETURN_VALUES.get(method.getReturnType().getName());
}
}
});
}
public static <A extends Annotation> A defaultsFor(Class<A> annotation) {
return annotation.cast(
Proxy.newProxyInstance(annotation.getClassLoader(), new Class[] { annotation },
new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.getDefaultValue();
}
}));
}
/**
* Reflectively get the value of a field.
*
* @param object Target object.
* @param fieldName The field name.
* @param <R> The return type.
* @return Value of the field on the object.
*/
@SuppressWarnings("unchecked")
public static <R> R getField(final Object object, final String fieldName) {
try {
return traverseClassHierarchy(object.getClass(), NoSuchFieldException.class, new InsideTraversal<R>() {
@Override
public R run(Class<?> traversalClass) throws Exception {
Field field = traversalClass.getDeclaredField(fieldName);
field.setAccessible(true);
return (R) field.get(object);
}
});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Reflectively set the value of a field.
*
* @param object Target object.
* @param fieldName The field name.
* @param fieldNewValue New value.
*/
public static void setField(final Object object, final String fieldName, final Object fieldNewValue) {
try {
traverseClassHierarchy(object.getClass(), NoSuchFieldException.class, new InsideTraversal<Void>() {
@Override
public Void run(Class<?> traversalClass) throws Exception {
Field field = traversalClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, fieldNewValue);
return null;
}
});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Reflectively set the value of a field.
*
* @param type Target type.
* @param object Target object.
* @param fieldName The field name.
* @param fieldNewValue New value.
*/
public static void setField(Class<?> type, final Object object, final String fieldName, final Object fieldNewValue) {
try {
Field field = type.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, fieldNewValue);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Reflectively get the value of a static field.
*
* @param field Field object.
* @param <R> The return type.
* @return Value of the field.
*/
@SuppressWarnings("unchecked")
public static <R> R getStaticField(Field field) {
try {
makeFieldVeryAccessible(field);
return (R) field.get(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Reflectively get the value of a static field.
*
* @param clazz Target class.
* @param fieldName The field name.
* @param <R> The return type.
* @return Value of the field.
*/
public static <R> R getStaticField(Class<?> clazz, String fieldName) {
try {
return getStaticField(clazz.getDeclaredField(fieldName));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Reflectively set the value of a static field.
*
* @param field Field object.
* @param fieldNewValue The new value.
*/
public static void setStaticField(Field field, Object fieldNewValue) {
try {
makeFieldVeryAccessible(field);
field.set(null, fieldNewValue);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Reflectively set the value of a static field.
*
* @param clazz Target class.
* @param fieldName The field name.
* @param fieldNewValue The new value.
*/
public static void setStaticField(Class<?> clazz, String fieldName, Object fieldNewValue) {
try {
setStaticField(clazz.getDeclaredField(fieldName), fieldNewValue);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Reflectively call an instance method on an object.
*
* @param instance Target object.
* @param methodName The method name to call.
* @param classParameters Array of parameter types and values.
* @param <R> The return type.
* @return The return value of the method.
*/
public static <R> R callInstanceMethod(final Object instance, final String methodName, ClassParameter<?>... classParameters) {
try {
final Class<?>[] classes = ClassParameter.getClasses(classParameters);
final Object[] values = ClassParameter.getValues(classParameters);
return traverseClassHierarchy(instance.getClass(), NoSuchMethodException.class, new InsideTraversal<R>() {
@Override
@SuppressWarnings("unchecked")
public R run(Class<?> traversalClass) throws Exception {
Method declaredMethod = traversalClass.getDeclaredMethod(methodName, classes);
declaredMethod.setAccessible(true);
return (R) declaredMethod.invoke(instance, values);
}
});
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof RuntimeException) {
throw (RuntimeException) e.getTargetException();
}
if (e.getTargetException() instanceof Error) {
throw (Error) e.getTargetException();
}
throw new RuntimeException(e.getTargetException());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Reflectively call an instance method on an object on a specific class.
*
* @param cl The class.
* @param instance Target object.
* @param methodName The method name to call.
* @param classParameters Array of parameter types and values.
* @param <R> The return type.
* @return The return value of the method.
*/
public static <R> R callInstanceMethod(Class<?> cl, final Object instance, final String methodName, ClassParameter<?>... classParameters) {
try {
final Class<?>[] classes = ClassParameter.getClasses(classParameters);
final Object[] values = ClassParameter.getValues(classParameters);
Method declaredMethod = cl.getDeclaredMethod(methodName, classes);
declaredMethod.setAccessible(true);
return (R) declaredMethod.invoke(instance, values);
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof RuntimeException) {
throw (RuntimeException) e.getTargetException();
}
if (e.getTargetException() instanceof Error) {
throw (Error) e.getTargetException();
}
throw new RuntimeException(e.getTargetException());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Reflectively call a static method on a class.
*
* @param clazz Target class.
* @param methodName The method name to call.
* @param classParameters Array of parameter types and values.
* @param <R> The return type.
* @return The return value of the method.
*/
@SuppressWarnings("unchecked")
public static <R> R callStaticMethod(Class<?> clazz, String methodName, ClassParameter<?>... classParameters) {
try {
Class<?>[] classes = ClassParameter.getClasses(classParameters);
Object[] values = ClassParameter.getValues(classParameters);
Method method = clazz.getDeclaredMethod(methodName, classes);
method.setAccessible(true);
return (R) method.invoke(null, values);
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof RuntimeException) {
throw (RuntimeException) e.getTargetException();
}
if (e.getTargetException() instanceof Error) {
throw (Error) e.getTargetException();
}
throw new RuntimeException(e.getTargetException());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Load a class.
*
* @param classLoader The class loader.
* @param fullyQualifiedClassName The fully qualified class name.
* @return The class object.
*/
public static Class<?> loadClass(ClassLoader classLoader, String fullyQualifiedClassName) {
try {
return classLoader.loadClass(fullyQualifiedClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* Create a new instance of a class
*
* @param cl The class object.
* @param <T> The class type.
* @return New class instance.
*/
public static <T> T newInstance(Class<T> cl) {
try {
return cl.getDeclaredConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException
| InvocationTargetException e) {
throw new RuntimeException(e);
}
}
/**
* Reflectively call the constructor of an object.
*
* @param clazz Target class.
* @param classParameters Array of parameter types and values.
* @param <R> The return type.
* @return The return value of the method.
*/
public static <R> R callConstructor(Class<? extends R> clazz, ClassParameter<?>... classParameters) {
try {
final Class<?>[] classes = ClassParameter.getClasses(classParameters);
final Object[] values = ClassParameter.getValues(classParameters);
Constructor<? extends R> constructor = clazz.getDeclaredConstructor(classes);
constructor.setAccessible(true);
return constructor.newInstance(values);
} catch (InstantiationException e) {
throw new RuntimeException("error instantiating " + clazz.getName(), e);
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof RuntimeException) {
throw (RuntimeException) e.getTargetException();
}
if (e.getTargetException() instanceof Error) {
throw (Error) e.getTargetException();
}
throw new RuntimeException(e.getTargetException());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static <R, E extends Exception> R traverseClassHierarchy(Class<?> targetClass, Class<? extends E> exceptionClass, InsideTraversal<R> insideTraversal) throws Exception {
Class<?> hierarchyTraversalClass = targetClass;
while (true) {
try {
return insideTraversal.run(hierarchyTraversalClass);
} catch (Exception e) {
if (!exceptionClass.isInstance(e)) {
throw e;
}
hierarchyTraversalClass = hierarchyTraversalClass.getSuperclass();
if (hierarchyTraversalClass == null) {
throw new RuntimeException(e);
}
}
}
}
private static void makeFieldVeryAccessible(Field field) throws NoSuchFieldException, IllegalAccessException {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
}
public static Object defaultValueForType(String returnType) {
return PRIMITIVE_RETURN_VALUES.get(returnType);
}
private interface InsideTraversal<R> {
R run(Class<?> traversalClass) throws Exception;
}
/**
* Typed parameter used with reflective method calls.
*
* @param <V> The value of the method parameter.
*/
public static class ClassParameter<V> {
public final Class<? extends V> clazz;
public final V val;
public ClassParameter(Class<? extends V> clazz, V val) {
this.clazz = clazz;
this.val = val;
}
public static <V> ClassParameter<V> from(Class<? extends V> clazz, V val) {
return new ClassParameter<>(clazz, val);
}
public static ClassParameter<?>[] fromComponentLists(Class<?>[] classes, Object[] values) {
ClassParameter<?>[] classParameters = new ClassParameter[classes.length];
for (int i = 0; i < classes.length; i++) {
classParameters[i] = ClassParameter.from(classes[i], values[i]);
}
return classParameters;
}
public static Class<?>[] getClasses(ClassParameter<?>... classParameters) {
Class<?>[] classes = new Class[classParameters.length];
for (int i = 0; i < classParameters.length; i++) {
Class<?> paramClass = classParameters[i].clazz;
classes[i] = paramClass;
}
return classes;
}
public static Object[] getValues(ClassParameter<?>... classParameters) {
Object[] values = new Object[classParameters.length];
for (int i = 0; i < classParameters.length; i++) {
Object paramValue = classParameters[i].val;
values[i] = paramValue;
}
return values;
}
}
/**
* String parameter used with reflective method calls.
*
* @param <V> The value of the method parameter.
*/
public static class StringParameter<V> {
public final String className;
public final V val;
public StringParameter(String className, V val) {
this.className = className;
this.val = val;
}
public static <V> StringParameter<V> from(String className, V val) {
return new StringParameter<>(className, val);
}
}
}