Files
Jackfruit/jackfruit/src/main/java/jackfruit/processor/ConfigProcessor.java
2022-11-27 17:12:53 +00:00

739 lines
34 KiB
Java

package jackfruit.processor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Generated;
import javax.annotation.processing.Messager;
import javax.annotation.processing.Processor;
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.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import org.apache.commons.configuration2.Configuration;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
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.TypeVariableName;
import jackfruit.JackfruitVersion;
import jackfruit.annotations.Comment;
import jackfruit.annotations.DefaultValue;
import jackfruit.annotations.Jackfruit;
import jackfruit.annotations.Key;
import jackfruit.annotations.ParserClass;
/**
* Useful references for writing an annotation processor:
* <ul>
* <li><a href=
* "https://www.javacodegeeks.com/2015/09/java-annotation-processors.html">https://www.javacodegeeks.com/2015/09/java-annotation-processors.html</a></li>
* <li><a href=
* "https://hannesdorfmann.com/annotation-processing/annotationprocessing101/">https://hannesdorfmann.com/annotation-processing/annotationprocessing101/</a></li>
* <li><a href=
* "http://www.javatronic.fr/articles/2014/08/31/how_to_make_sure_javac_is_using_a_specific_annotation_processor.html">http://www.javatronic.fr/articles/2014/08/31/how_to_make_sure_javac_is_using_a_specific_annotation_processor.html</a></li>
* </ul>
*
* @author nairah1
*
*/
@SupportedSourceVersion(SourceVersion.RELEASE_17)
@SupportedAnnotationTypes("jackfruit.annotations.Jackfruit")
@AutoService(Processor.class)
public class ConfigProcessor extends AbstractProcessor {
private List<Class<? extends Annotation>> supportedMethodAnnotations;
private Messager messager;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
supportedMethodAnnotations = new ArrayList<>();
supportedMethodAnnotations.add(Comment.class);
supportedMethodAnnotations.add(DefaultValue.class);
supportedMethodAnnotations.add(Key.class);
supportedMethodAnnotations.add(ParserClass.class);
messager = processingEnv.getMessager();
// find interfaces or abstract classes with the Jackfruit annotation
List<Element> annotatedElements = roundEnv.getElementsAnnotatedWith(Jackfruit.class).stream()
.filter(e -> e.getKind() == ElementKind.INTERFACE
|| (e.getKind() == ElementKind.CLASS && e.getModifiers().contains(Modifier.ABSTRACT)))
.collect(Collectors.toList());
for (Element element : annotatedElements) {
try {
if (element instanceof TypeElement) {
TypeElement annotatedType = (TypeElement) element;
Jackfruit configParams = (Jackfruit) annotatedType.getAnnotation(Jackfruit.class);
String prefix = configParams.prefix().strip();
if (prefix.length() > 0 && !prefix.endsWith("."))
prefix += ".";
// This is the templatized class with annotations to be processed (e.g.
// ConfigTemplate)
TypeVariableName tvn = TypeVariableName.get(annotatedType.getSimpleName().toString());
// This is the generic class (e.g. ConfigFactory<ConfigTemplate>)
ParameterizedTypeName ptn = ParameterizedTypeName
.get(ClassName.get(jackfruit.processor.ConfigFactory.class), tvn);
// This is the name of the class to create (e.g. ConfigTemplateFactory)
String factoryName = String.format("%sFactory", annotatedType.getSimpleName());
OffsetDateTime now = OffsetDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
AnnotationSpec generated = AnnotationSpec.builder(Generated.class)
.addMember("value", String.format("\"%s\"", JackfruitVersion.packageName))
.addMember("date", String.format("\"%s\"", formatter.format(now)))
.addMember("comments", String.format("\"version %s-%s\"", JackfruitVersion.dateString,
JackfruitVersion.rev))
.build();
TypeSpec.Builder classBuilder =
TypeSpec.classBuilder(factoryName).addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addSuperinterface(ptn).addAnnotation(generated);
/*-
// logger for the generated class
FieldSpec loggerField = FieldSpec.builder(org.apache.logging.log4j.Logger.class, "logger")
.initializer("$T.getLogger()", org.apache.logging.log4j.LogManager.class)
.addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC).build();
classBuilder.addField(loggerField);
*/
// this contains a hierarchy of parent classes
List<DeclaredType> classHierarchy = new ArrayList<>();
{
TypeElement thisElement = annotatedType;
TypeMirror superClass = thisElement.getSuperclass();
while (superClass.getKind() == TypeKind.DECLARED) {
DeclaredType superType = (DeclaredType) superClass;
// have to use asElement() here
if (superType.asElement().getAnnotation(Jackfruit.class) == null) {
break;
}
classHierarchy.add(superType);
thisElement = (TypeElement) superType.asElement();
superClass = thisElement.getSuperclass();
}
}
Collections.reverse(classHierarchy);
classHierarchy.add((DeclaredType) annotatedType.asType());
// create a list of methods annotated with DefaultValue - ignore everything else
Map<Name, ExecutableElement> enclosedMethods = new LinkedHashMap<>();
Map<Name, AnnotationBundle> defaultAnnotationsMap = new LinkedHashMap<>();
for (DeclaredType thisType : classHierarchy) {
for (Element e : thisType.asElement().getEnclosedElements()) {
if (e.getKind() == ElementKind.METHOD && e.getAnnotation(DefaultValue.class) != null
&& e instanceof ExecutableElement) {
ExecutableElement ex = (ExecutableElement) e;
enclosedMethods.put(ex.getSimpleName(), ex);
AnnotationBundle defaultValues = defaultAnnotationsMap.get(ex.getSimpleName());
defaultAnnotationsMap.put(ex.getSimpleName(),
buildAnnotationBundle(ex, defaultValues));
}
}
}
// holds the annotation information on each method
Map<ExecutableElement, AnnotationBundle> annotationsMap = new LinkedHashMap<>();
for (ExecutableElement e : enclosedMethods.values()) {
AnnotationBundle defaultValues = defaultAnnotationsMap.get(e.getSimpleName());
annotationsMap.put(e, buildAnnotationBundle(e, defaultValues));
}
// default constructor; initialize prefix
String prefixMemberName = "prefix";
classBuilder.addField(String.class, prefixMemberName, Modifier.PRIVATE, Modifier.FINAL);
MethodSpec constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC)
.addStatement("this.$N = $S", prefixMemberName, prefix).build();
classBuilder.addMethod(constructor);
// add a constructor where caller can set prefix
constructor =
MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC)
.addParameter(String.class, prefixMemberName)
.beginControlFlow("if ($N == null)", prefixMemberName)
.addStatement("$N = \"\"", prefixMemberName).endControlFlow()
.addStatement("$N = $N.strip()", prefixMemberName, prefixMemberName)
.addStatement("if (!$N.endsWith(\".\")) $N += $S", prefixMemberName,
prefixMemberName, ".")
.addStatement("this.$N = $N", prefixMemberName, prefixMemberName).build();
classBuilder.addMethod(constructor);
// generate the methods from the interface
List<MethodSpec> methods = new ArrayList<>();
for (Method m : ConfigFactory.class.getMethods()) {
if (m.isDefault())
continue;
if (m.getName().equals("toConfig")) {
MethodSpec toConfig = buildToConfig(tvn, m, annotationsMap, prefixMemberName);
methods.add(toConfig);
}
if (m.getName().equals("getTemplate")) {
MethodSpec getTemplate = buildGetTemplate(tvn, m, annotationsMap);
methods.add(getTemplate);
}
if (m.getName().equals("fromConfig")) {
MethodSpec fromConfig = buildFromConfig(tvn, m, annotationsMap, prefixMemberName);
methods.add(fromConfig);
}
}
methods.addAll(buildWithMethods(tvn, annotationsMap, prefixMemberName));
classBuilder.addMethods(methods);
TypeSpec thisClass = classBuilder.build();
// write the source code
PackageElement pkg = processingEnv.getElementUtils().getPackageOf(annotatedType);
JavaFileObject jfo =
processingEnv.getFiler().createSourceFile(pkg.getQualifiedName() + "." + factoryName);
JavaFile javaFile = JavaFile.builder(pkg.toString(), thisClass).build();
try (PrintWriter pw = new PrintWriter(jfo.openWriter())) {
javaFile.writeTo(pw);
}
messager.printMessage(Diagnostic.Kind.NOTE,
String.format("wrote %s", javaFile.toJavaFileObject().toUri()));
}
} catch (IOException e1) {
messager.printMessage(Diagnostic.Kind.ERROR, e1.getLocalizedMessage());
e1.printStackTrace();
}
}
return true;
}
/**
*
* @param e annotated method
* @param defaultValues default values for annotations - could be from a parent class
* @return annotation values
*/
private AnnotationBundle buildAnnotationBundle(ExecutableElement e,
AnnotationBundle defaultValues) {
ImmutableAnnotationBundle.Builder builder = ImmutableAnnotationBundle.builder();
builder.key(e.getSimpleName().toString());
builder.comment("");
builder.defaultValue("");
if (defaultValues != null) {
builder.key(defaultValues.key());
builder.comment(defaultValues.comment());
builder.defaultValue(defaultValues.defaultValue());
if (defaultValues.parserClass().isPresent())
builder.parserClass(defaultValues.parserClass().get());
}
Types types = processingEnv.getTypeUtils();
TypeMirror returnType = e.getReturnType();
TypeMirror erasure = types.erasure(returnType);
builder.erasure(erasure);
List<TypeMirror> typeArgs = new ArrayList<>();
if (erasure.getKind() == TypeKind.DECLARED) {
// these are the parameter types for a generic class
List<? extends TypeMirror> args = ((DeclaredType) returnType).getTypeArguments();
typeArgs.addAll(args);
} else if (erasure.getKind().isPrimitive()) {
// no type arguments here
} else {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format(
"Unsupported kind %s for type %s!", erasure.getKind().toString(), erasure.toString()));
}
builder.addAllTypeArgs(typeArgs);
List<Annotation> methodAnnotations = new ArrayList<>();
for (var a : supportedMethodAnnotations)
methodAnnotations.add(e.getAnnotation(a));
for (Annotation annotation : methodAnnotations) {
if (annotation == null)
continue;
if (annotation instanceof Key) {
builder.key(((Key) annotation).value());
} else if (annotation instanceof Comment) {
builder.comment(((Comment) annotation).value());
} else if (annotation instanceof DefaultValue) {
builder.defaultValue(((DefaultValue) annotation).value());
} else if (annotation instanceof ParserClass) {
ParserClass pc = (ParserClass) annotation;
// this works, but there has to be a better way?
TypeMirror tm;
try {
// see
// https://stackoverflow.com/questions/7687829/java-6-annotation-processing-getting-a-class-from-an-annotation
tm = processingEnv.getElementUtils().getTypeElement(pc.value().toString()).asType();
} catch (MirroredTypeException mte) {
tm = mte.getTypeMirror();
}
builder.parserClass(tm);
} else {
throw new IllegalArgumentException(
"Unknown annotation type " + annotation.getClass().getSimpleName());
}
}
AnnotationBundle bundle = builder.build();
if (ConfigProcessorUtils.isList(bundle.erasure(), processingEnv)
&& bundle.typeArgs().size() == 0)
messager.printMessage(Diagnostic.Kind.ERROR,
String.format("No parameter type for List on method %s!", e.getSimpleName()));
return bundle;
}
/**
* Build the method to generate an Apache Commons {@link Configuration} from an object
*
* @param tvn
* @param m
* @param annotationsMap
* @param prefix
* @return
*/
private MethodSpec buildToConfig(TypeVariableName tvn, Method m,
Map<ExecutableElement, AnnotationBundle> annotationsMap, String prefixMemberName) {
ParameterSpec ps = ParameterSpec.builder(tvn, "t").build();
ParameterSpec layout = ParameterSpec.builder(TypeVariableName.get(
org.apache.commons.configuration2.PropertiesConfigurationLayout.class.getCanonicalName()),
"layout").build();
MethodSpec.Builder methodBuilder =
MethodSpec.methodBuilder(m.getName()).addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC).returns(m.getGenericReturnType());
methodBuilder.addParameter(ps);
methodBuilder.addParameter(layout);
methodBuilder.addStatement("$T config = new $T()",
org.apache.commons.configuration2.PropertiesConfiguration.class,
org.apache.commons.configuration2.PropertiesConfiguration.class);
methodBuilder.addStatement("config.setLayout($N)", layout);
boolean needBlank = true;
for (ExecutableElement method : annotationsMap.keySet()) {
AnnotationBundle ab = annotationsMap.get(method);
String key = ab.key();
if (needBlank) {
methodBuilder.addStatement("$N.setBlankLinesBefore($N + $S, 1)", layout, prefixMemberName,
key);
needBlank = false;
}
TypeMirror parser = null;
String parserName = null;
if (ab.parserClass().isPresent()) {
parser = ab.parserClass().get();
parserName = method.getSimpleName() + "Parser";
methodBuilder.addStatement("$T " + parserName + " = new $T()", parser, parser);
}
if (ConfigProcessorUtils.isList(ab.erasure(), processingEnv)) {
// if it's a list, store a List<String> in the Apache configuration
TypeVariableName stringType = TypeVariableName.get(java.lang.String.class.getName());
ParameterizedTypeName listType =
ParameterizedTypeName.get(ClassName.get(java.util.List.class), stringType);
ParameterizedTypeName arrayListType =
ParameterizedTypeName.get(ClassName.get(java.util.ArrayList.class), stringType);
String listName = method.getSimpleName() + "List";
methodBuilder.addStatement("$T " + listName + " = new $T()", listType, arrayListType);
methodBuilder.beginControlFlow("for (var element : t.$L())", method.getSimpleName());
if (ab.parserClass().isPresent()) {
methodBuilder.addStatement("$L.add($L.toString(element))", listName, parserName);
} else {
TypeMirror typeArg = ab.typeArgs().get(0);
if (ConfigProcessorUtils.isByte(typeArg, processingEnv))
methodBuilder.addStatement("$L.add($T.toString(element))", listName,
java.lang.Byte.class);
if (ConfigProcessorUtils.isBoolean(typeArg, processingEnv))
methodBuilder.addStatement("$L.add($T.toString(element))", listName,
java.lang.Boolean.class);
if (ConfigProcessorUtils.isDouble(typeArg, processingEnv))
methodBuilder.addStatement("$L.add($T.toString(element))", listName,
java.lang.Double.class);
if (ConfigProcessorUtils.isFloat(typeArg, processingEnv))
methodBuilder.addStatement("$L.add($T.toString(element))", listName,
java.lang.Float.class);
if (ConfigProcessorUtils.isInteger(typeArg, processingEnv))
methodBuilder.addStatement("$L.add($T.toString(element))", listName,
java.lang.Integer.class);
if (ConfigProcessorUtils.isLong(typeArg, processingEnv))
methodBuilder.addStatement("$L.add($T.toString(element))", listName,
java.lang.Long.class);
if (ConfigProcessorUtils.isShort(typeArg, processingEnv))
methodBuilder.addStatement("$L.add($T.toString(element))", listName,
java.lang.Short.class);
if (ConfigProcessorUtils.isString(typeArg, processingEnv))
methodBuilder.addStatement("$L.add(element)", listName);
}
methodBuilder.endControlFlow();
methodBuilder.addStatement("config.setProperty($N + $S, $L)", prefixMemberName, key,
listName);
} else {
if (ab.parserClass().isPresent()) {
// store the serialized string as the property
methodBuilder.addStatement("config.setProperty($N + $S, $L.toString($N.$L()))",
prefixMemberName, key, parserName, ps, method.getSimpleName());
} else {
methodBuilder.addStatement("config.setProperty($N + $S, t.$L())", prefixMemberName, key,
method.getSimpleName());
}
}
// add the comment
if (ab.comment().length() > 0) {
String commentName = String.format("%sComment", method.getSimpleName());
methodBuilder.addStatement("$T $L = $S", String.class, commentName, ab.comment());
methodBuilder.addStatement("$N.setComment($N + $S, $L)", layout, prefixMemberName, key,
commentName);
}
}
methodBuilder.addCode("return config;");
return methodBuilder.build();
}
/**
* Create a method that returns a template object, populated by the default values
*
* @param tvn
* @param m
* @param annotationsMap
* @return
*/
private MethodSpec buildGetTemplate(TypeVariableName tvn, Method m,
Map<ExecutableElement, AnnotationBundle> annotationsMap) {
// this builds the getTemplate() method
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(m.getName())
.addAnnotation(Override.class).addModifiers(Modifier.PUBLIC).returns(tvn);
TypeSpec.Builder typeBuilder = TypeSpec.anonymousClassBuilder("").addSuperinterface(tvn);
for (ExecutableElement method : annotationsMap.keySet()) {
AnnotationBundle bundle = annotationsMap.get(method);
// this builds the method on the anonymous class
MethodSpec.Builder builder = MethodSpec.methodBuilder(method.getSimpleName().toString())
.addModifiers(Modifier.PUBLIC).addAnnotation(Override.class)
.returns(TypeName.get(method.getReturnType())).addJavadoc(bundle.comment());
TypeMirror parser = null;
String parserName = null;
if (bundle.parserClass().isPresent()) {
parser = bundle.parserClass().get();
parserName = method.getSimpleName() + "Parser";
builder.addStatement("$T $L = new $T()", parser, parserName, parser);
}
if (ConfigProcessorUtils.isList(bundle.erasure(), processingEnv)) {
TypeName argType = TypeName.get(bundle.typeArgs().get(0));
ParameterizedTypeName listType =
ParameterizedTypeName.get(ClassName.get(java.util.List.class), argType);
ParameterizedTypeName arrayListType =
ParameterizedTypeName.get(ClassName.get(java.util.ArrayList.class), argType);
String listName = method.getSimpleName() + "List";
builder.addStatement("$T " + listName + " = new $T()", listType, arrayListType);
builder.addStatement("String [] parts = ($S).split($S)", bundle.defaultValue(),
"[\\n\\r\\s]+");
builder.beginControlFlow("for (String part : parts)");
builder.beginControlFlow("if (part.trim().length() > 0)");
if (bundle.parserClass().isPresent()) {
builder.addStatement("$L.add($L.fromString(part))", listName, parserName);
} else {
TypeMirror typeArg = bundle.typeArgs().get(0);
if (ConfigProcessorUtils.isByte(typeArg, processingEnv))
builder.addStatement("$L.add($T.valueOf(part))", listName, java.lang.Byte.class);
if (ConfigProcessorUtils.isBoolean(typeArg, processingEnv))
builder.addStatement("$L.add($T.valueOf(part))", listName, java.lang.Boolean.class);
if (ConfigProcessorUtils.isDouble(typeArg, processingEnv))
builder.addStatement("$L.add($T.valueOf(part))", listName, java.lang.Double.class);
if (ConfigProcessorUtils.isFloat(typeArg, processingEnv))
builder.addStatement("$L.add($T.valueOf(part))", listName, java.lang.Float.class);
if (ConfigProcessorUtils.isInteger(typeArg, processingEnv))
builder.addStatement("$L.add($T.valueOf(part))", listName, java.lang.Integer.class);
if (ConfigProcessorUtils.isLong(typeArg, processingEnv))
builder.addStatement("$L.add($T.valueOf(part))", listName, java.lang.Long.class);
if (ConfigProcessorUtils.isShort(typeArg, processingEnv))
builder.addStatement("$L.add($T.valueOf(part))", listName, java.lang.Short.class);
if (ConfigProcessorUtils.isString(typeArg, processingEnv))
builder.addStatement("$L.add(part)", listName);
}
builder.endControlFlow();
builder.endControlFlow();
builder.addStatement("return $L", listName);
} else {
if (bundle.parserClass().isPresent()) {
builder.addStatement("return $L.fromString($S)", parserName, bundle.defaultValue());
} else {
if (ConfigProcessorUtils.isString(bundle.erasure(), processingEnv)) {
builder.addStatement("return $S", bundle.defaultValue());
} else {
if (bundle.defaultValue().trim().length() == 0) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
String.format("Default value on method %s is blank!", method.getSimpleName()));
}
builder.addStatement("return $L", bundle.defaultValue());
}
}
}
typeBuilder.addMethod(builder.build());
}
methodBuilder.addStatement("return $L", typeBuilder.build());
return methodBuilder.build();
}
/**
* Create a method to create a configuration from the object
*
* @param tvn
* @param m
* @param annotationsMap
* @param prefix
* @return
*/
private MethodSpec buildFromConfig(TypeVariableName tvn, Method m,
Map<ExecutableElement, AnnotationBundle> annotationsMap, String prefix) {
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(m.getName())
.addAnnotation(Override.class).addModifiers(Modifier.PUBLIC).returns(tvn)
.addParameter(org.apache.commons.configuration2.Configuration.class, "config");
TypeSpec.Builder typeBuilder = TypeSpec.anonymousClassBuilder("").addSuperinterface(tvn);
for (ExecutableElement method : annotationsMap.keySet()) {
AnnotationBundle bundle = annotationsMap.get(method);
MethodSpec.Builder builder = MethodSpec.methodBuilder(method.getSimpleName().toString())
.addModifiers(Modifier.PUBLIC).addAnnotation(Override.class)
.returns(TypeName.get(method.getReturnType())).addJavadoc(bundle.comment());
TypeMirror parser = null;
String parserName = null;
if (bundle.parserClass().isPresent()) {
parser = bundle.parserClass().get();
parserName = method.getSimpleName() + "Parser";
builder.addStatement("$T $L = new $T()", parser, parserName, parser);
}
if (ConfigProcessorUtils.isList(bundle.erasure(), processingEnv)) {
TypeName argType = TypeName.get(bundle.typeArgs().get(0));
ParameterizedTypeName listType =
ParameterizedTypeName.get(ClassName.get(java.util.List.class), argType);
ParameterizedTypeName arrayListType =
ParameterizedTypeName.get(ClassName.get(java.util.ArrayList.class), argType);
String listName = method.getSimpleName() + "List";
builder.addStatement("$T " + listName + " = new $T()", listType, arrayListType);
builder.addStatement("String [] parts = config.getStringArray($N + $S)", prefix,
bundle.key());
builder.beginControlFlow("for (String part : parts)");
builder.beginControlFlow("if (part.trim().length() > 0)");
if (bundle.parserClass().isPresent()) {
builder.addStatement("$L.add($L.fromString(part))", listName, parserName);
} else {
TypeMirror typeArg = bundle.typeArgs().get(0);
if (ConfigProcessorUtils.isByte(typeArg, processingEnv))
builder.addStatement("$L.add($T.valueOf(part))", listName, java.lang.Byte.class);
if (ConfigProcessorUtils.isBoolean(typeArg, processingEnv))
builder.addStatement("$L.add($T.valueOf(part))", listName, java.lang.Boolean.class);
if (ConfigProcessorUtils.isDouble(typeArg, processingEnv))
builder.addStatement("$L.add($T.valueOf(part))", listName, java.lang.Double.class);
if (ConfigProcessorUtils.isFloat(typeArg, processingEnv))
builder.addStatement("$L.add($T.valueOf(part))", listName, java.lang.Float.class);
if (ConfigProcessorUtils.isInteger(typeArg, processingEnv))
builder.addStatement("$L.add($T.valueOf(part))", listName, java.lang.Integer.class);
if (ConfigProcessorUtils.isLong(typeArg, processingEnv))
builder.addStatement("$L.add($T.valueOf(part))", listName, java.lang.Long.class);
if (ConfigProcessorUtils.isShort(typeArg, processingEnv))
builder.addStatement("$L.add($T.valueOf(part))", listName, java.lang.Short.class);
if (ConfigProcessorUtils.isString(typeArg, processingEnv))
builder.addStatement("$L.add(part)", listName);
}
builder.endControlFlow();
builder.endControlFlow();
builder.addStatement("return $L", listName);
} else {
if (bundle.parserClass().isPresent()) {
builder.addStatement("return $L.fromString(config.getString($N + $S))", parserName,
prefix, bundle.key());
} else {
if (ConfigProcessorUtils.isBoolean(bundle.erasure(), processingEnv)) {
builder.addStatement("return config.getBoolean($N + $S)", prefix, bundle.key());
} else if (ConfigProcessorUtils.isByte(bundle.erasure(), processingEnv)) {
builder.addStatement("return config.getByte($N + $S)", prefix, bundle.key());
} else if (ConfigProcessorUtils.isDouble(bundle.erasure(), processingEnv)) {
builder.addStatement("return config.getDouble($N + $S)", prefix, bundle.key());
} else if (ConfigProcessorUtils.isFloat(bundle.erasure(), processingEnv)) {
builder.addStatement("return config.getFloat($N + $S)", prefix, bundle.key());
} else if (ConfigProcessorUtils.isInteger(bundle.erasure(), processingEnv)) {
builder.addStatement("return config.getInt($N + $S)", prefix, bundle.key());
} else if (ConfigProcessorUtils.isLong(bundle.erasure(), processingEnv)) {
builder.addStatement("return config.getLong($N + $S)", prefix, bundle.key());
} else if (ConfigProcessorUtils.isShort(bundle.erasure(), processingEnv)) {
builder.addStatement("return config.getShort($N + $S)", prefix, bundle.key());
} else if (ConfigProcessorUtils.isString(bundle.erasure(), processingEnv)) {
builder.addStatement("return config.getString($N + $S)", prefix, bundle.key());
} else {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Can't handle return type " + m.getReturnType().getCanonicalName());
}
}
}
typeBuilder.addMethod(builder.build());
}
methodBuilder.addStatement("return $L", typeBuilder.build());
return methodBuilder.build();
}
/**
* Create a method for each member that allows it to be replaced.
*
* @param tvn
* @param annotationsMap
* @param prefixMemberName
* @return
*/
private List<MethodSpec> buildWithMethods(TypeVariableName tvn,
Map<ExecutableElement, AnnotationBundle> annotationsMap, String prefixMemberName) {
List<MethodSpec> withMethods = new ArrayList<>();
ParameterSpec ps = ParameterSpec.builder(tvn, "t").build();
for (ExecutableElement method : annotationsMap.keySet()) {
String methodName = method.getSimpleName().toString();
String camelCase = String.format("with%s%s", methodName.substring(0, 1).toUpperCase(),
methodName.substring(1));
TypeName propertiesConfigurationClass =
TypeName.get(org.apache.commons.configuration2.PropertiesConfiguration.class);
// method with object passed in as an argument
MethodSpec.Builder builder = MethodSpec.methodBuilder(camelCase).addModifiers(Modifier.PUBLIC)
.returns(propertiesConfigurationClass);
builder.addJavadoc("Replace the value of " + methodName);
builder.addParameter(ps);
builder.addParameter(
ParameterSpec.builder(TypeName.get(method.getReturnType()), "replaceValue").build());
builder.addStatement("$T config = toConfig($N)", propertiesConfigurationClass, ps);
builder.addStatement(String.format("return %s(config, replaceValue)", camelCase));
withMethods.add(builder.build());
// method with PropertiesConfiguration passed in as an argument
builder = MethodSpec.methodBuilder(camelCase).addModifiers(Modifier.PUBLIC)
.returns(propertiesConfigurationClass);
builder.addJavadoc("Replace the value of " + methodName);
builder.addParameter(ParameterSpec.builder(propertiesConfigurationClass, "config").build());
builder.addParameter(
ParameterSpec.builder(TypeName.get(method.getReturnType()), "replaceValue").build());
AnnotationBundle ab = annotationsMap.get(method);
String key = ab.key();
TypeMirror parser = null;
String parserName = null;
if (ab.parserClass().isPresent()) {
parser = ab.parserClass().get();
parserName = method.getSimpleName() + "Parser";
builder.addStatement("$T " + parserName + " = new $T()", parser, parser);
}
if (ConfigProcessorUtils.isList(ab.erasure(), processingEnv)) {
// if it's a list, store a List<String> in the Apache configuration
TypeVariableName stringType = TypeVariableName.get(java.lang.String.class.getName());
ParameterizedTypeName listType =
ParameterizedTypeName.get(ClassName.get(java.util.List.class), stringType);
ParameterizedTypeName arrayListType =
ParameterizedTypeName.get(ClassName.get(java.util.ArrayList.class), stringType);
String listName = method.getSimpleName() + "List";
builder.addStatement("$T " + listName + " = new $T()", listType, arrayListType);
builder.beginControlFlow("for (var element : replaceValue)");
if (ab.parserClass().isPresent()) {
builder.addStatement("$L.add($L.toString(element))", listName, parserName);
} else {
TypeMirror typeArg = ab.typeArgs().get(0);
if (ConfigProcessorUtils.isByte(typeArg, processingEnv))
builder.addStatement("$L.add($T.toString(element))", listName, java.lang.Byte.class);
if (ConfigProcessorUtils.isBoolean(typeArg, processingEnv))
builder.addStatement("$L.add($T.toString(element))", listName, java.lang.Boolean.class);
if (ConfigProcessorUtils.isDouble(typeArg, processingEnv))
builder.addStatement("$L.add($T.toString(element))", listName, java.lang.Double.class);
if (ConfigProcessorUtils.isFloat(typeArg, processingEnv))
builder.addStatement("$L.add($T.toString(element))", listName, java.lang.Float.class);
if (ConfigProcessorUtils.isInteger(typeArg, processingEnv))
builder.addStatement("$L.add($T.toString(element))", listName, java.lang.Integer.class);
if (ConfigProcessorUtils.isLong(typeArg, processingEnv))
builder.addStatement("$L.add($T.toString(element))", listName, java.lang.Long.class);
if (ConfigProcessorUtils.isShort(typeArg, processingEnv))
builder.addStatement("$L.add($T.toString(element))", listName, java.lang.Short.class);
if (ConfigProcessorUtils.isString(typeArg, processingEnv))
builder.addStatement("$L.add(element)", listName);
}
builder.endControlFlow();
builder.addStatement("config.setProperty($N + $S, $L)", prefixMemberName, key, listName);
} else {
if (ab.parserClass().isPresent()) {
// store the serialized string as the property
builder.addStatement("config.setProperty($N + $S, $L.toString(replaceValue))",
prefixMemberName, key, parserName);
} else {
builder.addStatement("config.setProperty($N + $S, replaceValue)", prefixMemberName, key);
}
}
builder.addStatement("return config");
withMethods.add(builder.build());
}
return withMethods;
}
}