diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 116179370c2a1b..9f8e13c3b20704 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -1489,7 +1489,7 @@ public class com/facebook/react/bridge/ReadableNativeArray : com/facebook/react/ public fun getDouble (I)D public fun getDynamic (I)Lcom/facebook/react/bridge/Dynamic; public fun getInt (I)I - public static fun getJNIPassCounter ()I + public static final fun getJNIPassCounter ()I public fun getLong (I)J public synthetic fun getMap (I)Lcom/facebook/react/bridge/ReadableMap; public fun getMap (I)Lcom/facebook/react/bridge/ReadableNativeMap; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaMethodWrapper.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaMethodWrapper.java index daa3eaca785272..d032e99805e472 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaMethodWrapper.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaMethodWrapper.java @@ -79,6 +79,9 @@ public String extractArgument( @Override public ReadableArray extractArgument( JSInstance jsInstance, ReadableArray jsArguments, int atIndex) { + if (jsArguments.isNull(atIndex) || jsArguments.getType(atIndex) != ReadableType.Array) { + return null; + } return jsArguments.getArray(atIndex); } }; @@ -97,6 +100,9 @@ public Dynamic extractArgument( @Override public ReadableMap extractArgument( JSInstance jsInstance, ReadableArray jsArguments, int atIndex) { + if (jsArguments.isNull(atIndex) || jsArguments.getType(atIndex) != ReadableType.Map) { + return null; + } return jsArguments.getMap(atIndex); } }; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeArray.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeArray.java deleted file mode 100644 index c46dbcc6dad564..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeArray.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.bridge; - -import com.facebook.jni.HybridData; -import com.facebook.proguard.annotations.DoNotStrip; - -/** Base class for an array whose members are stored in native code (C++). */ -@DoNotStrip -public abstract class NativeArray implements NativeArrayInterface { - static { - ReactBridge.staticInit(); - } - - protected NativeArray(HybridData hybridData) { - mHybridData = hybridData; - } - - @Override - public native String toString(); - - @DoNotStrip private HybridData mHybridData; -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeArray.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeArray.kt new file mode 100644 index 00000000000000..5275bf13d8d172 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeArray.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.bridge + +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip + +/** Base class for an array whose members are stored in native code (C++). */ +@DoNotStrip +public abstract class NativeArray +protected constructor(@field:DoNotStrip private val mHybridData: HybridData?) : + NativeArrayInterface { + external override fun toString(): String + + private companion object { + init { + ReactBridge.staticInit() + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArray.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArray.kt index de59f930ad5829..8d024a81b8e052 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArray.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArray.kt @@ -36,5 +36,5 @@ public interface ReadableArray { public fun size(): Int - public fun toArrayList(): ArrayList + public fun toArrayList(): ArrayList } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.java deleted file mode 100644 index 2f74df2334cac6..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.bridge; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.facebook.infer.annotation.Assertions; -import com.facebook.jni.HybridData; -import com.facebook.proguard.annotations.DoNotStrip; -import java.util.ArrayList; -import java.util.Arrays; - -/** - * Implementation of a NativeArray that allows read-only access to its members. This will generally - * be constructed and filled in native code so you shouldn't construct one yourself. - */ -@DoNotStrip -public class ReadableNativeArray extends NativeArray implements ReadableArray { - static { - ReactBridge.staticInit(); - } - - protected ReadableNativeArray(HybridData hybridData) { - super(hybridData); - } - - // WriteOnce but not in the constructor fields - private @Nullable Object[] mLocalArray; - private @Nullable ReadableType[] mLocalTypeArray; - - private static int jniPassCounter = 0; - - public static int getJNIPassCounter() { - return jniPassCounter; - } - - private Object[] getLocalArray() { - if (mLocalArray != null) { - return mLocalArray; - } - synchronized (this) { - // Make sure no concurrent call already updated - if (mLocalArray == null) { - jniPassCounter++; - mLocalArray = Assertions.assertNotNull(importArray()); - } - } - return mLocalArray; - } - - private native Object[] importArray(); - - private ReadableType[] getLocalTypeArray() { - if (mLocalTypeArray != null) { - return mLocalTypeArray; - } - synchronized (this) { - // Make sure no concurrent call already updated - if (mLocalTypeArray == null) { - jniPassCounter++; - Object[] tempArray = Assertions.assertNotNull(importTypeArray()); - mLocalTypeArray = Arrays.copyOf(tempArray, tempArray.length, ReadableType[].class); - } - } - return mLocalTypeArray; - } - - private native Object[] importTypeArray(); - - @Override - public int size() { - return getLocalArray().length; - } - - @Override - public boolean isNull(int index) { - return getLocalArray()[index] == null; - } - - @Override - public boolean getBoolean(int index) { - return ((Boolean) getLocalArray()[index]).booleanValue(); - } - - @Override - public double getDouble(int index) { - return ((Double) getLocalArray()[index]).doubleValue(); - } - - @Override - public int getInt(int index) { - return ((Double) getLocalArray()[index]).intValue(); - } - - @Override - public long getLong(int index) { - return ((Long) getLocalArray()[index]).longValue(); - } - - @Override - public @NonNull String getString(int index) { - return (String) getLocalArray()[index]; - } - - @Override - public @NonNull ReadableNativeArray getArray(int index) { - return (ReadableNativeArray) getLocalArray()[index]; - } - - @Override - public @NonNull ReadableNativeMap getMap(int index) { - return (ReadableNativeMap) getLocalArray()[index]; - } - - @Override - public @NonNull ReadableType getType(int index) { - return getLocalTypeArray()[index]; - } - - @Override - public @NonNull Dynamic getDynamic(int index) { - return DynamicFromArray.create(this, index); - } - - @Override - public int hashCode() { - return getLocalArray().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof ReadableNativeArray)) { - return false; - } - ReadableNativeArray other = (ReadableNativeArray) obj; - return Arrays.deepEquals(getLocalArray(), other.getLocalArray()); - } - - @Override - public @NonNull ArrayList toArrayList() { - ArrayList arrayList = new ArrayList<>(); - - for (int i = 0; i < this.size(); i++) { - switch (getType(i)) { - case Null: - arrayList.add(null); - break; - case Boolean: - arrayList.add(getBoolean(i)); - break; - case Number: - arrayList.add(getDouble(i)); - break; - case String: - arrayList.add(getString(i)); - break; - case Map: - arrayList.add(getMap(i).toHashMap()); - break; - case Array: - arrayList.add(getArray(i).toArrayList()); - break; - default: - throw new IllegalArgumentException("Could not convert object at index: " + i + "."); - } - } - return arrayList; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.kt new file mode 100644 index 00000000000000..7896e2be36e535 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.bridge + +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStripAny +import java.util.Arrays + +/** + * Implementation of a NativeArray that allows read-only access to its members. This will generally + * be constructed and filled in native code so you shouldn't construct one yourself. + */ +@DoNotStripAny +public open class ReadableNativeArray protected constructor(hybridData: HybridData?) : + NativeArray(hybridData), ReadableArray { + + private val localArray: Array by + lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + jniPassCounter++ + importArray() + } + + private external fun importArray(): Array + + private val localTypeArray: Array by + lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + jniPassCounter++ + importTypeArray() + } + + private external fun importTypeArray(): Array + + override fun size(): Int = localArray.size + + override fun isNull(index: Int): Boolean = localArray[index] == null + + override fun getBoolean(index: Int): Boolean = localArray[index] as Boolean + + override fun getDouble(index: Int): Double = localArray[index] as Double + + override fun getInt(index: Int): Int = getDouble(index).toInt() + + override fun getLong(index: Int): Long = localArray[index] as Long + + override fun getString(index: Int): String = localArray[index] as String + + override fun getArray(index: Int): ReadableNativeArray = localArray[index] as ReadableNativeArray + + override fun getMap(index: Int): ReadableNativeMap = localArray[index] as ReadableNativeMap + + override fun getType(index: Int): ReadableType = localTypeArray[index] + + override fun getDynamic(index: Int): Dynamic = DynamicFromArray.create(this, index) + + override fun hashCode(): Int = localArray.hashCode() + + override fun equals(other: Any?): Boolean = + if (other !is ReadableNativeArray) false else Arrays.deepEquals(localArray, other.localArray) + + override fun toArrayList(): ArrayList { + val arrayList = ArrayList() + for (i in 0 until size()) { + when (getType(i)) { + ReadableType.Null -> arrayList.add(null) + ReadableType.Boolean -> arrayList.add(getBoolean(i)) + ReadableType.Number -> arrayList.add(getDouble(i)) + ReadableType.String -> arrayList.add(getString(i)) + ReadableType.Map -> arrayList.add(getMap(i).toHashMap()) + ReadableType.Array -> arrayList.add(getArray(i).toArrayList()) + else -> throw IllegalArgumentException("Could not convert object at index: $i.") + } + } + return arrayList + } + + private companion object { + init { + ReactBridge.staticInit() + } + + private var jniPassCounter: Int = 0 + + @JvmStatic public fun getJNIPassCounter(): Int = jniPassCounter + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeArray.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeArray.java deleted file mode 100644 index 06ad5650e11c45..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeArray.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.bridge; - -import androidx.annotation.Nullable; -import com.facebook.infer.annotation.Assertions; -import com.facebook.jni.HybridData; -import com.facebook.proguard.annotations.DoNotStrip; - -/** - * Implementation of a write-only array stored in native memory. Use {@link Arguments#createArray()} - * if you need to stub out creating this class in a test. TODO(5815532): Check if consumed on read - */ -@DoNotStrip -public class WritableNativeArray extends ReadableNativeArray implements WritableArray { - static { - ReactBridge.staticInit(); - } - - public WritableNativeArray() { - super(initHybrid()); - } - - @Override - public native void pushNull(); - - @Override - public native void pushBoolean(boolean value); - - @Override - public native void pushDouble(double value); - - @Override - public native void pushInt(int value); - - @Override - public native void pushLong(long value); - - @Override - public native void pushString(@Nullable String value); - - // Note: this consumes the map so do not reuse it. - @Override - public void pushArray(@Nullable ReadableArray array) { - Assertions.assertCondition( - array == null || array instanceof ReadableNativeArray, "Illegal type provided"); - pushNativeArray((ReadableNativeArray) array); - } - - // Note: this consumes the map so do not reuse it. - @Override - public void pushMap(@Nullable ReadableMap map) { - Assertions.assertCondition( - map == null || map instanceof ReadableNativeMap, "Illegal type provided"); - pushNativeMap((ReadableNativeMap) map); - } - - private static native HybridData initHybrid(); - - private native void pushNativeArray(ReadableNativeArray array); - - private native void pushNativeMap(ReadableNativeMap map); -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeArray.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeArray.kt new file mode 100644 index 00000000000000..7cc0c6aa83b0e5 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeArray.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.bridge + +import com.facebook.infer.annotation.Assertions +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStripAny + +/** + * Implementation of a write-only array stored in native memory. Use [Arguments.createArray] if you + * need to stub out creating this class in a test. TODO(5815532): Check if consumed on read + */ +@DoNotStripAny +public open class WritableNativeArray : ReadableNativeArray(initHybrid()), WritableArray { + external override fun pushNull() + + external override fun pushBoolean(value: Boolean) + + external override fun pushDouble(value: Double) + + external override fun pushInt(value: Int) + + external override fun pushLong(value: Long) + + external override fun pushString(value: String?) + + // Note: this consumes the array so do not reuse it. + override fun pushArray(array: ReadableArray?) { + Assertions.assertCondition( + array == null || array is ReadableNativeArray, "Illegal type provided") + pushNativeArray(array as ReadableNativeArray?) + } + + // Note: this consumes the map so do not reuse it. + override fun pushMap(map: ReadableMap?) { + Assertions.assertCondition(map == null || map is ReadableNativeMap, "Illegal type provided") + pushNativeMap(map as ReadableNativeMap?) + } + + private external fun pushNativeArray(array: ReadableNativeArray?) + + private external fun pushNativeMap(map: ReadableNativeMap?) + + private companion object { + init { + ReactBridge.staticInit() + } + + @JvmStatic private external fun initHybrid(): HybridData? + } +} diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp index 1f4b7c1d495e48..c5341a2d61f696 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp @@ -22,6 +22,7 @@ #endif #include "CatalystInstanceImpl.h" +#include "NativeMap.h" #include "ReadableNativeArray.h" using facebook::xplat::module::CxxModule; diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.cpp index 9add683e2a4443..dc7e8dc154c8f3 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.cpp @@ -6,7 +6,6 @@ */ #include "ReadableNativeArray.h" - #include "ReadableNativeMap.h" using namespace facebook::jni; @@ -31,9 +30,10 @@ local_ref> ReadableNativeArray::importArray() { return jarray; } -local_ref> ReadableNativeArray::importTypeArray() { +local_ref> +ReadableNativeArray::importTypeArray() { auto size = static_cast(array_.size()); - auto jarray = JArrayClass::newArray(size); + auto jarray = JArrayClass::newArray(size); for (jint ii = 0; ii < size; ii++) { (*jarray)[ii] = ReadableType::getType(array_.at(ii).type()); } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.h index 02d9445192e29e..107df4bca090bf 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.h @@ -8,9 +8,7 @@ #pragma once #include "NativeArray.h" - #include "NativeCommon.h" -#include "NativeMap.h" namespace facebook::react { @@ -36,7 +34,7 @@ class ReadableNativeArray static void registerNatives(); jni::local_ref> importArray(); - jni::local_ref> importTypeArray(); + jni::local_ref> importTypeArray(); }; } // namespace facebook::react