• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.util;
2 
3 import java.lang.annotation.Annotation;
4 import java.lang.reflect.Constructor;
5 import java.lang.reflect.Field;
6 import java.lang.reflect.InvocationHandler;
7 import java.lang.reflect.InvocationTargetException;
8 import java.lang.reflect.Method;
9 import java.lang.reflect.Modifier;
10 import java.lang.reflect.Proxy;
11 import java.util.Collections;
12 import java.util.HashMap;
13 import java.util.Map;
14 
15 /**
16  * Collection of helper methods for calling methods and accessing fields reflectively.
17  */
18 @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"})
19 public class ReflectionHelpers {
20   private static final Map<String, Object> PRIMITIVE_RETURN_VALUES;
21 
22   static {
23     HashMap<String, Object> map = new HashMap<>();
24     map.put("boolean", Boolean.FALSE);
25     map.put("int", 0);
26     map.put("long", (long) 0);
27     map.put("float", (float) 0);
28     map.put("double", (double) 0);
29     map.put("short", (short) 0);
30     map.put("byte", (byte) 0);
31     PRIMITIVE_RETURN_VALUES = Collections.unmodifiableMap(map);
32   }
33 
createNullProxy(Class<T> clazz)34   public static <T> T createNullProxy(Class<T> clazz) {
35     return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
36         new Class[]{clazz}, new InvocationHandler() {
37           @Override
38           public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
39             return PRIMITIVE_RETURN_VALUES.get(method.getReturnType().getName());
40           }
41         });
42   }
43 
44   /**
45    * Create a proxy for the given class which returns other deep proxies from all it's methods.
46    *
47    * <p>The returned object will be an instance of the given class, but all methods will return
48    * either the "default" value for primitives, or another deep proxy for non-primitive types.
49    *
50    * <p>This should be used rarely, for cases where we need to create deep proxies in order not
51    * to crash. The inner proxies are impossible to configure, so there is no way to create
52    * meaningful behavior from a deep proxy. It serves mainly to prevent Null Pointer Exceptions.
53    * @param clazz the class to provide a proxy instance of.
54    * @return a new "Deep Proxy" instance of the given class.
55    */
56   public static <T> T createDeepProxy(Class<T> clazz) {
57     return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
58         new Class[] {clazz}, new InvocationHandler() {
59           @Override
60           public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
61             if (PRIMITIVE_RETURN_VALUES.containsKey(method.getReturnType().getName())) {
62               return PRIMITIVE_RETURN_VALUES.get(method.getReturnType().getName());
63             } else if (method.getReturnType() == Void.TYPE) {
64               return null;
65             } else {
66               return createDeepProxy(method.getReturnType());
67             }
68           }
69         });
70   }
71 
72   public static <T> T createDelegatingProxy(Class<T> clazz, final Object delegate) {
73     final Class delegateClass = delegate.getClass();
74     return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
75         new Class[]{clazz}, new InvocationHandler() {
76           @Override
77           public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
78             try {
79               Method delegateMethod = delegateClass.getMethod(method.getName(), method.getParameterTypes());
80               delegateMethod.setAccessible(true);
81               return delegateMethod.invoke(delegate, args);
82             } catch (NoSuchMethodException e) {
83               return PRIMITIVE_RETURN_VALUES.get(method.getReturnType().getName());
84             }
85           }
86         });
87   }
88 
89   public static <A extends Annotation> A defaultsFor(Class<A> annotation) {
90     return annotation.cast(
91         Proxy.newProxyInstance(annotation.getClassLoader(), new Class[] { annotation },
92             new InvocationHandler() {
93               @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
94                 return method.getDefaultValue();
95               }
96             }));
97   }
98 
99   /**
100    * Reflectively get the value of a field.
101    *
102    * @param object Target object.
103    * @param fieldName The field name.
104    * @param <R> The return type.
105    * @return Value of the field on the object.
106    */
107   @SuppressWarnings("unchecked")
108   public static <R> R getField(final Object object, final String fieldName) {
109     try {
110       return traverseClassHierarchy(object.getClass(), NoSuchFieldException.class, new InsideTraversal<R>() {
111         @Override
112         public R run(Class<?> traversalClass) throws Exception {
113           Field field = traversalClass.getDeclaredField(fieldName);
114           field.setAccessible(true);
115           return (R) field.get(object);
116         }
117       });
118     } catch (Exception e) {
119       throw new RuntimeException(e);
120     }
121   }
122 
123   /**
124    * Reflectively set the value of a field.
125    *
126    * @param object Target object.
127    * @param fieldName The field name.
128    * @param fieldNewValue New value.
129    */
130   public static void setField(final Object object, final String fieldName, final Object fieldNewValue) {
131     try {
132       traverseClassHierarchy(object.getClass(), NoSuchFieldException.class, new InsideTraversal<Void>() {
133         @Override
134         public Void run(Class<?> traversalClass) throws Exception {
135           Field field = traversalClass.getDeclaredField(fieldName);
136           field.setAccessible(true);
137           field.set(object, fieldNewValue);
138           return null;
139         }
140       });
141     } catch (Exception e) {
142       throw new RuntimeException(e);
143     }
144   }
145 
146   /**
147    * Reflectively set the value of a field.
148    *
149    * @param type Target type.
150    * @param object Target object.
151    * @param fieldName The field name.
152    * @param fieldNewValue New value.
153    */
154   public static void setField(Class<?> type, final Object object, final String fieldName, final Object fieldNewValue) {
155     try {
156       Field field = type.getDeclaredField(fieldName);
157       field.setAccessible(true);
158       field.set(object, fieldNewValue);
159     } catch (Exception e) {
160       throw new RuntimeException(e);
161     }
162   }
163 
164   /**
165    * Reflectively get the value of a static field.
166    *
167    * @param field Field object.
168    * @param <R> The return type.
169    * @return Value of the field.
170    */
171   @SuppressWarnings("unchecked")
172   public static <R> R getStaticField(Field field) {
173     try {
174       makeFieldVeryAccessible(field);
175       return (R) field.get(null);
176     } catch (Exception e) {
177       throw new RuntimeException(e);
178     }
179   }
180 
181   /**
182    * Reflectively get the value of a static field.
183    *
184    * @param clazz Target class.
185    * @param fieldName The field name.
186    * @param <R> The return type.
187    * @return Value of the field.
188    */
189   public static <R> R getStaticField(Class<?> clazz, String fieldName) {
190     try {
191       return getStaticField(clazz.getDeclaredField(fieldName));
192     } catch (Exception e) {
193       throw new RuntimeException(e);
194     }
195   }
196 
197   /**
198    * Reflectively set the value of a static field.
199    *
200    * @param field Field object.
201    * @param fieldNewValue The new value.
202    */
203   public static void setStaticField(Field field, Object fieldNewValue) {
204     try {
205       makeFieldVeryAccessible(field);
206       field.set(null, fieldNewValue);
207     } catch (Exception e) {
208       throw new RuntimeException(e);
209     }
210   }
211 
212   /**
213    * Reflectively set the value of a static field.
214    *
215    * @param clazz Target class.
216    * @param fieldName The field name.
217    * @param fieldNewValue The new value.
218    */
219   public static void setStaticField(Class<?> clazz, String fieldName, Object fieldNewValue) {
220     try {
221       setStaticField(clazz.getDeclaredField(fieldName), fieldNewValue);
222     } catch (Exception e) {
223       throw new RuntimeException(e);
224     }
225   }
226 
227   /**
228    * Reflectively call an instance method on an object.
229    *
230    * @param instance Target object.
231    * @param methodName The method name to call.
232    * @param classParameters Array of parameter types and values.
233    * @param <R> The return type.
234    * @return The return value of the method.
235    */
236   public static <R> R callInstanceMethod(final Object instance, final String methodName, ClassParameter<?>... classParameters) {
237     try {
238       final Class<?>[] classes = ClassParameter.getClasses(classParameters);
239       final Object[] values = ClassParameter.getValues(classParameters);
240 
241       return traverseClassHierarchy(instance.getClass(), NoSuchMethodException.class, new InsideTraversal<R>() {
242         @Override
243         @SuppressWarnings("unchecked")
244         public R run(Class<?> traversalClass) throws Exception {
245           Method declaredMethod = traversalClass.getDeclaredMethod(methodName, classes);
246           declaredMethod.setAccessible(true);
247           return (R) declaredMethod.invoke(instance, values);
248         }
249       });
250     } catch (InvocationTargetException e) {
251       if (e.getTargetException() instanceof RuntimeException) {
252         throw (RuntimeException) e.getTargetException();
253       }
254       if (e.getTargetException() instanceof Error) {
255         throw (Error) e.getTargetException();
256       }
257       throw new RuntimeException(e.getTargetException());
258     } catch (Exception e) {
259       throw new RuntimeException(e);
260     }
261   }
262 
263   /**
264    * Reflectively call an instance method on an object on a specific class.
265    *
266    * @param cl The class.
267    * @param instance Target object.
268    * @param methodName The method name to call.
269    * @param classParameters Array of parameter types and values.
270    * @param <R> The return type.
271    * @return The return value of the method.
272    */
273   public static <R> R callInstanceMethod(Class<?> cl, final Object instance, final String methodName, ClassParameter<?>... classParameters) {
274     try {
275       final Class<?>[] classes = ClassParameter.getClasses(classParameters);
276       final Object[] values = ClassParameter.getValues(classParameters);
277 
278       Method method = cl.getDeclaredMethod(methodName, classes);
279       method.setAccessible(true);
280       if (Modifier.isStatic(method.getModifiers())) {
281         throw new IllegalArgumentException(method + " is static");
282       }
283       return (R) method.invoke(instance, values);
284     } catch (InvocationTargetException e) {
285       if (e.getTargetException() instanceof RuntimeException) {
286         throw (RuntimeException) e.getTargetException();
287       }
288       if (e.getTargetException() instanceof Error) {
289         throw (Error) e.getTargetException();
290       }
291       throw new RuntimeException(e.getTargetException());
292     } catch (Exception e) {
293       throw new RuntimeException(e);
294     }
295   }
296 
297   /**
298    * Reflectively call a static method on a class.
299    *
300    * @param clazz Target class.
301    * @param methodName The method name to call.
302    * @param classParameters Array of parameter types and values.
303    * @param <R> The return type.
304    * @return The return value of the method.
305    */
306   @SuppressWarnings("unchecked")
307   public static <R> R callStaticMethod(Class<?> clazz, String methodName, ClassParameter<?>... classParameters) {
308     try {
309       Class<?>[] classes = ClassParameter.getClasses(classParameters);
310       Object[] values = ClassParameter.getValues(classParameters);
311 
312       Method method = clazz.getDeclaredMethod(methodName, classes);
313       method.setAccessible(true);
314       if (!Modifier.isStatic(method.getModifiers())) {
315         throw new IllegalArgumentException(method + " is not static");
316       }
317       return (R) method.invoke(null, values);
318     } catch (InvocationTargetException e) {
319       if (e.getTargetException() instanceof RuntimeException) {
320         throw (RuntimeException) e.getTargetException();
321       }
322       if (e.getTargetException() instanceof Error) {
323         throw (Error) e.getTargetException();
324       }
325       throw new RuntimeException(e.getTargetException());
326     } catch (NoSuchMethodException e) {
327       throw new RuntimeException("no such method " + clazz + "." + methodName, e);
328     } catch (Exception e) {
329       throw new RuntimeException(e);
330     }
331   }
332 
333   /**
334    * Load a class.
335    *
336    * @param classLoader The class loader.
337    * @param fullyQualifiedClassName The fully qualified class name.
338    * @return The class object.
339    */
340   public static Class<?> loadClass(ClassLoader classLoader, String fullyQualifiedClassName) {
341     try {
342       return classLoader.loadClass(fullyQualifiedClassName);
343     } catch (ClassNotFoundException e) {
344       throw new RuntimeException(e);
345     }
346   }
347 
348   /**
349    * Create a new instance of a class
350    *
351    * @param cl The class object.
352    * @param <T> The class type.
353    * @return New class instance.
354    */
355   public static <T> T newInstance(Class<T> cl) {
356     try {
357       return cl.getDeclaredConstructor().newInstance();
358     } catch (InstantiationException | IllegalAccessException | NoSuchMethodException
359         | InvocationTargetException e) {
360       throw new RuntimeException(e);
361     }
362   }
363 
364   /**
365    * Reflectively call the constructor of an object.
366    *
367    * @param clazz Target class.
368    * @param classParameters Array of parameter types and values.
369    * @param <R> The return type.
370    * @return The return value of the method.
371    */
372   public static <R> R callConstructor(Class<? extends R> clazz, ClassParameter<?>... classParameters) {
373     try {
374       final Class<?>[] classes = ClassParameter.getClasses(classParameters);
375       final Object[] values = ClassParameter.getValues(classParameters);
376 
377       Constructor<? extends R> constructor = clazz.getDeclaredConstructor(classes);
378       constructor.setAccessible(true);
379       return constructor.newInstance(values);
380     } catch (InstantiationException e) {
381       throw new RuntimeException("error instantiating " + clazz.getName(), e);
382     } catch (InvocationTargetException e) {
383       if (e.getTargetException() instanceof RuntimeException) {
384         throw (RuntimeException) e.getTargetException();
385       }
386       if (e.getTargetException() instanceof Error) {
387         throw (Error) e.getTargetException();
388       }
389       throw new RuntimeException(e.getTargetException());
390     } catch (Exception e) {
391       throw new RuntimeException(e);
392     }
393   }
394 
395   private static <R, E extends Exception> R traverseClassHierarchy(Class<?> targetClass, Class<? extends E> exceptionClass, InsideTraversal<R> insideTraversal) throws Exception {
396     Class<?> hierarchyTraversalClass = targetClass;
397     while (true) {
398       try {
399         return insideTraversal.run(hierarchyTraversalClass);
400       } catch (Exception e) {
401         if (!exceptionClass.isInstance(e)) {
402           throw e;
403         }
404         hierarchyTraversalClass = hierarchyTraversalClass.getSuperclass();
405         if (hierarchyTraversalClass == null) {
406           throw new RuntimeException(e);
407         }
408       }
409     }
410   }
411 
412   private static void makeFieldVeryAccessible(Field field) {
413     field.setAccessible(true);
414     // remove 'final' modifier if present
415     if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
416       Field modifiersField = getModifiersField();
417       modifiersField.setAccessible(true);
418       try {
419         modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
420       } catch (IllegalAccessException e) {
421 
422         throw new AssertionError(e);
423       }
424     }
425   }
426 
427   private static Field getModifiersField() {
428     try {
429       return Field.class.getDeclaredField("modifiers");
430     } catch (NoSuchFieldException e) {
431       try {
432         Method getFieldsMethod = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
433         getFieldsMethod.setAccessible(true);
434         Field[] fields = (Field[]) getFieldsMethod.invoke(Field.class, false);
435         for (Field modifiersField : fields) {
436           if ("modifiers".equals(modifiersField.getName())) {
437             return modifiersField;
438           }
439         }
440       } catch (ReflectiveOperationException innerE) {
441         throw new AssertionError(innerE);
442       }
443     }
444     throw new AssertionError();
445   }
446 
447   public static Object defaultValueForType(String returnType) {
448     return PRIMITIVE_RETURN_VALUES.get(returnType);
449   }
450 
451   private interface InsideTraversal<R> {
452     R run(Class<?> traversalClass) throws Exception;
453   }
454 
455   /**
456    * Typed parameter used with reflective method calls.
457    *
458    * @param <V> The value of the method parameter.
459    */
460   public static class ClassParameter<V> {
461     public final Class<? extends V> clazz;
462     public final V val;
463 
464     public ClassParameter(Class<? extends V> clazz, V val) {
465       this.clazz = clazz;
466       this.val = val;
467     }
468 
469     public static <V> ClassParameter<V> from(Class<? extends V> clazz, V val) {
470       return new ClassParameter<>(clazz, val);
471     }
472 
473     public static ClassParameter<?>[] fromComponentLists(Class<?>[] classes, Object[] values) {
474       ClassParameter<?>[] classParameters = new ClassParameter[classes.length];
475       for (int i = 0; i < classes.length; i++) {
476         classParameters[i] = ClassParameter.from(classes[i], values[i]);
477       }
478       return classParameters;
479     }
480 
481     public static Class<?>[] getClasses(ClassParameter<?>... classParameters) {
482       Class<?>[] classes = new Class[classParameters.length];
483       for (int i = 0; i < classParameters.length; i++) {
484         Class<?> paramClass = classParameters[i].clazz;
485         classes[i] = paramClass;
486       }
487       return classes;
488     }
489 
490     public static Object[] getValues(ClassParameter<?>... classParameters) {
491       Object[] values = new Object[classParameters.length];
492       for (int i = 0; i < classParameters.length; i++) {
493         Object paramValue = classParameters[i].val;
494         values[i] = paramValue;
495       }
496       return values;
497     }
498   }
499 
500   /**
501    * String parameter used with reflective method calls.
502    *
503    * @param <V> The value of the method parameter.
504    */
505   public static class StringParameter<V> {
506     public final String className;
507     public final V val;
508 
509     public StringParameter(String className, V val) {
510       this.className = className;
511       this.val = val;
512     }
513 
514     public static <V> StringParameter<V> from(String className, V val) {
515       return new StringParameter<>(className, val);
516     }
517   }
518 }
519