diff options
author | Ang Li <angli@google.com> | 2018-01-12 11:54:03 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-01-12 11:54:03 -0800 |
commit | c2d947efa173968571cbcc11376159647a9ff356 (patch) | |
tree | c8e06f22c7dffb5ffed4f7824120fb0dff8a5354 | |
parent | 4d02684ade1a312b39591a94a92328dc0b65b883 (diff) | |
download | mobly-snippet-lib-c2d947efa173968571cbcc11376159647a9ff356.tar.gz |
Support custom converters of non-primitive types. (#86)
Now users can supply custom logic for object serialization/de-serialization
through a centralized class, and snippet lib will automatically use the custom converters.
Added a new example to demonstrate this feature.
15 files changed, 532 insertions, 36 deletions
@@ -61,3 +61,6 @@ mobly snippet lib along with detailed tutorials. * [ex5_schedule_rpc](examples/ex5_schedule_rpc): Example of how to use the 'scheduleRpc' RPC to execute another RPC at a later time, potentially after device disconnection. +* [ex6_complex_type_conversion](examples/ex6_complex_type_conversion): Example of how to pass a + non-primitive type to the Rpc methods and return non-primitive type from Rpc methods by + supplying a type converter. diff --git a/examples/ex1_standalone_app/src/main/java/com/google/android/mobly/snippet/example6/ExampleSnippet.java b/examples/ex1_standalone_app/src/main/java/com/google/android/mobly/snippet/example6/ExampleSnippet.java new file mode 100644 index 0000000..6f5af65 --- /dev/null +++ b/examples/ex1_standalone_app/src/main/java/com/google/android/mobly/snippet/example6/ExampleSnippet.java @@ -0,0 +1,30 @@ +/* + * 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.example6; + +import com.google.android.mobly.snippet.Snippet; +import com.google.android.mobly.snippet.rpc.Rpc; + +public class ExampleSnippet implements Snippet { + @Rpc(description = "Returns the given integer with the prefix \"foo\"") + public String getFoo(Integer input) { + return "foo " + input; + } + + @Override + public void shutdown() {} +} diff --git a/examples/ex1_standalone_app/src/main/java/com/google/android/mobly/snippet/example6/ExampleSnippet2.java b/examples/ex1_standalone_app/src/main/java/com/google/android/mobly/snippet/example6/ExampleSnippet2.java new file mode 100644 index 0000000..10a259e --- /dev/null +++ b/examples/ex1_standalone_app/src/main/java/com/google/android/mobly/snippet/example6/ExampleSnippet2.java @@ -0,0 +1,47 @@ +/* + * 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.example6; + +import com.google.android.mobly.snippet.Snippet; +import com.google.android.mobly.snippet.rpc.Rpc; + +import com.google.android.mobly.snippet.rpc.RunOnUiThread; +import java.io.IOException; + +public class ExampleSnippet2 implements Snippet { + @Rpc(description = "Returns the given string with the prefix \"bar\"") + public String getBar(String input) { + return "bar " + input; + } + + @Rpc(description = "Throws an exception") + public String throwSomething() throws IOException { + throw new IOException("Example exception from throwSomething()"); + } + + @Rpc(description = "Throws an exception from the main thread") + // @RunOnUiThread makes this method execute on the main thread, but only has effect when + // invoked as an RPC. It does not affect how this method executes if invoked directly in Java. + // This annotation can also be applied to the constructor and the shutdown() method. + @RunOnUiThread + public String throwSomethingFromMainThread() throws IOException { + throw new IOException("Example exception from throwSomethingFromMainThread()"); + } + + @Override + public void shutdown() {} +} diff --git a/examples/ex6_complex_type_conversion/README.md b/examples/ex6_complex_type_conversion/README.md new file mode 100644 index 0000000..70d0289 --- /dev/null +++ b/examples/ex6_complex_type_conversion/README.md @@ -0,0 +1,108 @@ +# Complex Type Conversion in Snippet Example + +This tutorial shows you how to use a custom object in Snippet Lib. + +This example assumes basic familiarity with Snippet Lib as demonstrated in +[Example 1](../ex1_standalone_app/README.md). + +## Tutorial + +1. Use Android Studio to create a new app project, similar to + [Example 1](../ex1_standalone_app/README.md). + +1. Create a complex type in Java: + ```java + public class CustomType { + private String myValue; + CustomType(String value) { + myValue = value; + } + + String getMyValue() { + return myValue; + } + public void setMyValue(String newValue) { + myValue = newValue; + } + } + ``` +1. Create a Java class implementing `SnippetObjectConverter`, which defines how the complex type + should be converted against `JSONObject`: + ```java + public class ExampleObjectConverter implements SnippetObjectConverter { + @Override + public JSONObject serialize(Object object) throws JSONException { + JSONObject result = new JSONObject(); + if (object instanceof CustomType) { + CustomType input = (CustomType) object; + result.put("Value", input.getMyValue()); + return result; + } + return null; + } + + @Override + public Object deserialize(JSONObject jsonObject, Type type) throws JSONException { + if (type == CustomType.class) { + return new CustomType(jsonObject.getString("Value")); + } + return null; + } + } + ``` +1. Write a Java class implementing `Snippet` and add Rpc methods that takes your complex type as + a parameter and another Rpc method that returns the complext type directly. + + ```java + package com.my.app; + ... + public class ExampleSnippet implements Snippet { + @Rpc(description = "Pass a complex type as a snippet parameter.") + public String passComplexTypeToSnippet(CustomType input) { + Log.i("Old value is: " + input.getMyValue()); + return "The value in CustomType is: " + input.getMyValue(); + } + + @Rpc(description = "Returns a complex type from snippet.") + public CustomType returnComplexTypeFromSnippet(String value) { + return new CustomType(value); + } + @Override + public void shutdown() {} + } + ``` + +1. In `AndroidManifest.xml`, specify the converter class as a `meta-data` named + `mobly-object-converter` + + ```xml + <manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.my.app"> + <application> + <meta-data + android:name="mobly-object-converter" + android:value="com.my.app.ExampleObjectConverter" /> + ... + ``` + +## Running the example code + +This folder contains a fully working example of a standalone snippet apk. + +1. Compile the example + + ./gradlew examples:ex6_complex_type_conversion:assembleDebug + +1. Install the apk on your phone + + adb install -r ./examples/ex6_complex_type_conversion/build/outputs/apk/ex6_complex_type_conversion-debug.apk + +1. Use Mobly's `snippet_shell` from mobly to trigger the Rpc methods: + + snippet_shell.py com.google.android.mobly.snippet.example6 + + >>> s.passComplexTypeToSnippet({'Value': 'Hello'}) + 'The value in CustomType is: Hello' + >>> s.returnComplexTypeFromSnippet('Bye') + {'Value': 'Bye'} diff --git a/examples/ex6_complex_type_conversion/build.gradle b/examples/ex6_complex_type_conversion/build.gradle new file mode 100644 index 0000000..40d45bc --- /dev/null +++ b/examples/ex6_complex_type_conversion/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + buildToolsVersion '26.0.2' + + defaultConfig { + applicationId "com.google.android.mobly.snippet.example6" + minSdkVersion 16 + targetSdkVersion 26 + versionCode 1 + versionName "0.0.1" + } + lintOptions { + abortOnError true + checkAllWarnings true + warningsAsErrors true + } +} + +dependencies { + // The 'implementation project' dep is to compile against the snippet lib source in + // this repo. For your own snippets, you'll want to use the regular + // 'implementation' dep instead: + //implementation 'com.google.android.mobly:mobly-snippet-lib:1.2.0' + implementation project(':mobly-snippet-lib') +} diff --git a/examples/ex6_complex_type_conversion/src/main/AndroidManifest.xml b/examples/ex6_complex_type_conversion/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c1548e7 --- /dev/null +++ b/examples/ex6_complex_type_conversion/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.android.mobly.snippet.example6"> + + <application> + <!-- Required: list of all classes with @Rpc methods. --> + <meta-data + android:name="mobly-snippets" + android:value="com.google.android.mobly.snippet.example6.ExampleSnippet" /> + <!-- Optional: a class used for converting Java objects to/from JSON. --> + <meta-data + android:name="mobly-object-converter" + android:value="com.google.android.mobly.snippet.example6.ExampleObjectConverter" /> + <meta-data + android:name="mobly-log-tag" + android:value="MoblySnippetLibExample6" /> + </application> + + <instrumentation + android:name="com.google.android.mobly.snippet.SnippetRunner" + android:targetPackage="com.google.android.mobly.snippet.example6" /> +</manifest> diff --git a/examples/ex6_complex_type_conversion/src/main/java/com/google/android/mobly/snippet/example6/CustomType.java b/examples/ex6_complex_type_conversion/src/main/java/com/google/android/mobly/snippet/example6/CustomType.java new file mode 100644 index 0000000..223b63e --- /dev/null +++ b/examples/ex6_complex_type_conversion/src/main/java/com/google/android/mobly/snippet/example6/CustomType.java @@ -0,0 +1,21 @@ +package com.google.android.mobly.snippet.example6; + +/** + * A data class that defines a non-primitive type. + * + * This type is used to demonstrate serialization and de-serialization of complex type objects in + * Mobly Snippet Lib for Android. + */ +public class CustomType { + private String myValue; + CustomType(String value) { + myValue = value; + } + + String getMyValue() { + return myValue; + } + public void setMyValue(String newValue) { + myValue = newValue; + } +} diff --git a/examples/ex6_complex_type_conversion/src/main/java/com/google/android/mobly/snippet/example6/ExampleObjectConverter.java b/examples/ex6_complex_type_conversion/src/main/java/com/google/android/mobly/snippet/example6/ExampleObjectConverter.java new file mode 100644 index 0000000..5fc4f22 --- /dev/null +++ b/examples/ex6_complex_type_conversion/src/main/java/com/google/android/mobly/snippet/example6/ExampleObjectConverter.java @@ -0,0 +1,36 @@ +package com.google.android.mobly.snippet.example6; + +import android.os.Bundle; + +import com.google.android.mobly.snippet.SnippetObjectConverter; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Type; + + +/** + * Example showing how to supply custom object converter to Mobly Snippet Lib. + */ + +public class ExampleObjectConverter implements SnippetObjectConverter { + @Override + public JSONObject serialize(Object object) throws JSONException { + JSONObject result = new JSONObject(); + if (object instanceof CustomType) { + CustomType input = (CustomType) object; + result.put("Value", input.getMyValue()); + return result; + } + return null; + } + + @Override + public Object deserialize(JSONObject jsonObject, Type type) throws JSONException { + if (type == CustomType.class) { + return new CustomType(jsonObject.getString("Value")); + } + return null; + } +} diff --git a/examples/ex6_complex_type_conversion/src/main/java/com/google/android/mobly/snippet/example6/ExampleSnippet.java b/examples/ex6_complex_type_conversion/src/main/java/com/google/android/mobly/snippet/example6/ExampleSnippet.java new file mode 100644 index 0000000..3306f85 --- /dev/null +++ b/examples/ex6_complex_type_conversion/src/main/java/com/google/android/mobly/snippet/example6/ExampleSnippet.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017 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.example6; + +import com.google.android.mobly.snippet.Snippet; +import com.google.android.mobly.snippet.rpc.Rpc; +import com.google.android.mobly.snippet.util.Log; + +import java.util.ArrayList; + +/** + * Example snippet showing converting complex type objects using custom logic. + * + * For complex types in Java, one can supply a custom object converter to Snippet Lib to specify how + * each complex type should be serialized/de-serialized. With this, users don't have to explicitly + * call a serializer or de-serializer in every single Rpc method, which simplifies code. + */ +public class ExampleSnippet implements Snippet { + @Rpc(description = "Pass a complex type as a snippet parameter.") + public String passComplexTypeToSnippet(CustomType input) { + Log.i("Old value is: " + input.getMyValue()); + return "The value in CustomType is: " + input.getMyValue(); + } + + @Rpc(description = "Returns a complex type from snippet.") + public CustomType returnComplexTypeFromSnippet(String value) { + return new CustomType(value); + } + + /** + * Demonstrates serialization/de-serialization of a collection of custom type objects. + */ + @Rpc(description = "Update values for multiple CustomType objects.") + public ArrayList<CustomType> updateValues(ArrayList<CustomType> objects, String newValue) { + for (CustomType obj : objects) { + obj.setMyValue(newValue); + } + return objects; + } + + @Override + public void shutdown() {} +} diff --git a/settings.gradle b/settings.gradle index 7b845bc..08025db 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,5 +4,6 @@ include ( ':examples:ex2_espresso', ':examples:ex3_async_event', ':examples:ex4_uiautomator', - ':examples:ex5_schedule_rpc') + ':examples:ex5_schedule_rpc', + ':examples:ex6_complex_type_conversion') project(":mobly-snippet-lib").projectDir = file('third_party/sl4a') diff --git a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/SnippetObjectConverter.java b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/SnippetObjectConverter.java new file mode 100644 index 0000000..b9a101b --- /dev/null +++ b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/SnippetObjectConverter.java @@ -0,0 +1,39 @@ +package com.google.android.mobly.snippet; + +import java.lang.reflect.Type; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Interface for a converter that serializes and de-serializes objects. + * + * <p>Classes implementing this interface are meant to provide custom serialization/de-serialization + * logic for complex types. + * + * <p>Serialization here means converting a Java object to {@link JSONObject}, which can be + * transported over Snippet's Rpc protocol. De-serialization is this process in reverse. + */ +public interface SnippetObjectConverter { + /** + * Serializes a complex type object to {@link JSONObject}. + * + * <p>Return null to signify the complex type is not supported. + * + * @param object The object to convert to "serialize". + * @return A JSONObject representation of the input object, or `null` if the input object type + * is not supported. + * @throws JSONException + */ + JSONObject serialize(Object object) throws JSONException; + + /** + * Deserializes a {@link JSONObject} to a Java complex type object. + * + * @param jsonObject A {@link JSONObject} passed from the Rpc client. + * @param type The expected {@link Type} of the Java object. + * @return A Java object of the specified {@link Type}, or `null` if the {@link Type} is not + * supported. + * @throws JSONException + */ + Object deserialize(JSONObject jsonObject, Type type) throws JSONException; +} diff --git a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/SnippetManager.java b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/SnippetManager.java index 3fceb36..9632b8b 100644 --- a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/SnippetManager.java +++ b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/SnippetManager.java @@ -22,6 +22,7 @@ import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import com.google.android.mobly.snippet.Snippet; +import com.google.android.mobly.snippet.SnippetObjectConverter; import com.google.android.mobly.snippet.event.EventSnippet; import com.google.android.mobly.snippet.rpc.MethodDescriptor; import com.google.android.mobly.snippet.rpc.RpcMinSdk; @@ -46,10 +47,21 @@ import java.util.TreeSet; import java.util.concurrent.Callable; public class SnippetManager { - private static final String METADATA_TAG_NAME = "mobly-snippets"; + /** + * Name of the XML tag specifying what snippet classes to look for RPCs in. + * + * <p>Comma delimited list of full package names for classes that implements the Snippet + * interface. + */ + private static final String TAG_NAME_SNIPPET_LIST = "mobly-snippets"; + /** Name of the XML tag specifying the custom object converter class to use. */ + private static final String TAG_NAME_OBJECT_CONVERTER = "mobly-object-converter"; + private final Map<Class<? extends Snippet>, Snippet> mSnippets; /** A map of strings to known RPCs. */ private final Map<String, MethodDescriptor> mKnownRpcs; + /** The converter used to serialize and deserialize objects. */ + private SnippetObjectConverter mObjectConverter; private static SnippetManager sInstance = null; private boolean mShutdown = false; @@ -82,6 +94,8 @@ public class SnippetManager { if (sInstance != null) { throw new IllegalStateException("SnippetManager should not be re-initialized"); } + // Add custom object converter if user provided one. + SnippetObjectConverterManager.addConverter(findSnippetObjectConverterFromMetadata(context)); Collection<Class<? extends Snippet>> classList = findSnippetClassesFromMetadata(context); sInstance = new SnippetManager(classList); return sInstance; @@ -158,7 +172,7 @@ public class SnippetManager { return mShutdown; } - private static Set<Class<? extends Snippet>> findSnippetClassesFromMetadata(Context context) { + private static Bundle findMetadata(Context context) { ApplicationInfo appInfo; try { appInfo = @@ -170,13 +184,31 @@ public class SnippetManager { "Failed to find ApplicationInfo with package name: " + context.getPackageName()); } - Bundle metadata = appInfo.metaData; - String snippets = metadata.getString(METADATA_TAG_NAME); + return appInfo.metaData; + } + + private static Class<? extends SnippetObjectConverter> findSnippetObjectConverterFromMetadata( + Context context) { + String className = findMetadata(context).getString(TAG_NAME_OBJECT_CONVERTER); + if (className == null) { + Log.i("No object converter provided."); + return null; + } + try { + return (Class<? extends SnippetObjectConverter>) Class.forName(className); + } catch (ClassNotFoundException e) { + Log.e("Failed to find class " + className); + throw new RuntimeException(e); + } + } + + private static Set<Class<? extends Snippet>> findSnippetClassesFromMetadata(Context context) { + String snippets = findMetadata(context).getString(TAG_NAME_SNIPPET_LIST); if (snippets == null) { throw new IllegalStateException( "AndroidManifest.xml does not contain a <metadata> tag with " + "name=\"" - + METADATA_TAG_NAME + + TAG_NAME_SNIPPET_LIST + "\""); } String[] snippetClassNames = snippets.split("\\s*,\\s*"); diff --git a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/SnippetObjectConverterManager.java b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/SnippetObjectConverterManager.java new file mode 100644 index 0000000..df8af53 --- /dev/null +++ b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/SnippetObjectConverterManager.java @@ -0,0 +1,65 @@ +package com.google.android.mobly.snippet.manager; + +import com.google.android.mobly.snippet.SnippetObjectConverter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Type; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Manager for classes that implement {@link SnippetObjectConverter}. + * + * <p>This class is created to separate how Snippet Lib handles object conversion internally from + * how the conversion scheme for complex types is defined for users. + * + * <p>Snippet Lib can pull in the custom serializers and deserializers through here in various + * stages of execution, whereas users can have a clean interface for supplying these methods without + * worrying about internal states of Snippet Lib. + * + * <p>This gives us the flexibility of changing Snippet Lib internal structure or expanding support + * without impacting users. E.g. we can support multiple converter classes in the future. + */ +public class SnippetObjectConverterManager { + private static SnippetObjectConverter mConverter; + private static volatile SnippetObjectConverterManager mManager; + + private SnippetObjectConverterManager() {} + + public static synchronized SnippetObjectConverterManager getInstance() { + if (mManager == null) { + mManager = new SnippetObjectConverterManager(); + } + return mManager; + } + + static void addConverter(Class<? extends SnippetObjectConverter> converterClass) { + if (mConverter != null) { + throw new RuntimeException("A converter has been added, cannot add again."); + } + try { + mConverter = converterClass.getConstructor().newInstance(); + } catch (NoSuchMethodException e) { + throw new RuntimeException("No default constructor found for the converter class."); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause()); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } + } + + public Object objectToJson(Object object) throws JSONException { + if (mConverter == null) { + return null; + } + return mConverter.serialize(object); + } + + public Object jsonToObject(JSONObject jsonObject, Type type) throws JSONException { + if (mConverter == null) { + return null; + } + return mConverter.deserialize(jsonObject, type); + } +} diff --git a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonBuilder.java b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonBuilder.java index d005530..5b0065a 100644 --- a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonBuilder.java +++ b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonBuilder.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.ParcelUuid; +import com.google.android.mobly.snippet.manager.SnippetObjectConverterManager; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URL; @@ -36,6 +37,8 @@ import org.json.JSONObject; public class JsonBuilder { + private JsonBuilder() {} + @SuppressWarnings("unchecked") public static Object build(Object data) throws JSONException { if (data == null) { @@ -69,11 +72,11 @@ public class JsonBuilder { return data; } if (data instanceof Set<?>) { - List<Object> items = new ArrayList<Object>((Set<?>) data); + List<Object> items = new ArrayList<>((Set<?>) data); return buildJsonList(items); } if (data instanceof Collection<?>) { - List<Object> items = new ArrayList<Object>((Collection<?>) data); + List<Object> items = new ArrayList<>((Collection<?>) data); return buildJsonList(items); } if (data instanceof List<?>) { @@ -86,13 +89,13 @@ public class JsonBuilder { return buildJsonIntent((Intent) data); } if (data instanceof Map<?, ?>) { - // TODO(damonkohler): I would like to make this a checked cast if - // possible. + // TODO(damonkohler): I would like to make this a checked cast if possible. return buildJsonMap((Map<String, ?>) data); } if (data instanceof ParcelUuid) { return data.toString(); } + // TODO(xpconanfan): Deprecate the following default non-primitive type builders. if (data instanceof InetSocketAddress) { return buildInetSocketAddress((InetSocketAddress) data); } @@ -112,7 +115,11 @@ public class JsonBuilder { if (data instanceof Object[]) { return buildJSONArray((Object[]) data); } - + // Try with custom converter provided by user. + Object result = SnippetObjectConverterManager.getInstance().objectToJson(data); + if (result != null) { + return result; + } return data.toString(); } @@ -195,8 +202,4 @@ public class JsonBuilder { private static JSONObject buildUri(Uri uri) throws JSONException { return new JSONObject().put("Uri", build((uri != null) ? uri.toString() : "")); } - - private JsonBuilder() { - // This is a utility class. - } } 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 185b7be..ca836fd 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 @@ -20,6 +20,7 @@ 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; @@ -36,7 +37,7 @@ public final class MethodDescriptor { private final Method mMethod; private final Class<? extends Snippet> mClass; - public MethodDescriptor(Class<? extends Snippet> clazz, Method method) { + private MethodDescriptor(Class<? extends Snippet> clazz, Method method) { mClass = clazz; mMethod = method; } @@ -86,16 +87,12 @@ public final class MethodDescriptor { return manager.invoke(mClass, mMethod, args); } - /** - * Converts a parameter from JSON into a Java Object. - * - * @return TODO - */ + /** 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 - static Object convertParameter(final JSONArray parameters, int index, Type type) + 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 @@ -116,6 +113,8 @@ public final class MethodDescriptor { 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()]; @@ -140,6 +139,13 @@ public final class MethodDescriptor { } else if (type == JSONObject.class) { return parameters.getJSONObject(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)); } @@ -154,7 +160,7 @@ public final class MethodDescriptor { } } - public static Object buildIntent(JSONObject jsonObject) throws JSONException { + private static Object buildIntent(JSONObject jsonObject) throws JSONException { Intent intent = new Intent(); if (jsonObject.has("action")) { intent.setAction(jsonObject.getString("action")); @@ -191,7 +197,7 @@ public final class MethodDescriptor { return mMethod.getName(); } - public Type[] getGenericParameterTypes() { + private Type[] getGenericParameterTypes() { return mMethod.getGenericParameterTypes(); } @@ -199,7 +205,7 @@ public final class MethodDescriptor { return mMethod.isAnnotationPresent(AsyncRpc.class); } - public Class<? extends Snippet> getSnippetClass() { + Class<? extends Snippet> getSnippetClass() { return mClass; } @@ -216,7 +222,7 @@ public final class MethodDescriptor { * * @return derived help string */ - public String getHelp() { + String getHelp() { StringBuilder paramBuilder = new StringBuilder(); Class<?>[] parameterTypes = mMethod.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { @@ -225,15 +231,13 @@ public final class MethodDescriptor { } paramBuilder.append(parameterTypes[i].getSimpleName()); } - String help = - String.format( - Locale.US, - "%s %s(%s) returns %s // %s", - isAsync() ? "@AsyncRpc" : "@Rpc", - mMethod.getName(), - paramBuilder, - mMethod.getReturnType().getSimpleName(), - getAnnotationDescription()); - return help; + return String.format( + Locale.US, + "%s %s(%s) returns %s // %s", + isAsync() ? "@AsyncRpc" : "@Rpc", + mMethod.getName(), + paramBuilder, + mMethod.getReturnType().getSimpleName(), + getAnnotationDescription()); } } |