blob: c4bdbb7d1895da0b67b609c5238b0f824b3f6101 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ignite.configuration.processor.internal;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import com.squareup.javapoet.ArrayTypeName;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
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 com.squareup.javapoet.WildcardTypeName;
import org.apache.ignite.configuration.NamedConfigurationTree;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.configuration.internal.NamedListConfiguration;
import org.apache.ignite.configuration.storage.ConfigurationType;
import org.apache.ignite.configuration.tree.ConfigurationSource;
import org.apache.ignite.configuration.tree.ConfigurationVisitor;
import org.apache.ignite.configuration.tree.InnerNode;
import org.apache.ignite.configuration.tree.NamedListChange;
import org.apache.ignite.configuration.tree.NamedListNode;
import org.apache.ignite.configuration.tree.NamedListView;
import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
import static org.apache.ignite.configuration.processor.internal.Utils.suppressWarningsUnchecked;
/**
* Annotation processor that produces configuration classes.
*/
public class Processor extends AbstractProcessor {
/** Java file padding. */
private static final String INDENT = " ";
/** Wildcard (?) TypeName. */
private static final TypeName WILDCARD = WildcardTypeName.subtypeOf(Object.class);
/** Inherit doc javadoc. */
private static final String INHERIT_DOC = "{@inheritDoc}";
/** */
private static final ClassName ROOT_KEY_CLASSNAME = ClassName.get("org.apache.ignite.configuration", "RootKey");
/**
* Constructor.
*/
public Processor() {
}
/** {@inheritDoc} */
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
try {
return process0(roundEnvironment);
} catch (Throwable t) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to process configuration: " + sw.toString());
}
return false;
}
/**
* Processes a set of annotation types on type elements.
* @param roundEnvironment Processing environment.
* @return Whether or not the set of annotation types are claimed by this processor.
*/
private boolean process0(RoundEnvironment roundEnvironment) {
final Elements elementUtils = processingEnv.getElementUtils();
Map<TypeName, ConfigurationDescription> props = new HashMap<>();
List<ConfigurationDescription> roots = new ArrayList<>();
// All classes annotated with @Config
final List<TypeElement> annotatedConfigs = roundEnvironment
.getElementsAnnotatedWithAny(Set.of(ConfigurationRoot.class, Config.class)).stream()
.filter(element -> element.getKind() == ElementKind.CLASS)
.map(TypeElement.class::cast)
.collect(Collectors.toList());
if (annotatedConfigs.isEmpty())
return false;
for (TypeElement clazz : annotatedConfigs) {
// Get package name of the schema class
final PackageElement elementPackage = elementUtils.getPackageOf(clazz);
final String packageName = elementPackage.getQualifiedName().toString();
// Find all the fields of the schema
final List<VariableElement> fields = clazz.getEnclosedElements().stream()
.filter(el -> el.getKind() == ElementKind.FIELD)
.map(VariableElement.class::cast)
.collect(Collectors.toList());
ConfigurationRoot rootAnnotation = clazz.getAnnotation(ConfigurationRoot.class);
// Is root of the configuration
final boolean isRoot = rootAnnotation != null;
// Configuration name
final String configName = isRoot ? rootAnnotation.rootName() : null;
final ClassName schemaClassName = ClassName.get(packageName, clazz.getSimpleName().toString());
// Get name for generated configuration class and it's interface
final ClassName configClass = Utils.getConfigurationName(schemaClassName);
final ClassName configInterface = Utils.getConfigurationInterfaceName(schemaClassName);
ConfigurationDescription configDesc = new ConfigurationDescription(
configClass,
configName,
Utils.getViewName(schemaClassName),
Utils.getChangeName(schemaClassName)
);
// If root, then use it's package as package for Selectors and Keys
if (isRoot)
roots.add(configDesc);
TypeSpec.Builder configurationClassBuilder = TypeSpec.classBuilder(configClass)
.addSuperinterface(configInterface)
.addModifiers(FINAL);
TypeSpec.Builder configurationInterfaceBuilder = TypeSpec.interfaceBuilder(configInterface)
.addModifiers(PUBLIC);
CodeBlock.Builder constructorBodyBuilder = CodeBlock.builder();
for (VariableElement field : fields) {
if (field.getModifiers().contains(STATIC))
continue;
if (!field.getModifiers().contains(PUBLIC))
throw new ProcessorException("Field " + clazz.getQualifiedName() + "." + field + " must be public");
Element fieldTypeElement = processingEnv.getTypeUtils().asElement(field.asType());
// Get original field type (must be another configuration schema or "primitive" like String or long)
final TypeName baseType = TypeName.get(field.asType());
final String fieldName = field.getSimpleName().toString();
// Get configuration types (VIEW, INIT, CHANGE and so on)
final ConfigurationFieldTypes types = getTypes(field);
TypeName fieldType = types.getFieldType();
TypeName viewClassType = types.getViewClassType();
TypeName changeClassType = types.getChangeClassType();
final ConfigValue confAnnotation = field.getAnnotation(ConfigValue.class);
if (confAnnotation != null) {
if (fieldTypeElement.getAnnotation(Config.class) == null) {
throw new ProcessorException(
"Class for @ConfigValue field must be defined as @Config: " +
clazz.getQualifiedName() + "." + field.getSimpleName()
);
}
// Create DynamicConfiguration (descendant) field
final FieldSpec nestedConfigField =
FieldSpec
.builder(fieldType, fieldName, Modifier.PRIVATE, FINAL)
.build();
configurationClassBuilder.addField(nestedConfigField);
// Constructor statement
constructorBodyBuilder.addStatement("add($L = new $T(keys, $S, rootKey, changer))", fieldName, fieldType, fieldName);
}
final NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
if (namedConfigAnnotation != null) {
if (fieldTypeElement.getAnnotation(Config.class) == null) {
throw new ProcessorException(
"Class for @NamedConfigValue field must be defined as @Config: " +
clazz.getQualifiedName() + "." + field.getSimpleName()
);
}
// Create NamedListConfiguration<> field
final FieldSpec nestedConfigField = FieldSpec.builder(
fieldType,
fieldName,
Modifier.PRIVATE,
FINAL
).build();
configurationClassBuilder.addField(nestedConfigField);
// Constructor statement
constructorBodyBuilder.addStatement(
"add($L = new $T(keys, $S, rootKey, changer, (p, k) -> new $T(p, k, rootKey, changer)))",
fieldName,
fieldType,
fieldName,
Utils.getConfigurationName((ClassName) baseType)
);
}
final Value valueAnnotation = field.getAnnotation(Value.class);
if (valueAnnotation != null) {
// Must be a primitive or an array of the primitives (including java.lang.String)
if (!isPrimitiveOrArrayOfPrimitives(baseType)) {
throw new ProcessorException(
"@Value " + clazz.getQualifiedName() + "." + field.getSimpleName() + " field must" +
" have one of the following types: boolean, int, long, double, String or an array of " +
"aforementioned type."
);
}
// Create value (DynamicProperty<>) field
final FieldSpec generatedField = FieldSpec.builder(fieldType, fieldName, Modifier.PRIVATE, FINAL).build();
configurationClassBuilder.addField(generatedField);
// Constructor statement
constructorBodyBuilder.addStatement(
"add($L = new $T(keys, $S, rootKey, changer))",
fieldName, fieldType, fieldName
);
}
configDesc.getFields().add(new ConfigurationElement(fieldType, fieldName, viewClassType, changeClassType));
createGetters(configurationClassBuilder, configurationInterfaceBuilder, fieldName, types);
}
props.put(configClass, configDesc);
// Create VIEW, INIT and CHANGE classes
createPojoBindings(clazz, fields, schemaClassName, configurationClassBuilder, configurationInterfaceBuilder);
if (isRoot) {
ConfigurationType storageType = rootAnnotation.type();
createRootKeyField(configInterface, configurationInterfaceBuilder, configDesc, storageType, schemaClassName);
}
// Create constructors for configuration class
createConstructors(isRoot, configName, configurationClassBuilder, constructorBodyBuilder);
// Write configuration interface
buildClass(packageName, configurationInterfaceBuilder.build());
buildClass(packageName, configurationClassBuilder.build());
}
return true;
}
/** */
private void createRootKeyField(
ClassName configInterface,
TypeSpec.Builder configurationClassBuilder,
ConfigurationDescription configDesc,
ConfigurationType storageType,
ClassName schemaClassName
) {
ClassName viewClassName = Utils.getViewName(schemaClassName);
ParameterizedTypeName fieldTypeName = ParameterizedTypeName.get(ROOT_KEY_CLASSNAME, configInterface, viewClassName);
ClassName nodeClassName = Utils.getNodeName(schemaClassName);
ClassName cfgRegistryClassName = ClassName.get("org.apache.ignite.configuration", "ConfigurationRegistry");
FieldSpec keyField = FieldSpec.builder(fieldTypeName, "KEY", PUBLIC, STATIC, FINAL)
.initializer(
"$T.newRootKey($S, $T.$L, $T::new, $T::new)",
cfgRegistryClassName, configDesc.getName(), ConfigurationType.class, storageType, nodeClassName,
Utils.getConfigurationName(schemaClassName)
)
.build();
configurationClassBuilder.addField(keyField);
}
/**
* Create getters for configuration class.
*
* @param configurationClassBuilder
* @param configurationInterfaceBuilder
* @param fieldName
* @param types
*/
private void createGetters(
TypeSpec.Builder configurationClassBuilder,
TypeSpec.Builder configurationInterfaceBuilder,
String fieldName,
ConfigurationFieldTypes types
) {
MethodSpec interfaceGetMethod = MethodSpec.methodBuilder(fieldName)
.addModifiers(PUBLIC, ABSTRACT)
.returns(types.getInterfaceGetMethodType())
.build();
configurationInterfaceBuilder.addMethod(interfaceGetMethod);
MethodSpec.Builder getMethodBuilder = MethodSpec.methodBuilder(fieldName)
.addAnnotation(Override.class)
.addJavadoc(INHERIT_DOC)
.addModifiers(PUBLIC, FINAL)
.returns(types.getInterfaceGetMethodType());
if (Utils.isNamedConfiguration(types.getFieldType())) {
getMethodBuilder.addAnnotation(suppressWarningsUnchecked())
.addStatement("return ($T)$L", NamedConfigurationTree.class, fieldName);
}
else
getMethodBuilder.addStatement("return $L", fieldName);
configurationClassBuilder.addMethod(getMethodBuilder.build());
}
/**
* Get types for configuration classes generation.
* @param field
* @return Bundle with all types for configuration
*/
private ConfigurationFieldTypes getTypes(final VariableElement field) {
TypeName fieldType = null;
TypeName interfaceGetMethodType = null;
final TypeName baseType = TypeName.get(field.asType());
TypeName unwrappedType = baseType;
TypeName viewClassType = baseType;
TypeName changeClassType = baseType;
final ConfigValue confAnnotation = field.getAnnotation(ConfigValue.class);
if (confAnnotation != null) {
fieldType = Utils.getConfigurationName((ClassName) baseType);
interfaceGetMethodType = Utils.getConfigurationInterfaceName((ClassName) baseType);
unwrappedType = fieldType;
viewClassType = Utils.getViewName((ClassName) baseType);
changeClassType = Utils.getChangeName((ClassName) baseType);
}
final NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
if (namedConfigAnnotation != null) {
ClassName interfaceGetType = Utils.getConfigurationInterfaceName((ClassName) baseType);
viewClassType = Utils.getViewName((ClassName) baseType);
changeClassType = Utils.getChangeName((ClassName) baseType);
fieldType = ParameterizedTypeName.get(ClassName.get(NamedListConfiguration.class), interfaceGetType, viewClassType, changeClassType);
interfaceGetMethodType = ParameterizedTypeName.get(ClassName.get(NamedConfigurationTree.class), interfaceGetType, viewClassType, changeClassType);
}
final Value valueAnnotation = field.getAnnotation(Value.class);
if (valueAnnotation != null) {
// It is necessary to use class names without loading classes so that we won't
// accidentally get NoClassDefFoundError
ClassName dynPropClass = ClassName.get("org.apache.ignite.configuration.internal", "DynamicProperty");
ClassName confValueClass = ClassName.get("org.apache.ignite.configuration", "ConfigurationValue");
TypeName genericType = baseType;
if (genericType.isPrimitive()) {
genericType = genericType.box();
}
fieldType = ParameterizedTypeName.get(dynPropClass, genericType);
interfaceGetMethodType = ParameterizedTypeName.get(confValueClass, genericType);
}
return new ConfigurationFieldTypes(fieldType, unwrappedType, viewClassType, changeClassType, interfaceGetMethodType);
}
/**
* Wrapper for configuration schema types.
*/
private static class ConfigurationFieldTypes {
/** Field get method type. */
private final TypeName fieldType;
/** Configuration type (if marked with @ConfigValue or @NamedConfig), or original type (if marked with @Value) */
private final TypeName unwrappedType;
/** VIEW object type. */
private final TypeName viewClassType;
/** CHANGE object type. */
private final TypeName changeClassType;
/** Get method type for public interface. */
private final TypeName interfaceGetMethodType;
private ConfigurationFieldTypes(TypeName fieldType, TypeName unwrappedType, TypeName viewClassType, TypeName changeClassType, TypeName interfaceGetMethodType) {
this.fieldType = fieldType;
this.unwrappedType = unwrappedType;
this.viewClassType = viewClassType;
this.changeClassType = changeClassType;
this.interfaceGetMethodType = interfaceGetMethodType;
}
/** */
public TypeName getInterfaceGetMethodType() {
return interfaceGetMethodType;
}
/** */
public TypeName getFieldType() {
return fieldType;
}
/** */
public TypeName getUnwrappedType() {
return unwrappedType;
}
/** */
public TypeName getViewClassType() {
return viewClassType;
}
/** */
public TypeName getChangeClassType() {
return changeClassType;
}
}
/**
* Create configuration class constructors.
*
* @param isRoot Flag that indincates whether current configuration is root or not.
* @param configName Name of the root if configuration is root, {@code null} otherwise.
* @param configurationClassBuilder Configuration class builder.
* @param constructorBodyBuilder Constructor body.
*/
private void createConstructors(
boolean isRoot,
String configName,
TypeSpec.Builder configurationClassBuilder,
CodeBlock.Builder constructorBodyBuilder
) {
MethodSpec.Builder builder = MethodSpec.constructorBuilder().addModifiers(PUBLIC);
if (!isRoot) {
builder
.addParameter(ParameterizedTypeName.get(List.class, String.class), "prefix")
.addParameter(String.class, "key");
}
builder
.addParameter(ParameterizedTypeName.get(ROOT_KEY_CLASSNAME, WILDCARD, WILDCARD), "rootKey")
.addParameter(ClassName.get("org.apache.ignite.configuration", "ConfigurationChanger"), "changer");
if (isRoot)
builder.addStatement("super($T.emptyList(), $S, rootKey, changer)", Collections.class, configName);
else
builder.addStatement("super(prefix, key, rootKey, changer)");
MethodSpec constructorWithName = builder
.addCode(constructorBodyBuilder.build())
.build();
configurationClassBuilder.addMethod(constructorWithName);
}
/**
* Create VIEW, INIT and CHANGE classes and methods.
* @param clazz Original class for the schema.
* @param fields List of configuration fields.
* @param schemaClassName Class name of schema.
* @param configurationClassBuilder Configuration class builder.
*/
private void createPojoBindings(
TypeElement clazz,
List<VariableElement> fields,
ClassName schemaClassName,
TypeSpec.Builder configurationClassBuilder,
TypeSpec.Builder configurationInterfaceBuilder
) {
final ClassName viewClassTypeName = Utils.getViewName(schemaClassName);
final ClassName changeClassName = Utils.getChangeName(schemaClassName);
ClassName dynConfClass = ClassName.get("org.apache.ignite.configuration.internal", "DynamicConfiguration");
TypeName dynConfViewClassType = ParameterizedTypeName.get(dynConfClass, viewClassTypeName, changeClassName);
configurationClassBuilder.superclass(dynConfViewClassType);
ClassName confTreeInterface = ClassName.get("org.apache.ignite.configuration", "ConfigurationTree");
TypeName confTreeParameterized = ParameterizedTypeName.get(confTreeInterface, viewClassTypeName, changeClassName);
configurationInterfaceBuilder.addSuperinterface(confTreeParameterized);
// This code will be refactored in the future. Right now I don't want to entangle it with existing code
// generation. It has only a few considerable problems - hardcode and a lack of proper arrays handling.
// Clone method should be used to guarantee data integrity.
ClassName viewClsName = Utils.getViewName(schemaClassName);
ClassName changeClsName = Utils.getChangeName(schemaClassName);
ClassName nodeClsName = Utils.getNodeName(schemaClassName);
TypeSpec.Builder viewClsBuilder = TypeSpec.interfaceBuilder(viewClsName)
.addModifiers(PUBLIC);
TypeSpec.Builder changeClsBuilder = TypeSpec.interfaceBuilder(changeClsName)
.addModifiers(PUBLIC);
TypeSpec.Builder nodeClsBuilder = TypeSpec.classBuilder(nodeClsName)
.addModifiers(FINAL)
.superclass(ClassName.get(InnerNode.class))
.addSuperinterface(viewClsName)
.addSuperinterface(changeClsName)
// Cannot use "schemaClassName" here because it can't handle inner static classes.
.addField(FieldSpec.builder(ClassName.get(clazz), "_spec", PRIVATE, FINAL)
.initializer("new $T()", ClassName.get(clazz))
.build()
);
TypeVariableName t = TypeVariableName.get("T");
MethodSpec.Builder traverseChildrenBuilder = MethodSpec.methodBuilder("traverseChildren")
.addAnnotation(Override.class)
.addJavadoc(INHERIT_DOC)
.addModifiers(PUBLIC)
.addTypeVariable(t)
.returns(TypeName.VOID)
.addParameter(ParameterizedTypeName.get(ClassName.get(ConfigurationVisitor.class), t), "visitor");
MethodSpec.Builder traverseChildBuilder = MethodSpec.methodBuilder("traverseChild")
.addAnnotation(Override.class)
.addJavadoc(INHERIT_DOC)
.addModifiers(PUBLIC)
.addTypeVariable(t)
.returns(t)
.addException(NoSuchElementException.class)
.addParameter(ClassName.get(String.class), "key")
.addParameter(ParameterizedTypeName.get(ClassName.get(ConfigurationVisitor.class), t), "visitor")
.beginControlFlow("switch (key)");
MethodSpec.Builder constructBuilder = MethodSpec.methodBuilder("construct")
.addAnnotation(Override.class)
.addJavadoc(INHERIT_DOC)
.addModifiers(PUBLIC)
.returns(TypeName.VOID)
.addException(NoSuchElementException.class)
.addParameter(ClassName.get(String.class), "key")
.addParameter(ClassName.get(ConfigurationSource.class), "src")
.beginControlFlow("switch (key)");
MethodSpec.Builder constructDefaultBuilder = MethodSpec.methodBuilder("constructDefault")
.addAnnotation(Override.class)
.addJavadoc(INHERIT_DOC)
.addModifiers(PUBLIC)
.returns(TypeName.BOOLEAN)
.addException(NoSuchElementException.class)
.addParameter(ClassName.get(String.class), "key")
.beginControlFlow("switch (key)");
MethodSpec.Builder schemaTypeBuilder = MethodSpec.methodBuilder("schemaType")
.addAnnotation(Override.class)
.addJavadoc(INHERIT_DOC)
.addModifiers(PUBLIC)
.returns(ParameterizedTypeName.get(ClassName.get(Class.class), WILDCARD))
.addStatement("return _spec.getClass()");
ClassName consumerClsName = ClassName.get(Consumer.class);
for (VariableElement field : fields) {
Value valAnnotation = field.getAnnotation(Value.class);
String fieldName = field.getSimpleName().toString();
TypeName schemaFieldType = TypeName.get(field.asType());
boolean isArray = schemaFieldType instanceof ArrayTypeName;
boolean leafField = isPrimitiveOrArrayOfPrimitives(schemaFieldType)
|| !((ClassName)schemaFieldType).simpleName().contains("ConfigurationSchema");
boolean namedListField = field.getAnnotation(NamedConfigValue.class) != null;
TypeName viewFieldType = leafField ? schemaFieldType : Utils.getViewName((ClassName)schemaFieldType);
TypeName changeFieldType = leafField ? schemaFieldType : Utils.getChangeName((ClassName)schemaFieldType);
TypeName nodeFieldType = leafField ? schemaFieldType.box() : Utils.getNodeName((ClassName)schemaFieldType);
TypeName namedListParamType = nodeFieldType;
if (namedListField) {
viewFieldType = ParameterizedTypeName.get(ClassName.get(NamedListView.class), WildcardTypeName.subtypeOf(viewFieldType));
nodeFieldType = ParameterizedTypeName.get(ClassName.get(NamedListNode.class), nodeFieldType);
changeFieldType = ParameterizedTypeName.get(ClassName.get(NamedListChange.class), changeFieldType);
}
{
FieldSpec.Builder nodeFieldBuilder = FieldSpec.builder(nodeFieldType, fieldName, PRIVATE);
if (namedListField)
nodeFieldBuilder.initializer("new $T<>($T::new)", NamedListNode.class, namedListParamType);
nodeClsBuilder.addField(nodeFieldBuilder.build());
}
{
{
MethodSpec.Builder getMtdBuilder = MethodSpec.methodBuilder(fieldName)
.addModifiers(PUBLIC, ABSTRACT)
.returns(viewFieldType);
viewClsBuilder.addMethod(getMtdBuilder.build());
}
{
CodeBlock getStatement;
if (isArray)
getStatement = CodeBlock.builder().add("return $L.clone()", fieldName).build();
else
getStatement = CodeBlock.builder().add("return $L", fieldName).build();
MethodSpec.Builder nodeGetMtdBuilder = MethodSpec.methodBuilder(fieldName)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.returns(leafField ? viewFieldType : nodeFieldType)
.addStatement(getStatement);
nodeClsBuilder.addMethod(nodeGetMtdBuilder.build());
}
}
{
String changeMtdName = "change" + capitalize(fieldName);
{
MethodSpec.Builder changeMtdBuilder = MethodSpec.methodBuilder(changeMtdName)
.addModifiers(PUBLIC, ABSTRACT)
.returns(changeClsName);
if (valAnnotation != null)
changeMtdBuilder.addParameter(changeFieldType, fieldName);
else
changeMtdBuilder.addParameter(ParameterizedTypeName.get(consumerClsName, changeFieldType), fieldName);
changeClsBuilder.addMethod(changeMtdBuilder.build());
}
{
MethodSpec.Builder nodeChangeMtdBuilder = MethodSpec.methodBuilder(changeMtdName)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.returns(nodeClsName);
if (valAnnotation != null) {
CodeBlock changeStatement;
if (isArray)
changeStatement = CodeBlock.builder().add("this.$L = $L.clone()", fieldName, fieldName).build();
else
changeStatement = CodeBlock.builder().add("this.$L = $L", fieldName, fieldName).build();
nodeChangeMtdBuilder
.addParameter(changeFieldType, fieldName)
.addStatement(changeStatement);
}
else {
String paramName = fieldName + "Consumer";
nodeChangeMtdBuilder.addParameter(ParameterizedTypeName.get(consumerClsName, changeFieldType), paramName);
if (!namedListField) {
nodeChangeMtdBuilder.addStatement(
"if ($L == null) $L = new $T()",
fieldName,
fieldName,
nodeFieldType
);
nodeChangeMtdBuilder.addStatement("$L.accept($L)", paramName, fieldName);
}
else {
nodeChangeMtdBuilder.addAnnotation(suppressWarningsUnchecked());
nodeChangeMtdBuilder.addStatement("$L.accept((NamedListChange)$L)", paramName, fieldName);
}
}
nodeChangeMtdBuilder.addStatement("return this");
nodeClsBuilder.addMethod(nodeChangeMtdBuilder.build());
}
}
{
if (leafField) {
traverseChildrenBuilder.addStatement("visitor.visitLeafNode($S, $L)", fieldName, fieldName);
traverseChildBuilder
.addStatement("case $S: return visitor.visitLeafNode(key, $L)", fieldName, fieldName);
}
else if (namedListField) {
traverseChildrenBuilder.addStatement("visitor.visitNamedListNode($S, $L)", fieldName, fieldName);
traverseChildBuilder
.addStatement("case $S: return visitor.visitNamedListNode(key, $L)", fieldName, fieldName);
}
else {
traverseChildrenBuilder.addStatement("visitor.visitInnerNode($S, $L)", fieldName, fieldName);
traverseChildBuilder
.addStatement("case $S: return visitor.visitInnerNode(key, $L)", fieldName, fieldName);
}
}
{
if (leafField) {
constructBuilder.addStatement(
"case $S: $L = src == null ? null : src.unwrap($T.class)",
fieldName,
fieldName,
schemaFieldType.box()
)
.addStatement(INDENT + "break");
if (valAnnotation.hasDefault()) {
constructDefaultBuilder
.addStatement("case $S: $L = _spec.$L" + (isArray ? ".clone()" : ""), fieldName, fieldName, fieldName)
.addStatement(INDENT + "return true");
}
else
constructDefaultBuilder.addStatement("case $S: return false", fieldName);
}
else if (namedListField) {
constructBuilder
.addStatement(
"case $S: if (src == null) $L = new $T<>($T::new)",
fieldName,
fieldName,
NamedListNode.class,
namedListParamType
)
.addStatement(
INDENT + "else src.descend($L = $L.copy())",
fieldName,
fieldName
)
.addStatement(INDENT + "break");
}
else {
constructBuilder
.addStatement(
"case $S: if (src == null) $L = null",
fieldName,
fieldName
)
.addStatement(
INDENT + "else src.descend($L = ($L == null ? new $T() : ($T)$L.copy()))",
fieldName,
fieldName,
nodeFieldType,
nodeFieldType,
fieldName
)
.addStatement(INDENT + "break");
}
}
}
traverseChildBuilder
.addStatement("default: throw new $T(key)", NoSuchElementException.class)
.endControlFlow();
constructBuilder
.addStatement("default: throw new $T(key)", NoSuchElementException.class)
.endControlFlow();
constructDefaultBuilder
.addStatement("default: throw new $T(key)", NoSuchElementException.class)
.endControlFlow();
nodeClsBuilder
.addMethod(traverseChildrenBuilder.build())
.addMethod(traverseChildBuilder.build())
.addMethod(constructBuilder.build())
.addMethod(constructDefaultBuilder.build())
.addMethod(schemaTypeBuilder.build());
TypeSpec viewCls = viewClsBuilder.build();
TypeSpec changeCls = changeClsBuilder.build();
TypeSpec nodeCls = nodeClsBuilder.build();
buildClass(viewClsName.packageName(), viewCls);
buildClass(changeClsName.packageName(), changeCls);
buildClass(nodeClsName.packageName(), nodeCls);
}
/** */
private void buildClass(String packageName, TypeSpec cls) {
try {
JavaFile.builder(packageName, cls)
.indent(INDENT)
.build()
.writeTo(processingEnv.getFiler());
}
catch (IOException e) {
throw new ProcessorException("Failed to generate class " + packageName + "." + cls.name, e);
}
}
/** */
private static String capitalize(String name) {
return name.substring(0, 1).toUpperCase() + name.substring(1);
}
/**
* Checks whether TypeName is a primitive (or String) or an array of primitives (or Strings)
* @param typeName TypeName.
* @return {@code true} if type is primitive or array.
*/
private boolean isPrimitiveOrArrayOfPrimitives(TypeName typeName) {
String type = typeName.toString();
if (typeName instanceof ArrayTypeName)
type = ((ArrayTypeName) typeName).componentType.toString();
switch (type) {
case "boolean":
case "int":
case "long":
case "double":
case "java.lang.String":
return true;
default:
return false;
}
}
/** {@inheritDoc} */
@Override public Set<String> getSupportedAnnotationTypes() {
return Set.of(Config.class.getCanonicalName(), ConfigurationRoot.class.getCanonicalName());
}
/** {@inheritDoc} */
@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_11;
}
}