mirror of
https://github.com/JHUAPL/Jackfruit.git
synced 2026-01-06 21:13:54 -05:00
3 add include annotation (#4)
* add Included annotation * update version number * Add Include description in README * update version to 1.1.0
This commit is contained in:
44
README.md
44
README.md
@@ -17,7 +17,7 @@ Include Jackfruit in your project with the following POM:
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Find the latest version at [Maven Central](https://central.sonatype.com/).
|
||||
Find the latest version at [Maven Central](https://central.sonatype.com/artifact/edu.jhuapl.ses/jackfruit).
|
||||
|
||||
The annotation processor runs on any interface or abstract class annotated with `@Jackfruit`
|
||||
```
|
||||
@@ -65,7 +65,7 @@ public interface DemoInterface {
|
||||
}
|
||||
```
|
||||
|
||||
This corresponds to this Apache Configuration file:
|
||||
This corresponds to the following Apache Configuration file:
|
||||
```
|
||||
# One line comment
|
||||
prefix.key = 1
|
||||
@@ -138,7 +138,7 @@ Jackfruit annotations can be inherited by derived classes. The `@Jackfruit` ann
|
||||
|
||||
## Supported Annotations
|
||||
|
||||
The `@Jackfruit` annotation goes on the abstract type. The remaining annotations are for use on methods.
|
||||
The `@Jackfruit` annotation goes on the abstract type. The remaining annotations are for use on methods. Jackfruit will only process methods annotated either with `@DefaultValue` or `@Include`.
|
||||
|
||||
### Jackfruit
|
||||
This annotation goes on the abstract type to signify that it should be run through the annotation processor. There is an optional "prefix" argument that can be used to add a prefix to all of the configuration keys created by the processor. The Jackfruit annotation is not inherited by derived classes.
|
||||
@@ -156,7 +156,43 @@ The `@Comment` annotation specifies the comment that appears in the configuratio
|
||||
|
||||
### DefaultValue
|
||||
|
||||
The `@DefaultValue` annotation is a String used to initialize the parameter. This is a required annotation. If it is absent no other annotations on this method will be processed. Strings and primitives (and their corresponding wrapper types) are read natively. Other objects will need to use the `@ParserClass` annotation to specify a class which implements the `jackfruit.annotations.Parser` interface to convert the object to and from a String.
|
||||
The `@DefaultValue` annotation is a String used to initialize the parameter. Strings and primitives (and their corresponding wrapper types) are read natively. Other objects will need to use the `@ParserClass` annotation to specify a class which implements the `jackfruit.annotations.Parser` interface to convert the object to and from a String. This annotation must be present if `@Include` is not used.
|
||||
|
||||
### Include
|
||||
|
||||
The `@Include` annotation allows the user to include another configuration class within this one. For example, if ThisBlock.java contains
|
||||
|
||||
```
|
||||
@Jackfruit(prefix = "thisBlock")
|
||||
public interface ThisBlock {
|
||||
@Comment("thisBlock")
|
||||
@DefaultValue("1")
|
||||
int intMethod();
|
||||
|
||||
@Include
|
||||
OtherBlock otherBlock();
|
||||
}
|
||||
```
|
||||
and OtherBlock.java contains
|
||||
```
|
||||
@Jackfruit(prefix = "otherBlock")
|
||||
public interface OtherBlock {
|
||||
@Comment("OtherBlock")
|
||||
@DefaultValue("2")
|
||||
int intMethod();
|
||||
}
|
||||
|
||||
```
|
||||
running new ThisBlockFactory().getTemplate() will create
|
||||
```
|
||||
# thisBlock
|
||||
thisBlock.intMethod = 1
|
||||
|
||||
# OtherBlock
|
||||
otherBlock.intMethod = 2
|
||||
```
|
||||
|
||||
If `@Include` is present, no other annotations will be honored.
|
||||
|
||||
### Key
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<artifactId>jackfruit-parent</artifactId>
|
||||
<groupId>edu.jhuapl.ses</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>jackfruit-demo</artifactId>
|
||||
@@ -37,6 +37,8 @@
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -20,10 +20,8 @@ package jackfruit.demo;
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import jackfruit.annotations.Comment;
|
||||
import jackfruit.annotations.DefaultValue;
|
||||
import jackfruit.annotations.Jackfruit;
|
||||
import jackfruit.annotations.ParserClass;
|
||||
import jackfruit.annotations.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -98,6 +96,10 @@ public abstract class DemoClass extends DemoSuperClass {
|
||||
@ParserClass(SomeRandomClassParser.class)
|
||||
public abstract List<SomeRandomClass> randoms();
|
||||
|
||||
@Comment("Access another configuration block")
|
||||
@Include
|
||||
public abstract Included included();
|
||||
|
||||
public void noAnnotationsOnThisMethod() {
|
||||
System.out.println("This method was not processed since it has no DefaultValue annotation");
|
||||
}
|
||||
|
||||
16
demo/src/main/java/jackfruit/demo/Included.java
Normal file
16
demo/src/main/java/jackfruit/demo/Included.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package jackfruit.demo;
|
||||
|
||||
import jackfruit.annotations.Comment;
|
||||
import jackfruit.annotations.DefaultValue;
|
||||
import jackfruit.annotations.Jackfruit;
|
||||
|
||||
@Jackfruit(prefix = "included")
|
||||
public interface Included {
|
||||
|
||||
@DefaultValue("1")
|
||||
int includedIntMethod();
|
||||
|
||||
@DefaultValue("1.5")
|
||||
double includedDoubleMethod();
|
||||
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<artifactId>jackfruit-parent</artifactId>
|
||||
<groupId>edu.jhuapl.ses</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>jackfruit</artifactId>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
package jackfruit;
|
||||
|
||||
public class JackfruitVersion {
|
||||
public final static String version = "1.0-SNAPSHOT";
|
||||
public final static String version = "1.0.1-SNAPSHOT";
|
||||
public final static String packageName = "jackfruit";
|
||||
public final static String dateString = "23.09.02";
|
||||
public final static String dateString = "23.12.21";
|
||||
}
|
||||
|
||||
|
||||
21
jackfruit/src/main/java/jackfruit/annotations/Include.java
Normal file
21
jackfruit/src/main/java/jackfruit/annotations/Include.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package jackfruit.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* The Include annotation includes another class annotated with @Jackfruit. Example:
|
||||
* <p>
|
||||
* <code>
|
||||
* @Include
|
||||
* AnotherBlockType anotherBlockType();
|
||||
* </code>
|
||||
* </p>
|
||||
* This allows for a configuration type to access other configuration types
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Include {
|
||||
}
|
||||
@@ -21,49 +21,20 @@ package jackfruit.processor;
|
||||
*/
|
||||
|
||||
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 com.squareup.javapoet.*;
|
||||
import jackfruit.JackfruitVersion;
|
||||
import jackfruit.annotations.Comment;
|
||||
import jackfruit.annotations.DefaultValue;
|
||||
import jackfruit.annotations.Jackfruit;
|
||||
import jackfruit.annotations.Key;
|
||||
import jackfruit.annotations.ParserClass;
|
||||
import jackfruit.annotations.*;
|
||||
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.*;
|
||||
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.annotation.processing.*;
|
||||
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.element.*;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.MirroredTypeException;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
@@ -101,6 +72,7 @@ public class ConfigProcessor extends AbstractProcessor {
|
||||
supportedMethodAnnotations = new ArrayList<>();
|
||||
supportedMethodAnnotations.add(Comment.class);
|
||||
supportedMethodAnnotations.add(DefaultValue.class);
|
||||
supportedMethodAnnotations.add(Include.class);
|
||||
supportedMethodAnnotations.add(Key.class);
|
||||
supportedMethodAnnotations.add(ParserClass.class);
|
||||
|
||||
@@ -147,7 +119,8 @@ public class ConfigProcessor extends AbstractProcessor {
|
||||
.addMember(
|
||||
"comments",
|
||||
String.format(
|
||||
"\"version %s built %s\"", JackfruitVersion.version, JackfruitVersion.dateString))
|
||||
"\"version %s built %s\"",
|
||||
JackfruitVersion.version, JackfruitVersion.dateString))
|
||||
.build();
|
||||
|
||||
TypeSpec.Builder classBuilder =
|
||||
@@ -184,18 +157,26 @@ public class ConfigProcessor extends AbstractProcessor {
|
||||
Collections.reverse(classHierarchy);
|
||||
classHierarchy.add((DeclaredType) annotatedType.asType());
|
||||
|
||||
// create a list of methods annotated with DefaultValue - ignore everything else
|
||||
// create a map of methods annotated with DefaultValue
|
||||
Map<Name, ExecutableElement> enclosedMethods = new LinkedHashMap<>();
|
||||
// the default values for each method
|
||||
Map<Name, AnnotationBundle> defaultAnnotationsMap = new LinkedHashMap<>();
|
||||
// Map of included config types
|
||||
Map<Name, AnnotationBundle> includedMap = 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 ex) {
|
||||
enclosedMethods.put(ex.getSimpleName(), ex);
|
||||
AnnotationBundle defaultValues = defaultAnnotationsMap.get(ex.getSimpleName());
|
||||
defaultAnnotationsMap.put(
|
||||
ex.getSimpleName(), buildAnnotationBundle(ex, defaultValues));
|
||||
if (e.getKind() == ElementKind.METHOD && e instanceof ExecutableElement ex) {
|
||||
|
||||
if (ex.getAnnotation(Include.class) != null) {
|
||||
AnnotationBundle defaultValues = defaultAnnotationsMap.get(ex.getSimpleName());
|
||||
AnnotationBundle annotationBundle = buildAnnotationBundle(ex, defaultValues);
|
||||
includedMap.put(ex.getSimpleName(), annotationBundle);
|
||||
} else if (ex.getAnnotation(DefaultValue.class) != null) {
|
||||
enclosedMethods.put(ex.getSimpleName(), ex);
|
||||
AnnotationBundle defaultValues = defaultAnnotationsMap.get(ex.getSimpleName());
|
||||
AnnotationBundle annotationBundle = buildAnnotationBundle(ex, defaultValues);
|
||||
defaultAnnotationsMap.put(ex.getSimpleName(), annotationBundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,17 +220,19 @@ public class ConfigProcessor extends AbstractProcessor {
|
||||
if (m.isDefault()) continue;
|
||||
|
||||
if (m.getName().equals("toConfig")) {
|
||||
MethodSpec toConfig = buildToConfig(tvn, m, annotationsMap, prefixMemberName);
|
||||
MethodSpec toConfig =
|
||||
buildToConfig(tvn, m, annotationsMap, includedMap, prefixMemberName);
|
||||
methods.add(toConfig);
|
||||
}
|
||||
|
||||
if (m.getName().equals("getTemplate")) {
|
||||
MethodSpec getTemplate = buildGetTemplate(tvn, m, annotationsMap);
|
||||
MethodSpec getTemplate = buildGetTemplate(tvn, m, annotationsMap, includedMap);
|
||||
methods.add(getTemplate);
|
||||
}
|
||||
|
||||
if (m.getName().equals("fromConfig")) {
|
||||
MethodSpec fromConfig = buildFromConfig(tvn, m, annotationsMap, prefixMemberName);
|
||||
MethodSpec fromConfig =
|
||||
buildFromConfig(tvn, m, annotationsMap, includedMap, prefixMemberName);
|
||||
methods.add(fromConfig);
|
||||
}
|
||||
}
|
||||
@@ -316,8 +299,8 @@ public class ConfigProcessor extends AbstractProcessor {
|
||||
.printMessage(
|
||||
Diagnostic.Kind.ERROR,
|
||||
String.format(
|
||||
"Unsupported kind %s for type %s!",
|
||||
erasure.getKind().toString(), erasure));
|
||||
"Element %s: Unsupported kind %s for type %s!",
|
||||
e, erasure.getKind().toString(), erasure));
|
||||
}
|
||||
|
||||
builder.addAllTypeArgs(typeArgs);
|
||||
@@ -334,6 +317,8 @@ public class ConfigProcessor extends AbstractProcessor {
|
||||
builder.comment(((Comment) annotation).value());
|
||||
} else if (annotation instanceof DefaultValue) {
|
||||
builder.defaultValue(((DefaultValue) annotation).value());
|
||||
} else if (annotation instanceof Include) {
|
||||
// do nothing
|
||||
} else if (annotation instanceof ParserClass pc) {
|
||||
|
||||
// this works, but there has to be a better way?
|
||||
@@ -353,8 +338,7 @@ public class ConfigProcessor extends AbstractProcessor {
|
||||
}
|
||||
|
||||
AnnotationBundle bundle = builder.build();
|
||||
if (ConfigProcessorUtils.isList(bundle.erasure(), processingEnv)
|
||||
&& bundle.typeArgs().isEmpty())
|
||||
if (ConfigProcessorUtils.isList(bundle.erasure(), processingEnv) && bundle.typeArgs().isEmpty())
|
||||
messager.printMessage(
|
||||
Diagnostic.Kind.ERROR,
|
||||
String.format("No parameter type for List on method %s!", e.getSimpleName()));
|
||||
@@ -374,6 +358,7 @@ public class ConfigProcessor extends AbstractProcessor {
|
||||
TypeVariableName tvn,
|
||||
Method m,
|
||||
Map<ExecutableElement, AnnotationBundle> annotationsMap,
|
||||
Map<Name, AnnotationBundle> includedMap,
|
||||
String prefixMemberName) {
|
||||
ParameterSpec ps = ParameterSpec.builder(tvn, "t").build();
|
||||
ParameterSpec layout =
|
||||
@@ -383,6 +368,7 @@ public class ConfigProcessor extends AbstractProcessor {
|
||||
.getCanonicalName()),
|
||||
"layout")
|
||||
.build();
|
||||
|
||||
MethodSpec.Builder methodBuilder =
|
||||
MethodSpec.methodBuilder(m.getName())
|
||||
.addAnnotation(Override.class)
|
||||
@@ -481,6 +467,15 @@ public class ConfigProcessor extends AbstractProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
// add included classes
|
||||
Types types = processingEnv.getTypeUtils();
|
||||
for (Name name : includedMap.keySet()) {
|
||||
AnnotationBundle bundle = includedMap.get(name);
|
||||
String className = types.asElement(bundle.erasure()).getSimpleName().toString();
|
||||
methodBuilder.addStatement(
|
||||
"config.append(new $LFactory().toConfig(t.$L(), layout))", className, name);
|
||||
}
|
||||
|
||||
methodBuilder.addCode("return config;");
|
||||
|
||||
return methodBuilder.build();
|
||||
@@ -495,7 +490,12 @@ public class ConfigProcessor extends AbstractProcessor {
|
||||
* @return
|
||||
*/
|
||||
private MethodSpec buildGetTemplate(
|
||||
TypeVariableName tvn, Method m, Map<ExecutableElement, AnnotationBundle> annotationsMap) {
|
||||
TypeVariableName tvn,
|
||||
Method m,
|
||||
Map<ExecutableElement, AnnotationBundle> annotationsMap,
|
||||
Map<Name, AnnotationBundle> includedMap) {
|
||||
|
||||
Types types = processingEnv.getTypeUtils();
|
||||
|
||||
// this builds the getTemplate() method
|
||||
MethodSpec.Builder methodBuilder =
|
||||
@@ -505,6 +505,23 @@ public class ConfigProcessor extends AbstractProcessor {
|
||||
.returns(tvn);
|
||||
TypeSpec.Builder typeBuilder = TypeSpec.anonymousClassBuilder("").addSuperinterface(tvn);
|
||||
|
||||
for (Name name : includedMap.keySet()) {
|
||||
AnnotationBundle bundle = includedMap.get(name);
|
||||
|
||||
MethodSpec.Builder builder =
|
||||
MethodSpec.methodBuilder(name.toString())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(Override.class)
|
||||
.returns(TypeName.get(bundle.erasure()))
|
||||
.addJavadoc(bundle.comment());
|
||||
|
||||
builder.addStatement(
|
||||
"return new $LFactory().getTemplate()",
|
||||
types.asElement(bundle.erasure()).getSimpleName());
|
||||
|
||||
typeBuilder.addMethod(builder.build());
|
||||
}
|
||||
|
||||
for (ExecutableElement method : annotationsMap.keySet()) {
|
||||
AnnotationBundle bundle = annotationsMap.get(method);
|
||||
|
||||
@@ -602,6 +619,7 @@ public class ConfigProcessor extends AbstractProcessor {
|
||||
TypeVariableName tvn,
|
||||
Method m,
|
||||
Map<ExecutableElement, AnnotationBundle> annotationsMap,
|
||||
Map<Name, AnnotationBundle> includedMap,
|
||||
String prefix) {
|
||||
|
||||
MethodSpec.Builder methodBuilder =
|
||||
@@ -612,6 +630,25 @@ public class ConfigProcessor extends AbstractProcessor {
|
||||
.addParameter(org.apache.commons.configuration2.Configuration.class, "config");
|
||||
|
||||
TypeSpec.Builder typeBuilder = TypeSpec.anonymousClassBuilder("").addSuperinterface(tvn);
|
||||
|
||||
Types types = processingEnv.getTypeUtils();
|
||||
for (Name name : includedMap.keySet()) {
|
||||
AnnotationBundle bundle = includedMap.get(name);
|
||||
|
||||
MethodSpec.Builder builder =
|
||||
MethodSpec.methodBuilder(name.toString())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(Override.class)
|
||||
.returns(TypeName.get(bundle.erasure()))
|
||||
.addJavadoc(bundle.comment());
|
||||
|
||||
builder.addStatement(
|
||||
"return new $LFactory().fromConfig(config)",
|
||||
types.asElement(bundle.erasure()).getSimpleName());
|
||||
|
||||
typeBuilder.addMethod(builder.build());
|
||||
}
|
||||
|
||||
for (ExecutableElement method : annotationsMap.keySet()) {
|
||||
AnnotationBundle bundle = annotationsMap.get(method);
|
||||
MethodSpec.Builder builder =
|
||||
|
||||
Reference in New Issue
Block a user