// 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.Messager;
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.facebook.react.module.model.ReactModuleInfoProvider;
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;
import static javax.tools.Diagnostic.Kind.ERROR;
/**
* 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;
@SuppressFieldNotInitialized
private Messager mMessager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mFiler = processingEnv.getFiler();
mElements = processingEnv.getElementUtils();
mMessager = processingEnv.getMessager();
}
@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.javaModules(); // throws MirroredTypesException
} catch (MirroredTypesException mirroredTypesException) {
List<? extends TypeMirror> typeMirrors = mirroredTypesException.getTypeMirrors();
for (TypeMirror typeMirror : typeMirrors) {
nativeModules.add(typeMirror.toString());
}
}
MethodSpec getReactModuleInfosMethod;
try {
getReactModuleInfosMethod = MethodSpec.methodBuilder("getReactModuleInfos")
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addCode(getCodeBlockForReactModuleInfos(nativeModules))
.returns(MAP_TYPE)
.build();
} catch (ReactModuleSpecException reactModuleSpecException) {
mMessager.printMessage(ERROR, reactModuleSpecException.mMessage);
return false;
}
TypeSpec reactModulesInfosTypeSpec = TypeSpec.classBuilder(
fileName + "$$ReactModuleInfoProvider")
.addModifiers(Modifier.PUBLIC)
.addMethod(getReactModuleInfosMethod)
.addSuperinterface(ReactModuleInfoProvider.class)
.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)
throws ReactModuleSpecException {
CodeBlock.Builder builder = CodeBlock.builder();
if (nativeModules == null || nativeModules.isEmpty()) {
builder.addStatement("return Collections.emptyMap()");
} else {
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);
if (reactModule == null) {
throw new ReactModuleSpecException(
keyString + " not found by ReactModuleSpecProcessor. " +
"Did you forget to add the @ReactModule annotation to the native module?");
}
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();
}
private static class ReactModuleSpecException extends Exception {
public final String mMessage;
public ReactModuleSpecException(String message) {
mMessage = message;
}
}
}