aboutsummaryrefslogtreecommitdiff
path: root/shadowapi/src/main/java/org
diff options
context:
space:
mode:
authorChristian Williams <christianw@google.com>2017-06-12 16:17:02 -0700
committerChristian Williams <christianw@google.com>2017-06-13 13:15:59 -0700
commit793ee1db287b053127b6e60891c3dbfd1ce4bc54 (patch)
treefc8c666cbdc504ef7551d0825ce18f8dfbab0cd0 /shadowapi/src/main/java/org
parentd486243404abe939e36c80713b5d5b1a81d48f1a (diff)
downloadrobolectric-shadows-793ee1db287b053127b6e60891c3dbfd1ce4bc54.tar.gz
Rename projects.
Diffstat (limited to 'shadowapi/src/main/java/org')
-rw-r--r--shadowapi/src/main/java/org/robolectric/annotation/internal/DoNotInstrument.java16
-rw-r--r--shadowapi/src/main/java/org/robolectric/annotation/internal/Instrument.java16
-rw-r--r--shadowapi/src/main/java/org/robolectric/internal/IShadow.java25
-rw-r--r--shadowapi/src/main/java/org/robolectric/internal/Shadow.java9
-rw-r--r--shadowapi/src/main/java/org/robolectric/internal/ShadowExtractor.java15
-rw-r--r--shadowapi/src/main/java/org/robolectric/internal/ShadowProvider.java28
-rw-r--r--shadowapi/src/main/java/org/robolectric/shadow/api/Shadow.java72
-rw-r--r--shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java434
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);
+ }
+ }
+}