From 79d3eea0b72fc50dab44135d502be2fb39128520 Mon Sep 17 00:00:00 2001 From: drrefactor Date: Thu, 24 Apr 2025 04:58:43 -0700 Subject: [PATCH] refactor: Rewrite JavaModuleWrapper from Java to Kotlin (#50882) Summary: Rewrite of JavaModuleWrapper from Java to Kotlin in scope of https://github.com/facebook/react-native/issues/50513 ## Changelog: [ANDROID] [CHANGED] - Migrated JavaModuleWrapper to Kotlin Pull Request resolved: https://github.com/facebook/react-native/pull/50882 Test Plan: Test RNTester using old arch. SampleLegacyModule is the one I've used, it needs to be enabled for old arch though (RNTesterApplication.kt -> getPackages & getReactModuleInfoProvider). I may enable SampleLegacyModule for old arch to make testing easier. mateoguzmana It breaks on `getDynamic` on old arch, but I could filter these from examples or add some fallback in SampleLegacyModule.kt for old arch. Reviewed By: cortinico Differential Revision: D73576099 Pulled By: javache fbshipit-source-id: c940be27133258fa589571a600435fa478e6b51e --- .../react/bridge/JavaModuleWrapper.java | 156 ------------------ .../react/bridge/JavaModuleWrapper.kt | 143 ++++++++++++++++ .../react/bridge/BaseJavaModuleTest.kt | 2 +- 3 files changed, 144 insertions(+), 157 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.java deleted file mode 100644 index acdee40ab5b..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.java +++ /dev/null @@ -1,156 +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 static com.facebook.react.bridge.ReactMarkerConstants.CONVERT_CONSTANTS_END; -import static com.facebook.react.bridge.ReactMarkerConstants.CONVERT_CONSTANTS_START; -import static com.facebook.react.bridge.ReactMarkerConstants.GET_CONSTANTS_END; -import static com.facebook.react.bridge.ReactMarkerConstants.GET_CONSTANTS_START; -import static com.facebook.systrace.Systrace.TRACE_TAG_REACT; - -import androidx.annotation.Nullable; -import com.facebook.proguard.annotations.DoNotStrip; -import com.facebook.react.common.annotations.internal.LegacyArchitecture; -import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel; -import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger; -import com.facebook.react.turbomodule.core.interfaces.TurboModule; -import com.facebook.systrace.Systrace; -import com.facebook.systrace.SystraceMessage; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * This is part of the glue which wraps a java BaseJavaModule in a C++ NativeModule. This could all - * be in C++, but it's android-specific initialization code, and writing it this way is easier to - * read and means fewer JNI calls. - */ -@DoNotStrip -@LegacyArchitecture -class JavaModuleWrapper { - static { - LegacyArchitectureLogger.assertLegacyArchitecture( - "JavaModuleWrapper", LegacyArchitectureLogLevel.WARNING); - } - - interface NativeMethod { - void invoke(JSInstance jsInstance, ReadableArray parameters); - - String getType(); - } - - @DoNotStrip - public static class MethodDescriptor { - @DoNotStrip Method method; - @DoNotStrip String signature; - @DoNotStrip String name; - @DoNotStrip String type; - } - - private final JSInstance mJSInstance; - private final ModuleHolder mModuleHolder; - private final ArrayList mMethods; - private final ArrayList mDescs; - - public JavaModuleWrapper(JSInstance jsInstance, ModuleHolder moduleHolder) { - mJSInstance = jsInstance; - mModuleHolder = moduleHolder; - mMethods = new ArrayList<>(); - mDescs = new ArrayList<>(); - } - - @DoNotStrip - public BaseJavaModule getModule() { - return (BaseJavaModule) mModuleHolder.getModule(); - } - - @DoNotStrip - public String getName() { - return mModuleHolder.getName(); - } - - @DoNotStrip - private void findMethods() { - Systrace.beginSection(TRACE_TAG_REACT, "findMethods"); - - Class classForMethods = mModuleHolder.getModule().getClass(); - Class superClass = - (Class) classForMethods.getSuperclass(); - if (TurboModule.class.isAssignableFrom(superClass)) { - // For java module that is based on generated flow-type spec, inspect the - // spec abstract class instead, which is the super class of the given java - // module. - classForMethods = superClass; - } - Method[] targetMethods = classForMethods.getDeclaredMethods(); - - for (Method targetMethod : targetMethods) { - ReactMethod annotation = targetMethod.getAnnotation(ReactMethod.class); - if (annotation != null) { - String methodName = targetMethod.getName(); - MethodDescriptor md = new MethodDescriptor(); - JavaMethodWrapper method = - new JavaMethodWrapper(this, targetMethod, annotation.isBlockingSynchronousMethod()); - md.name = methodName; - md.type = method.getType(); - if (BaseJavaModule.METHOD_TYPE_SYNC.equals(md.type)) { - md.signature = method.getSignature(); - md.method = targetMethod; - } - mMethods.add(method); - mDescs.add(md); - } - } - Systrace.endSection(TRACE_TAG_REACT); - } - - @DoNotStrip - public List getMethodDescriptors() { - if (mDescs.isEmpty()) { - findMethods(); - } - return mDescs; - } - - @DoNotStrip - public @Nullable NativeMap getConstants() { - final String moduleName = getName(); - SystraceMessage.beginSection(TRACE_TAG_REACT, "JavaModuleWrapper.getConstants") - .arg("moduleName", moduleName) - .flush(); - ReactMarker.logMarker(GET_CONSTANTS_START, moduleName); - - BaseJavaModule baseJavaModule = getModule(); - - Systrace.beginSection(TRACE_TAG_REACT, "module.getConstants"); - Map map = baseJavaModule.getConstants(); - Systrace.endSection(TRACE_TAG_REACT); - - Systrace.beginSection(TRACE_TAG_REACT, "create WritableNativeMap"); - ReactMarker.logMarker(CONVERT_CONSTANTS_START, moduleName); - try { - return Arguments.makeNativeMap(map); - } finally { - ReactMarker.logMarker(CONVERT_CONSTANTS_END, moduleName); - Systrace.endSection(TRACE_TAG_REACT); - - ReactMarker.logMarker(GET_CONSTANTS_END, moduleName); - SystraceMessage.endSection(TRACE_TAG_REACT).flush(); - } - } - - @DoNotStrip - public void invoke(int methodId, ReadableNativeArray parameters) { - if (methodId >= mMethods.size()) { - return; - } - - mMethods.get(methodId).invoke(mJSInstance, parameters); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.kt new file mode 100644 index 00000000000..82ed73e8239 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.kt @@ -0,0 +1,143 @@ +/* + * 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.proguard.annotations.DoNotStrip +import com.facebook.react.common.annotations.internal.LegacyArchitecture +import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel +import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger.assertLegacyArchitecture +import com.facebook.react.turbomodule.core.interfaces.TurboModule +import com.facebook.systrace.Systrace +import com.facebook.systrace.Systrace.TRACE_TAG_REACT +import com.facebook.systrace.SystraceMessage +import java.lang.reflect.Method + +/** + * This is part of the glue which wraps a java BaseJavaModule in a C++ NativeModule. This could all + * be in C++, but it's android-specific initialization code, and writing it this way is easier to + * read and means fewer JNI calls. + */ +@DoNotStrip +@LegacyArchitecture +internal class JavaModuleWrapper( + private val jsInstance: JSInstance, + private val moduleHolder: ModuleHolder +) { + internal interface NativeMethod { + fun invoke(jsInstance: JSInstance, parameters: ReadableArray) + + val type: String + } + + @DoNotStrip + class MethodDescriptor { + @DoNotStrip var method: Method? = null + + @DoNotStrip var signature: String? = null + + @DoNotStrip var name: String? = null + + @DoNotStrip var type: String? = null + } + + private val methods = ArrayList() + private val descs = ArrayList() + + @get:DoNotStrip + val module: BaseJavaModule + get() = moduleHolder.module as BaseJavaModule + + @get:DoNotStrip + val name: String + get() = moduleHolder.name + + @DoNotStrip + private fun findMethods() { + Systrace.beginSection(TRACE_TAG_REACT, "findMethods") + + var classForMethods: Class = moduleHolder.module.javaClass + val superClass = classForMethods.superclass + if (TurboModule::class.java.isAssignableFrom(superClass)) { + // For java module that is based on generated flow-type spec, inspect the + // spec abstract class instead, which is the super class of the given Java + // module. + @Suppress("UNCHECKED_CAST") + classForMethods = superClass as Class + } + + val targetMethods = classForMethods.declaredMethods + for (targetMethod in targetMethods) { + targetMethod.getAnnotation(ReactMethod::class.java)?.let { annotation -> + val methodName = targetMethod.name + val md = MethodDescriptor() + val method = JavaMethodWrapper(this, targetMethod, annotation.isBlockingSynchronousMethod) + md.name = methodName + md.type = method.type + if (BaseJavaModule.METHOD_TYPE_SYNC == md.type) { + md.signature = method.signature + md.method = targetMethod + } + methods.add(method) + descs.add(md) + } + } + Systrace.endSection(TRACE_TAG_REACT) + } + + @get:DoNotStrip + val methodDescriptors: List + get() { + if (descs.isEmpty()) { + findMethods() + } + return descs + } + + @get:DoNotStrip + val constants: NativeMap + get() { + val moduleName = name + SystraceMessage.beginSection(TRACE_TAG_REACT, "JavaModuleWrapper.getConstants") + .arg("moduleName", moduleName) + .flush() + ReactMarker.logMarker(ReactMarkerConstants.GET_CONSTANTS_START, moduleName) + + val baseJavaModule = module + + Systrace.beginSection(TRACE_TAG_REACT, "module.getConstants") + val map = baseJavaModule.constants + Systrace.endSection(TRACE_TAG_REACT) + + Systrace.beginSection(TRACE_TAG_REACT, "create WritableNativeMap") + ReactMarker.logMarker(ReactMarkerConstants.CONVERT_CONSTANTS_START, moduleName) + try { + return Arguments.makeNativeMap(map) + } finally { + ReactMarker.logMarker(ReactMarkerConstants.CONVERT_CONSTANTS_END, moduleName) + Systrace.endSection(TRACE_TAG_REACT) + + ReactMarker.logMarker(ReactMarkerConstants.GET_CONSTANTS_END, moduleName) + SystraceMessage.endSection(TRACE_TAG_REACT).flush() + } + } + + @DoNotStrip + fun invoke(methodId: Int, parameters: ReadableNativeArray) { + if (methodId >= methods.size) { + return + } + + methods[methodId].invoke(jsInstance, parameters) + } + + companion object { + init { + assertLegacyArchitecture("JavaModuleWrapper", LegacyArchitectureLogLevel.WARNING) + } + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.kt index 71317183b93..4e01a11de62 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.kt @@ -35,7 +35,7 @@ class BaseJavaModuleTest { moduleWrapper = JavaModuleWrapper(jsInstance, moduleHolder) methods = moduleWrapper.methodDescriptors val generatedModuleHolder = ModuleHolder(GeneratedMethodsModule()) - generatedModuleWrapper = JavaModuleWrapper(null, generatedModuleHolder) + generatedModuleWrapper = JavaModuleWrapper(jsInstance, generatedModuleHolder) generatedMethods = generatedModuleWrapper.methodDescriptors arguments = mock(ReadableNativeArray::class.java) }