aboutsummaryrefslogtreecommitdiff
path: root/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/MethodDescriptor.java
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/MethodDescriptor.java')
-rw-r--r--third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/MethodDescriptor.java162
1 files changed, 157 insertions, 5 deletions
diff --git a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/MethodDescriptor.java b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/MethodDescriptor.java
index b9c8a7a..214ffe7 100644
--- a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/MethodDescriptor.java
+++ b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/MethodDescriptor.java
@@ -22,18 +22,25 @@ import com.google.android.mobly.snippet.Snippet;
import com.google.android.mobly.snippet.manager.SnippetManager;
import com.google.android.mobly.snippet.manager.SnippetObjectConverterManager;
import com.google.android.mobly.snippet.util.AndroidUtil;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/** An adapter that wraps {@code Method}. */
public final class MethodDescriptor {
+ private static final Map<Class<?>, TypeConverter<?>> typeConverters = populateConverters();
+
private final Method mMethod;
private final Class<? extends Snippet> mClass;
@@ -68,6 +75,7 @@ public final class MethodDescriptor {
* @throws Throwable the exception raised from executing the RPC method.
*/
public Object invoke(SnippetManager manager, final JSONArray parameters) throws Throwable {
+ final Annotation[][] annotations = getParameterAnnotations();
final Type[] parameterTypes = getGenericParameterTypes();
final Object[] args = new Object[parameterTypes.length];
@@ -79,6 +87,12 @@ public final class MethodDescriptor {
final Type parameterType = parameterTypes[i];
if (i < parameters.length()) {
args[i] = convertParameter(parameters, i, parameterType);
+ } else if (MethodDescriptor.hasDefaultValue(Arrays.asList(annotations[i]))) {
+ args[i] = MethodDescriptor.getDefaultValue(
+ parameterType, Arrays.asList(annotations[i]));
+ } else if (MethodDescriptor.isOptional(Arrays.asList(annotations[i]))) {
+ args[i] = MethodDescriptor.getOptionalValue(
+ parameterType, Arrays.asList(annotations[i]));
} else {
throw new RpcError("Argument " + (i + 1) + " is not present");
}
@@ -88,10 +102,6 @@ public final class MethodDescriptor {
}
/** Converts a parameter from JSON into a Java Object. */
- // TODO(damonkohler): This signature is a bit weird (auto-refactored). The obvious alternative
- // would be to work on one supplied parameter and return the converted parameter. However,
- // that's problematic because you lose the ability to call the getXXX methods on the JSON array.
- // @VisibleForTesting
private static Object convertParameter(final JSONArray parameters, int index, Type type)
throws JSONException, RpcError {
try {
@@ -165,7 +175,8 @@ public final class MethodDescriptor {
+ " should be of type "
+ ((Class<?>) type).getSimpleName()
+ ", but is of type "
- + parameters.get(index).getClass().getSimpleName());
+ + parameters.get(index).getClass().getSimpleName(),
+ e);
}
}
@@ -226,6 +237,11 @@ public final class MethodDescriptor {
Rpc annotation = mMethod.getAnnotation(Rpc.class);
return annotation.description();
}
+
+ public Annotation[][] getParameterAnnotations() {
+ return mMethod.getParameterAnnotations();
+ }
+
/**
* Returns a human-readable help text for this RPC, based on annotations in the source code.
*
@@ -249,4 +265,140 @@ public final class MethodDescriptor {
mMethod.getReturnType().getSimpleName(),
getAnnotationDescription());
}
+
+ /**
+ * Returns the default value for a parameter which has a default value.
+ *
+ * @param parameterType parameterType
+ * @param annotations annotations of the parameter
+ */
+ public static Object getDefaultValue(Type parameterType, Iterable<Annotation> annotations) {
+ for (Annotation a : annotations) {
+ if (a instanceof RpcDefault) {
+ RpcDefault defaultAnnotation = (RpcDefault) a;
+ TypeConverter<?> converter =
+ converterFor(parameterType, defaultAnnotation.converter());
+ return converter.convert(defaultAnnotation.value());
+ }
+ }
+ throw new IllegalStateException("No default value for " + parameterType);
+ }
+
+ /**
+ * Returns null for an optional parameter.
+ *
+ * @param parameterType parameterType
+ * @param annotations annotations of the parameter
+ */
+ public static Object getOptionalValue(Type parameterType, Iterable<Annotation> annotations) {
+ for (Annotation a : annotations) {
+ if (a instanceof RpcOptional) {
+ return null;
+ }
+ }
+ throw new IllegalStateException("No default value for " + parameterType);
+ }
+
+ @SuppressWarnings("rawtypes")
+ private static TypeConverter<?> converterFor(
+ Type parameterType, Class<? extends TypeConverter> converterClass) {
+ if (converterClass == TypeConverter.class) {
+ TypeConverter<?> converter = typeConverters.get(parameterType);
+ if (converter == null) {
+ throw new IllegalArgumentException(
+ String.format("No predefined converter found for %s", parameterType));
+ }
+ return converter;
+ }
+ try {
+ Constructor<?> constructor = converterClass.getConstructor(new Class<?>[0]);
+ return (TypeConverter<?>) constructor.newInstance(new Object[0]);
+ } catch (Exception e) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Cannot create converter from %s", converterClass.getCanonicalName()),
+ e);
+ }
+ }
+
+ /**
+ * Determines whether or not this parameter has default value.
+ *
+ * @param annotations annotations of the parameter
+ */
+ public static boolean hasDefaultValue(Iterable<Annotation> annotations) {
+ for (Annotation a : annotations) {
+ if (a instanceof RpcDefault) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determines whether or not this parameter is optional.
+ *
+ * @param annotations annotations of the parameter
+ */
+ public static boolean isOptional(Iterable<Annotation> annotations) {
+ for (Annotation a : annotations) {
+ if (a instanceof RpcOptional) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the converters for {@code String}, {@code Integer}, {@code Long},
+ * and {@code Boolean}.
+ */
+ private static Map<Class<?>, TypeConverter<?>> populateConverters() {
+ Map<Class<?>, TypeConverter<?>> converters = new HashMap<>();
+ converters.put(String.class, new TypeConverter<String>() {
+ @Override
+ public String convert(String value) {
+ return value;
+ }
+ });
+ converters.put(Integer.class, new TypeConverter<Integer>() {
+ @Override
+ public Integer convert(String input) {
+ try {
+ return Integer.decode(input);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(
+ String.format("'%s' is not a Integer", input), e);
+ }
+ }
+ });
+ converters.put(Long.class, new TypeConverter<Long>() {
+ @Override
+ public Long convert(String input) {
+ try {
+ return Long.decode(input);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(
+ String.format("'%s' is not a Long", input), e);
+ }
+ }
+ });
+ converters.put(Boolean.class, new TypeConverter<Boolean>() {
+ @Override
+ public Boolean convert(String input) {
+ if (input == null) {
+ return null;
+ }
+ input = input.toLowerCase(Locale.ROOT);
+ if (input.equals("true")) {
+ return Boolean.TRUE;
+ }
+ if (input.equals("false")) {
+ return Boolean.FALSE;
+ }
+ throw new IllegalArgumentException(String.format("'%s' is not a Boolean", input));
+ }
+ });
+ return converters;
+ }
}