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