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