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:

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests
-->

[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
This commit is contained in:
drrefactor
2025-04-24 04:58:43 -07:00
committed by Facebook GitHub Bot
parent c443bc18f5
commit 79d3eea0b7
3 changed files with 144 additions and 157 deletions
@@ -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<NativeMethod> mMethods;
private final ArrayList<MethodDescriptor> 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<? extends NativeModule> classForMethods = mModuleHolder.getModule().getClass();
Class<? extends NativeModule> superClass =
(Class<? extends NativeModule>) 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<MethodDescriptor> 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<String, Object> 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);
}
}
@@ -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<NativeMethod>()
private val descs = ArrayList<MethodDescriptor>()
@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<out NativeModule> = 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<out NativeModule>
}
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<MethodDescriptor>
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)
}
}
}
@@ -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)
}