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.java252
1 files changed, 252 insertions, 0 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
new file mode 100644
index 0000000..b9c8a7a
--- /dev/null
+++ b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/MethodDescriptor.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.android.mobly.snippet.rpc;
+
+import android.content.Intent;
+import android.net.Uri;
+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.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/** An adapter that wraps {@code Method}. */
+public final class MethodDescriptor {
+ private final Method mMethod;
+ private final Class<? extends Snippet> mClass;
+
+ private MethodDescriptor(Class<? extends Snippet> clazz, Method method) {
+ mClass = clazz;
+ mMethod = method;
+ }
+
+ @Override
+ public String toString() {
+ return mMethod.getDeclaringClass().getCanonicalName() + "." + mMethod.getName();
+ }
+
+ /** Collects all methods with {@code RPC} annotation from given class. */
+ public static Collection<MethodDescriptor> collectFrom(Class<? extends Snippet> clazz) {
+ List<MethodDescriptor> descriptors = new ArrayList<MethodDescriptor>();
+ for (Method method : clazz.getMethods()) {
+ if (method.isAnnotationPresent(Rpc.class)
+ || method.isAnnotationPresent(AsyncRpc.class)) {
+ descriptors.add(new MethodDescriptor(clazz, method));
+ }
+ }
+ return descriptors;
+ }
+
+ /**
+ * Invokes the call that belongs to this object with the given parameters. Wraps the response
+ * (possibly an exception) in a JSONObject.
+ *
+ * @param parameters {@code JSONArray} containing the parameters
+ * @return result
+ * @throws Throwable the exception raised from executing the RPC method.
+ */
+ public Object invoke(SnippetManager manager, final JSONArray parameters) throws Throwable {
+ final Type[] parameterTypes = getGenericParameterTypes();
+ final Object[] args = new Object[parameterTypes.length];
+
+ if (parameters.length() > args.length) {
+ throw new RpcError("Too many parameters specified.");
+ }
+
+ for (int i = 0; i < args.length; i++) {
+ final Type parameterType = parameterTypes[i];
+ if (i < parameters.length()) {
+ args[i] = convertParameter(parameters, i, parameterType);
+ } else {
+ throw new RpcError("Argument " + (i + 1) + " is not present");
+ }
+ }
+
+ return manager.invoke(mClass, mMethod, args);
+ }
+
+ /** 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 {
+ // We must handle null and numbers explicitly because we cannot magically cast them. We
+ // also need to convert implicitly from numbers to bools.
+ if (parameters.isNull(index)) {
+ return null;
+ } else if (type == Boolean.class || type == boolean.class) {
+ try {
+ return parameters.getBoolean(index);
+ } catch (JSONException e) {
+ return parameters.getInt(index) != 0;
+ }
+ } else if (type == Long.class || type == long.class) {
+ return parameters.getLong(index);
+ } else if (type == Double.class || type == double.class) {
+ return parameters.getDouble(index);
+ } else if (type == Integer.class || type == int.class) {
+ return parameters.getInt(index);
+ } else if (type == Intent.class) {
+ return buildIntent(parameters.getJSONObject(index));
+ } else if (type == String.class) {
+ return parameters.getString(index);
+ } else if (type == Integer[].class || type == int[].class) {
+ JSONArray list = parameters.getJSONArray(index);
+ Integer[] result = new Integer[list.length()];
+ for (int i = 0; i < list.length(); i++) {
+ result[i] = list.getInt(i);
+ }
+ return result;
+ } else if (type == Long[].class || type == long[].class) {
+ JSONArray list = parameters.getJSONArray(index);
+ Long[] result = new Long[list.length()];
+ for (int i = 0; i < list.length(); i++) {
+ result[i] = list.getLong(i);
+ }
+ return result;
+ } else if (type == Byte.class || type == byte[].class) {
+ JSONArray list = parameters.getJSONArray(index);
+ byte[] result = new byte[list.length()];
+ for (int i = 0; i < list.length(); i++) {
+ result[i] = (byte) list.getInt(i);
+ }
+ return result;
+ } else if (type == String[].class) {
+ JSONArray list = parameters.getJSONArray(index);
+ String[] result = new String[list.length()];
+ for (int i = 0; i < list.length(); i++) {
+ result[i] = list.getString(i);
+ }
+ return result;
+ } else if (type == JSONObject.class) {
+ return parameters.getJSONObject(index);
+ } else if (type == JSONArray.class) {
+ return parameters.getJSONArray(index);
+ } else {
+ // Try any custom converter provided.
+ Object object =
+ SnippetObjectConverterManager.getInstance()
+ .jsonToObject(parameters.getJSONObject(index), type);
+ if (object != null) {
+ return object;
+ }
+ // Magically cast the parameter to the right Java type.
+ return ((Class<?>) type).cast(parameters.get(index));
+ }
+ } catch (ClassCastException e) {
+ throw new RpcError(
+ "Argument "
+ + (index + 1)
+ + " should be of type "
+ + ((Class<?>) type).getSimpleName()
+ + ", but is of type "
+ + parameters.get(index).getClass().getSimpleName());
+ }
+ }
+
+ private static Object buildIntent(JSONObject jsonObject) throws JSONException {
+ Intent intent = new Intent();
+ if (jsonObject.has("action")) {
+ intent.setAction(jsonObject.getString("action"));
+ }
+ if (jsonObject.has("data") && jsonObject.has("type")) {
+ intent.setDataAndType(
+ Uri.parse(jsonObject.optString("data", null)),
+ jsonObject.optString("type", null));
+ } else if (jsonObject.has("data")) {
+ intent.setData(Uri.parse(jsonObject.optString("data", null)));
+ } else if (jsonObject.has("type")) {
+ intent.setType(jsonObject.optString("type", null));
+ }
+ if (jsonObject.has("packagename") && jsonObject.has("classname")) {
+ intent.setClassName(
+ jsonObject.getString("packagename"), jsonObject.getString("classname"));
+ }
+ if (jsonObject.has("flags")) {
+ intent.setFlags(jsonObject.getInt("flags"));
+ }
+ if (!jsonObject.isNull("extras")) {
+ AndroidUtil.putExtrasFromJsonObject(jsonObject.getJSONObject("extras"), intent);
+ }
+ if (!jsonObject.isNull("categories")) {
+ JSONArray categories = jsonObject.getJSONArray("categories");
+ for (int i = 0; i < categories.length(); i++) {
+ intent.addCategory(categories.getString(i));
+ }
+ }
+ return intent;
+ }
+
+ public String getName() {
+ return mMethod.getName();
+ }
+
+ private Type[] getGenericParameterTypes() {
+ return mMethod.getGenericParameterTypes();
+ }
+
+ public boolean isAsync() {
+ return mMethod.isAnnotationPresent(AsyncRpc.class);
+ }
+
+ Class<? extends Snippet> getSnippetClass() {
+ return mClass;
+ }
+
+ private String getAnnotationDescription() {
+ if (isAsync()) {
+ AsyncRpc annotation = mMethod.getAnnotation(AsyncRpc.class);
+ return annotation.description();
+ }
+ Rpc annotation = mMethod.getAnnotation(Rpc.class);
+ return annotation.description();
+ }
+ /**
+ * Returns a human-readable help text for this RPC, based on annotations in the source code.
+ *
+ * @return derived help string
+ */
+ String getHelp() {
+ StringBuilder paramBuilder = new StringBuilder();
+ Class<?>[] parameterTypes = mMethod.getParameterTypes();
+ for (int i = 0; i < parameterTypes.length; i++) {
+ if (i != 0) {
+ paramBuilder.append(", ");
+ }
+ paramBuilder.append(parameterTypes[i].getSimpleName());
+ }
+ return String.format(
+ Locale.US,
+ "%s %s(%s) returns %s // %s",
+ isAsync() ? "@AsyncRpc" : "@Rpc",
+ mMethod.getName(),
+ paramBuilder,
+ mMethod.getReturnType().getSimpleName(),
+ getAnnotationDescription());
+ }
+}