diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 38171dcff6b260..246d8051156074 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -921,9 +921,14 @@ public abstract interface class com/facebook/react/bridge/JavaScriptModule { } public final class com/facebook/react/bridge/JavaScriptModuleRegistry { + public static final field Companion Lcom/facebook/react/bridge/JavaScriptModuleRegistry$Companion; public fun ()V - public static fun getJSModuleName (Ljava/lang/Class;)Ljava/lang/String; - public fun getJavaScriptModule (Lcom/facebook/react/bridge/CatalystInstance;Ljava/lang/Class;)Lcom/facebook/react/bridge/JavaScriptModule; + public static final fun getJSModuleName (Ljava/lang/Class;)Ljava/lang/String; + public final fun getJavaScriptModule (Lcom/facebook/react/bridge/CatalystInstance;Ljava/lang/Class;)Lcom/facebook/react/bridge/JavaScriptModule; +} + +public final class com/facebook/react/bridge/JavaScriptModuleRegistry$Companion { + public final fun getJSModuleName (Ljava/lang/Class;)Ljava/lang/String; } public final class com/facebook/react/bridge/JsonWriterHelper { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.java deleted file mode 100644 index e5b32075a97209..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.java +++ /dev/null @@ -1,102 +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.Nullsafe; -import com.facebook.react.common.build.ReactBuildConfig; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; - -/** - * Class responsible for holding all the {@link JavaScriptModule}s. Uses Java proxy objects to - * dispatch method calls on JavaScriptModules to the bridge using the corresponding module and - * method ids so the proper function is executed in JavaScript. - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public final class JavaScriptModuleRegistry { - private final HashMap, JavaScriptModule> mModuleInstances; - - public JavaScriptModuleRegistry() { - mModuleInstances = new HashMap<>(); - } - - public synchronized T getJavaScriptModule( - CatalystInstance instance, Class moduleInterface) { - JavaScriptModule module = mModuleInstances.get(moduleInterface); - if (module != null) { - return (T) module; - } - - JavaScriptModule interfaceProxy = - (JavaScriptModule) - Proxy.newProxyInstance( - moduleInterface.getClassLoader(), - new Class[] {moduleInterface}, - new JavaScriptModuleInvocationHandler(instance, moduleInterface)); - mModuleInstances.put(moduleInterface, interfaceProxy); - return (T) interfaceProxy; - } - - private static class JavaScriptModuleInvocationHandler implements InvocationHandler { - private final CatalystInstance mCatalystInstance; - private final Class mModuleInterface; - private @Nullable String mName; - - public JavaScriptModuleInvocationHandler( - CatalystInstance catalystInstance, Class moduleInterface) { - mCatalystInstance = catalystInstance; - mModuleInterface = moduleInterface; - - if (ReactBuildConfig.DEBUG) { - Set methodNames = new HashSet<>(); - for (Method method : mModuleInterface.getDeclaredMethods()) { - if (!methodNames.add(method.getName())) { - throw new AssertionError( - "Method overloading is unsupported: " - + mModuleInterface.getName() - + "#" - + method.getName()); - } - } - } - } - - private String getJSModuleName() { - if (mName == null) { - // Getting the class name every call is expensive, so cache it - mName = JavaScriptModuleRegistry.getJSModuleName(mModuleInterface); - } - return mName; - } - - @Override - public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) - throws Throwable { - NativeArray jsArgs = args != null ? Arguments.fromJavaArgs(args) : new WritableNativeArray(); - mCatalystInstance.callFunction(getJSModuleName(), method.getName(), jsArgs); - return null; - } - } - - public static String getJSModuleName(Class jsModuleInterface) { - // With proguard obfuscation turned on, proguard apparently (poorly) emulates inner - // classes or something because Class#getSimpleName() no longer strips the outer - // class name. We manually strip it here if necessary. - String name = jsModuleInterface.getSimpleName(); - int dollarSignIndex = name.lastIndexOf('$'); - if (dollarSignIndex != -1) { - name = name.substring(dollarSignIndex + 1); - } - return name; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.kt new file mode 100644 index 00000000000000..3d3add8a3b7ee0 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.kt @@ -0,0 +1,93 @@ +/* + * 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.react.common.build.ReactBuildConfig +import java.lang.reflect.InvocationHandler +import java.lang.reflect.Method +import java.lang.reflect.Proxy + +/** + * Class responsible for holding all the [JavaScriptModule]s. Uses Java proxy objects to dispatch + * method calls on JavaScriptModules to the bridge using the corresponding module and method ids so + * the proper function is executed in JavaScript. + */ +public class JavaScriptModuleRegistry { + + private val moduleInstances: MutableMap, JavaScriptModule> = HashMap() + + @Synchronized + public fun getJavaScriptModule( + @Suppress("DEPRECATION") instance: CatalystInstance, + moduleInterface: Class + ): T { + val module = moduleInstances[moduleInterface] + if (module != null) { + @Suppress("UNCHECKED_CAST") + return module as T + } + + val proxy = + Proxy.newProxyInstance( + moduleInterface.classLoader, + arrayOf>(moduleInterface), + JavaScriptModuleInvocationHandler(instance, moduleInterface)) as JavaScriptModule + + moduleInstances[moduleInterface] = proxy + @Suppress("UNCHECKED_CAST") + return proxy as T + } + + private class JavaScriptModuleInvocationHandler( + @Suppress("DEPRECATION") private val catalystInstance: CatalystInstance, + private val moduleInterface: Class + ) : InvocationHandler { + + private var name: String? = null + + init { + if (ReactBuildConfig.DEBUG) { + val methodNames = mutableSetOf() + for (method in moduleInterface.declaredMethods) { + if (!methodNames.add(method.name)) { + throw AssertionError( + "Method overloading is unsupported: ${moduleInterface.name}#${method.name}") + } + } + } + } + + private fun getJSModuleName(): String { + // Getting the class name every call is expensive, so cache it + return name ?: getJSModuleName(moduleInterface).also { name = it } + } + + public override fun invoke(proxy: Any, method: Method, args: Array?): Any? { + val jsArgs = if (args != null) Arguments.fromJavaArgs(args) else WritableNativeArray() + catalystInstance.callFunction(getJSModuleName(), method.name, jsArgs) + return null + } + } + + public companion object { + /** + * With proguard obfuscation turned on, proguard apparently (poorly) emulates inner classes or + * something because Class#getSimpleName() no longer strips the outer class name. We manually + * strip it here if necessary. + */ + @JvmStatic + public fun getJSModuleName(jsModuleInterface: Class): String { + var name = jsModuleInterface.simpleName + val dollarSignIndex = name.lastIndexOf('$') + if (dollarSignIndex != -1) { + name = name.substring(dollarSignIndex + 1) + } + return name + } + } +}