blob: 50d10c945691036d20842d34abe445d6bf44a4e6 [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 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.WildcardTypeName;
import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
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 org.apache.ignite.configuration.ConfigurationTree;
import org.apache.ignite.configuration.ConfigurationValue;
import org.apache.ignite.configuration.Configurator;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.configuration.internal.DynamicConfiguration;
import org.apache.ignite.configuration.internal.DynamicProperty;
import org.apache.ignite.configuration.internal.NamedListConfiguration;
import org.apache.ignite.configuration.internal.selector.BaseSelectors;
import org.apache.ignite.configuration.internal.selector.Selector;
import org.apache.ignite.configuration.internal.validation.MemberKey;
import org.apache.ignite.configuration.processor.internal.pojo.ChangeClassGenerator;
import org.apache.ignite.configuration.processor.internal.pojo.InitClassGenerator;
import org.apache.ignite.configuration.processor.internal.pojo.ViewClassGenerator;
import org.apache.ignite.configuration.processor.internal.validation.ValidationGenerator;
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;
/**
* Annotation processor that produces configuration classes.
*/
public class Processor extends AbstractProcessor {
/** Wildcard (?) TypeName. */
private static final TypeName WILDCARD = WildcardTypeName.subtypeOf(Object.class);
/** Type of Configurator (every DynamicConfiguration has a Configurator field). */
private static final ParameterizedTypeName CONFIGURATOR_TYPE = ParameterizedTypeName.get(
ClassName.get(Configurator.class),
WildcardTypeName.subtypeOf(
ParameterizedTypeName.get(ClassName.get(DynamicConfiguration.class), WILDCARD, WILDCARD, WILDCARD)
)
);
/** Generator of VIEW classes. */
private ViewClassGenerator viewClassGenerator;
/** Generator of INIT classes. */
private InitClassGenerator initClassGenerator;
/** Generator of CHANGE classes. */
private ChangeClassGenerator changeClassGenerator;
/** Class file writer. */
private Filer filer;
/**
* Constructor.
*/
public Processor() {
}
/** {@inheritDoc} */
@Override public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
viewClassGenerator = new ViewClassGenerator(processingEnv);
initClassGenerator = new InitClassGenerator(processingEnv);
changeClassGenerator = new ChangeClassGenerator(processingEnv);
}
/** {@inheritDoc} */
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
final Elements elementUtils = processingEnv.getElementUtils();
Map<TypeName, ConfigurationDescription> props = new HashMap<>();
List<ConfigurationDescription> roots = new ArrayList<>();
// Package to use for Selectors and Keys classes
String packageForUtil = "";
// All classes annotated with @Config
final Set<TypeElement> annotatedConfigs = roundEnvironment.getElementsAnnotatedWith(Config.class).stream()
.filter(element -> element.getKind() == ElementKind.CLASS)
.map(TypeElement.class::cast)
.collect(Collectors.toSet());
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());
final Config classConfigAnnotation = clazz.getAnnotation(Config.class);
// Configuration name
final String configName = classConfigAnnotation.value();
// Is root of the configuration
final boolean isRoot = classConfigAnnotation.root();
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.getInitName(schemaClassName),
Utils.getChangeName(schemaClassName)
);
// If root, then use it's package as package for Selectors and Keys
if (isRoot) {
roots.add(configDesc);
packageForUtil = packageName;
}
TypeSpec.Builder configurationClassBuilder = TypeSpec.classBuilder(configClass)
.addSuperinterface(configInterface)
.addModifiers(PUBLIC, FINAL);
TypeSpec.Builder configurationInterfaceBuilder = TypeSpec.interfaceBuilder(configInterface)
.addModifiers(PUBLIC);
CodeBlock.Builder constructorBodyBuilder = CodeBlock.builder();
CodeBlock.Builder copyConstructorBodyBuilder = CodeBlock.builder();
for (VariableElement field : fields) {
// 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 getMethodType = types.getGetMethodType();
TypeName viewClassType = types.getViewClassType();
TypeName initClassType = types.getInitClassType();
TypeName changeClassType = types.getChangeClassType();
final ConfigValue confAnnotation = field.getAnnotation(ConfigValue.class);
if (confAnnotation != null) {
// Create DynamicConfiguration (descendant) field
final FieldSpec nestedConfigField =
FieldSpec
.builder(getMethodType, fieldName, Modifier.PRIVATE, FINAL)
.build();
configurationClassBuilder.addField(nestedConfigField);
// Constructor statement
constructorBodyBuilder.addStatement("add($L = new $T(qualifiedName, $S, false, configurator, this.root))", fieldName, getMethodType, fieldName);
// Copy constructor statement
copyConstructorBodyBuilder.addStatement("add($L = base.$L.copy(this.root))", fieldName, fieldName);
}
final NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
if (namedConfigAnnotation != null) {
ClassName fieldType = Utils.getConfigurationName((ClassName) baseType);
// Create NamedListConfiguration<> field
final FieldSpec nestedConfigField = FieldSpec.builder(
getMethodType,
fieldName,
Modifier.PRIVATE,
FINAL
).build();
configurationClassBuilder.addField(nestedConfigField);
// Constructor statement
constructorBodyBuilder.addStatement(
"add($L = new $T(qualifiedName, $S, configurator, this.root, (p, k) -> new $T(p, k, true, configurator, this.root)))",
fieldName,
getMethodType,
fieldName,
fieldType
);
// Copy constructor statement
copyConstructorBodyBuilder.addStatement("add($L = base.$L.copy(this.root))", fieldName, fieldName);
}
final Value valueAnnotation = field.getAnnotation(Value.class);
if (valueAnnotation != null) {
// Create value (DynamicProperty<>) field
final FieldSpec generatedField = FieldSpec.builder(getMethodType, fieldName, Modifier.PRIVATE, FINAL).build();
configurationClassBuilder.addField(generatedField);
final CodeBlock validatorsBlock = ValidationGenerator.generateValidators(field);
// Constructor statement
constructorBodyBuilder.addStatement(
"add($L = new $T(qualifiedName, $S, new $T($T.class, $S), this.configurator, this.root), $L)",
fieldName, getMethodType, fieldName, MemberKey.class, configClass, fieldName, validatorsBlock
);
// Copy constructor statement
copyConstructorBodyBuilder.addStatement("add($L = base.$L.copy(this.root))", fieldName, fieldName);
}
configDesc.getFields().add(new ConfigurationElement(getMethodType, fieldName, viewClassType, initClassType, changeClassType));
createGettersAndSetter(configurationClassBuilder, configurationInterfaceBuilder, fieldName, types, valueAnnotation);
}
props.put(configClass, configDesc);
// Create VIEW, INIT and CHANGE classes
createPojoBindings(packageName, fields, schemaClassName, configurationClassBuilder, configurationInterfaceBuilder);
// Create constructors for configuration class
createConstructors(configClass, configName, configurationClassBuilder, CONFIGURATOR_TYPE, constructorBodyBuilder, copyConstructorBodyBuilder);
// Create copy method for configuration class
createCopyMethod(configClass, configurationClassBuilder);
// Write configuration interface
JavaFile interfaceFile = JavaFile.builder(packageName, configurationInterfaceBuilder.build()).build();
try {
interfaceFile.writeTo(filer);
} catch (IOException e) {
throw new ProcessorException("Failed to create configuration class " + configClass.toString(), e);
}
// Write configuration
JavaFile classFile = JavaFile.builder(packageName, configurationClassBuilder.build()).build();
try {
classFile.writeTo(filer);
} catch (IOException e) {
throw new ProcessorException("Failed to create configuration class " + configClass.toString(), e);
}
}
// Get all generated configuration nodes
final List<ConfigurationNode> flattenConfig = roots.stream()
.map((ConfigurationDescription cfg) -> buildConfigForest(cfg, props))
.flatMap(Set::stream)
.collect(Collectors.toList());
// Generate Keys class
createKeysClass(packageForUtil, flattenConfig);
// Generate Selectors class
createSelectorsClass(packageForUtil, flattenConfig);
return true;
}
/**
* Create getters and setters for configuration class.
*
* @param configurationClassBuilder
* @param configurationInterfaceBuilder
* @param fieldName
* @param types
* @param valueAnnotation
*/
private void createGettersAndSetter(
TypeSpec.Builder configurationClassBuilder,
TypeSpec.Builder configurationInterfaceBuilder,
String fieldName,
ConfigurationFieldTypes types,
Value valueAnnotation
) {
MethodSpec interfaceGetMethod = MethodSpec.methodBuilder(fieldName)
.addModifiers(PUBLIC, ABSTRACT)
.returns(types.getInterfaceGetMethodType())
.build();
configurationInterfaceBuilder.addMethod(interfaceGetMethod);
MethodSpec getMethod = MethodSpec.methodBuilder(fieldName)
.addModifiers(PUBLIC, FINAL)
.returns(types.getGetMethodType())
.addStatement("return $L", fieldName)
.build();
configurationClassBuilder.addMethod(getMethod);
if (valueAnnotation != null) {
MethodSpec setMethod = MethodSpec
.methodBuilder(fieldName)
.addModifiers(PUBLIC, FINAL)
.addParameter(types.getUnwrappedType(), fieldName)
.addStatement("this.$L.change($L)", fieldName, fieldName)
.build();
configurationClassBuilder.addMethod(setMethod);
}
}
/**
* Get types for configuration classes generation.
* @param field
* @return Bundle with all types for configuration
*/
private ConfigurationFieldTypes getTypes(final VariableElement field) {
TypeName getMethodType = null;
TypeName interfaceGetMethodType = null;
final TypeName baseType = TypeName.get(field.asType());
TypeName unwrappedType = baseType;
TypeName viewClassType = baseType;
TypeName initClassType = baseType;
TypeName changeClassType = baseType;
final ConfigValue confAnnotation = field.getAnnotation(ConfigValue.class);
if (confAnnotation != null) {
getMethodType = Utils.getConfigurationName((ClassName) baseType);
interfaceGetMethodType = Utils.getConfigurationInterfaceName((ClassName) baseType);
unwrappedType = getMethodType;
viewClassType = Utils.getViewName((ClassName) baseType);
initClassType = Utils.getInitName((ClassName) baseType);
changeClassType = Utils.getChangeName((ClassName) baseType);
}
final NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
if (namedConfigAnnotation != null) {
ClassName fieldType = Utils.getConfigurationName((ClassName) baseType);
viewClassType = Utils.getViewName((ClassName) baseType);
initClassType = Utils.getInitName((ClassName) baseType);
changeClassType = Utils.getChangeName((ClassName) baseType);
getMethodType = ParameterizedTypeName.get(ClassName.get(NamedListConfiguration.class), viewClassType, fieldType, initClassType, changeClassType);
interfaceGetMethodType = ParameterizedTypeName.get(ClassName.get(NamedListConfiguration.class), viewClassType, fieldType, initClassType, changeClassType);
}
final Value valueAnnotation = field.getAnnotation(Value.class);
if (valueAnnotation != null) {
ClassName dynPropClass = ClassName.get(DynamicProperty.class);
ClassName confValueClass = ClassName.get(ConfigurationValue.class);
TypeName genericType = baseType;
if (genericType.isPrimitive()) {
genericType = genericType.box();
}
getMethodType = ParameterizedTypeName.get(dynPropClass, genericType);
interfaceGetMethodType = ParameterizedTypeName.get(confValueClass, genericType);
}
return new ConfigurationFieldTypes(getMethodType, unwrappedType, viewClassType, initClassType, changeClassType, interfaceGetMethodType);
}
/**
* Wrapper for configuration schema types.
*/
private static class ConfigurationFieldTypes {
/** Field get method type. */
private final TypeName getMethodType;
/** 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;
/** INIT object type. */
private final TypeName initClassType;
/** CHANGE object type. */
private final TypeName changeClassType;
/** Get method type for public interface. */
private final TypeName interfaceGetMethodType;
public ConfigurationFieldTypes(TypeName getMethodType, TypeName unwrappedType, TypeName viewClassType, TypeName initClassType, TypeName changeClassType, TypeName interfaceGetMethodType) {
this.getMethodType = getMethodType;
this.unwrappedType = unwrappedType;
this.viewClassType = viewClassType;
this.initClassType = initClassType;
this.changeClassType = changeClassType;
this.interfaceGetMethodType = interfaceGetMethodType;
}
/** */
public TypeName getInterfaceGetMethodType() {
return interfaceGetMethodType;
}
/** */
public TypeName getGetMethodType() {
return getMethodType;
}
/** */
public TypeName getUnwrappedType() {
return unwrappedType;
}
/** */
public TypeName getViewClassType() {
return viewClassType;
}
/** */
public TypeName getInitClassType() {
return initClassType;
}
/** */
public TypeName getChangeClassType() {
return changeClassType;
}
}
/**
* Create copy-method for configuration class.
*
* @param configClass Configuration class name.
* @param configurationClassBuilder Configuration class builder.
*/
private void createCopyMethod(ClassName configClass, TypeSpec.Builder configurationClassBuilder) {
MethodSpec copyMethod = MethodSpec.methodBuilder("copy")
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(DynamicConfiguration.class, "root")
.returns(configClass)
.addStatement("return new $T(this, root)", configClass)
.build();
configurationClassBuilder.addMethod(copyMethod);
}
/**
* Create configuration class constructors.
*
* @param configClass Configuration class name.
* @param configName Configuration name.
* @param configurationClassBuilder Configuration class builder.
* @param configuratorClassName Configurator (configuration wrapper) class name.
* @param constructorBodyBuilder Constructor body.
* @param copyConstructorBodyBuilder Copy constructor body.
*/
private void createConstructors(
ClassName configClass,
String configName,
TypeSpec.Builder configurationClassBuilder,
ParameterizedTypeName configuratorClassName,
CodeBlock.Builder constructorBodyBuilder,
CodeBlock.Builder copyConstructorBodyBuilder
) {
final MethodSpec constructorWithName = MethodSpec.constructorBuilder()
.addModifiers(PUBLIC)
.addParameter(String.class, "prefix")
.addParameter(String.class, "key")
.addParameter(boolean.class, "isNamed")
.addParameter(configuratorClassName, "configurator")
.addParameter(DynamicConfiguration.class, "root")
.addStatement("super(prefix, key, isNamed, configurator, root)")
.addCode(constructorBodyBuilder.build())
.build();
configurationClassBuilder.addMethod(constructorWithName);
final MethodSpec copyConstructor = MethodSpec.constructorBuilder()
.addModifiers(PRIVATE)
.addParameter(configClass, "base")
.addParameter(DynamicConfiguration.class, "root")
.addStatement("super(base.prefix, base.key, base.isNamed, base.configurator, root)")
.addCode(copyConstructorBodyBuilder.build())
.build();
configurationClassBuilder.addMethod(copyConstructor);
final MethodSpec emptyConstructor = MethodSpec.constructorBuilder()
.addModifiers(PUBLIC)
.addParameter(configuratorClassName, "configurator")
.addStatement("this($S, $S, false, configurator, null)", "", configName)
.build();
configurationClassBuilder.addMethod(emptyConstructor);
}
/**
* Create selectors.
*
* @param packageForUtil Package to place selectors class to.
* @param flattenConfig List of configuration nodes.
*/
private void createSelectorsClass(String packageForUtil, List<ConfigurationNode> flattenConfig) {
ClassName selectorsClassName = ClassName.get(packageForUtil, "Selectors");
final TypeSpec.Builder selectorsClass = TypeSpec.classBuilder(selectorsClassName)
.superclass(BaseSelectors.class)
.addModifiers(PUBLIC, FINAL);
final CodeBlock.Builder selectorsStaticBlockBuilder = CodeBlock.builder();
selectorsStaticBlockBuilder.addStatement("$T publicLookup = $T.publicLookup()", MethodHandles.Lookup.class, MethodHandles.class);
selectorsStaticBlockBuilder.beginControlFlow("try");
// For every configuration node create selector (based on a method call chain)
for (ConfigurationNode configNode : flattenConfig) {
String regex = "([a-z])([A-Z]+)";
String replacement = "$1_$2";
// Selector variable name (like LOCAL_BASELINE_AUTO_ADJUST_ENABLED)
final String varName = configNode.getName()
.replaceAll(regex, replacement)
.toUpperCase()
.replace(".", "_");
TypeName type = configNode.getType();
if (Utils.isNamedConfiguration(type))
type = Utils.unwrapNamedListConfigurationClass(type);
StringBuilder methodCall = new StringBuilder();
ConfigurationNode current = configNode;
ConfigurationNode root = null;
int namedCount = 0;
// Walk from node up to the root and create a method call chain
while (current != null) {
boolean isNamed = false;
if (Utils.isNamedConfiguration(current.getType())) {
namedCount++;
isNamed = true;
}
if (current.getParent() != null) {
String newMethodCall = "." + current.getOriginalName() + "()";
// if config is named, then create a call with name parameter
if (isNamed)
newMethodCall += ".get(name" + (namedCount - 1) + ")";
methodCall.insert(0, newMethodCall);
} else
root = current;
current = current.getParent();
}
TypeName selectorRec = Utils.getParameterized(ClassName.get(Selector.class), root.getType(), type, configNode.getView(), configNode.getInit(), configNode.getChange());
if (namedCount > 0) {
final MethodSpec.Builder builder = MethodSpec.methodBuilder(varName);
for (int i = 0; i < namedCount; i++) {
builder.addParameter(String.class, "name" + i);
}
selectorsClass.addMethod(
builder
.returns(selectorRec)
.addModifiers(PUBLIC, STATIC, FINAL)
.addStatement("return (root) -> root$L", methodCall.toString())
.build()
);
// Build a list of parameters for statement
List<Object> params = new ArrayList<>();
params.add(MethodHandle.class);
params.add(varName);
params.add(selectorsClassName);
params.add(varName);
params.add(MethodType.class);
params.add(Selector.class);
// For every named config in call chain -- add String (name) parameter
for (int i = 0; i < namedCount; i++) {
params.add(String.class);
}
// Create a string for name parameters
final String nameStringParameters = IntStream.range(0, namedCount).mapToObj(i -> "$T.class").collect(Collectors.joining(","));
selectorsStaticBlockBuilder.addStatement("$T $L = publicLookup.findStatic($T.class, $S, $T.methodType($T.class, " + nameStringParameters + "))", params.toArray());
selectorsStaticBlockBuilder.addStatement("put($S, $L)", configNode.getName(), varName);
}
else {
selectorsClass.addField(
FieldSpec.builder(selectorRec, varName)
.addModifiers(PUBLIC, STATIC, FINAL)
.initializer("(root) -> root$L", methodCall.toString())
.build()
);
selectorsStaticBlockBuilder.addStatement("put($S, $L)", configNode.getName(), varName);
}
}
selectorsStaticBlockBuilder
.nextControlFlow("catch ($T e)", Exception.class)
.endControlFlow();
selectorsClass.addStaticBlock(selectorsStaticBlockBuilder.build());
JavaFile selectorsClassFile = JavaFile.builder(selectorsClassName.packageName(), selectorsClass.build()).build();
try {
selectorsClassFile.writeTo(filer);
}
catch (IOException e) {
throw new ProcessorException("Failed to write class: " + e.getMessage(), e);
}
}
/**
* Create keys class.
*
* @param packageForUtil Package to place keys class to.
* @param flattenConfig List of configuration nodes.
*/
private void createKeysClass(String packageForUtil, List<ConfigurationNode> flattenConfig) {
final TypeSpec.Builder keysClass = TypeSpec.classBuilder("Keys").addModifiers(PUBLIC, FINAL);
for (ConfigurationNode node : flattenConfig) {
final String varName = node.getName().toUpperCase().replace(".", "_");
keysClass.addField(
FieldSpec.builder(String.class, varName)
.addModifiers(PUBLIC, STATIC, FINAL)
.initializer("$S", node.getName())
.build()
);
}
JavaFile keysClassFile = JavaFile.builder(packageForUtil, keysClass.build()).build();
try {
keysClassFile.writeTo(filer);
} catch (IOException e) {
throw new ProcessorException("Failed to write class: " + e.getMessage(), e);
}
}
/**
* Create VIEW, INIT and CHANGE classes and methods.
*
* @param packageName Configuration package name.
* @param fields List of configuration fields.
* @param schemaClassName Class name of schema.
* @param configurationClassBuilder Configuration class builder.
*/
private void createPojoBindings(
String packageName,
List<VariableElement> fields,
ClassName schemaClassName,
TypeSpec.Builder configurationClassBuilder,
TypeSpec.Builder configurationInterfaceBuilder
) {
final ClassName viewClassTypeName = Utils.getViewName(schemaClassName);
final ClassName initClassName = Utils.getInitName(schemaClassName);
final ClassName changeClassName = Utils.getChangeName(schemaClassName);
ClassName dynConfClass = ClassName.get(DynamicConfiguration.class);
TypeName dynConfViewClassType = ParameterizedTypeName.get(dynConfClass, viewClassTypeName, initClassName, changeClassName);
configurationClassBuilder.superclass(dynConfViewClassType);
ClassName confTreeInterface = ClassName.get(ConfigurationTree.class);
TypeName confTreeParameterized = ParameterizedTypeName.get(confTreeInterface, viewClassTypeName, changeClassName);
configurationInterfaceBuilder.addSuperinterface(confTreeParameterized);
try {
viewClassGenerator.create(packageName, viewClassTypeName, fields);
final MethodSpec toViewMethod = createToViewMethod(viewClassTypeName, fields);
configurationClassBuilder.addMethod(toViewMethod);
}
catch (IOException e) {
throw new ProcessorException("Failed to write class " + viewClassTypeName.toString(), e);
}
try {
changeClassGenerator.create(packageName, changeClassName, fields);
final MethodSpec changeMethod = createChangeMethod(changeClassName, fields);
configurationClassBuilder.addMethod(changeMethod);
}
catch (IOException e) {
throw new ProcessorException("Failed to write class " + changeClassName.toString(), e);
}
try {
initClassGenerator.create(packageName, initClassName, fields);
final MethodSpec initMethod = createInitMethod(initClassName, fields);
configurationClassBuilder.addMethod(initMethod);
}
catch (IOException e) {
throw new ProcessorException("Failed to write class " + initClassName.toString(), e);
}
}
/**
* Build configuration forest base on root configuration description and all processed configurations.
*
* @param root Root configuration description.
* @param props All configurations.
* @return All possible config trees.
*/
private Set<ConfigurationNode> buildConfigForest(ConfigurationDescription root, Map<TypeName, ConfigurationDescription> props) {
Set<ConfigurationNode> res = new HashSet<>();
Deque<ConfigurationNode> propsStack = new LinkedList<>();
ConfigurationNode rootNode = new ConfigurationNode(root.getType(), root.getName(), root.getName(), root.getView(), root.getInit(), root.getChange(), null);
propsStack.addFirst(rootNode);
// Walk through the all fields of every node and build a tree of configuration (more like chain)
while (!propsStack.isEmpty()) {
final ConfigurationNode current = propsStack.pollFirst();
// Get configuration type
TypeName type = current.getType();
if (Utils.isNamedConfiguration(type))
type = Utils.unwrapNamedListConfigurationClass(current.getType());
final ConfigurationDescription configDesc = props.get(type);
// Get fields of configuration
final List<ConfigurationElement> propertiesList = configDesc.getFields();
if (current.getName() != null && !current.getName().isEmpty())
// Add current node to result
res.add(current);
for (ConfigurationElement property : propertiesList) {
String qualifiedName = property.getName();
if (current.getName() != null && !current.getName().isEmpty())
qualifiedName = current.getName() + "." + qualifiedName;
final ConfigurationNode newChainElement = new ConfigurationNode(
property.getType(),
qualifiedName,
property.getName(),
property.getView(),
property.getInit(),
property.getChange(),
current
);
boolean isNamedConfig = false;
if (property.getType() instanceof ParameterizedTypeName) {
final ParameterizedTypeName parameterized = (ParameterizedTypeName) property.getType();
if (parameterized.rawType.equals(ClassName.get(NamedListConfiguration.class)))
isNamedConfig = true;
}
if (props.containsKey(property.getType()) || isNamedConfig)
// If it's not a leaf, add to stack
propsStack.add(newChainElement);
else
// otherwise, add to result
res.add(newChainElement);
}
}
return res;
}
/**
* Create {@link org.apache.ignite.configuration.ConfigurationProperty#value} method for configuration class.
*
* @param type VIEW method type.
* @param variables List of VIEW object's fields.
* @return toView() method.
*/
public MethodSpec createToViewMethod(TypeName type, List<VariableElement> variables) {
String args = variables.stream()
.map(v -> v.getSimpleName().toString() + ".value()")
.collect(Collectors.joining(", "));
final CodeBlock returnBlock = CodeBlock.builder()
.add("return new $T($L)", type, args)
.build();
return MethodSpec.methodBuilder("value")
.addModifiers(PUBLIC)
.addAnnotation(Override.class)
.returns(type)
.addStatement(returnBlock)
.build();
}
/**
* Create {@link org.apache.ignite.configuration.internal.Modifier#init(Object)} method (accepts INIT object) for configuration class.
*
* @param type INIT method type.
* @param variables List of INIT object's fields.
* @return Init method.
*/
public MethodSpec createInitMethod(TypeName type, List<VariableElement> variables) {
final CodeBlock.Builder builder = CodeBlock.builder();
for (VariableElement variable : variables) {
final String name = variable.getSimpleName().toString();
builder.beginControlFlow("if (initial.$L() != null)", name);
builder.addStatement("$L.init(initial.$L())", name, name);
builder.endControlFlow();
}
return MethodSpec.methodBuilder("init")
.addModifiers(PUBLIC)
.addAnnotation(Override.class)
.addParameter(type, "initial")
.addCode(builder.build())
.build();
}
/**
* Create {@link org.apache.ignite.configuration.internal.Modifier#change(Object)} method (accepts CHANGE object) for configuration class.
*
* @param type CHANGE method type.
* @param variables List of CHANGE object's fields.
* @return Change method.
*/
public MethodSpec createChangeMethod(TypeName type, List<VariableElement> variables) {
final CodeBlock.Builder builder = CodeBlock.builder();
for (VariableElement variable : variables) {
final Value valueAnnotation = variable.getAnnotation(Value.class);
if (valueAnnotation != null && valueAnnotation.immutable())
continue;
final String name = variable.getSimpleName().toString();
builder.beginControlFlow("if (changes.$L() != null)", name);
builder.addStatement("$L.changeWithoutValidation(changes.$L())", name, name);
builder.endControlFlow();
}
return MethodSpec.methodBuilder("changeWithoutValidation")
.addModifiers(PUBLIC)
.addAnnotation(Override.class)
.addParameter(type, "changes")
.addCode(builder.build())
.build();
}
/** {@inheritDoc} */
@Override public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(Config.class.getCanonicalName());
}
/** {@inheritDoc} */
@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_8;
}
}