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