All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.util.ReflectionHelpers Maven / Gradle / Ivy

The newest version!
package org.robolectric.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
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;
import java.util.Optional;
import javax.annotation.Nullable;
import org.robolectric.annotation.ClassName;

/** Collection of helper methods for calling methods and accessing fields reflectively. */
@SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals", "NewApi"})
public class ReflectionHelpers {

  private static final Map PRIMITIVE_RETURN_VALUES;
  private static final PerfStatsCollector perfStatsCollector = PerfStatsCollector.getInstance();

  static {
    HashMap map = new HashMap<>();
    map.put("boolean", Boolean.FALSE);
    map.put("int", 0);
    map.put("long", (long) 0);
    map.put("float", (float) 0);
    map.put("double", (double) 0);
    map.put("short", (short) 0);
    map.put("byte", (byte) 0);
    PRIMITIVE_RETURN_VALUES = Collections.unmodifiableMap(map);
  }

  /**
   * Create a proxy for the given class which returns default values for every method call.
   *
   * 

0 will be returned for any primitive return types, otherwise null will be returned. * * @param clazz the class to provide a proxy instance of. * @return a new "Null Proxy" instance of the given class. */ public static T createNullProxy(Class clazz) { return (T) Proxy.newProxyInstance( clazz.getClassLoader(), new Class[] {clazz}, (proxy, method, args) -> PRIMITIVE_RETURN_VALUES.get(method.getReturnType().getName())); } /** * Create a proxy for the given class which returns other deep proxies from all it's methods. * *

The returned object will be an instance of the given class, but all methods will return * either the "default" value for primitives, or another deep proxy for non-primitive types. * *

This should be used rarely, for cases where we need to create deep proxies in order not to * crash. The inner proxies are impossible to configure, so there is no way to create meaningful * behavior from a deep proxy. It serves mainly to prevent Null Pointer Exceptions. * * @param clazz the class to provide a proxy instance of. * @return a new "Deep Proxy" instance of the given class. */ public static T createDeepProxy(Class clazz) { return (T) Proxy.newProxyInstance( clazz.getClassLoader(), new Class[] {clazz}, (proxy, method, args) -> { if (PRIMITIVE_RETURN_VALUES.containsKey(method.getReturnType().getName())) { return PRIMITIVE_RETURN_VALUES.get(method.getReturnType().getName()); } else if (method.getReturnType().isInterface()) { return createDeepProxy(method.getReturnType()); } else { return null; } }); } /** * Create a proxy for the given class which can delegate method calls to another object. * *

If the delegate has no methods whose signature matches, a null (or 0 for primitive types) * return value will be returned. * * @param clazz the class to provide a proxy instance of. * @param delegate the object to delegate matching method calls to. A 'matching method' must have * exactlu the same method name and parameter class names as the desired method. * The @ClassName annotation can be applied to provide a custom class name. * @return a new "Delegating Proxy" instance of the given class. */ public static T createDelegatingProxy(Class clazz, final Object delegate) { final Class delegateClass = delegate.getClass(); return (T) Proxy.newProxyInstance( clazz.getClassLoader(), new Class[] {clazz}, (proxy, method, args) -> { try { Method delegateMethod = findDelegateMethod(delegateClass, method.getName(), method.getParameterTypes()); delegateMethod.setAccessible(true); return delegateMethod.invoke(delegate, args); } catch (NoSuchMethodException e) { return PRIMITIVE_RETURN_VALUES.get(method.getReturnType().getName()); } catch (InvocationTargetException e) { // Required to propagate the correct throwable. throw e.getTargetException(); } }); } private static Method findDelegateMethod( Class delegateClass, String methodName, Class[] parameterTypes) throws NoSuchMethodException { for (Method delegateMethod : delegateClass.getMethods()) { if (delegateMethod.getName().equals(methodName) && Modifier.isPublic(delegateMethod.getModifiers()) && parametersMatch( parameterTypes, delegateMethod.getParameterTypes(), delegateMethod.getParameterAnnotations())) { return delegateMethod; } } throw new NoSuchMethodException(); } private static boolean parametersMatch( Class[] parameterTypes, Class[] delegateParameterTypes, Annotation[][] parameterAnnotations) { if (parameterTypes.length != delegateParameterTypes.length) { return false; } for (int i = 0; i < parameterTypes.length; i++) { if (!parameterTypes[i].getName().equals(delegateParameterTypes[i].getName()) && !parameterTypes[i] .getName() .equals(getClassNameFromAnnotation(parameterAnnotations[i]))) { return false; } } return true; } @Nullable private static String getClassNameFromAnnotation(Annotation[] parameterAnnotations) { for (Annotation annotation : parameterAnnotations) { if (annotation.annotationType().equals(ClassName.class)) { return ((ClassName) annotation).value(); } } return null; } public static A defaultsFor(Class annotation) { return annotation.cast( Proxy.newProxyInstance( annotation.getClassLoader(), new Class[] {annotation}, (proxy, method, args) -> method.getDefaultValue())); } /** * Reflectively get the value of a field. * * @param object Target object. * @param fieldName The field name. * @param The return type. * @return Value of the field on the object. */ @SuppressWarnings("unchecked") public static R getField(final Object object, final String fieldName) { try { return traverseClassHierarchy( object.getClass(), NoSuchFieldException.class, traversalClass -> { 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, (InsideTraversal) traversalClass -> { 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 check if a class has a given field (static or non static). * * @param clazz Target class. * @param fieldName The field name. * @return boolean to indicate whether the field exists or not in clazz. */ public static boolean hasField(Class clazz, String fieldName) { try { Field field = clazz.getDeclaredField(fieldName); return (field != null); } catch (NoSuchFieldException e) { return false; } } /** * Reflectively get the value of a static field. * * @param field Field object. * @param The return type. * @return Value of the field. */ @SuppressWarnings("unchecked") public static R getStaticField(Field field) { try { field.setAccessible(true); 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 The return type. * @return Value of the field. */ public static 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 { if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) { throw new IllegalArgumentException("Cannot set the value of final field " + field); } field.setAccessible(true); 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 The return type. * @return The return value of the method. */ public static R callInstanceMethod( final Object instance, final String methodName, ClassParameter... classParameters) { perfStatsCollector.incrementCount( String.format( "ReflectionHelpers.callInstanceMethod-%s_%s", instance.getClass().getName(), methodName)); try { final Class[] classes = ClassParameter.getClasses(classParameters); final Object[] values = ClassParameter.getValues(classParameters); return traverseClassHierarchy( instance.getClass(), NoSuchMethodException.class, traversalClass -> { 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 The return type. * @return The return value of the method. */ public static R callInstanceMethod( Class cl, final Object instance, final String methodName, ClassParameter... classParameters) { perfStatsCollector.incrementCount( String.format("ReflectionHelpers.callInstanceMethod-%s_%s", cl.getName(), methodName)); try { final Class[] classes = ClassParameter.getClasses(classParameters); final Object[] values = ClassParameter.getValues(classParameters); Method method = cl.getDeclaredMethod(methodName, classes); method.setAccessible(true); if (Modifier.isStatic(method.getModifiers())) { throw new IllegalArgumentException(method + " is static"); } return (R) method.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); } } /** * Helper method for calling a static method using a class from a custom class loader * * @param classLoader The ClassLoader used to load class * @param fullyQualifiedClassName The full qualified class name with package name of the * ClassLoader will load * @param methodName The method name will be called * @param classParameters The input parameters will be used for method calling * @param Return type of the method * @return Return the value of the method */ public static R callStaticMethod( ClassLoader classLoader, String fullyQualifiedClassName, String methodName, ClassParameter... classParameters) { Class clazz = loadClass(classLoader, fullyQualifiedClassName); return callStaticMethod(clazz, methodName, classParameters); } /** * 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 The return type. * @return The return value of the method. */ @SuppressWarnings("unchecked") public static R callStaticMethod( Class clazz, String methodName, ClassParameter... classParameters) { perfStatsCollector.incrementCount( String.format("ReflectionHelpers.callStaticMethod-%s_%s", clazz.getName(), methodName)); try { Class[] classes = ClassParameter.getClasses(classParameters); Object[] values = ClassParameter.getValues(classParameters); Method method = clazz.getDeclaredMethod(methodName, classes); method.setAccessible(true); if (!Modifier.isStatic(method.getModifiers())) { throw new IllegalArgumentException(method + " is not static"); } 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 (NoSuchMethodException e) { throw new RuntimeException("no such method " + clazz + "." + methodName, e); } 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); } } /** * Attempt to load a class. * * @param classLoader The class loader. * @param fullyQualifiedClassName The fully qualified class name. * @return The class object, or null if class is not found. */ public static Optional> attemptLoadClass( ClassLoader classLoader, String fullyQualifiedClassName) { try { return Optional.of(classLoader.loadClass(fullyQualifiedClassName)); } catch (ClassNotFoundException e) { return Optional.empty(); } } /** * Create a new instance of a class * * @param cl The class object. * @param The class type. * @return New class instance. */ public static T newInstance(Class 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 The return type. * @return The return value of the method. */ public static R callConstructor( Class clazz, ClassParameter... classParameters) { perfStatsCollector.incrementCount("ReflectionHelpers.callConstructor-" + clazz.getName()); try { final Class[] classes = ClassParameter.getClasses(classParameters); final Object[] values = ClassParameter.getValues(classParameters); Constructor 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 traverseClassHierarchy( Class targetClass, Class exceptionClass, InsideTraversal 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); } } } } public static Object defaultValueForType(String returnType) { return PRIMITIVE_RETURN_VALUES.get(returnType); } private interface InsideTraversal { R run(Class traversalClass) throws Exception; } /** * Typed parameter used with reflective method calls. * * @param The value of the method parameter. */ public static class ClassParameter { public final Class clazz; public final V value; public ClassParameter(Class clazz, V value) { this.clazz = clazz; this.value = value; } public static ClassParameter from(Class clazz, V value) { return new ClassParameter<>(clazz, value); } 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].value; values[i] = paramValue; } return values; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy