From 605a0a62dc5841bbe9ccd7c3bbbdaa9addc5df94 Mon Sep 17 00:00:00 2001 From: Aaron Chiu Date: Thu, 1 Sep 2016 19:23:52 -0700 Subject: [PATCH] Add annotation processor to create static ReactModule infos Reviewed By: lexs Differential Revision: D3781016 fbshipit-source-id: 8169e8b55fc044df2230fd01e912c4e96a044f98 --- ReactAndroid/DEFS | 29 ++-- ReactAndroid/build.gradle | 1 + .../facebook/react/module/annotations/BUCK | 18 +++ .../annotations/ReactModule.java | 2 +- .../module/annotations/ReactModuleList.java | 26 ++++ .../java/com/facebook/react/module/model/BUCK | 16 ++ .../react/module/model/ReactModuleInfo.java | 25 +++ .../com/facebook/react/module/processing/BUCK | 22 +++ .../processing/ReactModuleSpecProcessor.java | 143 ++++++++++++++++++ .../com/facebook/react/modules/network/BUCK | 1 + .../modules/network/NetworkingModule.java | 6 +- 11 files changed, 277 insertions(+), 12 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/module/annotations/BUCK rename ReactAndroid/src/main/java/com/facebook/react/{bridge => module}/annotations/ReactModule.java (98%) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/module/annotations/ReactModuleList.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/module/model/BUCK create mode 100644 ReactAndroid/src/main/java/com/facebook/react/module/model/ReactModuleInfo.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/module/processing/BUCK create mode 100644 ReactAndroid/src/main/java/com/facebook/react/module/processing/ReactModuleSpecProcessor.java diff --git a/ReactAndroid/DEFS b/ReactAndroid/DEFS index be373c48b59..45d195b8cd2 100644 --- a/ReactAndroid/DEFS +++ b/ReactAndroid/DEFS @@ -46,16 +46,27 @@ def android_library( *args, **kwargs): - common_processors = [ - 'com.facebook.react.processing.ReactPropertyProcessor', - ] - common_processor_deps = [ - react_native_target('java/com/facebook/react/processing:processing'), - ] - if react_native_target('java/com/facebook/react/uimanager/annotations:annotations') in deps and name != 'processing': - annotation_processors = list(set(annotation_processors + common_processors)) - annotation_processor_deps = list(set(annotation_processor_deps + common_processor_deps)) + react_property_processors = [ + 'com.facebook.react.processing.ReactPropertyProcessor', + ] + react_property_processor_deps = [ + react_native_target('java/com/facebook/react/processing:processing'), + ] + + annotation_processors = list(set(annotation_processors + react_property_processors)) + annotation_processor_deps = list(set(annotation_processor_deps + react_property_processor_deps)) + + if react_native_target('java/com/facebook/react/module/annotations:annotations') in deps and name != 'processing': + react_module_processors = [ + 'com.facebook.react.module.processing.ReactModuleSpecProcessor', + ] + react_module_processor_deps = [ + react_native_target('java/com/facebook/react/module/processing:processing'), + ] + + annotation_processors = list(set(annotation_processors + react_module_processors)) + annotation_processor_deps = list(set(annotation_processor_deps + react_module_processor_deps)) original_android_library( name=name, diff --git a/ReactAndroid/build.gradle b/ReactAndroid/build.gradle index 8b67a72c8c1..8a9abb5ec61 100644 --- a/ReactAndroid/build.gradle +++ b/ReactAndroid/build.gradle @@ -245,6 +245,7 @@ android { java { srcDirs = ['src/main/java', 'src/main/libraries/soloader/java', 'src/main/jni/first-party/fb/jni/java'] exclude 'com/facebook/react/processing' + exclude 'com/facebook/react/module/processing' } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/module/annotations/BUCK b/ReactAndroid/src/main/java/com/facebook/react/module/annotations/BUCK new file mode 100644 index 00000000000..3682ea80f41 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/module/annotations/BUCK @@ -0,0 +1,18 @@ +include_defs('//ReactAndroid/DEFS') + +android_library( + name = 'annotations', + srcs = glob(['**/*.java']), + deps = [ + react_native_dep('third-party/java/infer-annotations:infer-annotations'), + react_native_dep('third-party/java/jsr-305:jsr-305'), + react_native_target('java/com/facebook/react/bridge:bridge'), + ], + visibility=[ + 'PUBLIC' + ] +) + +project_config( + src_target = ':annotations', +) diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/annotations/ReactModule.java b/ReactAndroid/src/main/java/com/facebook/react/module/annotations/ReactModule.java similarity index 98% rename from ReactAndroid/src/main/java/com/facebook/react/bridge/annotations/ReactModule.java rename to ReactAndroid/src/main/java/com/facebook/react/module/annotations/ReactModule.java index 50869d98485..f6cc8f8b7ae 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/annotations/ReactModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/module/annotations/ReactModule.java @@ -7,7 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -package com.facebook.react.bridge.annotations; +package com.facebook.react.module.annotations; import java.lang.annotation.Retention; import java.lang.annotation.Target; diff --git a/ReactAndroid/src/main/java/com/facebook/react/module/annotations/ReactModuleList.java b/ReactAndroid/src/main/java/com/facebook/react/module/annotations/ReactModuleList.java new file mode 100644 index 00000000000..dee1150bd50 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/module/annotations/ReactModuleList.java @@ -0,0 +1,26 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.module.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import com.facebook.react.bridge.NativeModule; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Annotates a function that returns a list of ModuleSpecs from which we get a list of NativeModules + * to create ReactModuleInfos from. + */ +@Retention(SOURCE) +@Target(TYPE) +public @interface ReactModuleList { + + /** + * The native modules in this list should be annotated with {@link ReactModule}. + * @return List of native modules. + */ + Class[] value(); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/module/model/BUCK b/ReactAndroid/src/main/java/com/facebook/react/module/model/BUCK new file mode 100644 index 00000000000..895b74613d5 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/module/model/BUCK @@ -0,0 +1,16 @@ +include_defs('//ReactAndroid/DEFS') + +android_library( + name = 'model', + srcs = glob(['**/*.java']), + deps = [ + react_native_dep('third-party/java/jsr-305:jsr-305'), + ], + visibility=[ + 'PUBLIC' + ] +) + +project_config( + src_target = ':model', +) diff --git a/ReactAndroid/src/main/java/com/facebook/react/module/model/ReactModuleInfo.java b/ReactAndroid/src/main/java/com/facebook/react/module/model/ReactModuleInfo.java new file mode 100644 index 00000000000..3c0a32b8c8e --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/module/model/ReactModuleInfo.java @@ -0,0 +1,25 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.module.model; + +/** + * Data holder class holding native module specifications. + */ +public class ReactModuleInfo { + + public final String mName; + public final boolean mCanOverrideExistingModule; + public final boolean mSupportsWebWorkers; + public final boolean mNeedsEagerInit; + + public ReactModuleInfo( + String name, + boolean canOverrideExistingModule, + boolean supportsWebWorkers, + boolean needsEagerInit) { + mName = name; + mCanOverrideExistingModule = canOverrideExistingModule; + mSupportsWebWorkers = supportsWebWorkers; + mNeedsEagerInit = needsEagerInit; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/module/processing/BUCK b/ReactAndroid/src/main/java/com/facebook/react/module/processing/BUCK new file mode 100644 index 00000000000..1d30a84266d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/module/processing/BUCK @@ -0,0 +1,22 @@ +include_defs('//ReactAndroid/DEFS') + +java_library( + name = 'processing', + srcs = glob(['*.java']), + source = '7', + target = '7', + deps = [ + react_native_dep('third-party/java/infer-annotations:infer-annotations'), + react_native_dep('third-party/java/javapoet:javapoet'), + react_native_dep('third-party/java/jsr-305:jsr-305'), + react_native_target('java/com/facebook/react/module/annotations:annotations'), + react_native_target('java/com/facebook/react/module/model:model'), + ], + visibility=[ + 'PUBLIC' + ] +) + +project_config( + src_target = ':processing', +) diff --git a/ReactAndroid/src/main/java/com/facebook/react/module/processing/ReactModuleSpecProcessor.java b/ReactAndroid/src/main/java/com/facebook/react/module/processing/ReactModuleSpecProcessor.java new file mode 100644 index 00000000000..34171a76846 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/module/processing/ReactModuleSpecProcessor.java @@ -0,0 +1,143 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.module.processing; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Filer; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.MirroredTypesException; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.facebook.infer.annotation.SuppressFieldNotInitialized; +import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.module.annotations.ReactModuleList; +import com.facebook.react.module.model.ReactModuleInfo; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +import static javax.lang.model.element.Modifier.PUBLIC; + +/** + * Generates a list of ReactModuleInfo for modules annotated with {@link ReactModule} in + * {@link ReactPackage}s annotated with {@link ReactModuleList}. + */ +@SupportedAnnotationTypes({ + "com.facebook.react.module.annotations.ReactModule", + "com.facebook.react.module.annotations.ReactModuleList", +}) +@SupportedSourceVersion(SourceVersion.RELEASE_7) +public class ReactModuleSpecProcessor extends AbstractProcessor { + + private static final TypeName MAP_TYPE = ParameterizedTypeName.get( + Map.class, + Class.class, + ReactModuleInfo.class); + private static final TypeName INSTANTIATED_MAP_TYPE = ParameterizedTypeName.get(HashMap.class); + + @SuppressFieldNotInitialized + private Filer mFiler; + @SuppressFieldNotInitialized + private Elements mElements; + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + + mFiler = processingEnv.getFiler(); + mElements = processingEnv.getElementUtils(); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + Set reactModuleListElements = roundEnv.getElementsAnnotatedWith( + ReactModuleList.class); + for (Element reactModuleListElement : reactModuleListElements) { + TypeElement typeElement = (TypeElement) reactModuleListElement; + ClassName className = ClassName.get(typeElement); + String packageName = ClassName.get(typeElement).packageName(); + String fileName = className.simpleName(); + + ReactModuleList reactModuleList = typeElement.getAnnotation(ReactModuleList.class); + List nativeModules = new ArrayList<>(); + try { + reactModuleList.value(); // throws MirroredTypesException + } catch (MirroredTypesException mirroredTypesException) { + List typeMirrors = mirroredTypesException.getTypeMirrors(); + for (TypeMirror typeMirror : typeMirrors) { + nativeModules.add(typeMirror.toString()); + } + } + + MethodSpec getReactModuleInfosMethod = MethodSpec.methodBuilder("getReactModuleInfos") + .addModifiers(PUBLIC) + // TODO add function to native module interface +// .addAnnotation(Override.class) + .addCode(getCodeBlockForReactModuleInfos(nativeModules)) + .returns(MAP_TYPE) + .build(); + + TypeSpec reactModulesInfosTypeSpec = TypeSpec.classBuilder( + fileName + "$$ReactModuleInfoProvider") + .addModifiers(Modifier.PUBLIC) + .addMethod(getReactModuleInfosMethod) + .build(); + + JavaFile javaFile = JavaFile.builder(packageName, reactModulesInfosTypeSpec) + .addFileComment("Generated by " + getClass().getName()) + .build(); + + try { + javaFile.writeTo(mFiler); + } catch (IOException e) { + e.printStackTrace(); + } + } + + return true; + } + + private CodeBlock getCodeBlockForReactModuleInfos(List nativeModules) { + CodeBlock.Builder builder = CodeBlock.builder() + .addStatement("$T map = new $T()", MAP_TYPE, INSTANTIATED_MAP_TYPE); + + for (String nativeModule : nativeModules) { + String keyString = nativeModule + ".class"; + + TypeElement typeElement = mElements.getTypeElement(nativeModule); + ReactModule reactModule = typeElement.getAnnotation(ReactModule.class); + String valueString = new StringBuilder() + .append("new ReactModuleInfo(") + .append("\"").append(reactModule.name()).append("\"").append(", ") + .append(reactModule.canOverrideExistingModule()).append(", ") + .append(reactModule.supportsWebWorkers()).append(", ") + .append(reactModule.needsEagerInit()) + .append(")") + .toString(); + + builder.addStatement("map.put(" + keyString + ", " + valueString + ")"); + } + builder.addStatement("return map"); + return builder.build(); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/network/BUCK index 494316468e2..ca9a18f84f9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/BUCK @@ -14,6 +14,7 @@ android_library( react_native_target('java/com/facebook/react/bridge:bridge'), react_native_target('java/com/facebook/react/common/network:network'), react_native_target('java/com/facebook/react/common:common'), + react_native_target('java/com/facebook/react/module/annotations:annotations'), react_native_target('java/com/facebook/react/modules/core:core'), ], visibility = [ diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java index 50f31239ca4..8884bdbf46d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java @@ -9,8 +9,6 @@ package com.facebook.react.modules.network; -import android.util.Base64; - import javax.annotation.Nullable; import java.io.IOException; @@ -21,6 +19,8 @@ import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; +import android.util.Base64; + import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ExecutorToken; import com.facebook.react.bridge.GuardedAsyncTask; @@ -31,6 +31,7 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.network.OkHttpCallUtil; +import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter; import okhttp3.Call; @@ -49,6 +50,7 @@ import okhttp3.ResponseBody; /** * Implements the XMLHttpRequest JavaScript interface. */ +@ReactModule(name = "RCTNetworking") public final class NetworkingModule extends ReactContextBaseJavaModule { private static final String CONTENT_ENCODING_HEADER_NAME = "content-encoding";