• 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   public static final Map<String, Object> PRIMITIVE_RETURN_VALUES =
21       Collections.unmodifiableMap(new HashMap<String, Object>() {{
22         put("boolean", Boolean.FALSE);
23         put("int", 0);
24         put("long", (long) 0);
25         put("float", (float) 0);
26         put("double", (double) 0);
27         put("short", (short) 0);
28         put("byte", (byte) 0);
29       }});
30 
createNullProxy(Class<T> clazz)31   public static <T> T createNullProxy(Class<T> clazz) {
32     return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
33         new Class[]{clazz}, new InvocationHandler() {
34           @Override
35           public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
36             return PRIMITIVE_RETURN_VALUES.get(method.getReturnType().getName());
37           }
38         });
39   }
40 
41   public static <T> T createDelegatingProxy(Class<T> clazz, final Object delegate) {
42     final Class delegateClass = delegate.getClass();
43     return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
44         new Class[]{clazz}, new InvocationHandler() {
45           @Override
46           public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
47             try {
48               Method delegateMethod = delegateClass.getMethod(method.getName(), method.getParameterTypes());
49               delegateMethod.setAccessible(true);
50               return delegateMethod.invoke(delegate, args);
51             } catch (NoSuchMethodException e) {
52               return PRIMITIVE_RETURN_VALUES.get(method.getReturnType().getName());
53             }
54           }
55         });
56   }
57 
58   public static <A extends Annotation> A defaultsFor(Class<A> annotation) {
59     return annotation.cast(
60         Proxy.newProxyInstance(annotation.getClassLoader(), new Class[] { annotation },
61             new InvocationHandler() {
62               @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
63                 return method.getDefaultValue();
64               }
65             }));
66   }
67 
68   /**
69    * Reflectively get the value of a field.
70    *
71    * @param object Target object.
72    * @param fieldName The field name.
73    * @param <R> The return type.
74    * @return Value of the field on the object.
75    */
76   @SuppressWarnings("unchecked")
77   public static <R> R getField(final Object object, final String fieldName) {
78     try {
79       return traverseClassHierarchy(object.getClass(), NoSuchFieldException.class, new InsideTraversal<R>() {
80         @Override
81         public R run(Class<?> traversalClass) throws Exception {
82           Field field = traversalClass.getDeclaredField(fieldName);
83           field.setAccessible(true);
84           return (R) field.get(object);
85         }
86       });
87     } catch (Exception e) {
88       throw new RuntimeException(e);
89     }
90   }
91 
92   /**
93    * Reflectively set the value of a field.
94    *
95    * @param object Target object.
96    * @param fieldName The field name.
97    * @param fieldNewValue New value.
98    */
99   public static void setField(final Object object, final String fieldName, final Object fieldNewValue) {
100     try {
101       traverseClassHierarchy(object.getClass(), NoSuchFieldException.class, new InsideTraversal<Void>() {
102         @Override
103         public Void run(Class<?> traversalClass) throws Exception {
104           Field field = traversalClass.getDeclaredField(fieldName);
105           field.setAccessible(true);
106           field.set(object, fieldNewValue);
107           return null;
108         }
109       });
110     } catch (Exception e) {
111       throw new RuntimeException(e);
112     }
113   }
114 
115   /**
116    * Reflectively set the value of a field.
117    *
118    * @param type Target type.
119    * @param object Target object.
120    * @param fieldName The field name.
121    * @param fieldNewValue New value.
122    */
123   public static void setField(Class<?> type, final Object object, final String fieldName, final Object fieldNewValue) {
124     try {
125       Field field = type.getDeclaredField(fieldName);
126       field.setAccessible(true);
127       field.set(object, fieldNewValue);
128     } catch (Exception e) {
129       throw new RuntimeException(e);
130     }
131   }
132 
133   /**
134    * Reflectively get the value of a static field.
135    *
136    * @param field Field object.
137    * @param <R> The return type.
138    * @return Value of the field.
139    */
140   @SuppressWarnings("unchecked")
141   public static <R> R getStaticField(Field field) {
142     try {
143       makeFieldVeryAccessible(field);
144       return (R) field.get(null);
145     } catch (Exception e) {
146       throw new RuntimeException(e);
147     }
148   }
149 
150   /**
151    * Reflectively get the value of a static field.
152    *
153    * @param clazz Target class.
154    * @param fieldName The field name.
155    * @param <R> The return type.
156    * @return Value of the field.
157    */
158   public static <R> R getStaticField(Class<?> clazz, String fieldName) {
159     try {
160       return getStaticField(clazz.getDeclaredField(fieldName));
161     } catch (Exception e) {
162       throw new RuntimeException(e);
163     }
164   }
165 
166   /**
167    * Reflectively set the value of a static field.
168    *
169    * @param field Field object.
170    * @param fieldNewValue The new value.
171    */
172   public static void setStaticField(Field field, Object fieldNewValue) {
173     try {
174       makeFieldVeryAccessible(field);
175       field.set(null, fieldNewValue);
176     } catch (Exception e) {
177       throw new RuntimeException(e);
178     }
179   }
180 
181   /**
182    * Reflectively set the value of a static field.
183    *
184    * @param clazz Target class.
185    * @param fieldName The field name.
186    * @param fieldNewValue The new value.
187    */
188   public static void setStaticField(Class<?> clazz, String fieldName, Object fieldNewValue) {
189     try {
190       setStaticField(clazz.getDeclaredField(fieldName), fieldNewValue);
191     } catch (Exception e) {
192       throw new RuntimeException(e);
193     }
194   }
195 
196   /**
197    * Reflectively call an instance method on an object.
198    *
199    * @param instance Target object.
200    * @param methodName The method name to call.
201    * @param classParameters Array of parameter types and values.
202    * @param <R> The return type.
203    * @return The return value of the method.
204    */
205   public static <R> R callInstanceMethod(final Object instance, final String methodName, ClassParameter<?>... classParameters) {
206     try {
207       final Class<?>[] classes = ClassParameter.getClasses(classParameters);
208       final Object[] values = ClassParameter.getValues(classParameters);
209 
210       return traverseClassHierarchy(instance.getClass(), NoSuchMethodException.class, new InsideTraversal<R>() {
211         @Override
212         @SuppressWarnings("unchecked")
213         public R run(Class<?> traversalClass) throws Exception {
214           Method declaredMethod = traversalClass.getDeclaredMethod(methodName, classes);
215           declaredMethod.setAccessible(true);
216           return (R) declaredMethod.invoke(instance, values);
217         }
218       });
219     } catch (InvocationTargetException e) {
220       if (e.getTargetException() instanceof RuntimeException) {
221         throw (RuntimeException) e.getTargetException();
222       }
223       if (e.getTargetException() instanceof Error) {
224         throw (Error) e.getTargetException();
225       }
226       throw new RuntimeException(e.getTargetException());
227     } catch (Exception e) {
228       throw new RuntimeException(e);
229     }
230   }
231 
232   /**
233    * Reflectively call an instance method on an object on a specific class.
234    *
235    * @param cl The class.
236    * @param instance Target object.
237    * @param methodName The method name to call.
238    * @param classParameters Array of parameter types and values.
239    * @param <R> The return type.
240    * @return The return value of the method.
241    */
242   public static <R> R callInstanceMethod(Class<?> cl, final Object instance, final String methodName, ClassParameter<?>... classParameters) {
243     try {
244       final Class<?>[] classes = ClassParameter.getClasses(classParameters);
245       final Object[] values = ClassParameter.getValues(classParameters);
246 
247       Method declaredMethod = cl.getDeclaredMethod(methodName, classes);
248       declaredMethod.setAccessible(true);
249       return (R) declaredMethod.invoke(instance, values);
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 a static method on a class.
265    *
266    * @param clazz Target class.
267    * @param methodName The method name to call.
268    * @param classParameters Array of parameter types and values.
269    * @param <R> The return type.
270    * @return The return value of the method.
271    */
272   @SuppressWarnings("unchecked")
273   public static <R> R callStaticMethod(Class<?> clazz, String methodName, ClassParameter<?>... classParameters) {
274     try {
275       Class<?>[] classes = ClassParameter.getClasses(classParameters);
276       Object[] values = ClassParameter.getValues(classParameters);
277 
278       Method method = clazz.getDeclaredMethod(methodName, classes);
279       method.setAccessible(true);
280       return (R) method.invoke(null, values);
281     } catch (InvocationTargetException e) {
282       if (e.getTargetException() instanceof RuntimeException) {
283         throw (RuntimeException) e.getTargetException();
284       }
285       if (e.getTargetException() instanceof Error) {
286         throw (Error) e.getTargetException();
287       }
288       throw new RuntimeException(e.getTargetException());
289     } catch (Exception e) {
290       throw new RuntimeException(e);
291     }
292   }
293 
294   /**
295    * Load a class.
296    *
297    * @param classLoader The class loader.
298    * @param fullyQualifiedClassName The fully qualified class name.
299    * @return The class object.
300    */
301   public static Class<?> loadClass(ClassLoader classLoader, String fullyQualifiedClassName) {
302     try {
303       return classLoader.loadClass(fullyQualifiedClassName);
304     } catch (ClassNotFoundException e) {
305       throw new RuntimeException(e);
306     }
307   }
308 
309   /**
310    * Create a new instance of a class
311    *
312    * @param cl The class object.
313    * @param <T> The class type.
314    * @return New class instance.
315    */
316   public static <T> T newInstance(Class<T> cl) {
317     try {
318       return cl.getDeclaredConstructor().newInstance();
319     } catch (InstantiationException | IllegalAccessException | NoSuchMethodException
320         | InvocationTargetException e) {
321       throw new RuntimeException(e);
322     }
323   }
324 
325   /**
326    * Reflectively call the constructor of an object.
327    *
328    * @param clazz Target class.
329    * @param classParameters Array of parameter types and values.
330    * @param <R> The return type.
331    * @return The return value of the method.
332    */
333   public static <R> R callConstructor(Class<? extends R> clazz, ClassParameter<?>... classParameters) {
334     try {
335       final Class<?>[] classes = ClassParameter.getClasses(classParameters);
336       final Object[] values = ClassParameter.getValues(classParameters);
337 
338       Constructor<? extends R> constructor = clazz.getDeclaredConstructor(classes);
339       constructor.setAccessible(true);
340       return constructor.newInstance(values);
341     } catch (InstantiationException e) {
342       throw new RuntimeException("error instantiating " + clazz.getName(), e);
343     } catch (InvocationTargetException e) {
344       if (e.getTargetException() instanceof RuntimeException) {
345         throw (RuntimeException) e.getTargetException();
346       }
347       if (e.getTargetException() instanceof Error) {
348         throw (Error) e.getTargetException();
349       }
350       throw new RuntimeException(e.getTargetException());
351     } catch (Exception e) {
352       throw new RuntimeException(e);
353     }
354   }
355 
356   private static <R, E extends Exception> R traverseClassHierarchy(Class<?> targetClass, Class<? extends E> exceptionClass, InsideTraversal<R> insideTraversal) throws Exception {
357     Class<?> hierarchyTraversalClass = targetClass;
358     while (true) {
359       try {
360         return insideTraversal.run(hierarchyTraversalClass);
361       } catch (Exception e) {
362         if (!exceptionClass.isInstance(e)) {
363           throw e;
364         }
365         hierarchyTraversalClass = hierarchyTraversalClass.getSuperclass();
366         if (hierarchyTraversalClass == null) {
367           throw new RuntimeException(e);
368         }
369       }
370     }
371   }
372 
373   private static void makeFieldVeryAccessible(Field field) throws NoSuchFieldException, IllegalAccessException {
374     field.setAccessible(true);
375 
376     Field modifiersField = Field.class.getDeclaredField("modifiers");
377     modifiersField.setAccessible(true);
378     modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
379   }
380 
381   public static Object defaultValueForType(String returnType) {
382     return PRIMITIVE_RETURN_VALUES.get(returnType);
383   }
384 
385   private interface InsideTraversal<R> {
386     R run(Class<?> traversalClass) throws Exception;
387   }
388 
389   /**
390    * Typed parameter used with reflective method calls.
391    *
392    * @param <V> The value of the method parameter.
393    */
394   public static class ClassParameter<V> {
395     public final Class<? extends V> clazz;
396     public final V val;
397 
398     public ClassParameter(Class<? extends V> clazz, V val) {
399       this.clazz = clazz;
400       this.val = val;
401     }
402 
403     public static <V> ClassParameter<V> from(Class<? extends V> clazz, V val) {
404       return new ClassParameter<>(clazz, val);
405     }
406 
407     public static ClassParameter<?>[] fromComponentLists(Class<?>[] classes, Object[] values) {
408       ClassParameter<?>[] classParameters = new ClassParameter[classes.length];
409       for (int i = 0; i < classes.length; i++) {
410         classParameters[i] = ClassParameter.from(classes[i], values[i]);
411       }
412       return classParameters;
413     }
414 
415     public static Class<?>[] getClasses(ClassParameter<?>... classParameters) {
416       Class<?>[] classes = new Class[classParameters.length];
417       for (int i = 0; i < classParameters.length; i++) {
418         Class<?> paramClass = classParameters[i].clazz;
419         classes[i] = paramClass;
420       }
421       return classes;
422     }
423 
424     public static Object[] getValues(ClassParameter<?>... classParameters) {
425       Object[] values = new Object[classParameters.length];
426       for (int i = 0; i < classParameters.length; i++) {
427         Object paramValue = classParameters[i].val;
428         values[i] = paramValue;
429       }
430       return values;
431     }
432   }
433 
434   /**
435    * String parameter used with reflective method calls.
436    *
437    * @param <V> The value of the method parameter.
438    */
439   public static class StringParameter<V> {
440     public final String className;
441     public final V val;
442 
443     public StringParameter(String className, V val) {
444       this.className = className;
445       this.val = val;
446     }
447 
448     public static <V> StringParameter<V> from(String className, V val) {
449       return new StringParameter<>(className, val);
450     }
451   }
452 }
453