Add annotation processor to create static ReactModule infos

Reviewed By: lexs

Differential Revision: D3781016

fbshipit-source-id: 8169e8b55fc044df2230fd01e912c4e96a044f98
This commit is contained in:
Aaron Chiu
2016-09-01 19:23:52 -07:00
committed by Facebook Github Bot 6
parent c06c1e1786
commit 605a0a62dc
11 changed files with 277 additions and 12 deletions

View File

@@ -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',
)

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.module.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.ExecutorToken;
import com.facebook.react.bridge.ReactContext;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotation for use on {@link BaseJavaModule}s to describe properties for that module.
*/
@Retention(RUNTIME)
@Target(TYPE)
public @interface ReactModule {
/**
* Name used to {@code require()} this module from JavaScript.
*/
String name();
/**
* True if you intend to override some other native module that was registered e.g. as part
* of a different package (such as the core one). Trying to override without returning true from
* this method is considered an error and will throw an exception during initialization. By
* default all modules return false.
*/
boolean canOverrideExistingModule() default false;
/**
* In order to support web workers, a module must be aware that it can be invoked from multiple
* different JS VMs. Supporting web workers means recognizing things like:
*
* 1) ids (e.g. timer ids, request ids, etc.) may only unique on a per-VM basis
* 2) the module needs to make sure to enqueue callbacks and JS module calls to the correct VM
*
* In order to facilitate this, modules that support web workers will have all their @ReactMethod-
* annotated methods passed a {@link ExecutorToken} as the first parameter before any arguments
* from JS. This ExecutorToken internally maps to a specific JS VM and can be used by the
* framework to route calls appropriately. In order to make JS module calls correctly, start using
* the version of {@link ReactContext#getJSModule(ExecutorToken, Class)} that takes an
* ExecutorToken. It will ensure that any calls you dispatch to the returned object will go to
* the right VM. For Callbacks, you don't have to do anything special -- the framework
* automatically tags them with the correct ExecutorToken when the are created.
*
* Note: even though calls can come from multiple JS VMs on multiple threads, calls to this module
* will still only occur on a single thread.
*
* @return whether this module supports web workers.
*/
boolean supportsWebWorkers() default false;
/**
* Whether this module needs to be loaded immediately.
*/
boolean needsEagerInit() default false;
}

View File

@@ -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<? extends NativeModule>[] value();
}

View File

@@ -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',
)

View File

@@ -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;
}
}

View File

@@ -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',
)

View File

@@ -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<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> 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<String> nativeModules = new ArrayList<>();
try {
reactModuleList.value(); // throws MirroredTypesException
} catch (MirroredTypesException mirroredTypesException) {
List<? extends TypeMirror> 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<String> 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();
}
}