From 793ee1db287b053127b6e60891c3dbfd1ce4bc54 Mon Sep 17 00:00:00 2001 From: Christian Williams Date: Mon, 12 Jun 2017 16:17:02 -0700 Subject: Rename projects. --- .../annotation/internal/DoNotInstrument.java | 16 + .../annotation/internal/Instrument.java | 16 + .../java/org/robolectric/internal/IShadow.java | 25 ++ .../main/java/org/robolectric/internal/Shadow.java | 9 + .../org/robolectric/internal/ShadowExtractor.java | 15 + .../org/robolectric/internal/ShadowProvider.java | 28 ++ .../java/org/robolectric/shadow/api/Shadow.java | 72 ++++ .../org/robolectric/util/ReflectionHelpers.java | 434 +++++++++++++++++++++ 8 files changed, 615 insertions(+) create mode 100644 shadowapi/src/main/java/org/robolectric/annotation/internal/DoNotInstrument.java create mode 100644 shadowapi/src/main/java/org/robolectric/annotation/internal/Instrument.java create mode 100644 shadowapi/src/main/java/org/robolectric/internal/IShadow.java create mode 100644 shadowapi/src/main/java/org/robolectric/internal/Shadow.java create mode 100644 shadowapi/src/main/java/org/robolectric/internal/ShadowExtractor.java create mode 100644 shadowapi/src/main/java/org/robolectric/internal/ShadowProvider.java create mode 100644 shadowapi/src/main/java/org/robolectric/shadow/api/Shadow.java create mode 100644 shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java (limited to 'shadowapi/src/main/java/org') diff --git a/shadowapi/src/main/java/org/robolectric/annotation/internal/DoNotInstrument.java b/shadowapi/src/main/java/org/robolectric/annotation/internal/DoNotInstrument.java new file mode 100644 index 000000000..4a77d97ab --- /dev/null +++ b/shadowapi/src/main/java/org/robolectric/annotation/internal/DoNotInstrument.java @@ -0,0 +1,16 @@ +package org.robolectric.annotation.internal; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that a class should not be stripped/instrumented under any circumstances. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface DoNotInstrument { +} diff --git a/shadowapi/src/main/java/org/robolectric/annotation/internal/Instrument.java b/shadowapi/src/main/java/org/robolectric/annotation/internal/Instrument.java new file mode 100644 index 000000000..c53e72ca2 --- /dev/null +++ b/shadowapi/src/main/java/org/robolectric/annotation/internal/Instrument.java @@ -0,0 +1,16 @@ +package org.robolectric.annotation.internal; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that a class should always be instrumented regardless of its package. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface Instrument { +} diff --git a/shadowapi/src/main/java/org/robolectric/internal/IShadow.java b/shadowapi/src/main/java/org/robolectric/internal/IShadow.java new file mode 100644 index 000000000..045989ca2 --- /dev/null +++ b/shadowapi/src/main/java/org/robolectric/internal/IShadow.java @@ -0,0 +1,25 @@ +package org.robolectric.internal; + +import org.robolectric.util.ReflectionHelpers; + +@SuppressWarnings("TypeParameterUnusedInFormals") +public interface IShadow { + T extract(Object instance); + + T newInstanceOf(Class clazz); + + T newInstance(Class clazz, Class[] parameterTypes, Object[] params); + + T directlyOn(T shadowedObject, Class clazz); + + @SuppressWarnings("unchecked") + R directlyOn(Object shadowedObject, String clazzName, String methodName, ReflectionHelpers.ClassParameter... paramValues); + + R directlyOn(T shadowedObject, Class clazz, String methodName, ReflectionHelpers.ClassParameter... paramValues); + + R directlyOn(Class clazz, String methodName, ReflectionHelpers.ClassParameter... paramValues); + + R invokeConstructor(Class clazz, R instance, ReflectionHelpers.ClassParameter... paramValues); + + String directMethodName(String methodName); +} diff --git a/shadowapi/src/main/java/org/robolectric/internal/Shadow.java b/shadowapi/src/main/java/org/robolectric/internal/Shadow.java new file mode 100644 index 000000000..d25bd20dc --- /dev/null +++ b/shadowapi/src/main/java/org/robolectric/internal/Shadow.java @@ -0,0 +1,9 @@ +package org.robolectric.internal; + +/** + * @deprecated Use {@link org.robolectric.shadow.api.Shadow} instead. + * This will be removed in a forthcoming release. + */ +@Deprecated +public class Shadow extends org.robolectric.shadow.api.Shadow { +} diff --git a/shadowapi/src/main/java/org/robolectric/internal/ShadowExtractor.java b/shadowapi/src/main/java/org/robolectric/internal/ShadowExtractor.java new file mode 100644 index 000000000..9beb769e4 --- /dev/null +++ b/shadowapi/src/main/java/org/robolectric/internal/ShadowExtractor.java @@ -0,0 +1,15 @@ +package org.robolectric.internal; + +/** + * @deprecated Use {@link org.robolectric.shadow.api.Shadow#extract(Object)} instead. + */ +@Deprecated +public class ShadowExtractor { + /** + * @deprecated Use {@link org.robolectric.shadow.api.Shadow#extract(Object)} instead. + */ + @Deprecated + public static Object extract(Object instance) { + return org.robolectric.shadow.api.Shadow.extract(instance); + } +} diff --git a/shadowapi/src/main/java/org/robolectric/internal/ShadowProvider.java b/shadowapi/src/main/java/org/robolectric/internal/ShadowProvider.java new file mode 100644 index 000000000..c8b46045d --- /dev/null +++ b/shadowapi/src/main/java/org/robolectric/internal/ShadowProvider.java @@ -0,0 +1,28 @@ +package org.robolectric.internal; + +import java.util.Map; + +/** + * Interface implemented by packages that provide shadows to Robolectric. + */ +public interface ShadowProvider { + + /** + * Reset the static state of all shadows provided by this package. + */ + void reset(); + + /** + * Array of Java package names that are shadowed by this package. + * + * @return Array of Java package names. + */ + String[] getProvidedPackageNames(); + + /** + * Return the mapping of class name to shadow name. + * + * @return Shadow mapping. + */ + Map getShadowMap(); +} diff --git a/shadowapi/src/main/java/org/robolectric/shadow/api/Shadow.java b/shadowapi/src/main/java/org/robolectric/shadow/api/Shadow.java new file mode 100644 index 000000000..d802b2c93 --- /dev/null +++ b/shadowapi/src/main/java/org/robolectric/shadow/api/Shadow.java @@ -0,0 +1,72 @@ +package org.robolectric.shadow.api; + +import org.robolectric.internal.IShadow; +import org.robolectric.util.ReflectionHelpers; +import org.robolectric.util.ReflectionHelpers.ClassParameter; + +public class Shadow { + @SuppressWarnings("unused") + private final static IShadow SHADOW_IMPL; + + static { + try { + SHADOW_IMPL = Class.forName("org.robolectric.internal.bytecode.ShadowImpl") + .asSubclass(IShadow.class).newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Retrieve corresponding Shadow of the object. + * @since 3.3 + */ + @SuppressWarnings("TypeParameterUnusedInFormals") + public static T extract(Object instance) { + return SHADOW_IMPL.extract(instance); + } + + public static T newInstanceOf(Class clazz) { + return SHADOW_IMPL.newInstanceOf(clazz); + } + + public static Object newInstanceOf(String className) { + try { + Class aClass = Shadow.class.getClassLoader().loadClass(className); + return SHADOW_IMPL.newInstanceOf(aClass); + } catch (ClassNotFoundException e) { + return null; + } + } + + public static T newInstance(Class clazz, Class[] parameterTypes, Object[] params) { + return SHADOW_IMPL.newInstance(clazz, parameterTypes, params); + } + + public static T directlyOn(T shadowedObject, Class clazz) { + return SHADOW_IMPL.directlyOn(shadowedObject, clazz); + } + + @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"}) + public static R directlyOn(Object shadowedObject, String clazzName, String methodName, ClassParameter... paramValues) { + return SHADOW_IMPL.directlyOn(shadowedObject, clazzName, methodName, paramValues); + } + + @SuppressWarnings("TypeParameterUnusedInFormals") + public static R directlyOn(T shadowedObject, Class clazz, String methodName, ClassParameter... paramValues) { + return SHADOW_IMPL.directlyOn(shadowedObject, clazz, methodName, paramValues); + } + + @SuppressWarnings("TypeParameterUnusedInFormals") + public static R directlyOn(Class clazz, String methodName, ClassParameter... paramValues) { + return SHADOW_IMPL.directlyOn(clazz, methodName, paramValues); + } + + public static R invokeConstructor(Class clazz, R instance, ClassParameter... paramValues) { + return SHADOW_IMPL.invokeConstructor(clazz, instance, paramValues); + } + + public static String directMethodName(String methodName) { + return SHADOW_IMPL.directMethodName(methodName); + } +} diff --git a/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java b/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java new file mode 100644 index 000000000..b32e409db --- /dev/null +++ b/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java @@ -0,0 +1,434 @@ +package org.robolectric.util; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Collection of helper methods for calling methods and accessing fields reflectively. + */ +@SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"}) +public class ReflectionHelpers { + public static final Map PRIMITIVE_RETURN_VALUES = + Collections.unmodifiableMap(new HashMap() {{ + put("boolean", Boolean.FALSE); + put("int", 0); + put("long", (long) 0); + put("float", (float) 0); + put("double", (double) 0); + put("short", (short) 0); + put("byte", (byte) 0); + }}); + + public static T createNullProxy(Class clazz) { + return (T) Proxy.newProxyInstance(clazz.getClassLoader(), + new Class[]{clazz}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return PRIMITIVE_RETURN_VALUES.get(method.getReturnType().getName()); + } + }); + } + + public static A defaultsFor(Class annotation) { + return annotation.cast( + Proxy.newProxyInstance(annotation.getClassLoader(), new Class[] { annotation }, + new InvocationHandler() { + @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return method.getDefaultValue(); + } + })); + } + + /** + * Reflectively get the value of a field. + * + * @param object Target object. + * @param fieldName The field name. + * @param The return type. + * @return Value of the field on the object. + */ + @SuppressWarnings("unchecked") + public static R getField(final Object object, final String fieldName) { + try { + return traverseClassHierarchy(object.getClass(), NoSuchFieldException.class, new InsideTraversal() { + @Override + public R run(Class traversalClass) throws Exception { + Field field = traversalClass.getDeclaredField(fieldName); + field.setAccessible(true); + return (R) field.get(object); + } + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Reflectively set the value of a field. + * + * @param object Target object. + * @param fieldName The field name. + * @param fieldNewValue New value. + */ + public static void setField(final Object object, final String fieldName, final Object fieldNewValue) { + try { + traverseClassHierarchy(object.getClass(), NoSuchFieldException.class, new InsideTraversal() { + @Override + public Void run(Class traversalClass) throws Exception { + Field field = traversalClass.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(object, fieldNewValue); + return null; + } + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Reflectively set the value of a field. + * + * @param type Target type. + * @param object Target object. + * @param fieldName The field name. + * @param fieldNewValue New value. + */ + public static void setField(Class type, final Object object, final String fieldName, final Object fieldNewValue) { + try { + Field field = type.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(object, fieldNewValue); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Reflectively get the value of a static field. + * + * @param field Field object. + * @param The return type. + * @return Value of the field. + */ + @SuppressWarnings("unchecked") + public static R getStaticField(Field field) { + try { + makeFieldVeryAccessible(field); + return (R) field.get(null); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Reflectively get the value of a static field. + * + * @param clazz Target class. + * @param fieldName The field name. + * @param The return type. + * @return Value of the field. + */ + public static R getStaticField(Class clazz, String fieldName) { + try { + return getStaticField(clazz.getDeclaredField(fieldName)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Reflectively set the value of a static field. + * + * @param field Field object. + * @param fieldNewValue The new value. + */ + public static void setStaticField(Field field, Object fieldNewValue) { + try { + makeFieldVeryAccessible(field); + field.set(null, fieldNewValue); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Reflectively set the value of a static field. + * + * @param clazz Target class. + * @param fieldName The field name. + * @param fieldNewValue The new value. + */ + public static void setStaticField(Class clazz, String fieldName, Object fieldNewValue) { + try { + setStaticField(clazz.getDeclaredField(fieldName), fieldNewValue); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Reflectively call an instance method on an object. + * + * @param instance Target object. + * @param methodName The method name to call. + * @param classParameters Array of parameter types and values. + * @param The return type. + * @return The return value of the method. + */ + public static R callInstanceMethod(final Object instance, final String methodName, ClassParameter... classParameters) { + try { + final Class[] classes = ClassParameter.getClasses(classParameters); + final Object[] values = ClassParameter.getValues(classParameters); + + return traverseClassHierarchy(instance.getClass(), NoSuchMethodException.class, new InsideTraversal() { + @Override + @SuppressWarnings("unchecked") + public R run(Class traversalClass) throws Exception { + Method declaredMethod = traversalClass.getDeclaredMethod(methodName, classes); + declaredMethod.setAccessible(true); + return (R) declaredMethod.invoke(instance, values); + } + }); + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof RuntimeException) { + throw (RuntimeException) e.getTargetException(); + } + if (e.getTargetException() instanceof Error) { + throw (Error) e.getTargetException(); + } + throw new RuntimeException(e.getTargetException()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Reflectively call an instance method on an object on a specific class. + * + * @param cl The class. + * @param instance Target object. + * @param methodName The method name to call. + * @param classParameters Array of parameter types and values. + * @param The return type. + * @return The return value of the method. + */ + public static R callInstanceMethod(Class cl, final Object instance, final String methodName, ClassParameter... classParameters) { + try { + final Class[] classes = ClassParameter.getClasses(classParameters); + final Object[] values = ClassParameter.getValues(classParameters); + + Method declaredMethod = cl.getDeclaredMethod(methodName, classes); + declaredMethod.setAccessible(true); + return (R) declaredMethod.invoke(instance, values); + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof RuntimeException) { + throw (RuntimeException) e.getTargetException(); + } + if (e.getTargetException() instanceof Error) { + throw (Error) e.getTargetException(); + } + throw new RuntimeException(e.getTargetException()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Reflectively call a static method on a class. + * + * @param clazz Target class. + * @param methodName The method name to call. + * @param classParameters Array of parameter types and values. + * @param The return type. + * @return The return value of the method. + */ + @SuppressWarnings("unchecked") + public static R callStaticMethod(Class clazz, String methodName, ClassParameter... classParameters) { + try { + Class[] classes = ClassParameter.getClasses(classParameters); + Object[] values = ClassParameter.getValues(classParameters); + + Method method = clazz.getDeclaredMethod(methodName, classes); + method.setAccessible(true); + return (R) method.invoke(null, values); + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof RuntimeException) { + throw (RuntimeException) e.getTargetException(); + } + if (e.getTargetException() instanceof Error) { + throw (Error) e.getTargetException(); + } + throw new RuntimeException(e.getTargetException()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Load a class. + * + * @param classLoader The class loader. + * @param fullyQualifiedClassName The fully qualified class name. + * @return The class object. + */ + public static Class loadClass(ClassLoader classLoader, String fullyQualifiedClassName) { + try { + return classLoader.loadClass(fullyQualifiedClassName); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + /** + * Create a new instance of a class + * + * @param cl The class object. + * @param The class type. + * @return New class instance. + */ + public static T newInstance(Class cl) { + try { + return cl.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + /** + * Reflectively call the constructor of an object. + * + * @param clazz Target class. + * @param classParameters Array of parameter types and values. + * @param The return type. + * @return The return value of the method. + */ + public static R callConstructor(Class clazz, ClassParameter... classParameters) { + try { + final Class[] classes = ClassParameter.getClasses(classParameters); + final Object[] values = ClassParameter.getValues(classParameters); + + Constructor constructor = clazz.getDeclaredConstructor(classes); + constructor.setAccessible(true); + return constructor.newInstance(values); + } catch (InstantiationException e) { + throw new RuntimeException("error instantiating " + clazz.getName(), e); + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof RuntimeException) { + throw (RuntimeException) e.getTargetException(); + } + if (e.getTargetException() instanceof Error) { + throw (Error) e.getTargetException(); + } + throw new RuntimeException(e.getTargetException()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static R traverseClassHierarchy(Class targetClass, Class exceptionClass, InsideTraversal insideTraversal) throws Exception { + Class hierarchyTraversalClass = targetClass; + while (true) { + try { + return insideTraversal.run(hierarchyTraversalClass); + } catch (Exception e) { + if (!exceptionClass.isInstance(e)) { + throw e; + } + hierarchyTraversalClass = hierarchyTraversalClass.getSuperclass(); + if (hierarchyTraversalClass == null) { + throw new RuntimeException(e); + } + } + } + } + + private static void makeFieldVeryAccessible(Field field) throws NoSuchFieldException, IllegalAccessException { + field.setAccessible(true); + + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + } + + public static Object defaultValueForType(String returnType) { + return PRIMITIVE_RETURN_VALUES.get(returnType); + } + + private interface InsideTraversal { + R run(Class traversalClass) throws Exception; + } + + /** + * Typed parameter used with reflective method calls. + * + * @param The value of the method parameter. + */ + public static class ClassParameter { + public final Class clazz; + public final V val; + + public ClassParameter(Class clazz, V val) { + this.clazz = clazz; + this.val = val; + } + + public static ClassParameter from(Class clazz, V val) { + return new ClassParameter<>(clazz, val); + } + + public static ClassParameter[] fromComponentLists(Class[] classes, Object[] values) { + ClassParameter[] classParameters = new ClassParameter[classes.length]; + for (int i = 0; i < classes.length; i++) { + classParameters[i] = ClassParameter.from(classes[i], values[i]); + } + return classParameters; + } + + public static Class[] getClasses(ClassParameter... classParameters) { + Class[] classes = new Class[classParameters.length]; + for (int i = 0; i < classParameters.length; i++) { + Class paramClass = classParameters[i].clazz; + classes[i] = paramClass; + } + return classes; + } + + public static Object[] getValues(ClassParameter... classParameters) { + Object[] values = new Object[classParameters.length]; + for (int i = 0; i < classParameters.length; i++) { + Object paramValue = classParameters[i].val; + values[i] = paramValue; + } + return values; + } + } + + /** + * String parameter used with reflective method calls. + * + * @param The value of the method parameter. + */ + public static class StringParameter { + public final String className; + public final V val; + + public StringParameter(String className, V val) { + this.className = className; + this.val = val; + } + + public static StringParameter from(String className, V val) { + return new StringParameter<>(className, val); + } + } +} -- cgit v1.2.3