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