mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-06 09:17:55 +08:00
Add annotation processor for @ReactProp
Summary: The annotation processor finds subclasses of ViewManager and ShadowNode and generates classes that can both provide a mapping of property names to their type as well as a way of setting these properties. This avoids having to do reflection to find the properties. The annotation processor is currently not working when building with Gradle. public Reviewed By: astreet Differential Revision: D2748958 fb-gh-sync-id: ded5b072d236ebf19fb43eaf704fc7f221a82c26
This commit is contained in:
committed by
facebook-github-bot-4
parent
c1b7a369af
commit
57f6cbb3dc
@@ -227,6 +227,9 @@ android {
|
||||
jni.srcDirs = []
|
||||
jniLibs.srcDir "$buildDir/react-ndk/exported"
|
||||
res.srcDirs = ['src/main/res/devsupport', 'src/main/res/shell']
|
||||
java {
|
||||
exclude 'com/facebook/react/processing'
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
|
||||
@@ -0,0 +1,691 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.react.processing;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
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.ElementKind;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.facebook.infer.annotation.SuppressFieldNotInitialized;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.uimanager.annotations.ReactPropertyHolder;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.annotations.ReactPropGroup;
|
||||
|
||||
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 com.squareup.javapoet.TypeVariableName;
|
||||
|
||||
import static javax.lang.model.element.Modifier.*;
|
||||
import static javax.tools.Diagnostic.Kind.ERROR;
|
||||
import static javax.tools.Diagnostic.Kind.WARNING;
|
||||
|
||||
/**
|
||||
* This annotation processor crawls subclasses of ReactShadowNode and ViewManager and finds their
|
||||
* exported properties with the @ReactProp or @ReactGroupProp annotation. It generates a class
|
||||
* per shadow node/view manager that is named {@code <classname>$$PropSetter}. This class contains methods
|
||||
* to retrieve the name and type of all methods and a way to set these properties without
|
||||
* reflection.
|
||||
*/
|
||||
@SupportedAnnotationTypes("com.facebook.react.uimanager.annotations.ReactPropertyHolder")
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_7)
|
||||
public class ReactPropertyProcessor extends AbstractProcessor {
|
||||
private static final Map<TypeName, String> DEFAULT_TYPES;
|
||||
private static final Set<TypeName> BOXED_PRIMITIVES;
|
||||
|
||||
private static final TypeName PROPS_TYPE =
|
||||
ClassName.get("com.facebook.react.uimanager", "CatalystStylesDiffMap");
|
||||
private static final TypeName STRING_TYPE = TypeName.get(String.class);
|
||||
private static final TypeName READABLE_MAP_TYPE = TypeName.get(ReadableMap.class);
|
||||
private static final TypeName READABLE_ARRAY_TYPE = TypeName.get(ReadableArray.class);
|
||||
|
||||
private static final TypeName VIEW_MANAGER_TYPE =
|
||||
ClassName.get("com.facebook.react.uimanager", "ViewManager");
|
||||
private static final TypeName SHADOW_NODE_TYPE =
|
||||
ClassName.get("com.facebook.react.uimanager", "ReactShadowNode");
|
||||
|
||||
private static final ClassName VIEW_MANAGER_SETTER_TYPE =
|
||||
ClassName.get(
|
||||
"com.facebook.react.uimanager",
|
||||
"ViewManagerPropertyUpdater",
|
||||
"ViewManagerSetter");
|
||||
private static final ClassName SHADOW_NODE_SETTER_TYPE =
|
||||
ClassName.get(
|
||||
"com.facebook.react.uimanager",
|
||||
"ViewManagerPropertyUpdater",
|
||||
"ShadowNodeSetter");
|
||||
|
||||
private static final TypeName PROPERTY_MAP_TYPE =
|
||||
ParameterizedTypeName.get(Map.class, String.class, String.class);
|
||||
private static final TypeName CONCRETE_PROPERTY_MAP_TYPE =
|
||||
ParameterizedTypeName.get(HashMap.class, String.class, String.class);
|
||||
|
||||
private static final TypeName MAPPINGS_MAP_TYPE =
|
||||
ParameterizedTypeName.get(Map.class, String.class, Integer.class);
|
||||
private static final TypeName CONCRETE_MAPPINGS_MAP_TYPE =
|
||||
ParameterizedTypeName.get(HashMap.class, String.class, Integer.class);
|
||||
|
||||
private final Map<ClassName, ClassInfo> mClasses;
|
||||
|
||||
@SuppressFieldNotInitialized
|
||||
private Filer mFiler;
|
||||
@SuppressFieldNotInitialized
|
||||
private Messager mMessager;
|
||||
@SuppressFieldNotInitialized
|
||||
private Elements mElements;
|
||||
@SuppressFieldNotInitialized
|
||||
private Types mTypes;
|
||||
|
||||
static {
|
||||
DEFAULT_TYPES = new HashMap<>();
|
||||
|
||||
// Primitives
|
||||
DEFAULT_TYPES.put(TypeName.BOOLEAN, "boolean");
|
||||
DEFAULT_TYPES.put(TypeName.DOUBLE, "number");
|
||||
DEFAULT_TYPES.put(TypeName.FLOAT, "number");
|
||||
DEFAULT_TYPES.put(TypeName.INT, "number");
|
||||
|
||||
// Boxed primitives
|
||||
DEFAULT_TYPES.put(TypeName.BOOLEAN.box(), "boolean");
|
||||
DEFAULT_TYPES.put(TypeName.INT.box(), "number");
|
||||
|
||||
// Class types
|
||||
DEFAULT_TYPES.put(STRING_TYPE, "String");
|
||||
DEFAULT_TYPES.put(READABLE_ARRAY_TYPE, "Array");
|
||||
DEFAULT_TYPES.put(READABLE_MAP_TYPE, "Map");
|
||||
|
||||
BOXED_PRIMITIVES = new HashSet<>();
|
||||
BOXED_PRIMITIVES.add(TypeName.BOOLEAN.box());
|
||||
BOXED_PRIMITIVES.add(TypeName.FLOAT.box());
|
||||
BOXED_PRIMITIVES.add(TypeName.INT.box());
|
||||
}
|
||||
|
||||
public ReactPropertyProcessor() {
|
||||
mClasses = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void init(ProcessingEnvironment processingEnv) {
|
||||
super.init(processingEnv);
|
||||
|
||||
mFiler = processingEnv.getFiler();
|
||||
mMessager = processingEnv.getMessager();
|
||||
mElements = processingEnv.getElementUtils();
|
||||
mTypes = processingEnv.getTypeUtils();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
// Clear properties from previous rounds
|
||||
mClasses.clear();
|
||||
|
||||
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(ReactPropertyHolder.class);
|
||||
for (Element element : elements) {
|
||||
try {
|
||||
TypeElement classType = (TypeElement) element;
|
||||
ClassName className = ClassName.get(classType);
|
||||
mClasses.put(className, parseClass(className, classType));
|
||||
} catch (Exception e) {
|
||||
error(element, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
for (ClassInfo classInfo : mClasses.values()) {
|
||||
try {
|
||||
if (!shouldIgnoreClass(classInfo)) {
|
||||
generateCode(classInfo, classInfo.mProperties);
|
||||
} else if (shouldWarnClass(classInfo)) {
|
||||
warning(classInfo.mElement, "Class was skipped. Classes need to be non-private.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
error(e.getMessage());
|
||||
} catch (ReactPropertyException e) {
|
||||
error(e.element, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
error(classInfo.mElement, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private ClassInfo parseClass(ClassName className, TypeElement typeElement) {
|
||||
TypeName targetType = getTargetType(typeElement.asType());
|
||||
TypeName viewType = targetType.equals(SHADOW_NODE_TYPE) ? null : targetType;
|
||||
|
||||
ClassInfo classInfo = new ClassInfo(className, typeElement, viewType);
|
||||
|
||||
PropertyInfo.Builder propertyBuilder = new PropertyInfo.Builder(mTypes, mElements, classInfo);
|
||||
for (Element element : mElements.getAllMembers(typeElement)) {
|
||||
ReactProp prop = element.getAnnotation(ReactProp.class);
|
||||
ReactPropGroup propGroup = element.getAnnotation(ReactPropGroup.class);
|
||||
|
||||
try {
|
||||
if (prop != null || propGroup != null) {
|
||||
checkElement(element);
|
||||
}
|
||||
|
||||
if (prop != null) {
|
||||
classInfo.addProperty(propertyBuilder.build(element, new RegularProperty(prop)));
|
||||
} else if (propGroup != null) {
|
||||
for (int i = 0, size = propGroup.names().length; i < size; i++) {
|
||||
classInfo.addProperty(propertyBuilder.build(element, new GroupProperty(propGroup, i)));
|
||||
}
|
||||
}
|
||||
} catch (ReactPropertyException e) {
|
||||
error(e.element, e.getMessage());
|
||||
}
|
||||
}
|
||||
return classInfo;
|
||||
}
|
||||
|
||||
private TypeName getTargetType(TypeMirror mirror) {
|
||||
TypeName typeName = TypeName.get(mirror);
|
||||
if (typeName instanceof ParameterizedTypeName) {
|
||||
ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName;
|
||||
if (parameterizedTypeName.rawType.equals(VIEW_MANAGER_TYPE)) {
|
||||
return parameterizedTypeName.typeArguments.get(0);
|
||||
}
|
||||
} else if (typeName.equals(SHADOW_NODE_TYPE)) {
|
||||
return SHADOW_NODE_TYPE;
|
||||
} else if (typeName.equals(TypeName.OBJECT)) {
|
||||
throw new IllegalArgumentException("Could not find target type");
|
||||
}
|
||||
|
||||
List<? extends TypeMirror> types = mTypes.directSupertypes(mirror);
|
||||
return getTargetType(types.get(0));
|
||||
}
|
||||
|
||||
private void generateCode(ClassInfo classInfo, List<PropertyInfo> properties)
|
||||
throws IOException, ReactPropertyException {
|
||||
MethodSpec getMethods = MethodSpec.methodBuilder("getProperties")
|
||||
.addModifiers(PUBLIC)
|
||||
.addAnnotation(Override.class)
|
||||
.returns(PROPERTY_MAP_TYPE)
|
||||
.addCode(generateGetProperties(properties))
|
||||
.build();
|
||||
|
||||
TypeName superType = getSuperType(classInfo);
|
||||
ClassName className = classInfo.mClassName;
|
||||
|
||||
String holderClassName =
|
||||
getClassName((TypeElement) classInfo.mElement, className.packageName()) + "$$PropsSetter";
|
||||
TypeSpec holderClass = TypeSpec.classBuilder(holderClassName)
|
||||
.addSuperinterface(superType)
|
||||
.addModifiers(PUBLIC)
|
||||
.addField(MAPPINGS_MAP_TYPE, "mappings", PRIVATE, STATIC, FINAL)
|
||||
.addStaticBlock(generatePropertyMappings(properties))
|
||||
.addMethod(generateSetPropertySpec(classInfo, properties))
|
||||
.addMethod(getMethods)
|
||||
.build();
|
||||
|
||||
JavaFile javaFile = JavaFile.builder(className.packageName(), holderClass)
|
||||
.addFileComment("Generated by " + getClass().getName())
|
||||
.build();
|
||||
|
||||
javaFile.writeTo(mFiler);
|
||||
}
|
||||
|
||||
private String getClassName(TypeElement type, String packageName) {
|
||||
int packageLen = packageName.length() + 1;
|
||||
return type.getQualifiedName().toString().substring(packageLen).replace('.', '$');
|
||||
}
|
||||
|
||||
private static TypeName getSuperType(ClassInfo classInfo) {
|
||||
switch (classInfo.getType()) {
|
||||
case VIEW_MANAGER:
|
||||
return ParameterizedTypeName.get(
|
||||
VIEW_MANAGER_SETTER_TYPE,
|
||||
classInfo.mClassName,
|
||||
classInfo.mViewType);
|
||||
case SHADOW_NODE:
|
||||
return ParameterizedTypeName.get(SHADOW_NODE_SETTER_TYPE, classInfo.mClassName);
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
private static CodeBlock generatePropertyMappings(List<PropertyInfo> properties) {
|
||||
if (properties.isEmpty()) {
|
||||
return CodeBlock.builder()
|
||||
.addStatement("mappings = $T.emptyMap()", Collections.class)
|
||||
.build();
|
||||
}
|
||||
|
||||
CodeBlock.Builder builder = CodeBlock.builder()
|
||||
.addStatement("mappings = new $T($L)", CONCRETE_MAPPINGS_MAP_TYPE, properties.size());
|
||||
|
||||
for (int i = 0, size = properties.size(); i < size; i++) {
|
||||
PropertyInfo propertyInfo = properties.get(i);
|
||||
builder.addStatement("mappings.put($S, $L)", propertyInfo.mProperty.name(), i);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static MethodSpec generateSetPropertySpec(
|
||||
ClassInfo classInfo,
|
||||
List<PropertyInfo> properties) {
|
||||
MethodSpec.Builder builder = MethodSpec.methodBuilder("setProperty")
|
||||
.addModifiers(PUBLIC)
|
||||
.addAnnotation(Override.class)
|
||||
.returns(TypeName.VOID);
|
||||
|
||||
switch (classInfo.getType()) {
|
||||
case VIEW_MANAGER:
|
||||
builder
|
||||
.addParameter(classInfo.mClassName, "manager")
|
||||
.addParameter(classInfo.mViewType, "view");
|
||||
break;
|
||||
case SHADOW_NODE:
|
||||
builder
|
||||
.addParameter(classInfo.mClassName, "node");
|
||||
break;
|
||||
}
|
||||
|
||||
return builder
|
||||
.addParameter(STRING_TYPE, "name")
|
||||
.addParameter(PROPS_TYPE, "props")
|
||||
.addCode(generateSetProperty(classInfo, properties))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static CodeBlock generateSetProperty(ClassInfo info, List<PropertyInfo> properties) {
|
||||
if (properties.isEmpty()) {
|
||||
return CodeBlock.builder().build();
|
||||
}
|
||||
|
||||
CodeBlock.Builder builder = CodeBlock.builder()
|
||||
.addStatement("Integer id = mappings.get(name)")
|
||||
.addStatement("if (id == null) return");
|
||||
|
||||
builder.add("switch (id) {\n").indent();
|
||||
for (int i = 0, size = properties.size(); i < size; i++) {
|
||||
PropertyInfo propertyInfo = properties.get(i);
|
||||
builder
|
||||
.add("case $L:\n", i)
|
||||
.indent();
|
||||
|
||||
switch (info.getType()) {
|
||||
case VIEW_MANAGER:
|
||||
builder.add("manager.$L(view, ", propertyInfo.methodName);
|
||||
break;
|
||||
case SHADOW_NODE:
|
||||
builder.add("node.$L(", propertyInfo.methodName);
|
||||
break;
|
||||
}
|
||||
if (propertyInfo.mProperty instanceof GroupProperty) {
|
||||
builder.add("$L, ", ((GroupProperty) propertyInfo.mProperty).mGroupIndex);
|
||||
}
|
||||
if (BOXED_PRIMITIVES.contains(propertyInfo.propertyType)) {
|
||||
builder.add("props.isNull(name) ? null : ");
|
||||
}
|
||||
getPropertyExtractor(propertyInfo, builder);
|
||||
builder.addStatement(")");
|
||||
|
||||
builder
|
||||
.addStatement("break")
|
||||
.unindent();
|
||||
}
|
||||
builder.unindent().add("}\n");
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static CodeBlock.Builder getPropertyExtractor(
|
||||
PropertyInfo info,
|
||||
CodeBlock.Builder builder) {
|
||||
TypeName propertyType = info.propertyType;
|
||||
if (propertyType.equals(STRING_TYPE)) {
|
||||
return builder.add("props.getString(name)");
|
||||
} else if (propertyType.equals(READABLE_ARRAY_TYPE)) {
|
||||
return builder.add("props.getArray(name)");
|
||||
} else if (propertyType.equals(READABLE_MAP_TYPE)) {
|
||||
return builder.add("props.getMap(name)");
|
||||
}
|
||||
|
||||
if (BOXED_PRIMITIVES.contains(propertyType)) {
|
||||
propertyType = propertyType.unbox();
|
||||
}
|
||||
|
||||
if (propertyType.equals(TypeName.BOOLEAN)) {
|
||||
return builder.add("props.getBoolean(name, $L)", info.mProperty.defaultBoolean());
|
||||
} if (propertyType.equals(TypeName.DOUBLE)) {
|
||||
double defaultDouble = info.mProperty.defaultDouble();
|
||||
if (Double.isNaN(defaultDouble)) {
|
||||
return builder.add("props.getDouble(name, $T.NaN)", Double.class);
|
||||
} else {
|
||||
return builder.add("props.getDouble(name, $Lf)", defaultDouble);
|
||||
}
|
||||
}
|
||||
if (propertyType.equals(TypeName.FLOAT)) {
|
||||
float defaultFloat = info.mProperty.defaultFloat();
|
||||
if (Float.isNaN(defaultFloat)) {
|
||||
return builder.add("props.getFloat(name, $T.NaN)", Float.class);
|
||||
} else {
|
||||
return builder.add("props.getFloat(name, $Lf)", defaultFloat);
|
||||
}
|
||||
}
|
||||
if (propertyType.equals(TypeName.INT)) {
|
||||
return builder.add("props.getInt(name, $L)", info.mProperty.defaultInt());
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
private static CodeBlock generateGetProperties(List<PropertyInfo> properties)
|
||||
throws ReactPropertyException {
|
||||
if (properties.isEmpty()) {
|
||||
return CodeBlock.builder()
|
||||
.addStatement("return $T.emptyMap()", Collections.class)
|
||||
.build();
|
||||
}
|
||||
|
||||
CodeBlock.Builder builder = CodeBlock.builder()
|
||||
.addStatement(
|
||||
"$T props = new $T($L)",
|
||||
PROPERTY_MAP_TYPE,
|
||||
CONCRETE_PROPERTY_MAP_TYPE,
|
||||
properties.size());
|
||||
|
||||
for (PropertyInfo propertyInfo : properties) {
|
||||
try {
|
||||
String typeName = getPropertypTypeName(propertyInfo.mProperty, propertyInfo.propertyType);
|
||||
builder.addStatement("props.put($S, $S)", propertyInfo.mProperty.name(), typeName);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ReactPropertyException(e.getMessage(), propertyInfo);
|
||||
}
|
||||
}
|
||||
|
||||
return builder
|
||||
.addStatement("return props")
|
||||
.build();
|
||||
}
|
||||
|
||||
private static String getPropertypTypeName(Property property, TypeName propertyType) {
|
||||
String defaultType = DEFAULT_TYPES.get(propertyType);
|
||||
String useDefaultType = property instanceof RegularProperty ?
|
||||
ReactProp.USE_DEFAULT_TYPE : ReactPropGroup.USE_DEFAULT_TYPE;
|
||||
return useDefaultType.equals(property.customType()) ? defaultType : property.customType();
|
||||
}
|
||||
|
||||
private static void checkElement(Element element) throws ReactPropertyException {
|
||||
if (element.getKind() == ElementKind.METHOD
|
||||
&& element.getModifiers().contains(PUBLIC)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ReactPropertyException(
|
||||
"@ReactProp and @ReachPropGroup annotation must be on a public method",
|
||||
element);
|
||||
}
|
||||
|
||||
private static boolean shouldIgnoreClass(ClassInfo classInfo) {
|
||||
return classInfo.mElement.getModifiers().contains(PRIVATE)
|
||||
|| classInfo.mElement.getModifiers().contains(ABSTRACT)
|
||||
|| classInfo.mViewType instanceof TypeVariableName;
|
||||
}
|
||||
|
||||
private static boolean shouldWarnClass(ClassInfo classInfo) {
|
||||
return classInfo.mElement.getModifiers().contains(PRIVATE);
|
||||
}
|
||||
|
||||
private void error(Element element, String message) {
|
||||
mMessager.printMessage(ERROR, message, element);
|
||||
}
|
||||
|
||||
private void error(String message) {
|
||||
mMessager.printMessage(ERROR, message);
|
||||
}
|
||||
|
||||
private void warning(Element element, String message) {
|
||||
mMessager.printMessage(WARNING, message, element);
|
||||
}
|
||||
|
||||
private interface Property {
|
||||
String name();
|
||||
String customType();
|
||||
double defaultDouble();
|
||||
float defaultFloat();
|
||||
int defaultInt();
|
||||
boolean defaultBoolean();
|
||||
}
|
||||
|
||||
private static class RegularProperty implements Property {
|
||||
private final ReactProp mProp;
|
||||
|
||||
public RegularProperty(ReactProp prop) {
|
||||
mProp = prop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return mProp.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String customType() {
|
||||
return mProp.customType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double defaultDouble() {
|
||||
return mProp.defaultDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float defaultFloat() {
|
||||
return mProp.defaultFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int defaultInt() {
|
||||
return mProp.defaultInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean defaultBoolean() {
|
||||
return mProp.defaultBoolean();
|
||||
}
|
||||
}
|
||||
|
||||
private static class GroupProperty implements Property {
|
||||
private final ReactPropGroup mProp;
|
||||
private final int mGroupIndex;
|
||||
|
||||
public GroupProperty(ReactPropGroup prop, int groupIndex) {
|
||||
mProp = prop;
|
||||
mGroupIndex = groupIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return mProp.names()[mGroupIndex];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String customType() {
|
||||
return mProp.customType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double defaultDouble() {
|
||||
return mProp.defaultDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float defaultFloat() {
|
||||
return mProp.defaultFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int defaultInt() {
|
||||
return mProp.defaultInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean defaultBoolean() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private enum SettableType {
|
||||
VIEW_MANAGER,
|
||||
SHADOW_NODE
|
||||
}
|
||||
|
||||
private static class ClassInfo {
|
||||
public final ClassName mClassName;
|
||||
public final Element mElement;
|
||||
public final @Nullable TypeName mViewType;
|
||||
public final List<PropertyInfo> mProperties;
|
||||
|
||||
public ClassInfo(ClassName className, TypeElement element, @Nullable TypeName viewType) {
|
||||
mClassName = className;
|
||||
mElement = element;
|
||||
mViewType = viewType;
|
||||
mProperties = new ArrayList<>();
|
||||
}
|
||||
|
||||
public SettableType getType() {
|
||||
return mViewType == null ? SettableType.SHADOW_NODE : SettableType.VIEW_MANAGER;
|
||||
}
|
||||
|
||||
public void addProperty(PropertyInfo propertyInfo) throws ReactPropertyException {
|
||||
String name = propertyInfo.mProperty.name();
|
||||
if (checkPropertyExists(name)) {
|
||||
throw new ReactPropertyException(
|
||||
"Module " + mClassName + " has already registered a property named \"" +
|
||||
name + '"', propertyInfo);
|
||||
}
|
||||
|
||||
mProperties.add(propertyInfo);
|
||||
}
|
||||
|
||||
private boolean checkPropertyExists(String name) {
|
||||
for (PropertyInfo propertyInfo : mProperties) {
|
||||
if (propertyInfo.mProperty.name().equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static class PropertyInfo {
|
||||
public final String methodName;
|
||||
public final TypeName propertyType;
|
||||
public final Element element;
|
||||
public final Property mProperty;
|
||||
|
||||
private PropertyInfo(
|
||||
String methodName,
|
||||
TypeName propertyType,
|
||||
Element element,
|
||||
Property property) {
|
||||
this.methodName = methodName;
|
||||
this.propertyType = propertyType;
|
||||
this.element = element;
|
||||
mProperty = property;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final Types mTypes;
|
||||
private final Elements mElements;
|
||||
private final ClassInfo mClassInfo;
|
||||
|
||||
public Builder(Types types, Elements elements, ClassInfo classInfo) {
|
||||
mTypes = types;
|
||||
mElements = elements;
|
||||
mClassInfo = classInfo;
|
||||
}
|
||||
|
||||
public PropertyInfo build(Element element, Property property)
|
||||
throws ReactPropertyException {
|
||||
String methodName = element.getSimpleName().toString();
|
||||
|
||||
ExecutableElement method = (ExecutableElement) element;
|
||||
List<? extends VariableElement> parameters = method.getParameters();
|
||||
|
||||
if (parameters.size() != getArgCount(mClassInfo.getType(), property)) {
|
||||
throw new ReactPropertyException("Wrong number of args", element);
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
if (mClassInfo.getType() == SettableType.VIEW_MANAGER) {
|
||||
TypeMirror mirror = parameters.get(index++).asType();
|
||||
if (!mTypes.isSubtype(mirror, mElements.getTypeElement("android.view.View").asType())) {
|
||||
throw new ReactPropertyException("First argument must be a subclass of View", element);
|
||||
}
|
||||
}
|
||||
|
||||
if (property instanceof GroupProperty) {
|
||||
TypeName indexType = TypeName.get(parameters.get(index++).asType());
|
||||
if (!indexType.equals(TypeName.INT)) {
|
||||
throw new ReactPropertyException(
|
||||
"Argument " + index + " must be an int for @ReactPropGroup",
|
||||
element);
|
||||
}
|
||||
}
|
||||
|
||||
TypeName propertyType = TypeName.get(parameters.get(index++).asType());
|
||||
if (!DEFAULT_TYPES.containsKey(propertyType)) {
|
||||
throw new ReactPropertyException(
|
||||
"Argument " + index + " must be of a supported type",
|
||||
element);
|
||||
}
|
||||
|
||||
return new PropertyInfo(methodName, propertyType, element, property);
|
||||
}
|
||||
|
||||
private static int getArgCount(SettableType type, Property property) {
|
||||
int baseCount = type == SettableType.SHADOW_NODE ? 1 : 2;
|
||||
return property instanceof GroupProperty ? baseCount + 1 : baseCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReactPropertyException extends Exception {
|
||||
public final Element element;
|
||||
|
||||
public ReactPropertyException(String message, PropertyInfo propertyInfo) {
|
||||
super(message);
|
||||
this.element = propertyInfo.element;
|
||||
}
|
||||
|
||||
public ReactPropertyException(String message, Element element) {
|
||||
super(message);
|
||||
this.element = element;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,10 @@ package com.facebook.react.uimanager;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
import com.facebook.csslayout.CSSNode;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
import com.facebook.react.uimanager.annotations.ReactPropertyHolder;
|
||||
|
||||
/**
|
||||
* Base node class for representing virtual tree of React nodes. Shadow nodes are used primarily
|
||||
@@ -42,6 +40,7 @@ import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
* children (e.g. {@link #getNativeChildCount()}). See {@link NativeViewHierarchyOptimizer} for more
|
||||
* information.
|
||||
*/
|
||||
@ReactPropertyHolder
|
||||
public class ReactShadowNode extends CSSNode {
|
||||
|
||||
private int mReactTag;
|
||||
@@ -170,17 +169,7 @@ public class ReactShadowNode extends CSSNode {
|
||||
}
|
||||
|
||||
public final void updateProperties(CatalystStylesDiffMap props) {
|
||||
Map<String, ViewManagersPropertyCache.PropSetter> propSetters =
|
||||
ViewManagersPropertyCache.getNativePropSettersForShadowNodeClass(getClass());
|
||||
ReadableMap propMap = props.mBackingMap;
|
||||
ReadableMapKeySetIterator iterator = propMap.keySetIterator();
|
||||
while (iterator.hasNextKey()) {
|
||||
String key = iterator.nextKey();
|
||||
ViewManagersPropertyCache.PropSetter setter = propSetters.get(key);
|
||||
if (setter != null) {
|
||||
setter.updateShadowNodeProp(this, props);
|
||||
}
|
||||
}
|
||||
ViewManagerPropertyUpdater.updateProps(this, props);
|
||||
onAfterUpdateTransaction();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,32 +16,22 @@ import java.util.Map;
|
||||
import android.view.View;
|
||||
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
import com.facebook.react.touch.CatalystInterceptingViewGroup;
|
||||
import com.facebook.react.touch.JSResponderHandler;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.annotations.ReactPropGroup;
|
||||
import com.facebook.react.uimanager.annotations.ReactPropertyHolder;
|
||||
|
||||
/**
|
||||
* Class responsible for knowing how to create and update catalyst Views of a given type. It is also
|
||||
* responsible for creating and updating CSSNode subclasses used for calculating position and size
|
||||
* for the corresponding native view.
|
||||
*/
|
||||
@ReactPropertyHolder
|
||||
public abstract class ViewManager<T extends View, C extends ReactShadowNode> {
|
||||
|
||||
public final void updateProperties(T viewToUpdate, CatalystStylesDiffMap props) {
|
||||
Map<String, ViewManagersPropertyCache.PropSetter> propSetters =
|
||||
ViewManagersPropertyCache.getNativePropSettersForViewManagerClass(getClass());
|
||||
ReadableMap propMap = props.mBackingMap;
|
||||
ReadableMapKeySetIterator iterator = propMap.keySetIterator();
|
||||
while (iterator.hasNextKey()) {
|
||||
String key = iterator.nextKey();
|
||||
ViewManagersPropertyCache.PropSetter setter = propSetters.get(key);
|
||||
if (setter != null) {
|
||||
setter.updateViewProp(this, viewToUpdate, props);
|
||||
}
|
||||
}
|
||||
ViewManagerPropertyUpdater.updateProps(this, viewToUpdate, props);
|
||||
onAfterUpdateTransaction(viewToUpdate);
|
||||
}
|
||||
|
||||
@@ -206,6 +196,6 @@ public abstract class ViewManager<T extends View, C extends ReactShadowNode> {
|
||||
}
|
||||
|
||||
public Map<String, String> getNativeProps() {
|
||||
return ViewManagersPropertyCache.getNativePropsForView(getClass(), getShadowNodeClass());
|
||||
return ViewManagerPropertyUpdater.getNativeProps(getClass(), getShadowNodeClass());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.react.uimanager;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
|
||||
public class ViewManagerPropertyUpdater {
|
||||
public interface Settable {
|
||||
Map<String, String> getProperties();
|
||||
}
|
||||
|
||||
public interface ViewManagerSetter<T extends ViewManager, V extends View> extends Settable {
|
||||
void setProperty(T manager, V view, String name, CatalystStylesDiffMap props);
|
||||
}
|
||||
|
||||
public interface ShadowNodeSetter<T extends ReactShadowNode> extends Settable {
|
||||
void setProperty(T node, String name, CatalystStylesDiffMap props);
|
||||
}
|
||||
|
||||
private static final String TAG = "ViewManagerPropertyUpdater";
|
||||
|
||||
private static final Map<Class<?>, ViewManagerSetter<?, ?>> VIEW_MANAGER_SETTER_MAP =
|
||||
new HashMap<>();
|
||||
private static final Map<Class<?>, ShadowNodeSetter<?>> SHADOW_NODE_SETTER_MAP = new HashMap<>();
|
||||
|
||||
public static <T extends ViewManager, V extends View> void updateProps(
|
||||
T manager,
|
||||
V v,
|
||||
CatalystStylesDiffMap props) {
|
||||
ViewManagerSetter<T, V> setter = findManagerSetter(manager.getClass());
|
||||
ReadableMap propMap = props.mBackingMap;
|
||||
ReadableMapKeySetIterator iterator = propMap.keySetIterator();
|
||||
while (iterator.hasNextKey()) {
|
||||
String key = iterator.nextKey();
|
||||
setter.setProperty(manager, v, key, props);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T extends ReactShadowNode> void updateProps(T node, CatalystStylesDiffMap props) {
|
||||
ShadowNodeSetter<T> setter = findNodeSetter(node.getClass());
|
||||
ReadableMap propMap = props.mBackingMap;
|
||||
ReadableMapKeySetIterator iterator = propMap.keySetIterator();
|
||||
while (iterator.hasNextKey()) {
|
||||
String key = iterator.nextKey();
|
||||
setter.setProperty(node, key, props);
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, String> getNativeProps(
|
||||
Class<? extends ViewManager> viewManagerTopClass,
|
||||
Class<? extends ReactShadowNode> shadowNodeTopClass) {
|
||||
Map<String, String> props = new HashMap<>();
|
||||
props.putAll(findManagerSetter(viewManagerTopClass).getProperties());
|
||||
props.putAll(findNodeSetter(shadowNodeTopClass).getProperties());
|
||||
return props;
|
||||
}
|
||||
|
||||
private static <T extends ViewManager, V extends View> ViewManagerSetter<T, V> findManagerSetter(
|
||||
Class<? extends ViewManager> managerClass) {
|
||||
@SuppressWarnings("unchecked")
|
||||
ViewManagerSetter<T, V> setter =
|
||||
(ViewManagerSetter<T, V>) VIEW_MANAGER_SETTER_MAP.get(managerClass);
|
||||
if (setter == null) {
|
||||
setter = findGeneratedSetter(managerClass);
|
||||
if (setter == null) {
|
||||
setter = new FallbackViewManagerSetter<>(managerClass);
|
||||
}
|
||||
VIEW_MANAGER_SETTER_MAP.put(managerClass, setter);
|
||||
}
|
||||
|
||||
return setter;
|
||||
}
|
||||
|
||||
private static <T extends ReactShadowNode> ShadowNodeSetter<T> findNodeSetter(
|
||||
Class<? extends ReactShadowNode> nodeClass) {
|
||||
@SuppressWarnings("unchecked")
|
||||
ShadowNodeSetter<T> setter = (ShadowNodeSetter<T>) SHADOW_NODE_SETTER_MAP.get(nodeClass);
|
||||
if (setter == null) {
|
||||
setter = findGeneratedSetter(nodeClass);
|
||||
if (setter == null) {
|
||||
setter = new FallbackShadowNodeSetter<>(nodeClass);
|
||||
}
|
||||
SHADOW_NODE_SETTER_MAP.put(nodeClass, setter);
|
||||
}
|
||||
|
||||
return setter;
|
||||
}
|
||||
|
||||
private static <T> T findGeneratedSetter(Class<?> cls) {
|
||||
String clsName = cls.getName();
|
||||
try {
|
||||
Class<?> setterClass = Class.forName(clsName + "$$PropsSetter");
|
||||
//noinspection unchecked
|
||||
return (T) setterClass.newInstance();
|
||||
} catch (ClassNotFoundException e) {
|
||||
FLog.w(TAG, "Could not find generated setter for " + cls);
|
||||
return null;
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new RuntimeException("Unable to instantiate methods getter for " + clsName, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FallbackViewManagerSetter<T extends ViewManager, V extends View>
|
||||
implements ViewManagerSetter<T, V> {
|
||||
private final Map<String, ViewManagersPropertyCache.PropSetter> mPropSetters;
|
||||
|
||||
private FallbackViewManagerSetter(Class<? extends ViewManager> viewManagerClass) {
|
||||
mPropSetters =
|
||||
ViewManagersPropertyCache.getNativePropSettersForViewManagerClass(viewManagerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(T manager, V v, String name, CatalystStylesDiffMap props) {
|
||||
ViewManagersPropertyCache.PropSetter setter = mPropSetters.get(name);
|
||||
if (setter != null) {
|
||||
setter.updateViewProp(manager, v, props);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProperties() {
|
||||
Map<String, String> nativeProps = new HashMap<>();
|
||||
for (ViewManagersPropertyCache.PropSetter setter : mPropSetters.values()) {
|
||||
nativeProps.put(setter.getPropName(), setter.getPropType());
|
||||
}
|
||||
return nativeProps;
|
||||
}
|
||||
}
|
||||
|
||||
private static class FallbackShadowNodeSetter<T extends ReactShadowNode>
|
||||
implements ShadowNodeSetter<T> {
|
||||
private final Map<String, ViewManagersPropertyCache.PropSetter> mPropSetters;
|
||||
|
||||
private FallbackShadowNodeSetter(Class<? extends ReactShadowNode> shadowNodeClass) {
|
||||
mPropSetters =
|
||||
ViewManagersPropertyCache.getNativePropSettersForShadowNodeClass(shadowNodeClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(ReactShadowNode node, String name, CatalystStylesDiffMap props) {
|
||||
ViewManagersPropertyCache.PropSetter setter = mPropSetters.get(name);
|
||||
if (setter != null) {
|
||||
setter.updateShadowNodeProp(node, props);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProperties() {
|
||||
Map<String, String> nativeProps = new HashMap<>();
|
||||
for (ViewManagersPropertyCache.PropSetter setter : mPropSetters.values()) {
|
||||
nativeProps.put(setter.getPropName(), setter.getPropType());
|
||||
}
|
||||
return nativeProps;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.react.uimanager.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Inherited
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface ReactPropertyHolder {
|
||||
}
|
||||
@@ -32,7 +32,7 @@ public class ReactSwitchManager extends SimpleViewManager<ReactSwitch> {
|
||||
|
||||
private static final String REACT_CLASS = "AndroidSwitch";
|
||||
|
||||
private static class ReactSwitchShadowNode extends LayoutShadowNode implements
|
||||
static class ReactSwitchShadowNode extends LayoutShadowNode implements
|
||||
CSSNode.MeasureFunction {
|
||||
|
||||
private int mWidth;
|
||||
|
||||
@@ -84,14 +84,6 @@ public class ReactViewManager extends ViewGroupManager<ReactViewGroup> {
|
||||
null : ReactDrawableHelper.createDrawableFromJSDescription(view.getContext(), bg));
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.BORDER_WIDTH, defaultFloat = CSSConstants.UNDEFINED)
|
||||
public void setBorderWidth(ReactViewGroup view, float width) {
|
||||
if (!CSSConstants.isUndefined(width)) {
|
||||
width = PixelUtil.toPixelFromDIP(width);
|
||||
}
|
||||
view.setBorderWidth(Spacing.ALL, width);
|
||||
}
|
||||
|
||||
@ReactProp(name = ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS)
|
||||
public void setRemoveClippedSubviews(ReactViewGroup view, boolean removeClippedSubviews) {
|
||||
view.setRemoveClippedSubviews(removeClippedSubviews);
|
||||
|
||||
Reference in New Issue
Block a user