diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeMap.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeMap.java index 9b5ded014..2c683f945 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeMap.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeMap.java @@ -9,7 +9,7 @@ package com.facebook.react.bridge; -import com.facebook.jni.Countable; +import com.facebook.jni.HybridData; import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.soloader.SoLoader; @@ -17,18 +17,18 @@ import com.facebook.soloader.SoLoader; * Base class for a Map whose keys and values are stored in native code (C++). */ @DoNotStrip -public abstract class NativeMap extends Countable { - +public abstract class NativeMap { static { SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB); } - public NativeMap() { - initialize(); + public NativeMap(HybridData hybridData) { + mHybridData = hybridData; } @Override public native String toString(); - private native void initialize(); + @DoNotStrip + private HybridData mHybridData; } 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 9d1fec631..f3782ca08 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java @@ -9,7 +9,7 @@ package com.facebook.react.bridge; -import com.facebook.jni.Countable; +import com.facebook.jni.HybridData; import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.soloader.SoLoader; @@ -27,6 +27,10 @@ public class ReadableNativeMap extends NativeMap implements ReadableMap { SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB); } + protected ReadableNativeMap(HybridData hybridData) { + super(hybridData); + } + @Override public native boolean hasKey(String name); @Override @@ -51,7 +55,7 @@ public class ReadableNativeMap extends NativeMap implements ReadableMap { return new ReadableNativeMapKeySetIterator(this); } - public HashMaptoHashMap() { + public HashMap toHashMap() { ReadableMapKeySetIterator iterator = keySetIterator(); HashMap hashMap = new HashMap<>(); @@ -87,14 +91,17 @@ public class ReadableNativeMap extends NativeMap implements ReadableMap { * Implementation of a {@link ReadableNativeMap} iterator in native memory. */ @DoNotStrip - private static class ReadableNativeMapKeySetIterator extends Countable - implements ReadableMapKeySetIterator { + private static class ReadableNativeMapKeySetIterator implements ReadableMapKeySetIterator { + @DoNotStrip + private final HybridData mHybridData; - private final ReadableNativeMap mReadableNativeMap; + // Need to hold a strong ref to the map so that our native references remain valid. + @DoNotStrip + private final ReadableNativeMap mMap; public ReadableNativeMapKeySetIterator(ReadableNativeMap readableNativeMap) { - mReadableNativeMap = readableNativeMap; - initialize(mReadableNativeMap); + mMap = readableNativeMap; + mHybridData = initHybrid(readableNativeMap); } @Override @@ -102,6 +109,6 @@ public class ReadableNativeMap extends NativeMap implements ReadableMap { @Override public native String nextKey(); - private native void initialize(ReadableNativeMap readableNativeMap); + private static native HybridData initHybrid(ReadableNativeMap readableNativeMap); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeArray.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeArray.java index c0f65d072..26fe2dd11 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeArray.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeArray.java @@ -57,7 +57,7 @@ public class WritableNativeArray extends ReadableNativeArray implements Writable pushNativeMap((WritableNativeMap) map); } - private native static HybridData initHybrid(); + private static native HybridData initHybrid(); private native void pushNativeArray(WritableNativeArray array); private native void pushNativeMap(WritableNativeMap map); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.java index 1388be0ce..d30827ade 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.java @@ -9,6 +9,7 @@ package com.facebook.react.bridge; +import com.facebook.jni.HybridData; import com.facebook.infer.annotation.Assertions; import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.soloader.SoLoader; @@ -20,7 +21,6 @@ import com.facebook.soloader.SoLoader; */ @DoNotStrip public class WritableNativeMap extends ReadableNativeMap implements WritableMap { - static { SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB); } @@ -59,6 +59,12 @@ public class WritableNativeMap extends ReadableNativeMap implements WritableMap mergeNativeMap((ReadableNativeMap) source); } + public WritableNativeMap() { + super(initHybrid()); + } + + private static native HybridData initHybrid(); + private native void putNativeMap(String key, WritableNativeMap value); private native void putNativeArray(String key, WritableNativeArray value); private native void mergeNativeMap(ReadableNativeMap source); diff --git a/ReactAndroid/src/main/jni/react/jni/Android.mk b/ReactAndroid/src/main/jni/react/jni/Android.mk index 42e43e8ce..742e2161d 100644 --- a/ReactAndroid/src/main/jni/react/jni/Android.mk +++ b/ReactAndroid/src/main/jni/react/jni/Android.mk @@ -7,13 +7,19 @@ LOCAL_MODULE := libreactnativejni LOCAL_SRC_FILES := \ JExecutorToken.cpp \ JMessageQueueThread.cpp \ - JniJSModulesUnbundle.cpp \ JSCPerfLogging.cpp \ JSLoader.cpp \ JSLogging.cpp \ + JniJSModulesUnbundle.cpp \ NativeArray.cpp \ + NativeCommon.cpp \ + NativeMap.cpp \ OnLoad.cpp \ ProxyExecutor.cpp \ + ReadableNativeArray.cpp \ + ReadableNativeMap.cpp \ + WritableNativeArray.cpp \ + WritableNativeMap.cpp \ LOCAL_C_INCLUDES := $(LOCAL_PATH) LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../.. $(LOCAL_PATH)/.. diff --git a/ReactAndroid/src/main/jni/react/jni/BUCK b/ReactAndroid/src/main/jni/react/jni/BUCK index 0941c5f7c..9bd754fb7 100644 --- a/ReactAndroid/src/main/jni/react/jni/BUCK +++ b/ReactAndroid/src/main/jni/react/jni/BUCK @@ -31,11 +31,17 @@ jni_library( 'JMessageQueueThread.cpp', 'JSCPerfLogging.cpp', 'JSLoader.cpp', + 'JSLogging.cpp', 'JniJSModulesUnbundle.cpp', 'NativeArray.cpp', + 'NativeCommon.cpp', + 'NativeMap.cpp', 'OnLoad.cpp', 'ProxyExecutor.cpp', - 'JSLogging.cpp', + 'ReadableNativeArray.cpp', + 'ReadableNativeMap.cpp', + 'WritableNativeArray.cpp', + 'WritableNativeMap.cpp', ], headers = [ 'JSLoader.h', @@ -50,8 +56,13 @@ jni_library( 'WebWorkers.h', ], exported_headers = [ + 'NativeCommon.h', 'NativeArray.h', + 'NativeMap.h', 'ReadableNativeArray.h', + 'ReadableNativeMap.h', + 'WritableNativeArray.h', + 'WritableNativeMap.h', ], preprocessor_flags = [ '-DLOG_TAG="ReactNativeJNI"', diff --git a/ReactAndroid/src/main/jni/react/jni/NativeArray.cpp b/ReactAndroid/src/main/jni/react/jni/NativeArray.cpp index 1bf903469..5d01bf7a6 100644 --- a/ReactAndroid/src/main/jni/react/jni/NativeArray.cpp +++ b/ReactAndroid/src/main/jni/react/jni/NativeArray.cpp @@ -5,23 +5,24 @@ #include #include +#include "NativeCommon.h" + +using namespace facebook::jni; + namespace facebook { namespace react { NativeArray::NativeArray(folly::dynamic a) : array(std::move(a)) { if (!array.isArray()) { - jni::throwNewJavaException("com/facebook/react/bridge/UnexpectedNativeTypeException", + throwNewJavaException(exceptions::gUnexpectedNativeTypeExceptionClass, "expected Array, got a %s", array.typeName()); } } -jstring NativeArray::toString() { - if (isConsumed) { - jni::throwNewJavaException("com/facebook/react/bridge/ObjectAlreadyConsumedException", - "Array already consumed"); - } - return jni::make_jstring(folly::toJson(array).c_str()).release(); +local_ref NativeArray::toString() { + exceptions::throwIfObjectAlreadyConsumed(this, "Array already consumed"); + return make_jstring(folly::toJson(array).c_str()); } void NativeArray::registerNatives() { diff --git a/ReactAndroid/src/main/jni/react/jni/NativeArray.h b/ReactAndroid/src/main/jni/react/jni/NativeArray.h index 0fc69d5ac..ef69e1724 100644 --- a/ReactAndroid/src/main/jni/react/jni/NativeArray.h +++ b/ReactAndroid/src/main/jni/react/jni/NativeArray.h @@ -17,7 +17,7 @@ class NativeArray : public jni::HybridClass { bool isConsumed = false; folly::dynamic array; - jstring toString(); + jni::local_ref toString(); static void registerNatives(); diff --git a/ReactAndroid/src/main/jni/react/jni/NativeCommon.cpp b/ReactAndroid/src/main/jni/react/jni/NativeCommon.cpp new file mode 100644 index 000000000..0f5007350 --- /dev/null +++ b/ReactAndroid/src/main/jni/react/jni/NativeCommon.cpp @@ -0,0 +1,76 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "NativeCommon.h" + +using namespace facebook::jni; + +namespace facebook { +namespace react { + +namespace exceptions { +const char *gUnexpectedNativeTypeExceptionClass = + "com/facebook/react/bridge/UnexpectedNativeTypeException"; +} + +namespace { + +local_ref getTypeField(const char* fieldName) { + static auto cls = ReadableType::javaClassStatic(); + auto field = cls->getStaticField(fieldName); + return cls->getStaticFieldValue(field); +} + +alias_ref getNullValue() { + static alias_ref val = make_global(getTypeField("Null")).release(); + return val; +} + +alias_ref getBooleanValue() { + static alias_ref val = make_global(getTypeField("Boolean")).release(); + return val; +} + +alias_ref getNumberValue() { + static alias_ref val = make_global(getTypeField("Number")).release(); + return val; +} + +alias_ref getStringValue() { + static alias_ref val = make_global(getTypeField("String")).release(); + return val; +} + +alias_ref getMapValue() { + static alias_ref val = make_global(getTypeField("Map")).release(); + return val; +} + +alias_ref getArrayValue() { + static alias_ref val = make_global(getTypeField("Array")).release(); + return val; +} + +} // namespace + +local_ref ReadableType::getType(folly::dynamic::Type type) { + switch (type) { + case folly::dynamic::Type::NULLT: + return make_local(getNullValue()); + case folly::dynamic::Type::BOOL: + return make_local(getBooleanValue()); + case folly::dynamic::Type::DOUBLE: + case folly::dynamic::Type::INT64: + return make_local(getNumberValue()); + case folly::dynamic::Type::STRING: + return make_local(getStringValue()); + case folly::dynamic::Type::OBJECT: + return make_local(getMapValue()); + case folly::dynamic::Type::ARRAY: + return make_local(getArrayValue()); + default: + throwNewJavaException(exceptions::gUnexpectedNativeTypeExceptionClass, "Unknown type"); + } +} + +} // namespace react +} // namespace facebook diff --git a/ReactAndroid/src/main/jni/react/jni/NativeCommon.h b/ReactAndroid/src/main/jni/react/jni/NativeCommon.h new file mode 100644 index 000000000..2f9a4a4d6 --- /dev/null +++ b/ReactAndroid/src/main/jni/react/jni/NativeCommon.h @@ -0,0 +1,31 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include +#include + +namespace facebook { +namespace react { + +struct ReadableType : public jni::JavaClass { + static auto constexpr kJavaDescriptor = "Lcom/facebook/react/bridge/ReadableType;"; + + static jni::local_ref getType(folly::dynamic::Type type); +}; + +namespace exceptions { + +extern const char *gUnexpectedNativeTypeExceptionClass; + +template +void throwIfObjectAlreadyConsumed(const T& t, const char* msg) { + if (t->isConsumed) { + jni::throwNewJavaException("com/facebook/react/bridge/ObjectAlreadyConsumedException", msg); + } +} + +} // namespace exceptions + +} // namespace react +} // namespace facebook diff --git a/ReactAndroid/src/main/jni/react/jni/NativeMap.cpp b/ReactAndroid/src/main/jni/react/jni/NativeMap.cpp new file mode 100644 index 000000000..92af29e67 --- /dev/null +++ b/ReactAndroid/src/main/jni/react/jni/NativeMap.cpp @@ -0,0 +1,28 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "NativeMap.h" + +#include "NativeCommon.h" + +using namespace facebook::jni; + +namespace facebook { +namespace react { + +std::string NativeMap::toString() { + throwIfConsumed(); + return ("{ NativeMap: " + folly::toJson(map_) + " }").c_str(); +} + +void NativeMap::registerNatives() { + registerHybrid({ + makeNativeMethod("toString", NativeMap::toString), + }); +} + +void NativeMap::throwIfConsumed() { + exceptions::throwIfObjectAlreadyConsumed(this, "Map already consumed"); +} + +} // namespace react +} // namespace facebook diff --git a/ReactAndroid/src/main/jni/react/jni/NativeMap.h b/ReactAndroid/src/main/jni/react/jni/NativeMap.h new file mode 100644 index 000000000..4246195ba --- /dev/null +++ b/ReactAndroid/src/main/jni/react/jni/NativeMap.h @@ -0,0 +1,33 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include +#include +#include + +namespace facebook { +namespace react { + +class NativeMap : public jni::HybridClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/facebook/react/bridge/NativeMap;"; + + explicit NativeMap(folly::dynamic s) : isConsumed(false), map_(s) {} + + std::string toString(); + + bool isConsumed; + void throwIfConsumed(); + + static void registerNatives(); + protected: + + folly::dynamic map_; + + friend HybridBase; + friend class ReadableNativeMapKeySetIterator; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index 001c7237e..8d60719c4 100644 --- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -22,6 +22,7 @@ #include "JExecutorTokenFactory.h" #include "JNativeRunnable.h" #include "JSLoader.h" +#include "NativeCommon.h" #include "ReadableNativeArray.h" #include "ProxyExecutor.h" #include "OnLoad.h" @@ -30,6 +31,7 @@ #include "JSLogging.h" #include "JSCPerfLogging.h" #include "WebWorkers.h" +#include "WritableNativeMap.h" #include #ifdef WITH_FBSYSTRACE @@ -42,492 +44,6 @@ using namespace facebook::jni; namespace facebook { namespace react { -static jclass gReadableNativeMapClass; -static jmethodID gReadableNativeMapCtor; - -namespace exceptions { - -static const char *gUnexpectedNativeTypeExceptionClass = - "com/facebook/react/bridge/UnexpectedNativeTypeException"; - -template -void throwIfObjectAlreadyConsumed(const T& t, const char* msg) { - if (t->isConsumed) { - throwNewJavaException("com/facebook/react/bridge/ObjectAlreadyConsumedException", msg); - } -} - -} - -struct NativeMap : public Countable { - // Whether this map has been added to another array or map and no longer has a valid map value - bool isConsumed = false; - folly::dynamic map = folly::dynamic::object; -}; - -struct ReadableNativeMapKeySetIterator : public Countable { - folly::dynamic::const_item_iterator iterator; - RefPtr mapRef; - - ReadableNativeMapKeySetIterator(folly::dynamic::const_item_iterator&& it, - const RefPtr& mapRef_) - : iterator(std::move(it)) - , mapRef(mapRef_) {} -}; - -static jobject createReadableNativeMapWithContents(JNIEnv* env, folly::dynamic map) { - if (map.isNull()) { - return nullptr; - } - - if (!map.isObject()) { - throwNewJavaException(exceptions::gUnexpectedNativeTypeExceptionClass, - "expected Map, got a %s", map.typeName()); - } - - jobject jnewMap = env->NewObject(gReadableNativeMapClass, gReadableNativeMapCtor); - if (env->ExceptionCheck()) { - return nullptr; - } - auto nativeMap = extractRefPtr(env, jnewMap); - nativeMap->map = std::move(map); - return jnewMap; -} - -namespace type { - -static jclass gReadableReactType; -static jobject gTypeNullValue; -static jobject gTypeBooleanValue; -static jobject gTypeNumberValue; -static jobject gTypeStringValue; -static jobject gTypeMapValue; -static jobject gTypeArrayValue; - -static jobject getTypeValue(JNIEnv* env, const char* fieldName) { - jfieldID fieldID = env->GetStaticFieldID( - gReadableReactType, fieldName, "Lcom/facebook/react/bridge/ReadableType;"); - jobject typeValue = env->GetStaticObjectField(gReadableReactType, fieldID); - return env->NewGlobalRef(typeValue); -} - -static void initialize(JNIEnv* env) { - gTypeNullValue = getTypeValue(env, "Null"); - gTypeBooleanValue = getTypeValue(env, "Boolean"); - gTypeNumberValue = getTypeValue(env, "Number"); - gTypeStringValue = getTypeValue(env, "String"); - gTypeMapValue = getTypeValue(env, "Map"); - gTypeArrayValue = getTypeValue(env, "Array"); -} - -static jobject getType(folly::dynamic::Type type) { - switch (type) { - case folly::dynamic::Type::NULLT: - return type::gTypeNullValue; - case folly::dynamic::Type::BOOL: - return type::gTypeBooleanValue; - case folly::dynamic::Type::DOUBLE: - case folly::dynamic::Type::INT64: - return type::gTypeNumberValue; - case folly::dynamic::Type::STRING: - return type::gTypeStringValue; - case folly::dynamic::Type::OBJECT: - return type::gTypeMapValue; - case folly::dynamic::Type::ARRAY: - return type::gTypeArrayValue; - default: - throwNewJavaException(exceptions::gUnexpectedNativeTypeExceptionClass, "Unknown type"); - } -} - -} - -// This attribute exports the ctor symbol, so ReadableNativeArray to be -// constructed from other DSOs. -__attribute__((visibility("default"))) -ReadableNativeArray::ReadableNativeArray(folly::dynamic array) - : HybridBase(std::move(array)) {} - -void ReadableNativeArray::mapException(const std::exception& ex) { - if (dynamic_cast(&ex) != nullptr) { - throwNewJavaException(exceptions::gUnexpectedNativeTypeExceptionClass, ex.what()); - } -} - -jint ReadableNativeArray::getSize() { - return array.size(); -} - -jboolean ReadableNativeArray::isNull(jint index) { - return array.at(index).isNull() ? JNI_TRUE : JNI_FALSE; -} - -jboolean ReadableNativeArray::getBoolean(jint index) { - return array.at(index).getBool() ? JNI_TRUE : JNI_FALSE; -} - -jdouble ReadableNativeArray::getDouble(jint index) { - const folly::dynamic& val = array.at(index); - if (val.isInt()) { - return val.getInt(); - } - return val.getDouble(); -} - -jint ReadableNativeArray::getInt(jint index) { - auto integer = array.at(index).getInt(); - static_assert(std::is_same::value, - "folly::dynamic int is not int64_t"); - jint javaint = static_cast(integer); - if (integer != javaint) { - throwNewJavaException( - exceptions::gUnexpectedNativeTypeExceptionClass, - "Value '%lld' doesn't fit into a 32 bit signed int", integer); - } - return javaint; -} - -const char* ReadableNativeArray::getString(jint index) { - const folly::dynamic& dyn = array.at(index); - if (dyn.isNull()) { - return nullptr; - } - return dyn.getString().c_str(); -} - -jni::local_ref ReadableNativeArray::getArray(jint index) { - auto& elem = array.at(index); - if (elem.isNull()) { - return jni::local_ref(nullptr); - } else { - return ReadableNativeArray::newObjectCxxArgs(elem); - } -} - -// Export getMap() so we can workaround constructing ReadableNativeMap -__attribute__((visibility("default"))) -jobject ReadableNativeArray::getMap(jint index) { - return createReadableNativeMapWithContents(Environment::current(), array.at(index)); -} - -jobject ReadableNativeArray::getType(jint index) { - return type::getType(array.at(index).type()); -} - -void ReadableNativeArray::registerNatives() { - jni::registerNatives("com/facebook/react/bridge/ReadableNativeArray", { - makeNativeMethod("size", ReadableNativeArray::getSize), - makeNativeMethod("isNull", ReadableNativeArray::isNull), - makeNativeMethod("getBoolean", ReadableNativeArray::getBoolean), - makeNativeMethod("getDouble", ReadableNativeArray::getDouble), - makeNativeMethod("getInt", ReadableNativeArray::getInt), - makeNativeMethod("getString", ReadableNativeArray::getString), - makeNativeMethod("getArray", ReadableNativeArray::getArray), - makeNativeMethod("getMap", "(I)Lcom/facebook/react/bridge/ReadableNativeMap;", - ReadableNativeArray::getMap), - makeNativeMethod("getType", "(I)Lcom/facebook/react/bridge/ReadableType;", - ReadableNativeArray::getType), - }); -} - -namespace { - -struct WritableNativeArray - : public jni::HybridClass { - static constexpr const char* kJavaDescriptor = "Lcom/facebook/react/bridge/WritableNativeArray;"; - - WritableNativeArray() - : HybridBase(folly::dynamic::array()) {} - - static local_ref initHybrid(alias_ref) { - return makeCxxInstance(); - } - - void pushNull() { - exceptions::throwIfObjectAlreadyConsumed(this, "Array already consumed"); - array.push_back(nullptr); - } - - void pushBoolean(jboolean value) { - exceptions::throwIfObjectAlreadyConsumed(this, "Array already consumed"); - array.push_back(value == JNI_TRUE); - } - - void pushDouble(jdouble value) { - exceptions::throwIfObjectAlreadyConsumed(this, "Receiving array already consumed"); - array.push_back(value); - } - - void pushInt(jint value) { - exceptions::throwIfObjectAlreadyConsumed(this, "Receiving array already consumed"); - array.push_back(value); - } - - void pushString(jstring value) { - if (value == NULL) { - pushNull(); - return; - } - exceptions::throwIfObjectAlreadyConsumed(this, "Receiving array already consumed"); - array.push_back(wrap_alias(value)->toStdString()); - } - - void pushNativeArray(WritableNativeArray* otherArray) { - if (otherArray == NULL) { - pushNull(); - return; - } - exceptions::throwIfObjectAlreadyConsumed(this, "Receiving array already consumed"); - exceptions::throwIfObjectAlreadyConsumed(otherArray, "Array to push already consumed"); - array.push_back(std::move(otherArray->array)); - otherArray->isConsumed = true; - } - - void pushNativeMap(jobject jmap) { - if (jmap == NULL) { - pushNull(); - return; - } - exceptions::throwIfObjectAlreadyConsumed(this, "Receiving array already consumed"); - auto map = extractRefPtr(Environment::current(), jmap); - exceptions::throwIfObjectAlreadyConsumed(map, "Map to push already consumed"); - array.push_back(std::move(map->map)); - map->isConsumed = true; - } - - static void registerNatives() { - jni::registerNatives("com/facebook/react/bridge/WritableNativeArray", { - makeNativeMethod("initHybrid", WritableNativeArray::initHybrid), - makeNativeMethod("pushNull", WritableNativeArray::pushNull), - makeNativeMethod("pushBoolean", WritableNativeArray::pushBoolean), - makeNativeMethod("pushDouble", WritableNativeArray::pushDouble), - makeNativeMethod("pushInt", WritableNativeArray::pushInt), - makeNativeMethod("pushString", WritableNativeArray::pushString), - makeNativeMethod("pushNativeArray", WritableNativeArray::pushNativeArray), - makeNativeMethod("pushNativeMap", "(Lcom/facebook/react/bridge/WritableNativeMap;)V", - WritableNativeArray::pushNativeMap), - }); - } -}; - -} - -namespace map { - -static void initialize(JNIEnv* env, jobject obj) { - auto map = createNew(); - setCountableForJava(env, obj, std::move(map)); -} - -static jstring toString(JNIEnv* env, jobject obj) { - auto nativeMap = extractRefPtr(env, obj); - exceptions::throwIfObjectAlreadyConsumed(nativeMap, "Map already consumed"); - LocalString string( - ("{ NativeMap: " + folly::toJson(nativeMap->map) + " }").c_str()); - return static_cast(env->NewLocalRef(string.string())); -} - -namespace writable { - -static void putNull(JNIEnv* env, jobject obj, jstring key) { - auto map = extractRefPtr(env, obj); - exceptions::throwIfObjectAlreadyConsumed(map, "Receiving map already consumed"); - map->map.insert(fromJString(env, key), nullptr); -} - -static void putBoolean(JNIEnv* env, jobject obj, jstring key, jboolean value) { - auto map = extractRefPtr(env, obj); - exceptions::throwIfObjectAlreadyConsumed(map, "Receiving map already consumed"); - map->map.insert(fromJString(env, key), value == JNI_TRUE); -} - -static void putDouble(JNIEnv* env, jobject obj, jstring key, jdouble value) { - auto map = extractRefPtr(env, obj); - exceptions::throwIfObjectAlreadyConsumed(map, "Receiving map already consumed"); - map->map.insert(fromJString(env, key), value); -} - -static void putInt(JNIEnv* env, jobject obj, jstring key, jint value) { - auto map = extractRefPtr(env, obj); - exceptions::throwIfObjectAlreadyConsumed(map, "Receiving map already consumed"); - map->map.insert(fromJString(env, key), value); -} - -static void putString(JNIEnv* env, jobject obj, jstring key, jstring value) { - if (value == NULL) { - putNull(env, obj, key); - return; - } - auto map = extractRefPtr(env, obj); - exceptions::throwIfObjectAlreadyConsumed(map, "Receiving map already consumed"); - map->map.insert(fromJString(env, key), fromJString(env, value)); -} - -static void putArray(JNIEnv* env, jobject obj, jstring key, - WritableNativeArray::jhybridobject value) { - if (value == NULL) { - putNull(env, obj, key); - return; - } - auto parentMap = extractRefPtr(env, obj); - exceptions::throwIfObjectAlreadyConsumed(parentMap, "Receiving map already consumed"); - auto arrayValue = cthis(wrap_alias(value)); - exceptions::throwIfObjectAlreadyConsumed(arrayValue, "Array to put already consumed"); - parentMap->map.insert(fromJString(env, key), std::move(arrayValue->array)); - arrayValue->isConsumed = true; -} - -static void putMap(JNIEnv* env, jobject obj, jstring key, jobject value) { - if (value == NULL) { - putNull(env, obj, key); - return; - } - auto parentMap = extractRefPtr(env, obj); - exceptions::throwIfObjectAlreadyConsumed(parentMap, "Receiving map already consumed"); - auto mapValue = extractRefPtr(env, value); - exceptions::throwIfObjectAlreadyConsumed(mapValue, "Map to put already consumed"); - parentMap->map.insert(fromJString(env, key), std::move(mapValue->map)); - mapValue->isConsumed = true; -} - -static void mergeMap(JNIEnv* env, jobject obj, jobject source) { - auto sourceMap = extractRefPtr(env, source); - exceptions::throwIfObjectAlreadyConsumed(sourceMap, "Source map already consumed"); - auto destMap = extractRefPtr(env, obj); - exceptions::throwIfObjectAlreadyConsumed(destMap, "Destination map already consumed"); - - // std::map#insert doesn't overwrite the value, therefore we need to clean values for keys - // that already exists before merging dest map into source map - for (auto sourceIt : sourceMap->map.items()) { - destMap->map.erase(sourceIt.first); - destMap->map.insert(std::move(sourceIt.first), std::move(sourceIt.second)); - } -} - -} // namespace writable - -namespace readable { - -static const char *gNoSuchKeyExceptionClass = "com/facebook/react/bridge/NoSuchKeyException"; - -static jboolean hasKey(JNIEnv* env, jobject obj, jstring keyName) { - auto nativeMap = extractRefPtr(env, obj); - auto& map = nativeMap->map; - bool found = map.find(fromJString(env, keyName)) != map.items().end(); - return found ? JNI_TRUE : JNI_FALSE; -} - -static const folly::dynamic& getMapValue(JNIEnv* env, jobject obj, jstring keyName) { - auto nativeMap = extractRefPtr(env, obj); - std::string key = fromJString(env, keyName); - try { - return nativeMap->map.at(key); - } catch (const std::out_of_range& ex) { - throwNewJavaException(gNoSuchKeyExceptionClass, ex.what()); - } -} - -static jboolean isNull(JNIEnv* env, jobject obj, jstring keyName) { - return getMapValue(env, obj, keyName).isNull() ? JNI_TRUE : JNI_FALSE; -} - -static jboolean getBooleanKey(JNIEnv* env, jobject obj, jstring keyName) { - try { - return getMapValue(env, obj, keyName).getBool() ? JNI_TRUE : JNI_FALSE; - } catch (const folly::TypeError& ex) { - throwNewJavaException(exceptions::gUnexpectedNativeTypeExceptionClass, ex.what()); - } -} - -static jdouble getDoubleKey(JNIEnv* env, jobject obj, jstring keyName) { - const folly::dynamic& val = getMapValue(env, obj, keyName); - if (val.isInt()) { - return val.getInt(); - } - try { - return val.getDouble(); - } catch (const folly::TypeError& ex) { - throwNewJavaException(exceptions::gUnexpectedNativeTypeExceptionClass, ex.what()); - } -} - -static jint getIntKey(JNIEnv* env, jobject obj, jstring keyName) { - try { - auto integer = getMapValue(env, obj, keyName).getInt(); - jint javaint = static_cast(integer); - if (integer != javaint) { - throwNewJavaException( - exceptions::gUnexpectedNativeTypeExceptionClass, - "Value '%lld' doesn't fit into a 32 bit signed int", integer); - } - return javaint; - } catch (const folly::TypeError& ex) { - throwNewJavaException(exceptions::gUnexpectedNativeTypeExceptionClass, ex.what()); - } -} - -static jstring getStringKey(JNIEnv* env, jobject obj, jstring keyName) { - const folly::dynamic& val = getMapValue(env, obj, keyName); - if (val.isNull()) { - return nullptr; - } - try { - LocalString value(val.getString().c_str()); - return static_cast(env->NewLocalRef(value.string())); - } catch (const folly::TypeError& ex) { - throwNewJavaException(exceptions::gUnexpectedNativeTypeExceptionClass, ex.what()); - } -} - -static jni::local_ref getArrayKey( - jni::alias_ref obj, jstring keyName) { - auto& value = getMapValue(Environment::current(), obj.get(), keyName); - if (value.isNull()) { - return jni::local_ref(nullptr); - } else { - return ReadableNativeArray::newObjectCxxArgs(value); - } -} - -static jobject getMapKey(JNIEnv* env, jobject obj, jstring keyName) { - return createReadableNativeMapWithContents(env, getMapValue(env, obj, keyName)); -} - -static jobject getValueType(JNIEnv* env, jobject obj, jstring keyName) { - return type::getType(getMapValue(env, obj, keyName).type()); -} - -} // namespace readable - -namespace iterator { - -static void initialize(JNIEnv* env, jobject obj, jobject nativeMapObj) { - auto nativeMap = extractRefPtr(env, nativeMapObj); - auto mapIterator = createNew( - nativeMap->map.items().begin(), nativeMap); - setCountableForJava(env, obj, std::move(mapIterator)); -} - -static jboolean hasNextKey(JNIEnv* env, jobject obj) { - auto nativeIterator = extractRefPtr(env, obj); - return ((nativeIterator->iterator != nativeIterator->mapRef.get()->map.items().end()) - ? JNI_TRUE : JNI_FALSE); -} - -static jstring getNextKey(JNIEnv* env, jobject obj) { - auto nativeIterator = extractRefPtr(env, obj); - if (JNI_FALSE == hasNextKey(env, obj)) { - throwNewJavaException("com/facebook/react/bridge/InvalidIteratorException", - "No such element exists"); - } - LocalString value(nativeIterator->iterator->first.c_str()); - ++nativeIterator->iterator; - return static_cast(env->NewLocalRef(value.string())); -} - -} // namespace iterator -} // namespace map - namespace { namespace runnable { @@ -758,7 +274,7 @@ static void loadScriptFromAssets(JNIEnv* env, jobject obj, jobject assetManager, static void loadScriptFromFile(JNIEnv* env, jobject obj, jstring fileName, jstring sourceURL) { jclass markerClass = env->FindClass("com/facebook/react/bridge/ReactMarker"); - auto bridge = jni::extractRefPtr(env, obj); + auto bridge = extractRefPtr(env, obj); auto fileNameStr = fileName == NULL ? "" : fromJString(env, fileName); env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromFile_start")); auto script = fileName == NULL ? "" : react::loadScriptFromFile(fileNameStr); @@ -769,7 +285,7 @@ static void loadScriptFromFile(JNIEnv* env, jobject obj, jstring fileName, jstri "sourceURL", sourceURLStr); #endif env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromFile_read")); - loadApplicationScript(bridge, script, jni::fromJString(env, sourceURL)); + loadApplicationScript(bridge, script, fromJString(env, sourceURL)); if (env->ExceptionCheck()) { return; } @@ -902,17 +418,14 @@ private: folly::dynamic m_jscConfig; }; -static void createJSCExecutor(JNIEnv *env, jobject obj, jobject jscConfig) { - auto nativeMap = extractRefPtr(env, jscConfig); - exceptions::throwIfObjectAlreadyConsumed(nativeMap, "Map to push already consumed"); - auto executor = createNew(std::move(nativeMap->map)); - nativeMap->isConsumed = true; - setCountableForJava(env, obj, std::move(executor)); +static void createJSCExecutor(alias_ref obj, WritableNativeMap* jscConfig) { + auto executor = createNew(jscConfig->consume()); + setCountableForJava(Environment::current(), obj.get(), std::move(executor)); } static void createProxyExecutor(JNIEnv *env, jobject obj, jobject executorInstance) { auto executor = - createNew(jni::make_global(jni::adopt_local(executorInstance))); + createNew(make_global(adopt_local(executorInstance))); setCountableForJava(env, obj, std::move(executor)); } @@ -943,68 +456,19 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { PerfLogging::installNativeHooks = addNativePerfLoggingHooks; JSLogging::nativeHook = nativeLoggingHook; - // get the current env - JNIEnv* env = Environment::current(); - - auto readableTypeClass = findClassLocal("com/facebook/react/bridge/ReadableType"); - type::gReadableReactType = (jclass)env->NewGlobalRef(readableTypeClass.get()); - type::initialize(env); - NativeArray::registerNatives(); ReadableNativeArray::registerNatives(); WritableNativeArray::registerNatives(); JNativeRunnable::registerNatives(); registerJSLoaderNatives(); - registerNatives("com/facebook/react/bridge/NativeMap", { - makeNativeMethod("initialize", map::initialize), - makeNativeMethod("toString", map::toString), - }); - - jclass readableMapClass = env->FindClass("com/facebook/react/bridge/ReadableNativeMap"); - gReadableNativeMapClass = (jclass)env->NewGlobalRef(readableMapClass); - gReadableNativeMapCtor = env->GetMethodID(readableMapClass, "", "()V"); - wrap_alias(readableMapClass)->registerNatives({ - makeNativeMethod("hasKey", map::readable::hasKey), - makeNativeMethod("isNull", map::readable::isNull), - makeNativeMethod("getBoolean", map::readable::getBooleanKey), - makeNativeMethod("getDouble", map::readable::getDoubleKey), - makeNativeMethod("getInt", map::readable::getIntKey), - makeNativeMethod("getString", map::readable::getStringKey), - makeNativeMethod("getArray", map::readable::getArrayKey), - makeNativeMethod( - "getMap", "(Ljava/lang/String;)Lcom/facebook/react/bridge/ReadableNativeMap;", - map::readable::getMapKey), - makeNativeMethod( - "getType", "(Ljava/lang/String;)Lcom/facebook/react/bridge/ReadableType;", - map::readable::getValueType), - }); - - registerNatives("com/facebook/react/bridge/WritableNativeMap", { - makeNativeMethod("putNull", map::writable::putNull), - makeNativeMethod("putBoolean", map::writable::putBoolean), - makeNativeMethod("putDouble", map::writable::putDouble), - makeNativeMethod("putInt", map::writable::putInt), - makeNativeMethod("putString", map::writable::putString), - makeNativeMethod("putNativeArray", map::writable::putArray), - makeNativeMethod( - "putNativeMap", "(Ljava/lang/String;Lcom/facebook/react/bridge/WritableNativeMap;)V", - map::writable::putMap), - makeNativeMethod( - "mergeNativeMap", "(Lcom/facebook/react/bridge/ReadableNativeMap;)V", - map::writable::mergeMap) - }); - - registerNatives("com/facebook/react/bridge/ReadableNativeMap$ReadableNativeMapKeySetIterator", { - makeNativeMethod("initialize", "(Lcom/facebook/react/bridge/ReadableNativeMap;)V", - map::iterator::initialize), - makeNativeMethod("hasNextKey", map::iterator::hasNextKey), - makeNativeMethod("nextKey", map::iterator::getNextKey), - }); + NativeMap::registerNatives(); + ReadableNativeMap::registerNatives(); + WritableNativeMap::registerNatives(); + ReadableNativeMapKeySetIterator::registerNatives(); registerNatives("com/facebook/react/bridge/JSCJavaScriptExecutor", { - makeNativeMethod("initialize", "(Lcom/facebook/react/bridge/WritableNativeMap;)V", - executors::createJSCExecutor), + makeNativeMethod("initialize", executors::createJSCExecutor), }); registerNatives("com/facebook/react/bridge/ProxyJavaScriptExecutor", { @@ -1013,6 +477,9 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { executors::createProxyExecutor), }); + // get the current env + JNIEnv* env = Environment::current(); + jclass callbackClass = env->FindClass("com/facebook/react/bridge/ReactCallback"); bridge::gCallbackMethod = env->GetMethodID(callbackClass, "call", "(Lcom/facebook/react/bridge/ExecutorToken;IILcom/facebook/react/bridge/ReadableNativeArray;)V"); bridge::gOnBatchCompleteMethod = env->GetMethodID(callbackClass, "onBatchComplete", "()V"); diff --git a/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.cpp b/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.cpp new file mode 100644 index 000000000..d4e9faa3f --- /dev/null +++ b/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.cpp @@ -0,0 +1,102 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "ReadableNativeArray.h" + +#include "ReadableNativeMap.h" + +using namespace facebook::jni; + +namespace facebook { +namespace react { + + +// This attribute exports the ctor symbol, so ReadableNativeArray to be +// constructed from other DSOs. +__attribute__((visibility("default"))) +ReadableNativeArray::ReadableNativeArray(folly::dynamic array) + : HybridBase(std::move(array)) {} + +void ReadableNativeArray::mapException(const std::exception& ex) { + if (dynamic_cast(&ex) != nullptr) { + throwNewJavaException(exceptions::gUnexpectedNativeTypeExceptionClass, ex.what()); + } +} + +jint ReadableNativeArray::getSize() { + return array.size(); +} + +jboolean ReadableNativeArray::isNull(jint index) { + return array.at(index).isNull() ? JNI_TRUE : JNI_FALSE; +} + +jboolean ReadableNativeArray::getBoolean(jint index) { + return array.at(index).getBool() ? JNI_TRUE : JNI_FALSE; +} + +jdouble ReadableNativeArray::getDouble(jint index) { + const folly::dynamic& val = array.at(index); + if (val.isInt()) { + return val.getInt(); + } + return val.getDouble(); +} + +jint ReadableNativeArray::getInt(jint index) { + auto integer = array.at(index).getInt(); + static_assert(std::is_same::value, + "folly::dynamic int is not int64_t"); + jint javaint = static_cast(integer); + if (integer != javaint) { + throwNewJavaException( + exceptions::gUnexpectedNativeTypeExceptionClass, + "Value '%lld' doesn't fit into a 32 bit signed int", integer); + } + return javaint; +} + +const char* ReadableNativeArray::getString(jint index) { + const folly::dynamic& dyn = array.at(index); + if (dyn.isNull()) { + return nullptr; + } + return dyn.getString().c_str(); +} + +local_ref ReadableNativeArray::getArray(jint index) { + auto& elem = array.at(index); + if (elem.isNull()) { + return local_ref(nullptr); + } else { + return ReadableNativeArray::newObjectCxxArgs(elem); + } +} + +local_ref ReadableNativeArray::getType(jint index) { + return ReadableType::getType(array.at(index).type()); +} + +// Export getMap() so we can workaround constructing ReadableNativeMap +__attribute__((visibility("default"))) +local_ref ReadableNativeArray::getMap(jint index) { + // TODO(cjhopman): ... this moves the map?!? + return ReadableNativeMap::createWithContents(std::move(array.at(index))); +} + +void ReadableNativeArray::registerNatives() { + registerHybrid({ + makeNativeMethod("size", ReadableNativeArray::getSize), + makeNativeMethod("isNull", ReadableNativeArray::isNull), + makeNativeMethod("getBoolean", ReadableNativeArray::getBoolean), + makeNativeMethod("getDouble", ReadableNativeArray::getDouble), + makeNativeMethod("getInt", ReadableNativeArray::getInt), + makeNativeMethod("getString", ReadableNativeArray::getString), + makeNativeMethod("getArray", ReadableNativeArray::getArray), + makeNativeMethod("getMap", jmethod_traits::descriptor(), + ReadableNativeArray::getMap), + makeNativeMethod("getType", ReadableNativeArray::getType), + }); +} + +} // namespace react +} // namespace facebook diff --git a/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.h b/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.h index 5407d0e8a..f8d4b5023 100644 --- a/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.h +++ b/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.h @@ -4,6 +4,9 @@ #include "NativeArray.h" +#include "NativeCommon.h" +#include "NativeMap.h" + namespace facebook { namespace react { @@ -26,8 +29,10 @@ class ReadableNativeArray : public jni::HybridClass getArray(jint index); - jobject getMap(jint index); - jobject getType(jint index); + // This actually returns a ReadableNativeMap::JavaPart, but due to + // limitations of fbjni, we can't specify that here. + jni::local_ref getMap(jint index); + jni::local_ref getType(jint index); static void registerNatives(); }; diff --git a/ReactAndroid/src/main/jni/react/jni/ReadableNativeMap.cpp b/ReactAndroid/src/main/jni/react/jni/ReadableNativeMap.cpp new file mode 100644 index 000000000..7b8d4a1fa --- /dev/null +++ b/ReactAndroid/src/main/jni/react/jni/ReadableNativeMap.cpp @@ -0,0 +1,150 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "ReadableNativeMap.h" + +using namespace facebook::jni; + +namespace facebook { +namespace react { + +namespace { +const char *gNoSuchKeyExceptionClass = "com/facebook/react/bridge/NoSuchKeyException"; +} // namespace + +void ReadableNativeMap::mapException(const std::exception& ex) { + if (dynamic_cast(&ex) != nullptr) { + throwNewJavaException(exceptions::gUnexpectedNativeTypeExceptionClass, ex.what()); + } +} + +bool ReadableNativeMap::hasKey(const std::string& key) { + return map_.find(key) != map_.items().end(); +} + +const folly::dynamic& ReadableNativeMap::getMapValue(const std::string& key) { + try { + return map_.at(key); + } catch (const std::out_of_range& ex) { + throwNewJavaException(gNoSuchKeyExceptionClass, ex.what()); + } +} + +bool ReadableNativeMap::isNull(const std::string& key) { + return getMapValue(key).isNull(); +} + +bool ReadableNativeMap::getBooleanKey(const std::string& key) { + return getMapValue(key).getBool(); +} + +double ReadableNativeMap::getDoubleKey(const std::string& key) { + const folly::dynamic& val = getMapValue(key); + if (val.isInt()) { + return val.getInt(); + } + return val.getDouble(); +} + +jint ReadableNativeMap::getIntKey(const std::string& key) { + auto integer = getMapValue(key).getInt(); + jint javaint = static_cast(integer); + if (integer != javaint) { + throwNewJavaException( + exceptions::gUnexpectedNativeTypeExceptionClass, + "Value '%lld' doesn't fit into a 32 bit signed int", integer); + } + return javaint; +} + +local_ref ReadableNativeMap::getStringKey(const std::string& key) { + const folly::dynamic& val = getMapValue(key); + if (val.isNull()) { + return local_ref(nullptr); + } + return make_jstring(val.getString().c_str()); +} + +local_ref ReadableNativeMap::getArrayKey(const std::string& key) { + auto& value = getMapValue(key); + if (value.isNull()) { + return local_ref(nullptr); + } else { + return ReadableNativeArray::newObjectCxxArgs(value); + } +} + +local_ref ReadableNativeMap::getMapKey(const std::string& key) { + auto& value = getMapValue(key); + if (value.isNull()) { + return local_ref(nullptr); + } else if (!value.isObject()) { + throwNewJavaException(exceptions::gUnexpectedNativeTypeExceptionClass, + "expected Map, got a %s", value.typeName()); + } else { + return ReadableNativeMap::newObjectCxxArgs(value); + } +} + +local_ref ReadableNativeMap::getValueType(const std::string& key) { + return ReadableType::getType(getMapValue(key).type()); +} + +local_ref ReadableNativeMap::createWithContents(folly::dynamic&& map) { + if (map.isNull()) { + return local_ref(nullptr); + } + + if (!map.isObject()) { + throwNewJavaException(exceptions::gUnexpectedNativeTypeExceptionClass, + "expected Map, got a %s", map.typeName()); + } + + return newObjectCxxArgs(std::move(map)); +} + +void ReadableNativeMap::registerNatives() { + registerHybrid({ + makeNativeMethod("hasKey", ReadableNativeMap::hasKey), + makeNativeMethod("isNull", ReadableNativeMap::isNull), + makeNativeMethod("getBoolean", ReadableNativeMap::getBooleanKey), + makeNativeMethod("getDouble", ReadableNativeMap::getDoubleKey), + makeNativeMethod("getInt", ReadableNativeMap::getIntKey), + makeNativeMethod("getString", ReadableNativeMap::getStringKey), + makeNativeMethod("getArray", ReadableNativeMap::getArrayKey), + makeNativeMethod("getMap", ReadableNativeMap::getMapKey), + makeNativeMethod("getType", ReadableNativeMap::getValueType), + }); +} + +ReadableNativeMapKeySetIterator::ReadableNativeMapKeySetIterator(const folly::dynamic& map) + : iter_(map.items().begin()) + , map_(map) {} + +local_ref ReadableNativeMapKeySetIterator::initHybrid(alias_ref, ReadableNativeMap* nativeMap) { + return makeCxxInstance(nativeMap->map_); +} + +bool ReadableNativeMapKeySetIterator::hasNextKey() { + return iter_ != map_.items().end(); +} + +local_ref ReadableNativeMapKeySetIterator::nextKey() { + if (!hasNextKey()) { + throwNewJavaException("com/facebook/react/bridge/InvalidIteratorException", + "No such element exists"); + } + auto ret = make_jstring(iter_->first.c_str()); + ++iter_; + return ret; +} + +void ReadableNativeMapKeySetIterator::registerNatives() { + registerHybrid({ + makeNativeMethod("hasNextKey", ReadableNativeMapKeySetIterator::hasNextKey), + makeNativeMethod("nextKey", ReadableNativeMapKeySetIterator::nextKey), + makeNativeMethod("initHybrid", ReadableNativeMapKeySetIterator::initHybrid), + }); +} + +} // namespace react +} // namespace facebook diff --git a/ReactAndroid/src/main/jni/react/jni/ReadableNativeMap.h b/ReactAndroid/src/main/jni/react/jni/ReadableNativeMap.h new file mode 100644 index 000000000..8741dff67 --- /dev/null +++ b/ReactAndroid/src/main/jni/react/jni/ReadableNativeMap.h @@ -0,0 +1,59 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include +#include +#include + +#include "NativeCommon.h" +#include "NativeMap.h" +#include "ReadableNativeArray.h" + +namespace facebook { +namespace react { + +struct WritableNativeMap; + +struct ReadableNativeMap : jni::HybridClass { + static auto constexpr kJavaDescriptor = "Lcom/facebook/react/bridge/ReadableNativeMap;"; + + bool hasKey(const std::string& key); + const folly::dynamic& getMapValue(const std::string& key); + bool isNull(const std::string& key); + bool getBooleanKey(const std::string& key); + double getDoubleKey(const std::string& key); + jint getIntKey(const std::string& key); + jni::local_ref getStringKey(const std::string& key); + jni::local_ref getArrayKey(const std::string& key); + jni::local_ref getMapKey(const std::string& key); + jni::local_ref getValueType(const std::string& key); + static jni::local_ref createWithContents(folly::dynamic&& map); + + static void mapException(const std::exception& ex); + + static void registerNatives(); + + using HybridBase::HybridBase; + friend HybridBase; + friend class WritableNativeMap; +}; + +struct ReadableNativeMapKeySetIterator : jni::HybridClass { + static auto constexpr kJavaDescriptor = "Lcom/facebook/react/bridge/ReadableNativeMap$ReadableNativeMapKeySetIterator;"; + + ReadableNativeMapKeySetIterator(const folly::dynamic& map); + + bool hasNextKey(); + jni::local_ref nextKey(); + + static jni::local_ref initHybrid(jni::alias_ref, ReadableNativeMap* nativeMap); + static void registerNatives(); + + folly::dynamic::const_item_iterator iter_; + // The Java side holds a strong ref to the Java ReadableNativeMap. + const folly::dynamic& map_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactAndroid/src/main/jni/react/jni/WritableNativeArray.cpp b/ReactAndroid/src/main/jni/react/jni/WritableNativeArray.cpp new file mode 100644 index 000000000..1ff8fecec --- /dev/null +++ b/ReactAndroid/src/main/jni/react/jni/WritableNativeArray.cpp @@ -0,0 +1,83 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "WritableNativeArray.h" + +#include "WritableNativeMap.h" + +using namespace facebook::jni; + +namespace facebook { +namespace react { + +WritableNativeArray::WritableNativeArray() + : HybridBase(folly::dynamic::array()) {} + +local_ref WritableNativeArray::initHybrid(alias_ref) { + return makeCxxInstance(); +} + +void WritableNativeArray::pushNull() { + exceptions::throwIfObjectAlreadyConsumed(this, "Array already consumed"); + array.push_back(nullptr); +} + +void WritableNativeArray::pushBoolean(jboolean value) { + exceptions::throwIfObjectAlreadyConsumed(this, "Array already consumed"); + array.push_back(value == JNI_TRUE); +} + +void WritableNativeArray::pushDouble(jdouble value) { + exceptions::throwIfObjectAlreadyConsumed(this, "Receiving array already consumed"); + array.push_back(value); +} + +void WritableNativeArray::pushInt(jint value) { + exceptions::throwIfObjectAlreadyConsumed(this, "Receiving array already consumed"); + array.push_back(value); +} + +void WritableNativeArray::pushString(jstring value) { + if (value == NULL) { + pushNull(); + return; + } + exceptions::throwIfObjectAlreadyConsumed(this, "Receiving array already consumed"); + array.push_back(wrap_alias(value)->toStdString()); +} + +void WritableNativeArray::pushNativeArray(WritableNativeArray* otherArray) { + if (otherArray == NULL) { + pushNull(); + return; + } + exceptions::throwIfObjectAlreadyConsumed(this, "Receiving array already consumed"); + exceptions::throwIfObjectAlreadyConsumed(otherArray, "Array to push already consumed"); + array.push_back(std::move(otherArray->array)); + otherArray->isConsumed = true; +} + +void WritableNativeArray::pushNativeMap(WritableNativeMap* map) { + if (map == NULL) { + pushNull(); + return; + } + exceptions::throwIfObjectAlreadyConsumed(this, "Receiving array already consumed"); + map->throwIfConsumed(); + array.push_back(map->consume()); +} + +void WritableNativeArray::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", WritableNativeArray::initHybrid), + makeNativeMethod("pushNull", WritableNativeArray::pushNull), + makeNativeMethod("pushBoolean", WritableNativeArray::pushBoolean), + makeNativeMethod("pushDouble", WritableNativeArray::pushDouble), + makeNativeMethod("pushInt", WritableNativeArray::pushInt), + makeNativeMethod("pushString", WritableNativeArray::pushString), + makeNativeMethod("pushNativeArray", WritableNativeArray::pushNativeArray), + makeNativeMethod("pushNativeMap", WritableNativeArray::pushNativeMap), + }); +} + +} // namespace react +} // namespace facebook diff --git a/ReactAndroid/src/main/jni/react/jni/WritableNativeArray.h b/ReactAndroid/src/main/jni/react/jni/WritableNativeArray.h new file mode 100644 index 000000000..e47769517 --- /dev/null +++ b/ReactAndroid/src/main/jni/react/jni/WritableNativeArray.h @@ -0,0 +1,35 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include +#include +#include + +#include "ReadableNativeArray.h" + +namespace facebook { +namespace react { + +struct WritableNativeMap; + +struct WritableNativeArray + : public jni::HybridClass { + static constexpr const char* kJavaDescriptor = "Lcom/facebook/react/bridge/WritableNativeArray;"; + + WritableNativeArray(); + static jni::local_ref initHybrid(jni::alias_ref); + + void pushNull(); + void pushBoolean(jboolean value); + void pushDouble(jdouble value); + void pushInt(jint value); + void pushString(jstring value); + void pushNativeArray(WritableNativeArray* otherArray); + void pushNativeMap(WritableNativeMap* map); + + static void registerNatives(); +}; + +} +} diff --git a/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.cpp b/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.cpp new file mode 100644 index 000000000..1a911b515 --- /dev/null +++ b/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.cpp @@ -0,0 +1,105 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "WritableNativeMap.h" + +using namespace facebook::jni; + +namespace facebook { +namespace react { + +WritableNativeMap::WritableNativeMap() + : HybridBase(folly::dynamic::object()) {} + +WritableNativeMap::WritableNativeMap(folly::dynamic&& val) + : HybridBase(std::move(val)) { + if (!map_.isObject()) { + throw std::runtime_error("WritableNativeMap value must be an object."); + } +} + +local_ref WritableNativeMap::initHybrid(alias_ref) { + return makeCxxInstance(); +} + +folly::dynamic WritableNativeMap::consume() { + throwIfConsumed(); + isConsumed = true; + return std::move(map_); +} + +void WritableNativeMap::putNull(std::string key) { + throwIfConsumed(); + map_.insert(std::move(key), nullptr); +} + +void WritableNativeMap::putBoolean(std::string key, bool val) { + throwIfConsumed(); + map_.insert(std::move(key), val); +} + +void WritableNativeMap::putDouble(std::string key, double val) { + throwIfConsumed(); + map_.insert(std::move(key), val); +} + +void WritableNativeMap::putInt(std::string key, int val) { + throwIfConsumed(); + map_.insert(std::move(key), val); +} + +void WritableNativeMap::putString(std::string key, alias_ref val) { + if (!val) { + putNull(std::move(key)); + return; + } + throwIfConsumed(); + map_.insert(std::move(key), val->toString()); +} + +void WritableNativeMap::putNativeArray(std::string key, alias_ref val) { + if (!val) { + putNull(std::move(key)); + return; + } + throwIfConsumed(); + auto array = val->cthis(); + exceptions::throwIfObjectAlreadyConsumed(array, "Array to put already consumed"); + map_.insert(key, std::move(array->array)); + array->isConsumed = true; +} + +void WritableNativeMap::putNativeMap(std::string key, alias_ref val) { + if (!val) { + putNull(std::move(key)); + return; + } + throwIfConsumed(); + auto other = val->cthis(); + map_.insert(std::move(key), other->consume()); +} + +void WritableNativeMap::mergeNativeMap(ReadableNativeMap* other) { + throwIfConsumed(); + other->throwIfConsumed(); + + for (auto sourceIt : other->map_.items()) { + map_[sourceIt.first] = sourceIt.second; + } +} + +void WritableNativeMap::registerNatives() { + registerHybrid({ + makeNativeMethod("putNull", WritableNativeMap::putNull), + makeNativeMethod("putBoolean", WritableNativeMap::putBoolean), + makeNativeMethod("putDouble", WritableNativeMap::putDouble), + makeNativeMethod("putInt", WritableNativeMap::putInt), + makeNativeMethod("putString", WritableNativeMap::putString), + makeNativeMethod("putNativeArray", WritableNativeMap::putNativeArray), + makeNativeMethod("putNativeMap", WritableNativeMap::putNativeMap), + makeNativeMethod("mergeNativeMap", WritableNativeMap::mergeNativeMap), + makeNativeMethod("initHybrid", WritableNativeMap::initHybrid), + }); +} + +} // namespace react +} // namespace facebook diff --git a/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.h b/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.h new file mode 100644 index 000000000..cf9cd95a0 --- /dev/null +++ b/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.h @@ -0,0 +1,40 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include +#include +#include + +#include "ReadableNativeMap.h" +#include "WritableNativeArray.h" + +namespace facebook { +namespace react { + +struct WritableNativeMap : jni::HybridClass { + static auto constexpr kJavaDescriptor = "Lcom/facebook/react/bridge/WritableNativeMap;"; + + WritableNativeMap(); + WritableNativeMap(folly::dynamic&& val); + + static jni::local_ref initHybrid(jni::alias_ref); + + folly::dynamic consume(); + + void putNull(std::string key); + void putBoolean(std::string key, bool val); + void putDouble(std::string key, double val); + void putInt(std::string key, int val); + void putString(std::string key, jni::alias_ref val); + void putNativeArray(std::string key, jni::alias_ref val); + void putNativeMap(std::string key, jni::alias_ref val); + void mergeNativeMap(ReadableNativeMap* other); + + static void registerNatives(); + + friend HybridBase; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactAndroid/src/main/jni/xreact/jni/MethodInvoker.cpp b/ReactAndroid/src/main/jni/xreact/jni/MethodInvoker.cpp index 5dcfd3e62..218f6c9d0 100644 --- a/ReactAndroid/src/main/jni/xreact/jni/MethodInvoker.cpp +++ b/ReactAndroid/src/main/jni/xreact/jni/MethodInvoker.cpp @@ -144,7 +144,7 @@ jvalue extract(std::weak_ptr& instance, ExecutorToken token, char type break; case 'M': // HACK: Workaround for constructing ReadableNativeMap - value.l = ExposedReadableNativeArray(folly::dynamic::array(arg)).getMap(0); + value.l = ExposedReadableNativeArray(folly::dynamic::array(arg)).getMap(0).release(); break; case 'X': value.l = extractCallback(instance, token, arg).release();