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