From a86e39480299a74c4e47bc64c714d32926cb680f Mon Sep 17 00:00:00 2001 From: artus9033 Date: Wed, 9 Apr 2025 21:57:29 +0200 Subject: [PATCH 1/4] refactor: migrate DynamicFromMap to Kotlin --- .../facebook/react/bridge/DynamicFromMap.java | 119 ------------------ .../facebook/react/bridge/DynamicFromMap.kt | 83 ++++++++++++ 2 files changed, 83 insertions(+), 119 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.java deleted file mode 100644 index d2a166a8eb6e27..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.java +++ /dev/null @@ -1,119 +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 androidx.core.util.Pools.SimplePool; -import com.facebook.infer.annotation.Nullsafe; -import java.util.Objects; - -/** Implementation of Dynamic wrapping a ReadableMap. */ -@Nullsafe(Nullsafe.Mode.LOCAL) -class DynamicFromMap implements Dynamic { - private static final ThreadLocal> sPool = - new ThreadLocal>() { - @Override - protected SimplePool initialValue() { - return new SimplePool<>(10); - } - }; - - private @Nullable ReadableMap mMap; - private @Nullable String mName; - - // This is a pools object. Hide the constructor. - private DynamicFromMap() {} - - public static DynamicFromMap create(ReadableMap map, String name) { - SimplePool poolValueHolder = sPool.get(); - DynamicFromMap dynamic = null; - if (poolValueHolder != null) { - dynamic = poolValueHolder.acquire(); - } - if (dynamic == null) { - dynamic = new DynamicFromMap(); - } - dynamic.mMap = map; - dynamic.mName = name; - return dynamic; - } - - @Override - public void recycle() { - mMap = null; - mName = null; - SimplePool poolValueHolder = sPool.get(); - if (poolValueHolder != null) { - poolValueHolder.release(this); - } - } - - @Override - public boolean isNull() { - if (mMap == null || mName == null) { - throw new IllegalStateException("This dynamic value has been recycled"); - } - return mMap.isNull(mName); - } - - @Override - public boolean asBoolean() { - if (mMap == null || mName == null) { - throw new IllegalStateException("This dynamic value has been recycled"); - } - return mMap.getBoolean(mName); - } - - @Override - public double asDouble() { - if (mMap == null || mName == null) { - throw new IllegalStateException("This dynamic value has been recycled"); - } - return mMap.getDouble(mName); - } - - @Override - public int asInt() { - if (mMap == null || mName == null) { - throw new IllegalStateException("This dynamic value has been recycled"); - } - return mMap.getInt(mName); - } - - @Override - public @Nullable String asString() { - if (mMap == null || mName == null) { - throw new IllegalStateException("This dynamic value has been recycled"); - } - return mMap.getString(mName); - } - - @Override - public @Nullable ReadableArray asArray() { - if (mMap == null || mName == null) { - throw new IllegalStateException("This dynamic value has been recycled"); - } - return mMap.getArray(mName); - } - - @Override - public ReadableMap asMap() { - if (mMap == null || mName == null) { - throw new IllegalStateException("This dynamic value has been recycled"); - } - return Objects.requireNonNull(mMap.getMap(mName)); - } - - @Override - public ReadableType getType() { - if (mMap == null || mName == null) { - throw new IllegalStateException("This dynamic value has been recycled"); - } - return mMap.getType(mName); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.kt new file mode 100644 index 00000000000000..4581bd1c80b7e6 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.kt @@ -0,0 +1,83 @@ +/* + * 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.core.util.Pools.SimplePool + +/** Implementation of Dynamic wrapping a ReadableMap. */ +internal class DynamicFromMap +// This is a pools object. Hide the constructor. +private constructor() : Dynamic { + private var mMap: ReadableMap? = null + private var mName: String? = null + + override fun recycle() { + mMap = null + mName = null + sPool.get()?.release(this) + } + + override val isNull: Boolean + get() { + check(!(mMap == null || mName == null)) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } + return mMap!!.isNull(mName!!) + } + + override fun asBoolean(): Boolean { + check(!(mMap == null || mName == null)) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } + return mMap!!.getBoolean(mName!!) + } + + override fun asDouble(): Double { + check(!(mMap == null || mName == null)) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } + return mMap!!.getDouble(mName!!) + } + + override fun asInt(): Int { + check(!(mMap == null || mName == null)) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } + return mMap!!.getInt(mName!!) + } + + override fun asString(): String? { + check(!(mMap == null || mName == null)) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } + return mMap!!.getString(mName!!) + } + + override fun asArray(): ReadableArray? { + check(!(mMap == null || mName == null)) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } + return mMap!!.getArray(mName!!) + } + + override fun asMap(): ReadableMap? { + check(!(mMap == null || mName == null)) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } + return mMap!!.getMap(mName!!) + } + + override val type: ReadableType + get() { + check(!(mMap == null || mName == null)) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } + return mMap!!.getType(mName!!) + } + + companion object { + private val sPool: ThreadLocal> = + ThreadLocal.withInitial { SimplePool(10) } + + private const val DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE = + "This dynamic value has been recycled" + + fun create(map: ReadableMap?, name: String?): DynamicFromMap { + val dynamic = sPool.get()?.acquire() ?: DynamicFromMap() + + return dynamic.apply { + mMap = map + mName = name + } + } + } +} From 407bc9620d6979ee5a78a37ff1d555dd292278a9 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Thu, 10 Apr 2025 00:17:49 +0200 Subject: [PATCH 2/4] refactor: changes after CR --- .../facebook/react/bridge/DynamicFromMap.kt | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.kt index 4581bd1c80b7e6..58158e79ff058f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.kt @@ -13,57 +13,61 @@ import androidx.core.util.Pools.SimplePool internal class DynamicFromMap // This is a pools object. Hide the constructor. private constructor() : Dynamic { - private var mMap: ReadableMap? = null - private var mName: String? = null + private var map: ReadableMap? = null + private var name: String? = null override fun recycle() { - mMap = null - mName = null + map = null + name = null sPool.get()?.release(this) } override val isNull: Boolean get() { - check(!(mMap == null || mName == null)) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } - return mMap!!.isNull(mName!!) + return accessMapSafely { map, name -> map.isNull(name) } } override fun asBoolean(): Boolean { - check(!(mMap == null || mName == null)) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } - return mMap!!.getBoolean(mName!!) + return accessMapSafely { map, name -> map.getBoolean(name) } } override fun asDouble(): Double { - check(!(mMap == null || mName == null)) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } - return mMap!!.getDouble(mName!!) + return accessMapSafely { map, name -> map.getDouble(name) } } override fun asInt(): Int { - check(!(mMap == null || mName == null)) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } - return mMap!!.getInt(mName!!) + return accessMapSafely { map, name -> map.getInt(name) } } override fun asString(): String? { - check(!(mMap == null || mName == null)) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } - return mMap!!.getString(mName!!) + return accessMapSafely { map, name -> map.getString(name) } } override fun asArray(): ReadableArray? { - check(!(mMap == null || mName == null)) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } - return mMap!!.getArray(mName!!) + return accessMapSafely { map, name -> map.getArray(name) } } override fun asMap(): ReadableMap? { - check(!(mMap == null || mName == null)) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } - return mMap!!.getMap(mName!!) + return accessMapSafely { map, name -> map.getMap(name) } } override val type: ReadableType get() { - check(!(mMap == null || mName == null)) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } - return mMap!!.getType(mName!!) + return accessMapSafely { map, name -> map.getType(name) } } + /** + * Asserts that both map and name are non-null and invokes the lambda with + * + * @param executor the callback to be invoked with non-null-asserted prop values + */ + private fun accessMapSafely(executor: (map: ReadableMap, name: String) -> T): T { + val name = checkNotNull(name) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } + val map = checkNotNull(map) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE } + + return executor(map, name) + } + companion object { private val sPool: ThreadLocal> = ThreadLocal.withInitial { SimplePool(10) } @@ -71,12 +75,12 @@ private constructor() : Dynamic { private const val DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE = "This dynamic value has been recycled" - fun create(map: ReadableMap?, name: String?): DynamicFromMap { + fun create(map: ReadableMap, name: String): DynamicFromMap { val dynamic = sPool.get()?.acquire() ?: DynamicFromMap() return dynamic.apply { - mMap = map - mName = name + this.map = map + this.name = name } } } From eba535d3f4998ffcf85feb4c8ea23b9052ce13a8 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Thu, 10 Apr 2025 00:21:44 +0200 Subject: [PATCH 3/4] test: implemented unit tests for DynamicFromMap --- .../react/bridge/DynamicFromMapTest.kt | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/DynamicFromMapTest.kt diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/DynamicFromMapTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/DynamicFromMapTest.kt new file mode 100644 index 00000000000000..c5b9ad122986b6 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/DynamicFromMapTest.kt @@ -0,0 +1,67 @@ +/* + * 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 org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Test + +/** Tests for [DynamicFromMap] */ +class DynamicFromMapTest { + @Test + fun testGetDynamic() { + assertThat(getDynamicFromMap("int").asInt()).isEqualTo(1) + assertThat(getDynamicFromMap("double").asDouble()).isEqualTo(2.0) + assertThat(getDynamicFromMap("string").asString()).isEqualTo("str") + assertThat(getDynamicFromMap("boolean").asBoolean()).isEqualTo(false) + assertThat(getDynamicFromMap("array").asArray()).isEqualTo(testSource.getArray("array")) + assertThat(getDynamicFromMap("map").asMap()).isEqualTo(testSource.getMap("map")) + assertThat(getDynamicFromMap("null").isNull).isEqualTo(true) + assertThat(getDynamicFromMap("int").type).isEqualTo(ReadableType.Number) + } + + @Test + fun testGetFromRecycledDynamic() { + assertThatThrownBy { getRecycledDynamicFromMap("int").asInt() } + assertThatThrownBy { getRecycledDynamicFromMap("double").asDouble() } + assertThatThrownBy { getRecycledDynamicFromMap("string").asString() } + assertThatThrownBy { getRecycledDynamicFromMap("boolean").asMap() } + assertThatThrownBy { getRecycledDynamicFromMap("array").asArray() } + assertThatThrownBy { getRecycledDynamicFromMap("map").asBoolean() } + assertThatThrownBy { getRecycledDynamicFromMap("null").isNull } + assertThatThrownBy { getRecycledDynamicFromMap("anything").type } + } + + private fun getDynamicFromMap(key: String): Dynamic { + return DynamicFromMap.create(testSource, key) + } + + private fun getRecycledDynamicFromMap(key: String): Dynamic { + return getDynamicFromMap(key).apply { recycle() } + } + + companion object { + private val testSource = + JavaOnlyMap.of( + "boolean", + false, + "array", + JavaOnlyArray.of(), + "map", + JavaOnlyMap.of(), + "null", + null, + "int", + 1, + "double", + 2.0, + "string", + "str", + ) + } +} From de8165f25192ee6e9243fd95bb0170a42a259a3f Mon Sep 17 00:00:00 2001 From: artus9033 Date: Thu, 10 Apr 2025 00:27:55 +0200 Subject: [PATCH 4/4] chore: add docstrings --- .../src/main/java/com/facebook/react/bridge/DynamicFromMap.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.kt index 58158e79ff058f..60546ca9f4bc2a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/DynamicFromMap.kt @@ -60,6 +60,7 @@ private constructor() : Dynamic { * Asserts that both map and name are non-null and invokes the lambda with * * @param executor the callback to be invoked with non-null-asserted prop values + * @return value returned by the executor */ private fun accessMapSafely(executor: (map: ReadableMap, name: String) -> T): T { val name = checkNotNull(name) { DYNAMIC_VALUE_RECYCLED_FAILURE_MESSAGE }