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