diff options
author | Christian Williams <christianw@google.com> | 2017-06-12 16:17:02 -0700 |
---|---|---|
committer | Christian Williams <christianw@google.com> | 2017-06-13 13:15:59 -0700 |
commit | 793ee1db287b053127b6e60891c3dbfd1ce4bc54 (patch) | |
tree | fc8c666cbdc504ef7551d0825ce18f8dfbab0cd0 /shadowapi/src/main/java/org | |
parent | d486243404abe939e36c80713b5d5b1a81d48f1a (diff) | |
download | robolectric-shadows-793ee1db287b053127b6e60891c3dbfd1ce4bc54.tar.gz |
Rename projects.
Diffstat (limited to 'shadowapi/src/main/java/org')
8 files changed, 615 insertions, 0 deletions
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> T extract(Object instance); + + <T> T newInstanceOf(Class<T> clazz); + + <T> T newInstance(Class<T> clazz, Class[] parameterTypes, Object[] params); + + <T> T directlyOn(T shadowedObject, Class<T> clazz); + + @SuppressWarnings("unchecked") + <R> R directlyOn(Object shadowedObject, String clazzName, String methodName, ReflectionHelpers.ClassParameter... paramValues); + + <R, T> R directlyOn(T shadowedObject, Class<T> clazz, String methodName, ReflectionHelpers.ClassParameter... paramValues); + + <R, T> R directlyOn(Class<T> clazz, String methodName, ReflectionHelpers.ClassParameter... paramValues); + + <R> R invokeConstructor(Class<? extends R> 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<String, String> 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> T extract(Object instance) { + return SHADOW_IMPL.extract(instance); + } + + public static <T> T newInstanceOf(Class<T> 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> T newInstance(Class<T> clazz, Class[] parameterTypes, Object[] params) { + return SHADOW_IMPL.newInstance(clazz, parameterTypes, params); + } + + public static <T> T directlyOn(T shadowedObject, Class<T> clazz) { + return SHADOW_IMPL.directlyOn(shadowedObject, clazz); + } + + @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"}) + public static <R> R directlyOn(Object shadowedObject, String clazzName, String methodName, ClassParameter... paramValues) { + return SHADOW_IMPL.directlyOn(shadowedObject, clazzName, methodName, paramValues); + } + + @SuppressWarnings("TypeParameterUnusedInFormals") + public static <R, T> R directlyOn(T shadowedObject, Class<T> clazz, String methodName, ClassParameter... paramValues) { + return SHADOW_IMPL.directlyOn(shadowedObject, clazz, methodName, paramValues); + } + + @SuppressWarnings("TypeParameterUnusedInFormals") + public static <R, T> R directlyOn(Class<T> clazz, String methodName, ClassParameter... paramValues) { + return SHADOW_IMPL.directlyOn(clazz, methodName, paramValues); + } + + public static <R> R invokeConstructor(Class<? extends R> 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<String, Object> PRIMITIVE_RETURN_VALUES = + Collections.unmodifiableMap(new HashMap<String, Object>() {{ + 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> T createNullProxy(Class<T> 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 extends Annotation> A defaultsFor(Class<A> 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 <R> The return type. + * @return Value of the field on the object. + */ + @SuppressWarnings("unchecked") + public static <R> R getField(final Object object, final String fieldName) { + try { + return traverseClassHierarchy(object.getClass(), NoSuchFieldException.class, new InsideTraversal<R>() { + @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<Void>() { + @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 <R> The return type. + * @return Value of the field. + */ + @SuppressWarnings("unchecked") + public static <R> 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 <R> The return type. + * @return Value of the field. + */ + public static <R> 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 <R> The return type. + * @return The return value of the method. + */ + public static <R> 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<R>() { + @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 <R> The return type. + * @return The return value of the method. + */ + public static <R> 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 <R> The return type. + * @return The return value of the method. + */ + @SuppressWarnings("unchecked") + public static <R> 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 <T> The class type. + * @return New class instance. + */ + public static <T> T newInstance(Class<T> 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 <R> The return type. + * @return The return value of the method. + */ + public static <R> R callConstructor(Class<? extends R> clazz, ClassParameter<?>... classParameters) { + try { + final Class<?>[] classes = ClassParameter.getClasses(classParameters); + final Object[] values = ClassParameter.getValues(classParameters); + + Constructor<? extends R> 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, E extends Exception> R traverseClassHierarchy(Class<?> targetClass, Class<? extends E> exceptionClass, InsideTraversal<R> 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> { + R run(Class<?> traversalClass) throws Exception; + } + + /** + * Typed parameter used with reflective method calls. + * + * @param <V> The value of the method parameter. + */ + public static class ClassParameter<V> { + public final Class<? extends V> clazz; + public final V val; + + public ClassParameter(Class<? extends V> clazz, V val) { + this.clazz = clazz; + this.val = val; + } + + public static <V> ClassParameter<V> from(Class<? extends V> 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 <V> The value of the method parameter. + */ + public static class StringParameter<V> { + public final String className; + public final V val; + + public StringParameter(String className, V val) { + this.className = className; + this.val = val; + } + + public static <V> StringParameter<V> from(String className, V val) { + return new StringParameter<>(className, val); + } + } +} |