From 82fcff1a7d8ecac2e3673400d7ea6ca2c468aa77 Mon Sep 17 00:00:00 2001 From: Ruslan Shestopalyuk Date: Thu, 16 May 2024 05:32:45 -0700 Subject: [PATCH] Migrate *NativeMap classes to Kotlin (#44581) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/44581 # Changelog: [Internal] - This converts the vertical of NativeArray/ReadableNativeArray/WritableNativeArray classes to Kotlin. Reviewed By: javache Differential Revision: D57329244 --- .../ReactAndroid/api/ReactAndroid.api | 18 +- .../com/facebook/react/bridge/NativeMap.java | 28 -- .../com/facebook/react/bridge/NativeMap.kt | 23 ++ .../react/bridge/ReadableNativeMap.java | 298 ------------------ .../react/bridge/ReadableNativeMap.kt | 195 ++++++++++++ .../react/bridge/WritableNativeMap.java | 84 ----- .../react/bridge/WritableNativeMap.kt | 69 ++++ 7 files changed, 303 insertions(+), 412 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeMap.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeMap.kt delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.kt delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.kt diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 340a04b69ec299..0e01ff68d478f1 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -59,6 +59,7 @@ public abstract class com/facebook/react/HeadlessJsTaskService : android/app/Ser public final class com/facebook/react/JSEngineResolutionAlgorithm : java/lang/Enum { public static final field HERMES Lcom/facebook/react/JSEngineResolutionAlgorithm; public static final field JSC Lcom/facebook/react/JSEngineResolutionAlgorithm; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/JSEngineResolutionAlgorithm; public static fun values ()[Lcom/facebook/react/JSEngineResolutionAlgorithm; } @@ -926,6 +927,7 @@ public final class com/facebook/react/bridge/MemoryPressure : java/lang/Enum { public static final field CRITICAL Lcom/facebook/react/bridge/MemoryPressure; public static final field MODERATE Lcom/facebook/react/bridge/MemoryPressure; public static final field UI_HIDDEN Lcom/facebook/react/bridge/MemoryPressure; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/bridge/MemoryPressure; public static fun values ()[Lcom/facebook/react/bridge/MemoryPressure; } @@ -1411,7 +1413,7 @@ public class com/facebook/react/bridge/ReadableNativeMap : com/facebook/react/br public fun getDynamic (Ljava/lang/String;)Lcom/facebook/react/bridge/Dynamic; public fun getEntryIterator ()Ljava/util/Iterator; public fun getInt (Ljava/lang/String;)I - public static fun getJNIPassCounter ()I + public static final fun getJNIPassCounter ()I public fun getLong (Ljava/lang/String;)J public synthetic fun getMap (Ljava/lang/String;)Lcom/facebook/react/bridge/ReadableMap; public fun getMap (Ljava/lang/String;)Lcom/facebook/react/bridge/ReadableNativeMap; @@ -1431,6 +1433,7 @@ public final class com/facebook/react/bridge/ReadableType : java/lang/Enum { public static final field Null Lcom/facebook/react/bridge/ReadableType; public static final field Number Lcom/facebook/react/bridge/ReadableType; public static final field String Lcom/facebook/react/bridge/ReadableType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/bridge/ReadableType; public static fun values ()[Lcom/facebook/react/bridge/ReadableType; } @@ -1539,7 +1542,7 @@ public class com/facebook/react/bridge/WritableNativeArray : com/facebook/react/ public fun pushString (Ljava/lang/String;)V } -public class com/facebook/react/bridge/WritableNativeMap : com/facebook/react/bridge/ReadableNativeMap, com/facebook/react/bridge/WritableMap { +public final class com/facebook/react/bridge/WritableNativeMap : com/facebook/react/bridge/ReadableNativeMap, com/facebook/react/bridge/WritableMap { public fun ()V public fun copy ()Lcom/facebook/react/bridge/WritableMap; public fun merge (Lcom/facebook/react/bridge/ReadableMap;)V @@ -1685,6 +1688,7 @@ public final class com/facebook/react/common/LifecycleState : java/lang/Enum { public static final field BEFORE_CREATE Lcom/facebook/react/common/LifecycleState; public static final field BEFORE_RESUME Lcom/facebook/react/common/LifecycleState; public static final field RESUMED Lcom/facebook/react/common/LifecycleState; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/common/LifecycleState; public static fun values ()[Lcom/facebook/react/common/LifecycleState; } @@ -1853,6 +1857,7 @@ public final class com/facebook/react/common/mapbuffer/MapBuffer$DataType : java public static final field LONG Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static final field MAP Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static final field STRING Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static fun values ()[Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; } @@ -2426,6 +2431,7 @@ public final class com/facebook/react/devsupport/interfaces/ErrorType : java/lan public static final field JS Lcom/facebook/react/devsupport/interfaces/ErrorType; public static final field NATIVE Lcom/facebook/react/devsupport/interfaces/ErrorType; public final fun getDisplayName ()Ljava/lang/String; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public fun toString ()Ljava/lang/String; public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/devsupport/interfaces/ErrorType; public static fun values ()[Lcom/facebook/react/devsupport/interfaces/ErrorType; @@ -4211,6 +4217,7 @@ public final class com/facebook/react/uimanager/LengthPercentage$Companion { public final class com/facebook/react/uimanager/LengthPercentageType : java/lang/Enum { public static final field PERCENT Lcom/facebook/react/uimanager/LengthPercentageType; public static final field POINT Lcom/facebook/react/uimanager/LengthPercentageType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/uimanager/LengthPercentageType; public static fun values ()[Lcom/facebook/react/uimanager/LengthPercentageType; } @@ -4264,6 +4271,7 @@ public final class com/facebook/react/uimanager/NativeKind : java/lang/Enum { public static final field LEAF Lcom/facebook/react/uimanager/NativeKind; public static final field NONE Lcom/facebook/react/uimanager/NativeKind; public static final field PARENT Lcom/facebook/react/uimanager/NativeKind; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/uimanager/NativeKind; public static fun values ()[Lcom/facebook/react/uimanager/NativeKind; } @@ -4340,6 +4348,7 @@ public final class com/facebook/react/uimanager/PointerEvents : java/lang/Enum { public static final field NONE Lcom/facebook/react/uimanager/PointerEvents; public static final fun canBeTouchTarget (Lcom/facebook/react/uimanager/PointerEvents;)Z public static final fun canChildrenBeTouchTarget (Lcom/facebook/react/uimanager/PointerEvents;)Z + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static final fun parsePointerEvents (Ljava/lang/String;)Lcom/facebook/react/uimanager/PointerEvents; public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/uimanager/PointerEvents; public static fun values ()[Lcom/facebook/react/uimanager/PointerEvents; @@ -5788,6 +5797,7 @@ public final class com/facebook/react/uimanager/events/TouchEventType : java/lan public static final field END Lcom/facebook/react/uimanager/events/TouchEventType; public static final field MOVE Lcom/facebook/react/uimanager/events/TouchEventType; public static final field START Lcom/facebook/react/uimanager/events/TouchEventType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static final fun getJSEventName (Lcom/facebook/react/uimanager/events/TouchEventType;)Ljava/lang/String; public final fun getJsName ()Ljava/lang/String; public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/uimanager/events/TouchEventType; @@ -5811,6 +5821,7 @@ public final class com/facebook/react/uimanager/layoutanimation/InterpolatorType public static final field LINEAR Lcom/facebook/react/uimanager/layoutanimation/InterpolatorType; public static final field SPRING Lcom/facebook/react/uimanager/layoutanimation/InterpolatorType; public static final fun fromString (Ljava/lang/String;)Lcom/facebook/react/uimanager/layoutanimation/InterpolatorType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/uimanager/layoutanimation/InterpolatorType; public static fun values ()[Lcom/facebook/react/uimanager/layoutanimation/InterpolatorType; } @@ -5846,6 +5857,7 @@ public final class com/facebook/react/uimanager/style/BorderRadiusProp : java/la public static final field BORDER_TOP_LEFT_RADIUS Lcom/facebook/react/uimanager/style/BorderRadiusProp; public static final field BORDER_TOP_RIGHT_RADIUS Lcom/facebook/react/uimanager/style/BorderRadiusProp; public static final field BORDER_TOP_START_RADIUS Lcom/facebook/react/uimanager/style/BorderRadiusProp; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/uimanager/style/BorderRadiusProp; public static fun values ()[Lcom/facebook/react/uimanager/style/BorderRadiusProp; } @@ -6309,6 +6321,7 @@ public final class com/facebook/react/views/image/ImageResizeMethod : java/lang/ public static final field AUTO Lcom/facebook/react/views/image/ImageResizeMethod; public static final field RESIZE Lcom/facebook/react/views/image/ImageResizeMethod; public static final field SCALE Lcom/facebook/react/views/image/ImageResizeMethod; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/views/image/ImageResizeMethod; public static fun values ()[Lcom/facebook/react/views/image/ImageResizeMethod; } @@ -7037,6 +7050,7 @@ public final class com/facebook/react/views/scroll/ScrollEventType : java/lang/E public static final field MOMENTUM_BEGIN Lcom/facebook/react/views/scroll/ScrollEventType; public static final field MOMENTUM_END Lcom/facebook/react/views/scroll/ScrollEventType; public static final field SCROLL Lcom/facebook/react/views/scroll/ScrollEventType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static final fun getJSEventName (Lcom/facebook/react/views/scroll/ScrollEventType;)Ljava/lang/String; public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/views/scroll/ScrollEventType; public static fun values ()[Lcom/facebook/react/views/scroll/ScrollEventType; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeMap.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeMap.java deleted file mode 100644 index fdf9504f37de50..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeMap.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 a Map whose keys and values are stored in native code (C++). */ -@DoNotStrip -public abstract class NativeMap { - static { - ReactBridge.staticInit(); - } - - public NativeMap(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/NativeMap.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeMap.kt new file mode 100644 index 00000000000000..0400ed715dcd03 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeMap.kt @@ -0,0 +1,23 @@ +/* + * 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 a Map whose keys and values are stored in native code (C++). */ +@DoNotStrip +public abstract class NativeMap(@field:DoNotStrip private val mHybridData: HybridData?) { + 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/ReadableNativeMap.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java deleted file mode 100644 index 7e796f7cdaf7f5..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java +++ /dev/null @@ -1,298 +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.HashMap; -import java.util.Iterator; -import java.util.Map; - -/** - * 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. - */ -@DoNotStrip -public class ReadableNativeMap extends NativeMap implements ReadableMap { - static { - ReactBridge.staticInit(); - } - - protected ReadableNativeMap(HybridData hybridData) { - super(hybridData); - } - - private @Nullable String[] mKeys; - private @Nullable HashMap mLocalMap; - private @Nullable HashMap mLocalTypeMap; - private static int mJniCallCounter; - - public static int getJNIPassCounter() { - return mJniCallCounter; - } - - private void ensureKeysAreImported() { - if (mKeys != null) { - return; - } - synchronized (this) { - mKeys = Assertions.assertNotNull(importKeys()); - mJniCallCounter++; - } - } - - private HashMap getLocalMap() { - if (mLocalMap != null) { - return mLocalMap; - } - synchronized (this) { - ensureKeysAreImported(); - if (mLocalMap == null) { - Object[] values = Assertions.assertNotNull(importValues()); - mJniCallCounter++; - int length = mKeys.length; - mLocalMap = new HashMap<>(length); - for (int i = 0; i < length; i++) { - mLocalMap.put(mKeys[i], values[i]); - } - } - } - return mLocalMap; - } - - private native String[] importKeys(); - - private native Object[] importValues(); - - private @NonNull HashMap getLocalTypeMap() { - if (mLocalTypeMap != null) { - return mLocalTypeMap; - } - synchronized (this) { - ensureKeysAreImported(); - if (mLocalTypeMap == null) { - Object[] types = Assertions.assertNotNull(importTypes()); - mJniCallCounter++; - int length = mKeys.length; - mLocalTypeMap = new HashMap<>(length); - for (int i = 0; i < length; i++) { - mLocalTypeMap.put(mKeys[i], (ReadableType) types[i]); - } - } - } - return mLocalTypeMap; - } - - private native Object[] importTypes(); - - @Override - public boolean hasKey(@NonNull String name) { - return getLocalMap().containsKey(name); - } - - @Override - public boolean isNull(@NonNull String name) { - if (getLocalMap().containsKey(name)) { - return getLocalMap().get(name) == null; - } - throw new NoSuchKeyException(name); - } - - private @NonNull Object getValue(@NonNull String name) { - if (hasKey(name) && !(isNull(name))) { - return Assertions.assertNotNull(getLocalMap().get(name)); - } - throw new NoSuchKeyException(name); - } - - private T getValue(String name, Class type) { - Object value = getValue(name); - checkInstance(name, value, type); - return (T) value; - } - - private @Nullable Object getNullableValue(String name) { - if (hasKey(name)) { - return getLocalMap().get(name); - } - return null; - } - - private @Nullable T getNullableValue(String name, Class type) { - Object value = getNullableValue(name); - checkInstance(name, value, type); - return (T) value; - } - - private void checkInstance(String name, Object value, Class type) { - if (value != null && !type.isInstance(value)) { - throw new UnexpectedNativeTypeException( - "Value for " - + name - + " cannot be cast from " - + value.getClass().getSimpleName() - + " to " - + type.getSimpleName()); - } - } - - @Override - public boolean getBoolean(@NonNull String name) { - return getValue(name, Boolean.class).booleanValue(); - } - - @Override - public double getDouble(@NonNull String name) { - return getValue(name, Double.class).doubleValue(); - } - - @Override - public int getInt(@NonNull String name) { - // All numbers coming out of native are doubles, so cast here then truncate - return getValue(name, Double.class).intValue(); - } - - @Override - public long getLong(@NonNull String name) { - return getValue(name, Long.class).longValue(); - } - - @Override - public @Nullable String getString(@NonNull String name) { - return getNullableValue(name, String.class); - } - - @Override - public @Nullable ReadableArray getArray(@NonNull String name) { - return getNullableValue(name, ReadableArray.class); - } - - @Override - public @Nullable ReadableNativeMap getMap(@NonNull String name) { - return getNullableValue(name, ReadableNativeMap.class); - } - - @Override - public @NonNull ReadableType getType(@NonNull String name) { - if (getLocalTypeMap().containsKey(name)) { - return Assertions.assertNotNull(getLocalTypeMap().get(name)); - } - throw new NoSuchKeyException(name); - } - - @Override - public @NonNull Dynamic getDynamic(@NonNull String name) { - return DynamicFromMap.create(this, name); - } - - @Override - public @NonNull Iterator> getEntryIterator() { - synchronized (this) { - ensureKeysAreImported(); - - final String[] iteratorKeys = mKeys; - final Object[] iteratorValues = Assertions.assertNotNull(importValues()); - mJniCallCounter++; - - return new Iterator>() { - int currentIndex = 0; - - @Override - public boolean hasNext() { - return currentIndex < iteratorKeys.length; - } - - @Override - public Map.Entry next() { - final int index = currentIndex++; - return new Map.Entry() { - @Override - public String getKey() { - return iteratorKeys[index]; - } - - @Override - public Object getValue() { - return iteratorValues[index]; - } - - @Override - public Object setValue(Object value) { - throw new UnsupportedOperationException( - "Can't set a value while iterating over a ReadableNativeMap"); - } - }; - } - }; - } - } - - @Override - public @NonNull ReadableMapKeySetIterator keySetIterator() { - ensureKeysAreImported(); - final String[] iteratorKeys = mKeys; - return new ReadableMapKeySetIterator() { - int currentIndex = 0; - - @Override - public boolean hasNextKey() { - return currentIndex < iteratorKeys.length; - } - - @Override - public String nextKey() { - return iteratorKeys[currentIndex++]; - } - }; - } - - @Override - public int hashCode() { - return getLocalMap().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof ReadableNativeMap)) { - return false; - } - ReadableNativeMap other = (ReadableNativeMap) obj; - return getLocalMap().equals(other.getLocalMap()); - } - - @Override - public @NonNull HashMap toHashMap() { - // we can almost just return getLocalMap(), but we need to convert nested arrays and maps to the - // correct types first - HashMap hashMap = new HashMap<>(getLocalMap()); - Iterator iterator = hashMap.keySet().iterator(); - - while (iterator.hasNext()) { - String key = (String) iterator.next(); - switch (getType(key)) { - case Null: - case Boolean: - case Number: - case String: - break; - case Map: - hashMap.put(key, Assertions.assertNotNull(getMap(key)).toHashMap()); - break; - case Array: - hashMap.put(key, Assertions.assertNotNull(getArray(key)).toArrayList()); - break; - default: - throw new IllegalArgumentException("Could not convert object with key: " + key + "."); - } - } - return hashMap; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.kt new file mode 100644 index 00000000000000..6c1b1619e8ca7d --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.kt @@ -0,0 +1,195 @@ +/* + * 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 android.annotation.SuppressLint +import com.facebook.infer.annotation.Assertions +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStripAny + +/** + * 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. + */ +@DoNotStripAny +public open class ReadableNativeMap protected constructor(hybridData: HybridData?) : + NativeMap(hybridData), ReadableMap { + private val keys: Array by + lazy(LazyThreadSafetyMode.SYNCHRONIZED) { importKeys().also { jniPassCounter++ } } + + private val localMap: HashMap by + lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + val length = keys.size + val res = HashMap(length) + val values = importValues() + for (i in 0 until length) { + res[keys[i]] = values[i] + } + jniPassCounter++ + res + } + + private val localTypeMap: HashMap by + lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + val length = keys.size + val res = HashMap(length) + val types = importTypes() + for (i in 0 until length) { + res[keys[i]] = types[i] as ReadableType + } + jniPassCounter++ + res + } + + private external fun importKeys(): Array + + private external fun importValues(): Array + + private external fun importTypes(): Array + + override fun hasKey(name: String): Boolean = localMap.containsKey(name) + + override fun isNull(name: String): Boolean { + if (localMap.containsKey(name)) { + return localMap[name] == null + } + throw NoSuchKeyException(name) + } + + @SuppressLint("ReflectionMethodUse") + private inline fun checkInstance(name: String, instance: Any?, type: Class): T = + instance as? T + ?: throw UnexpectedNativeTypeException( + "Value for $name cannot be cast from ${instance?.javaClass?.simpleName ?: "NULL"} to ${type.simpleName}") + + private fun getValue(name: String): Any { + if (hasKey(name)) { + return Assertions.assertNotNull(localMap[name]) + } + throw NoSuchKeyException(name) + } + + private inline fun getValue(name: String, type: Class): T = + checkInstance(name, getValue(name), type) + + private fun getNullableValue(name: String): Any? = localMap.get(name) + + private inline fun getNullableValue(name: String, type: Class): T? { + val res = getNullableValue(name) + if (res == null) { + return null + } else { + return checkInstance(name, res, type) + } + } + + override fun getBoolean(name: String): Boolean = getValue(name, Boolean::class.java) + + override fun getDouble(name: String): Double = getValue(name, Double::class.java) + + // All numbers coming out of native are doubles, so cast here then truncate + override fun getInt(name: String): Int = getValue(name, Double::class.java).toInt() + + override fun getLong(name: String): Long = getValue(name, Long::class.java) + + override fun getString(name: String): String? = getNullableValue(name, String::class.java) + + override fun getArray(name: String): ReadableArray? = + getNullableValue(name, ReadableArray::class.java) + + override fun getMap(name: String): ReadableNativeMap? = + getNullableValue(name, ReadableNativeMap::class.java) + + override fun getType(name: String): ReadableType { + if (localTypeMap.containsKey(name)) { + return Assertions.assertNotNull(localTypeMap[name]) + } + throw NoSuchKeyException(name) + } + + override fun getDynamic(name: String): Dynamic = DynamicFromMap.create(this, name) + + override fun getEntryIterator(): Iterator> { + synchronized(this) { + val iteratorKeys = keys + val iteratorValues = importValues() + jniPassCounter++ + return object : Iterator> { + var currentIndex = 0 + + override fun hasNext(): Boolean { + return currentIndex < iteratorKeys.size + } + + override fun next(): Map.Entry { + val index = currentIndex++ + return object : MutableMap.MutableEntry { + override val key: String + get() = iteratorKeys[index] + + override val value: Any + get() = iteratorValues[index] + + override fun setValue(newValue: Any): Any { + throw UnsupportedOperationException( + "Can't set a value while iterating over a ReadableNativeMap") + } + } + } + } + } + } + + override fun keySetIterator(): ReadableMapKeySetIterator { + val iteratorKeys = keys + return object : ReadableMapKeySetIterator { + var currentIndex = 0 + + override fun hasNextKey(): Boolean = currentIndex < iteratorKeys.size + + override fun nextKey(): String = iteratorKeys[currentIndex++] + } + } + + override fun hashCode(): Int = localMap.hashCode() + + override fun equals(other: Any?): Boolean = + if (other !is ReadableNativeMap) { + false + } else localMap == other.localMap + + override fun toHashMap(): HashMap { + // we can almost just return getLocalMap(), but we need to convert nested arrays and maps to the + // correct types first + val hashMap = HashMap(localMap) + val iterator: Iterator<*> = hashMap.keys.iterator() + while (iterator.hasNext()) { + val key = iterator.next() as String + when (getType(key)) { + ReadableType.Null, + ReadableType.Boolean, + ReadableType.Number, + ReadableType.String -> {} + ReadableType.Map -> hashMap[key] = Assertions.assertNotNull(getMap(key)).toHashMap() + ReadableType.Array -> hashMap[key] = Assertions.assertNotNull(getArray(key)).toArrayList() + else -> throw IllegalArgumentException("Could not convert object with key: $key.") + } + } + return hashMap + } + + 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/WritableNativeMap.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.java deleted file mode 100644 index 228b3065de7a39..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.java +++ /dev/null @@ -1,84 +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; - -/** - * Implementation of a write-only map stored in native memory. Use {@link Arguments#createMap()} if - * you need to stub out creating this class in a test. TODO(5815532): Check if consumed on read - */ -@DoNotStrip -public class WritableNativeMap extends ReadableNativeMap implements WritableMap { - static { - ReactBridge.staticInit(); - } - - @Override - public native void putBoolean(@NonNull String key, boolean value); - - @Override - public native void putDouble(@NonNull String key, double value); - - @Override - public native void putInt(@NonNull String key, int value); - - @Override - public native void putLong(@NonNull String key, long value); - - @Override - public native void putNull(@NonNull String key); - - @Override - public native void putString(@NonNull String key, @Nullable String value); - - @Override - public void putMap(@NonNull String key, @Nullable ReadableMap value) { - Assertions.assertCondition( - value == null || value instanceof ReadableNativeMap, "Illegal type provided"); - putNativeMap(key, (ReadableNativeMap) value); - } - - // Note: this consumes the map so do not reuse it. - @Override - public void putArray(@NonNull String key, @Nullable ReadableArray value) { - Assertions.assertCondition( - value == null || value instanceof ReadableNativeArray, "Illegal type provided"); - putNativeArray(key, (ReadableNativeArray) value); - } - - // Note: this **DOES NOT** consume the source map - @Override - public void merge(@NonNull ReadableMap source) { - Assertions.assertCondition(source instanceof ReadableNativeMap, "Illegal type provided"); - mergeNativeMap((ReadableNativeMap) source); - } - - @Override - public WritableMap copy() { - final WritableNativeMap target = new WritableNativeMap(); - target.merge(this); - return target; - } - - public WritableNativeMap() { - super(initHybrid()); - } - - private static native HybridData initHybrid(); - - private native void putNativeMap(String key, ReadableNativeMap value); - - private native void putNativeArray(String key, ReadableNativeArray value); - - private native void mergeNativeMap(ReadableNativeMap source); -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.kt new file mode 100644 index 00000000000000..af90cae5eba8c1 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.kt @@ -0,0 +1,69 @@ +/* + * 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.DoNotStrip + +/** + * Implementation of a write-only map stored in native memory. Use [Arguments.createMap] if you need + * to stub out creating this class in a test. TODO(5815532): Check if consumed on read + */ +@DoNotStrip +public class WritableNativeMap : ReadableNativeMap(initHybrid()), WritableMap { + external override fun putBoolean(key: String, value: Boolean) + + external override fun putDouble(key: String, value: Double) + + external override fun putInt(key: String, value: Int) + + external override fun putLong(key: String, value: Long) + + external override fun putNull(key: String) + + external override fun putString(key: String, value: String?) + + override fun putMap(key: String, value: ReadableMap?) { + Assertions.assertCondition(value == null || value is ReadableNativeMap, "Illegal type provided") + putNativeMap(key, value as ReadableNativeMap?) + } + + // Note: this consumes the map so do not reuse it. + override fun putArray(key: String, value: ReadableArray?) { + Assertions.assertCondition( + value == null || value is ReadableNativeArray, "Illegal type provided") + putNativeArray(key, value as ReadableNativeArray?) + } + + // Note: this **DOES NOT** consume the source map + override fun merge(source: ReadableMap) { + Assertions.assertCondition(source is ReadableNativeMap, "Illegal type provided") + mergeNativeMap(source as ReadableNativeMap) + } + + override fun copy(): WritableMap { + val target = WritableNativeMap() + target.merge(this) + return target + } + + private external fun putNativeMap(key: String, value: ReadableNativeMap?) + + private external fun putNativeArray(key: String, value: ReadableNativeArray?) + + private external fun mergeNativeMap(source: ReadableNativeMap) + + private companion object { + init { + ReactBridge.staticInit() + } + + @JvmStatic private external fun initHybrid(): HybridData? + } +}