From e3c8d80b3c213711f0a13e0f40372fb7e26b5dfe Mon Sep 17 00:00:00 2001 From: Emil Sjolander Date: Sun, 8 Jan 2017 04:28:14 -0800 Subject: [PATCH] Add dynamic type for javascript arguments passed over bridge with unkown type Reviewed By: astreet Differential Revision: D4380882 fbshipit-source-id: f1b9fb9cf727d003dcc2264626e75fc300a47dee --- ...alystNativeJSToJavaParametersTestCase.java | 23 ++++++++ .../js/TestJSToJavaParametersModule.js | 4 ++ .../facebook/react/bridge/BaseJavaModule.java | 13 ++++ .../com/facebook/react/bridge/Dynamic.java | 24 ++++++++ .../react/bridge/DynamicFromArray.java | 59 +++++++++++++++++++ .../facebook/react/bridge/DynamicFromMap.java | 59 +++++++++++++++++++ .../facebook/react/bridge/JavaOnlyArray.java | 5 ++ .../facebook/react/bridge/JavaOnlyMap.java | 7 +++ .../facebook/react/bridge/ReadableArray.java | 1 + .../facebook/react/bridge/ReadableMap.java | 1 + .../react/bridge/ReadableNativeArray.java | 6 +- .../react/bridge/ReadableNativeMap.java | 7 ++- .../processing/ReactPropertyProcessor.java | 5 ++ .../react/uimanager/ReactStylesDiffMap.java | 6 ++ 14 files changed, 217 insertions(+), 3 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/bridge/Dynamic.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromArray.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.java diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJSToJavaParametersTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJSToJavaParametersTestCase.java index 13fa94908..7b691d277 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJSToJavaParametersTestCase.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJSToJavaParametersTestCase.java @@ -16,6 +16,7 @@ import java.util.Set; import com.facebook.react.bridge.BaseJavaModule; import com.facebook.react.bridge.CatalystInstance; +import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.InvalidIteratorException; import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NoSuchKeyException; @@ -45,6 +46,7 @@ public class CatalystNativeJSToJavaParametersTestCase extends ReactIntegrationTe private interface TestJSToJavaParametersModule extends JavaScriptModule { void returnBasicTypes(); + void returnDynamicTypes(); void returnArrayWithBasicTypes(); void returnNestedArray(); @@ -113,6 +115,17 @@ public class CatalystNativeJSToJavaParametersTestCase extends ReactIntegrationTe assertNull(args[3]); } + public void testDynamicType() { + mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnDynamicTypes(); + waitForBridgeAndUIIdle(); + + List dynamicCalls = mRecordingTestModule.getDynamicCalls(); + assertEquals(2, dynamicCalls.size()); + + assertEquals("foo", dynamicCalls.get(0).asString()); + assertEquals(3.14, dynamicCalls.get(1).asDouble()); + } + public void testArrayWithBasicTypes() { mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnArrayWithBasicTypes(); waitForBridgeAndUIIdle(); @@ -673,6 +686,7 @@ public class CatalystNativeJSToJavaParametersTestCase extends ReactIntegrationTe private final List mBasicTypesCalls = new ArrayList(); private final List mArrayCalls = new ArrayList(); private final List mMapCalls = new ArrayList(); + private final List mDynamicCalls = new ArrayList(); @Override public String getName() { @@ -694,6 +708,11 @@ public class CatalystNativeJSToJavaParametersTestCase extends ReactIntegrationTe mMapCalls.add(map); } + @ReactMethod + public void receiveDynamic(Dynamic dynamic) { + mDynamicCalls.add(dynamic); + } + public List getBasicTypesCalls() { return mBasicTypesCalls; } @@ -705,5 +724,9 @@ public class CatalystNativeJSToJavaParametersTestCase extends ReactIntegrationTe public List getMapCalls() { return mMapCalls; } + + public List getDynamicCalls() { + return mDynamicCalls; + } } } diff --git a/ReactAndroid/src/androidTest/js/TestJSToJavaParametersModule.js b/ReactAndroid/src/androidTest/js/TestJSToJavaParametersModule.js index 57380f3bc..5f7f73724 100644 --- a/ReactAndroid/src/androidTest/js/TestJSToJavaParametersModule.js +++ b/ReactAndroid/src/androidTest/js/TestJSToJavaParametersModule.js @@ -18,6 +18,10 @@ var TestJSToJavaParametersModule = { returnBasicTypes: function() { Recording.receiveBasicTypes('foo', 3.14, true, null); }, + returnDynamicTypes: function() { + Recording.receiveDynamic('foo'); + Recording.receiveDynamic(3.14); + }, returnArrayWithBasicTypes: function() { Recording.receiveArray(['foo', 3.14, -111, true, null]); }, diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java index 773a0e2b7..28ae53f36 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java @@ -116,6 +116,15 @@ public abstract class BaseJavaModule implements NativeModule { } }; + static final private ArgumentExtractor ARGUMENT_EXTRACTOR_DYNAMIC = + new ArgumentExtractor() { + @Override + public Dynamic extractArgument( + CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) { + return new DynamicFromArray(jsArguments, atIndex); + } + }; + static final private ArgumentExtractor ARGUMENT_EXTRACTOR_MAP = new ArgumentExtractor() { @Override @@ -259,6 +268,8 @@ public abstract class BaseJavaModule implements NativeModule { argumentExtractors[i] = ARGUMENT_EXTRACTOR_MAP; } else if (argumentClass == ReadableArray.class) { argumentExtractors[i] = ARGUMENT_EXTRACTOR_ARRAY; + } else if (argumentClass == Dynamic.class) { + argumentExtractors[i] = ARGUMENT_EXTRACTOR_DYNAMIC; } else { throw new RuntimeException( "Got unknown argument class: " + argumentClass.getSimpleName()); @@ -484,6 +495,8 @@ public abstract class BaseJavaModule implements NativeModule { return 'M'; } else if (paramClass == ReadableArray.class) { return 'A'; + } else if (paramClass == Dynamic.class) { + return 'Y'; } else { throw new RuntimeException( "Got unknown param class: " + paramClass.getSimpleName()); diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/Dynamic.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/Dynamic.java new file mode 100644 index 000000000..efb66400b --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/Dynamic.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.bridge; + +/** + * Type representing a piece of data with unkown runtime type. Useful for allowing javascript to + * pass one of multiple types down to the native layer. + */ +public interface Dynamic { + boolean asBoolean(); + double asDouble(); + int asInt(); + String asString(); + ReadableArray asArray(); + ReadableMap asMap(); + ReadableType getType(); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromArray.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromArray.java new file mode 100644 index 000000000..e215de5ea --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromArray.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.bridge; + +/** + * Implementation of Dynamic wrapping a ReadableArray. + */ +public class DynamicFromArray implements Dynamic { + + private final ReadableArray mArray; + private final int mIndex; + + public DynamicFromArray(ReadableArray array, int index) { + mArray = array; + mIndex = index; + } + + @Override + public boolean asBoolean() { + return mArray.getBoolean(mIndex); + } + + @Override + public double asDouble() { + return mArray.getDouble(mIndex); + } + + @Override + public int asInt() { + return mArray.getInt(mIndex); + } + + @Override + public String asString() { + return mArray.getString(mIndex); + } + + @Override + public ReadableArray asArray() { + return mArray.getArray(mIndex); + } + + @Override + public ReadableMap asMap() { + return mArray.getMap(mIndex); + } + + @Override + public ReadableType getType() { + return mArray.getType(mIndex); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.java new file mode 100644 index 000000000..e6be260ff --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.bridge; + +/** + * Implementation of Dynamic wrapping a ReadableMap. + */ +public class DynamicFromMap implements Dynamic { + + private final ReadableMap mMap; + private final String mName; + + public DynamicFromMap(ReadableMap map, String name) { + mMap = map; + mName = name; + } + + @Override + public boolean asBoolean() { + return mMap.getBoolean(mName); + } + + @Override + public double asDouble() { + return mMap.getDouble(mName); + } + + @Override + public int asInt() { + return mMap.getInt(mName); + } + + @Override + public String asString() { + return mMap.getString(mName); + } + + @Override + public ReadableArray asArray() { + return mMap.getArray(mName); + } + + @Override + public ReadableMap asMap() { + return mMap.getMap(mName); + } + + @Override + public ReadableType getType() { + return mMap.getType(mName); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyArray.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyArray.java index 344533471..680f49d39 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyArray.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyArray.java @@ -88,6 +88,11 @@ public class JavaOnlyArray implements ReadableArray, WritableArray { return (JavaOnlyMap) mBackingList.get(index); } + @Override + public Dynamic getDynamic(int index) { + return new DynamicFromArray(this, index); + } + @Override public ReadableType getType(int index) { Object object = mBackingList.get(index); diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java index c9e7c3114..1b9ca0111 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java @@ -88,6 +88,11 @@ public class JavaOnlyMap implements ReadableMap, WritableMap { return (JavaOnlyArray) mBackingMap.get(name); } + @Override + public Dynamic getDynamic(String name) { + return new DynamicFromMap(this, name); + } + @Override public ReadableType getType(String name) { Object value = mBackingMap.get(name); @@ -103,6 +108,8 @@ public class JavaOnlyMap implements ReadableMap, WritableMap { return ReadableType.Map; } else if (value instanceof ReadableArray) { return ReadableType.Array; + } else if (value instanceof Dynamic) { + return ((Dynamic) value).getType(); } else { throw new IllegalArgumentException("Invalid value " + value.toString() + " for key " + name + "contained in JavaOnlyMap"); diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArray.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArray.java index 47e5ed30c..bcaa03eb2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArray.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArray.java @@ -23,5 +23,6 @@ public interface ReadableArray { String getString(int index); ReadableArray getArray(int index); ReadableMap getMap(int index); + Dynamic getDynamic(int index); ReadableType getType(int index); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableMap.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableMap.java index b9e83f2d9..f81e9ae0f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableMap.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableMap.java @@ -23,6 +23,7 @@ public interface ReadableMap { String getString(String name); ReadableArray getArray(String name); ReadableMap getMap(String name); + Dynamic getDynamic(String name); ReadableType getType(String name); ReadableMapKeySetIterator keySetIterator(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.java index 2f81d301b..a0216d822 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.java @@ -11,7 +11,6 @@ package com.facebook.react.bridge; import com.facebook.jni.HybridData; import com.facebook.proguard.annotations.DoNotStrip; -import com.facebook.soloader.SoLoader; import java.util.ArrayList; @@ -48,6 +47,11 @@ public class ReadableNativeArray extends NativeArray implements ReadableArray { @Override public native ReadableType getType(int index); + @Override + public Dynamic getDynamic(int index) { + return new DynamicFromArray(this, index); + } + public ArrayList toArrayList() { ArrayList arrayList = new ArrayList<>(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java index ea289ac66..87b6b1298 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java @@ -11,11 +11,9 @@ package com.facebook.react.bridge; import com.facebook.jni.HybridData; import com.facebook.proguard.annotations.DoNotStrip; -import com.facebook.soloader.SoLoader; import java.util.HashMap; - /** * Implementation of a read-only map in native memory. This will generally be constructed and filled * in native code so you shouldn't construct one yourself. @@ -49,6 +47,11 @@ public class ReadableNativeMap extends NativeMap implements ReadableMap { @Override public native ReadableType getType(String name); + @Override + public Dynamic getDynamic(String name) { + return new DynamicFromMap(this, name); + } + @Override public ReadableMapKeySetIterator keySetIterator() { return new ReadableNativeMapKeySetIterator(this); diff --git a/ReactAndroid/src/main/java/com/facebook/react/processing/ReactPropertyProcessor.java b/ReactAndroid/src/main/java/com/facebook/react/processing/ReactPropertyProcessor.java index 47c5ebcc7..7febcdbf4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/processing/ReactPropertyProcessor.java +++ b/ReactAndroid/src/main/java/com/facebook/react/processing/ReactPropertyProcessor.java @@ -33,6 +33,7 @@ import java.util.Set; import com.facebook.infer.annotation.SuppressFieldNotInitialized; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.Dynamic; import com.facebook.react.uimanager.annotations.ReactPropertyHolder; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactPropGroup; @@ -68,6 +69,7 @@ public class ReactPropertyProcessor extends AbstractProcessor { private static final TypeName STRING_TYPE = TypeName.get(String.class); private static final TypeName READABLE_MAP_TYPE = TypeName.get(ReadableMap.class); private static final TypeName READABLE_ARRAY_TYPE = TypeName.get(ReadableArray.class); + private static final TypeName DYNAMIC_TYPE = TypeName.get(Dynamic.class); private static final TypeName VIEW_MANAGER_TYPE = ClassName.get("com.facebook.react.uimanager", "ViewManager"); @@ -118,6 +120,7 @@ public class ReactPropertyProcessor extends AbstractProcessor { DEFAULT_TYPES.put(STRING_TYPE, "String"); DEFAULT_TYPES.put(READABLE_ARRAY_TYPE, "Array"); DEFAULT_TYPES.put(READABLE_MAP_TYPE, "Map"); + DEFAULT_TYPES.put(DYNAMIC_TYPE, "Dynamic"); BOXED_PRIMITIVES = new HashSet<>(); BOXED_PRIMITIVES.add(TypeName.BOOLEAN.box()); @@ -365,6 +368,8 @@ public class ReactPropertyProcessor extends AbstractProcessor { return builder.add("props.getArray(name)"); } else if (propertyType.equals(READABLE_MAP_TYPE)) { return builder.add("props.getMap(name)"); + } else if (propertyType.equals(DYNAMIC_TYPE)) { + return builder.add("props.getDynamic(name)"); } if (BOXED_PRIMITIVES.contains(propertyType)) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactStylesDiffMap.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactStylesDiffMap.java index 2259abb31..601acfe45 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactStylesDiffMap.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactStylesDiffMap.java @@ -15,6 +15,7 @@ import android.view.View; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.Dynamic; /** * Wrapper for {@link ReadableMap} which should be used for styles property map. It extends @@ -82,6 +83,11 @@ public class ReactStylesDiffMap { return mBackingMap.getMap(key); } + @Nullable + public Dynamic getDynamic(String key) { + return mBackingMap.getDynamic(key); + } + @Override public String toString() { return "{ " + getClass().getSimpleName() + ": " + mBackingMap.toString() + " }";