/**
* Copyright (c) 2017-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.litho.specmodels.generator;
import javax.lang.model.element.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
import com.facebook.litho.specmodels.model.ClassNames;
import com.facebook.litho.specmodels.model.EventDeclarationModel;
import com.facebook.litho.specmodels.model.PropModel;
import com.facebook.litho.specmodels.model.SpecModel;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ArrayTypeName;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;
/**
* Class that generates the builder for a Component.
*/
public class BuilderGenerator {
private static final String BUILDER = "Builder";
private static final String BUILDER_POOL_FIELD = "mBuilderPool";
private static final ClassName BUILDER_CLASS_NAME = ClassName.bestGuess(BUILDER);
private static final String CONTEXT_MEMBER_NAME = "mContext";
private static final String CONTEXT_PARAM_NAME = "context";
private static final String REQUIRED_PROPS_NAMES = "REQUIRED_PROPS_NAMES";
private static final String REQUIRED_PROPS_COUNT = "REQUIRED_PROPS_COUNT";
private BuilderGenerator() {
}
public static TypeSpecDataHolder generate(SpecModel specModel) {
return TypeSpecDataHolder.newBuilder()
.addTypeSpecDataHolder(generateFactoryMethod(specModel))
.addMethod(generateCreateBuilderMethodWithStyle(specModel))
.addMethod(generateCreateBuilderMethod(specModel))
.addTypeSpecDataHolder(generateBuilder(specModel))
.build();
}
static MethodSpec generateCreateBuilderMethod(SpecModel specModel) {
return MethodSpec.methodBuilder("create")
.addModifiers(Modifier.PUBLIC)
.returns(BUILDER_CLASS_NAME)
.addParameter(specModel.getContextClass(), "context")
.addStatement("return create(context, 0, 0)")
.addModifiers(!specModel.hasInjectedDependencies() ? Modifier.STATIC : Modifier.FINAL)
.build();
}
static MethodSpec generateCreateBuilderMethodWithStyle(SpecModel specModel) {
return MethodSpec.methodBuilder("create")
.addModifiers(Modifier.PUBLIC)
.returns(BUILDER_CLASS_NAME)
.addParameter(specModel.getContextClass(), "context")
.addParameter(int.class, "defStyleAttr")
.addParameter(int.class, "defStyleRes")
.addStatement(
"return new$L(context, defStyleAttr, defStyleRes, new $L())",
BUILDER_CLASS_NAME,
ComponentImplGenerator.getImplClassName(specModel))
.addModifiers(!specModel.hasInjectedDependencies() ? Modifier.STATIC : Modifier.FINAL)
.build();
}
static TypeSpecDataHolder generateFactoryMethod(SpecModel specModel) {
final String implClassName = ComponentImplGenerator.getImplClassName(specModel);
final String implParamName = ComponentImplGenerator.getImplInstanceName(specModel);
final ClassName stateClass = ClassName.bestGuess(implClassName);
final ParameterizedTypeName synchronizedPoolClass =
ParameterizedTypeName.get(ClassNames.SYNCHRONIZED_POOL, BUILDER_CLASS_NAME);
final FieldSpec.Builder poolField = FieldSpec.builder(synchronizedPoolClass, BUILDER_POOL_FIELD)
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.initializer("new $T(2)", synchronizedPoolClass);
final MethodSpec.Builder factoryMethod = MethodSpec.methodBuilder(getFactoryMethodName())
.addModifiers(Modifier.PRIVATE)
.returns(BUILDER_CLASS_NAME)
.addParameter(specModel.getContextClass(), "context")
.addStatement("$T builder = $L.acquire()", BUILDER_CLASS_NAME, BUILDER_POOL_FIELD)
.beginControlFlow("if (builder == null)")
.addStatement("builder = new $T()", BUILDER_CLASS_NAME)
.endControlFlow();
if (specModel.isStylingSupported()) {
factoryMethod
.addParameter(int.class, "defStyleAttr")
.addParameter(int.class, "defStyleRes")
.addParameter(stateClass, implParamName)
.addStatement(
"builder.init(context, defStyleAttr, defStyleRes, $L)", implParamName);
} else {
factoryMethod
.addParameter(stateClass, implParamName)
.addStatement("builder.init(context, $L)", implParamName);
}
factoryMethod.addStatement("return builder");
if (!specModel.hasInjectedDependencies() || specModel.getTypeVariables().isEmpty()) {
factoryMethod.addModifiers(Modifier.STATIC);
poolField.addModifiers(Modifier.STATIC);
}
return TypeSpecDataHolder.newBuilder()
.addMethod(factoryMethod.build())
.addField(poolField.build())
.build();
}
static TypeSpecDataHolder generateBuilder(SpecModel specModel) {
final String implClassName = ComponentImplGenerator.getImplClassName(specModel);
final String implParamName = ComponentImplGenerator.getImplInstanceName(specModel);
final String implMemberInstanceName = getImplMemberInstanceName(specModel);
final ClassName implClass = ClassName.bestGuess(implClassName);
final MethodSpec.Builder initMethodSpec = MethodSpec.methodBuilder("init")
.addModifiers(Modifier.PRIVATE)
.addParameter(specModel.getContextClass(), CONTEXT_PARAM_NAME);
if (specModel.isStylingSupported()) {
initMethodSpec
.addParameter(int.class, "defStyleAttr")
.addParameter(int.class, "defStyleRes")
.addParameter(implClass, implParamName)
.addStatement("super.init(context, defStyleAttr, defStyleRes, $L)", implParamName);
} else {
initMethodSpec
.addParameter(implClass, implParamName)
.addStatement("super.init(context, $L)", implParamName);
}
initMethodSpec
.addStatement("$L = $L", implMemberInstanceName, implParamName)
.addStatement("$L = $L", CONTEXT_MEMBER_NAME, CONTEXT_PARAM_NAME);
final TypeSpec.Builder propsBuilderClassBuilder = TypeSpec.classBuilder(BUILDER)
.addModifiers(Modifier.PUBLIC)
.superclass(
ParameterizedTypeName.get(
ClassName.get(
specModel.getComponentClass().packageName(),
specModel.getComponentClass().simpleName(),
BUILDER),
specModel.getComponentTypeName()))
.addField(implClass, implMemberInstanceName)
.addField(specModel.getContextClass(), CONTEXT_MEMBER_NAME);
final List<String> requiredPropNames = new ArrayList<>();
int numRequiredProps = 0;
for (PropModel prop : specModel.getProps()) {
if (!prop.isOptional()) {
numRequiredProps++;
requiredPropNames.add(prop.getName());
}
}
if (numRequiredProps > 0) {
final FieldSpec.Builder requiredPropsNamesBuilder =
FieldSpec.builder(
String[].class,
REQUIRED_PROPS_NAMES,
Modifier.PRIVATE,
Modifier.FINAL)
.initializer("new String[] {$L}", commaSeparateAndQuoteStrings(requiredPropNames));
if (!specModel.hasInjectedDependencies()) {
requiredPropsNamesBuilder.addModifiers(Modifier.STATIC);
}
propsBuilderClassBuilder.addField(requiredPropsNamesBuilder.build());
final FieldSpec.Builder requiredPropsCountBuilder =
FieldSpec.builder(
int.class,
REQUIRED_PROPS_COUNT,
Modifier.PRIVATE,
Modifier.FINAL)
.initializer("$L", numRequiredProps);
if (!specModel.hasInjectedDependencies()) {
requiredPropsCountBuilder.addModifiers(Modifier.STATIC);
}
propsBuilderClassBuilder.addField(requiredPropsCountBuilder.build());
propsBuilderClassBuilder
.addField(FieldSpec.builder(
BitSet.class,
"mRequired",
Modifier.PRIVATE)
.initializer("new $T($L)", BitSet.class, REQUIRED_PROPS_COUNT)
.build());
initMethodSpec.addStatement("mRequired.clear()");
}
propsBuilderClassBuilder.addMethod(initMethodSpec.build());
// If there are no type variables, then this class can always be static.
// If the component implementation class is static, and there are type variables, then this
// class can be static but must shadow the type variables from the class.
// If the component implementation class is not static, and there are type variables, then this
// class is not static and we get the type variables from the class.
final boolean isBuilderStatic = specModel.getTypeVariables().isEmpty() ||
!specModel.hasInjectedDependencies();
if (isBuilderStatic) {
propsBuilderClassBuilder.addModifiers(Modifier.STATIC);
if (!specModel.getTypeVariables().isEmpty()) {
propsBuilderClassBuilder.addTypeVariables(specModel.getTypeVariables());
}
}
int requiredPropIndex = 0;
for (PropModel prop : specModel.getProps()) {
generatePropsBuilderMethods(specModel, prop, requiredPropIndex)
.addToTypeSpec(propsBuilderClassBuilder);
if (!prop.isOptional()) {
requiredPropIndex++;
}
}
for (EventDeclarationModel eventDeclaration : specModel.getEventDeclarations()) {
propsBuilderClassBuilder.addMethod(
generateEventDeclarationBuilderMethod(specModel, eventDeclaration));
}
propsBuilderClassBuilder
.addMethod(generateKeySetterMethod())
.addMethod(generateBuildMethod(specModel, numRequiredProps))
.addMethod(generateReleaseMethod(specModel));
return TypeSpecDataHolder.newBuilder().addType(propsBuilderClassBuilder.build()).build();
}
static String getFactoryMethodName() {
return "new" + BUILDER;
}
private static String getImplMemberInstanceName(SpecModel specModel) {
return "m" + ComponentImplGenerator.getImplClassName(specModel);
}
private static String commaSeparateAndQuoteStrings(List<String> strings) {
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < strings.size(); i++) {
sb.append('"');
sb.append(strings.get(i));
sb.append('"');
if (i < strings.size() - 1) {
sb.append(", ");
}
}
return sb.toString();
}
static TypeSpecDataHolder generatePropsBuilderMethods(
SpecModel specModel,
PropModel prop,
int requiredIndex) {
final TypeSpecDataHolder.Builder dataHolder = TypeSpecDataHolder.newBuilder();
if (prop.hasVarArgs()) {
dataHolder.addMethod(varArgBuilder(specModel, prop, requiredIndex));
ParameterizedTypeName type = (ParameterizedTypeName) prop.getType();
if (getRawType(type.typeArguments.get(0)).equals(ClassNames.COMPONENT)) {
dataHolder.addMethod(varArgBuilderBuilder(prop, requiredIndex));
}
return dataHolder.build();
}
switch (prop.getResType()) {
case STRING:
dataHolder.addMethod(regularBuilder(specModel, prop, requiredIndex));
dataHolder.addMethod(
resBuilder(specModel, prop, requiredIndex, ClassNames.STRING_RES, "resolveString"));
dataHolder.addMethod(resWithVarargsBuilder(
specModel,
prop,
requiredIndex,
ClassNames.STRING_RES,
"resolveString",
TypeName.OBJECT,
"formatArgs"));
dataHolder.addTypeSpecDataHolder(
attrBuilders(specModel, prop, requiredIndex, ClassNames.STRING_RES, "resolveString"));
break;
case STRING_ARRAY:
dataHolder.addMethod(regularBuilder(specModel, prop, requiredIndex));
dataHolder.addMethod(
resBuilder(specModel, prop, requiredIndex, ClassNames.ARRAY_RES, "resolveStringArray"));
dataHolder.addTypeSpecDataHolder(
attrBuilders(
specModel, prop, requiredIndex, ClassNames.ARRAY_RES, "resolveStringArray"));
break;
case INT:
dataHolder.addMethod(regularBuilder(specModel, prop, requiredIndex));
dataHolder.addMethod(
resBuilder(specModel, prop, requiredIndex, ClassNames.INT_RES, "resolveInt"));
dataHolder.addTypeSpecDataHolder(
attrBuilders(specModel, prop, requiredIndex, ClassNames.INT_RES, "resolveInt"));
break;
case INT_ARRAY:
dataHolder.addMethod(regularBuilder(specModel, prop, requiredIndex));
dataHolder.addMethod(
resBuilder(specModel, prop, requiredIndex, ClassNames.ARRAY_RES, "resolveIntArray"));
dataHolder.addTypeSpecDataHolder(
attrBuilders(specModel, prop, requiredIndex, ClassNames.ARRAY_RES, "resolveIntArray"));
break;
case BOOL:
dataHolder.addMethod(regularBuilder(specModel, prop, requiredIndex));
dataHolder.addMethod(
resBuilder(specModel, prop, requiredIndex, ClassNames.BOOL_RES, "resolveBool"));
dataHolder.addTypeSpecDataHolder(
attrBuilders(specModel, prop, requiredIndex, ClassNames.BOOL_RES, "resolveBool"));
break;
case COLOR:
dataHolder.addMethod(
regularBuilder(specModel, prop, requiredIndex, annotation(ClassNames.COLOR_INT)));
dataHolder.addMethod(
resBuilder(specModel, prop, requiredIndex, ClassNames.COLOR_RES, "resolveColor"));
dataHolder.addTypeSpecDataHolder(
attrBuilders(specModel, prop, requiredIndex, ClassNames.COLOR_RES, "resolveColor"));
break;
case DIMEN_SIZE:
dataHolder.addMethod(pxBuilder(specModel, prop, requiredIndex));
dataHolder.addMethod(
resBuilder(specModel, prop, requiredIndex, ClassNames.DIMEN_RES, "resolveDimenSize"));
dataHolder.addTypeSpecDataHolder(
attrBuilders(specModel, prop, requiredIndex, ClassNames.DIMEN_RES, "resolveDimenSize"));
dataHolder.addMethod(dipBuilder(specModel, prop, requiredIndex));
break;
case DIMEN_TEXT:
dataHolder.addMethod(pxBuilder(specModel, prop, requiredIndex));
dataHolder.addMethod(
resBuilder(specModel, prop, requiredIndex, ClassNames.DIMEN_RES, "resolveDimenSize"));
dataHolder.addTypeSpecDataHolder(
attrBuilders(specModel, prop, requiredIndex, ClassNames.DIMEN_RES, "resolveDimenSize"));
dataHolder.addMethod(dipBuilder(specModel, prop, requiredIndex));
dataHolder.addMethod(sipBuilder(specModel, prop, requiredIndex));
break;
case DIMEN_OFFSET:
dataHolder.addMethod(pxBuilder(specModel, prop, requiredIndex));
dataHolder.addMethod(
resBuilder(specModel, prop, requiredIndex, ClassNames.DIMEN_RES, "resolveDimenOffset"));
dataHolder.addTypeSpecDataHolder(
attrBuilders(
specModel,
prop,
requiredIndex,
ClassNames.DIMEN_RES,
"resolveDimenOffset"));
dataHolder.addMethod(dipBuilder(specModel, prop, requiredIndex));
break;
case FLOAT:
dataHolder.addMethod(regularBuilder(specModel, prop, requiredIndex));
dataHolder.addMethod(
resBuilder(specModel, prop, requiredIndex, ClassNames.DIMEN_RES, "resolveFloat"));
dataHolder.addTypeSpecDataHolder(
attrBuilders(specModel, prop, requiredIndex, ClassNames.DIMEN_RES, "resolveFloat"));
break;
case DRAWABLE:
dataHolder.addMethod(regularBuilder(specModel, prop, requiredIndex));
dataHolder.addMethod(
resBuilder(
specModel,
prop,
requiredIndex,
ClassNames.DRAWABLE_RES,
"resolveDrawable"));
dataHolder.addTypeSpecDataHolder(
attrBuilders(
specModel,
prop,
requiredIndex,
ClassNames.DRAWABLE_RES,
"resolveDrawable"));
break;
case NONE:
if (prop.getType().equals(specModel.getComponentClass())) {
dataHolder.addMethod(componentBuilder(specModel, prop, requiredIndex));
} else {
dataHolder.addMethod(regularBuilder(specModel, prop, requiredIndex));
}
break;
}
if (getRawType(prop.getType()).equals(ClassNames.COMPONENT)) {
dataHolder.addMethod(
builderBuilder(specModel, prop, requiredIndex, ClassNames.COMPONENT_BUILDER));
}
if (getRawType(prop.getType()).equals(ClassNames.REFERENCE)) {
dataHolder.addMethod(
builderBuilder(specModel, prop, requiredIndex, ClassNames.REFERENCE_BUILDER));
}
return dataHolder.build();
}
static TypeName getRawType(TypeName type) {
return type instanceof ParameterizedTypeName ? ((ParameterizedTypeName) type).rawType : type;
}
private static MethodSpec componentBuilder(
SpecModel specModel,
PropModel prop,
int requiredIndex) {
return builder(
specModel,
prop,
requiredIndex,
prop.getName(),
Arrays.asList(parameter(prop, prop.getType(), prop.getName())),
"$L == null ? null : $L.makeShallowCopy()",
prop.getName(),
prop.getName());
}
private static MethodSpec regularBuilder(
SpecModel specModel,
PropModel prop,
int requiredIndex,
AnnotationSpec... extraAnnotations) {
return builder(
specModel,
prop,
requiredIndex,
prop.getName(),
Arrays.asList(parameter(prop, prop.getType(), prop.getName(), extraAnnotations)),
prop.getName());
}
private static MethodSpec varArgBuilder(
SpecModel specModel,
PropModel prop,
int requiredIndex,
AnnotationSpec... extraAnnotations) {
ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) prop.getType();
TypeName singleParameterType = parameterizedTypeName.typeArguments.get(0);
String varArgName = prop.getVarArgsSingleName();
final String propName = prop.getName();
final String implMemberInstanceName = getImplMemberInstanceName(specModel);
final ParameterizedTypeName varArgType = (ParameterizedTypeName) prop.getType();
final ParameterizedTypeName listType = ParameterizedTypeName.get(
ClassName.get(ArrayList.class),
varArgType.typeArguments.get(0));
CodeBlock codeBlock = CodeBlock.builder()
.beginControlFlow("if (this.$L.$L == null)", implMemberInstanceName, propName)
.addStatement("this.$L.$L = new $T()", implMemberInstanceName, propName, listType)
.endControlFlow()
.addStatement(
"this.$L.$L.add($L)",
implMemberInstanceName,
propName,
varArgName)
.build();
return getMethodSpecBuilder(
prop,
requiredIndex,
varArgName,
Arrays.asList(parameter(prop, singleParameterType, varArgName, extraAnnotations)),
codeBlock).build();
}
private static MethodSpec varArgBuilderBuilder(
PropModel prop,
int requiredIndex) {
String varArgName = prop.getVarArgsSingleName();
final ParameterizedTypeName varArgType = (ParameterizedTypeName) prop.getType();
final TypeName internalType = varArgType.typeArguments.get(0);
CodeBlock codeBlock = CodeBlock.builder()
.addStatement("$L($L.build())", varArgName, varArgName + "Builder")
.build();
TypeName builderParameterType = ParameterizedTypeName.get(
ClassNames.COMPONENT_BUILDER,
getBuilderGenericTypes(internalType));
return getMethodSpecBuilder(
prop,
requiredIndex,
varArgName,
Arrays.asList(parameter(prop, builderParameterType, varArgName + "Builder")),
codeBlock).build();
}
private static MethodSpec resBuilder(
SpecModel specModel,
PropModel prop,
int requiredIndex,
ClassName annotationClassName,
String resolver) {
return builder(
specModel,
prop,
requiredIndex,
prop.getName() + "Res",
Arrays.asList(parameter(prop, TypeName.INT, "resId", annotation(annotationClassName))),
"$L(resId)",
resolver + "Res");
}
private static MethodSpec resWithVarargsBuilder(
SpecModel specModel,
PropModel prop,
int requiredIndex,
ClassName annotationClassName,
String resolver,
TypeName varargsType,
String varargsName) {
return getMethodSpecBuilder(
specModel,
prop,
requiredIndex,
prop.getName() + "Res",
Arrays.asList(
parameter(prop, TypeName.INT, "resId", annotation(annotationClassName)),
ParameterSpec.builder(ArrayTypeName.of(varargsType), varargsName).build()),
"$L(resId, " + varargsName + ")",
resolver + "Res")
.varargs(true)
.build();
}
private static TypeSpecDataHolder attrBuilders(
SpecModel specModel,
PropModel prop,
int requiredIndex,
ClassName annotationClassName,
String resolver) {
final TypeSpecDataHolder.Builder dataHolder = TypeSpecDataHolder.newBuilder();
dataHolder.addMethod(builder(
specModel,
prop,
requiredIndex,
prop.getName() + "Attr",
Arrays.asList(
parameter(prop, TypeName.INT, "attrResId", annotation(ClassNames.ATTR_RES)),
parameter(prop, TypeName.INT, "defResId", annotation(annotationClassName))),
"$L(attrResId, defResId)",
resolver + "Attr"));
dataHolder.addMethod(builder(
specModel,
prop,
requiredIndex,
prop.getName() + "Attr",
Arrays.asList(parameter(prop, TypeName.INT, "attrResId", annotation(ClassNames.ATTR_RES))),
"$L(attrResId, 0)",
resolver + "Attr"));
return dataHolder.build();
}
private static MethodSpec pxBuilder(
SpecModel specModel,
PropModel prop,
int requiredIndex) {
return builder(
specModel,
prop,
requiredIndex,
prop.getName() + "Px",
Arrays.asList(parameter(prop, prop.getType(), prop.getName(), annotation(ClassNames.PX))),
prop.getName());
}
private static MethodSpec dipBuilder(
SpecModel specModel,
PropModel prop,
int requiredIndex) {
AnnotationSpec dipAnnotation = AnnotationSpec.builder(ClassNames.DIMENSION)
.addMember("unit", "$T.DP", ClassNames.DIMENSION)
.build();
return builder(
specModel,
prop,
requiredIndex,
prop.getName() + "Dip",
Arrays.asList(parameter(prop, TypeName.FLOAT, "dips", dipAnnotation)),
"dipsToPixels(dips)");
}
private static MethodSpec sipBuilder(
SpecModel specModel,
PropModel prop,
int requiredIndex) {
AnnotationSpec spAnnotation = AnnotationSpec.builder(ClassNames.DIMENSION)
.addMember("unit", "$T.SP", ClassNames.DIMENSION)
.build();
return builder(
specModel,
prop,
requiredIndex,
prop.getName() + "Sp",
Arrays.asList(parameter(prop, TypeName.FLOAT, "sips", spAnnotation)),
"sipsToPixels(sips)");
}
private static MethodSpec builderBuilder(
SpecModel specModel,
PropModel prop,
int requiredIndex,
ClassName builderClass) {
return builder(
specModel,
prop,
requiredIndex,
prop.getName(),
Arrays.asList(parameter(
prop,
ParameterizedTypeName.get(builderClass, getBuilderGenericTypes(prop)),
prop.getName() + "Builder")),
"$L.build()",
prop.getName() + "Builder");
}
private static TypeName[] getBuilderGenericTypes(PropModel prop) {
return getBuilderGenericTypes(prop.getType());
}
private static TypeName[] getBuilderGenericTypes(TypeName type) {
final TypeName typeParameter =
type instanceof ParameterizedTypeName &&
!((ParameterizedTypeName) type).typeArguments.isEmpty() ?
((ParameterizedTypeName) type).typeArguments.get(0) :
WildcardTypeName.subtypeOf(ClassNames.COMPONENT_LIFECYCLE);
return new TypeName[]{typeParameter};
}
private static ParameterSpec parameter(
PropModel prop,
TypeName type,
String name,
AnnotationSpec... extraAnnotations) {
final ParameterSpec.Builder builder =
ParameterSpec.builder(type, name)
.addAnnotations(prop.getExternalAnnotations());
for (AnnotationSpec annotation : extraAnnotations) {
builder.addAnnotation(annotation);
}
return builder.build();
}
private static AnnotationSpec annotation(ClassName className) {
return AnnotationSpec.builder(className).build();
}
private static MethodSpec builder(
SpecModel specModel,
PropModel prop,
int requiredIndex,
String name,
List<ParameterSpec> parameters,
String statement,
Object... formatObjects) {
return getMethodSpecBuilder(
specModel,
prop,
requiredIndex,
name,
parameters,
statement,
formatObjects).build();
}
private static MethodSpec.Builder getMethodSpecBuilder(
SpecModel specModel,
PropModel prop,
int requiredIndex,
String name,
List<ParameterSpec> parameters,
String statement,
Object... formatObjects) {
CodeBlock codeBlock = CodeBlock.builder()
.add("this.$L.$L = ", getImplMemberInstanceName(specModel), prop.getName())
.addStatement(statement, formatObjects)
.build();
return getMethodSpecBuilder(prop, requiredIndex, name, parameters, codeBlock);
}
private static MethodSpec.Builder getMethodSpecBuilder(
PropModel prop,
int requiredIndex,
String name,
List<ParameterSpec> parameters,
CodeBlock codeBlock) {
final MethodSpec.Builder builder =
MethodSpec.methodBuilder(name)
.addModifiers(Modifier.PUBLIC)
.returns(BUILDER_CLASS_NAME)
.addCode(codeBlock);
for (ParameterSpec param : parameters) {
builder.addParameter(param);
}
if (!prop.isOptional()) {
builder.addStatement("$L.set($L)", "mRequired", requiredIndex);
}
builder.addStatement("return this");
return builder;
}
private static MethodSpec generateEventDeclarationBuilderMethod(
SpecModel specModel,
EventDeclarationModel eventDeclaration) {
final String eventHandlerName =
ComponentImplGenerator.getEventHandlerInstanceName(eventDeclaration.name);
return MethodSpec.methodBuilder(eventHandlerName)
.addModifiers(Modifier.PUBLIC)
.returns(BUILDER_CLASS_NAME)
.addParameter(ClassNames.EVENT_HANDLER, eventHandlerName)
.addStatement("this.$L.$L = $L", getImplMemberInstanceName(specModel), eventHandlerName, eventHandlerName)
.addStatement("return this")
.build();
}
private static MethodSpec generateKeySetterMethod() {
return MethodSpec.methodBuilder("key")
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassNames.STRING, "key")
.addStatement("super.setKey(key)")
.addStatement("return this")
.returns(BUILDER_CLASS_NAME)
.build();
}
private static MethodSpec generateBuildMethod(SpecModel specModel, int numRequiredProps) {
final MethodSpec.Builder buildMethodBuilder = MethodSpec.methodBuilder("build")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(ParameterizedTypeName.get(
specModel.getComponentClass(),
specModel.getComponentTypeName()));
if (numRequiredProps > 0) {
buildMethodBuilder
.addStatement(
"checkArgs($L, $L, $L)",
REQUIRED_PROPS_COUNT,
"mRequired",
REQUIRED_PROPS_NAMES);
}
return buildMethodBuilder
.addStatement(
"$L $L = $L",
ComponentImplGenerator.getImplClassName(specModel),
ComponentImplGenerator.getImplInstanceName(specModel),
getImplMemberInstanceName(specModel))
.addStatement("release()")
.addStatement("return $L", ComponentImplGenerator.getImplInstanceName(specModel))
.build();
}
private static MethodSpec generateReleaseMethod(SpecModel specModel) {
return MethodSpec.methodBuilder("release")
.addAnnotation(Override.class)
.addModifiers(Modifier.PROTECTED)
.addStatement("super.release()")
.addStatement(getImplMemberInstanceName(specModel) + " = null")
.addStatement(CONTEXT_MEMBER_NAME + " = null")
.addStatement("$L.release(this)", BUILDER_POOL_FIELD)
.build();
}
}