diff --git a/README.md b/README.md index 0e0f448..ad2a824 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Quick start -Jackfruit processes annotations on Java interfaces to generate code that can read and write Apache Configuration files. The `demo` module includes sample code. In the top level directory, run `mvn clean package`, which will build the annotation library, run the annotation processor on the file `demo/src/main/java/jackfruit/demo/DemoConfig.java`, and generate the class `demo/target/generated-sources/annotations/jackfruit/demo/DemoConfigFactory.java`. The file `demo/src/main/java/jackfruit/demo/JackfruitDemo.java` shows some simple examples of use. +Jackfruit processes annotations on Java interfaces to generate code that can read and write Apache Configuration files. The `demo` module includes sample code. In the top level directory, run `mvn clean package`, which will build the annotation library, run the annotation processor on the file `demo/src/main/java/jackfruit/demo/DemoInterface.java`, and generate the class `demo/target/generated-sources/annotations/jackfruit/demo/DemoInterfaceFactory.java`. The file `demo/src/main/java/jackfruit/demo/JackfruitDemo.java` shows some simple examples of use. ## Introduction @@ -19,46 +19,66 @@ Include Jackfruit in your project with the following POM: Find the latest version at [Surfshop](http://surfshop:8082/ui/repos/tree/General/libs-snapshot-local/edu/jhuapl/ses/srn/jackfruit/). -An example of an annotated interface is +The annotation processor runs on any interface or abstract class annotated with `@Jackfruit` ``` @Jackfruit(prefix = "prefix") -public interface DemoConfig { +public interface DemoInterface { // default key is field name @Key("key") - @Comment("field comment") - @DefaultValue("0") - public int intMethod(); + @Comment("One line comment") + @DefaultValue("1") + int intMethod(); + @Comment("This is a very long comment line that really should be wrapped into more than one line but that's really up to you.") @DefaultValue("0.") - public Double doubleMethod(); + Double doubleMethod(); + @Comment(""" + This is a multiline + java text block. + + This is a new paragraph. + """) @DefaultValue("Default String") - public String StringMethod(); + String StringMethod(); - @Comment("This string is serialized into an object") + @Comment("This string is serialized into an object\n\tThis comment contains a newline character, and this line starts with a tab.") @DefaultValue("serialized string") @ParserClass(SomeRandomClassParser.class) - public SomeRandomClass randomClass(); + SomeRandomClass randomClass(); @Comment("List of Doubles") @DefaultValue("0. 5.34 17") List doubles(); - + @Comment("List of RandomClass") - @DefaultValue("obj1 obj2") + @DefaultValue(""" + obj1 + obj2 + + obj3 obj4 + """) @ParserClass(SomeRandomClassParser.class) List randoms(); + } ``` This corresponds to this Apache Configuration file: ``` -# field comment -prefix.key = 0 +# One line comment +prefix.key = 1 +# This is a very long comment line that really should be wrapped into more than one line but that's really up to you. prefix.doubleMethod = 0.0 +# This is a multiline +# java text block. + +# This is a new paragraph. + prefix.StringMethod = Default String # This string is serialized into an object +# This comment contains a newline character, and this line starts with a tab. prefix.randomClass = serialized string # List of Doubles prefix.doubles = 0.0 @@ -67,15 +87,17 @@ prefix.doubles = 17.0 # List of RandomClass prefix.randoms = obj1 prefix.randoms = obj2 +prefix.randoms = obj3 +prefix.randoms = obj4 ``` -The annotation processor generates a class called DemoConfigFactory. An example of use is +The annotation processor generates a class called DemoInterfaceFactory. An example of use is ``` // this factory is built by the annotation processor after reading the DemoConfig interface - DemoConfigFactory factory = new DemoConfigFactory(); + DemoInterfaceFactory factory = new DemoInterfaceFactory(); // get an example config object - DemoConfig template = factory.getTemplate(); + DemoInterface template = factory.getTemplate(); // generate an Apache PropertiesConfiguration object. This can be written out to a file PropertiesConfiguration config = factory.toConfig(template); @@ -104,19 +126,22 @@ The annotation processor generates a class called DemoConfigFactory. An example template = factory.fromConfig(config); // get one of the config object's properties - System.out.printf("config.StringMethod() = %s\n", template.StringMethod()); + System.out.println("\n*** Retrieving a configuration value"); + List randoms = template.randoms(); + for (SomeRandomClass random : randoms) + System.out.println("random.toUpperCase() = " + random.toUpperCase()); ``` ## Supported Annotations -The `@Jackfruit` annotation goes on the interface. The remaining annotations are for use on the interface methods. +The `@Jackfruit` annotation goes on the abstract type. The remaining annotations are for use on methods. ### Jackfruit -This annotation goes on the interface 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. +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. ### Comment -The `@Comment` annotation specifies the comment that appears in the configuration file above the parameter itself. +The `@Comment` annotation specifies the comment that appears in the configuration file above the parameter itself. This can be a multiline text block and include newlines and tabs. ### DefaultValue diff --git a/demo/src/main/java/jackfruit/demo/DemoClass.java b/demo/src/main/java/jackfruit/demo/DemoClass.java index 016c04c..8167264 100644 --- a/demo/src/main/java/jackfruit/demo/DemoClass.java +++ b/demo/src/main/java/jackfruit/demo/DemoClass.java @@ -15,7 +15,7 @@ public abstract class DemoClass { @DefaultValue("1") public abstract int intMethod(); - @Comment("This is a very long comment line that really should be wrapped into more than one line") + @Comment("This is a very long comment line that really should be wrapped into more than one line but that's really up to you.") @DefaultValue("0.") public abstract Double doubleMethod(); @@ -38,12 +38,17 @@ public abstract class DemoClass { public abstract List doubles(); @Comment("List of RandomClass") - @DefaultValue("obj1 obj2") + @DefaultValue(""" + obj1 + obj2 + + obj3 obj4 + """) @ParserClass(SomeRandomClassParser.class) public abstract List randoms(); public void noAnnotationsOnThisMethod() { - System.out.println("This method was not processed"); + System.out.println("This method was not processed since it has no DefaultValue annotation"); } } diff --git a/demo/src/main/java/jackfruit/demo/DemoInterface.java b/demo/src/main/java/jackfruit/demo/DemoInterface.java index 8ab0a97..e1e5394 100644 --- a/demo/src/main/java/jackfruit/demo/DemoInterface.java +++ b/demo/src/main/java/jackfruit/demo/DemoInterface.java @@ -49,7 +49,7 @@ public interface DemoInterface { @DefaultValue("1") int intMethod(); - @Comment("This is a very long comment line that really should be wrapped into more than one line") + @Comment("This is a very long comment line that really should be wrapped into more than one line but that's really up to you.") @DefaultValue("0.") Double doubleMethod(); @@ -72,7 +72,12 @@ public interface DemoInterface { List doubles(); @Comment("List of RandomClass") - @DefaultValue("obj1 obj2") + @DefaultValue(""" + obj1 + obj2 + + obj3 obj4 + """) @ParserClass(SomeRandomClassParser.class) List randoms(); diff --git a/demo/src/main/java/jackfruit/demo/JackfruitDemo.java b/demo/src/main/java/jackfruit/demo/JackfruitDemo.java index 9774e2e..490f5e7 100644 --- a/demo/src/main/java/jackfruit/demo/JackfruitDemo.java +++ b/demo/src/main/java/jackfruit/demo/JackfruitDemo.java @@ -13,19 +13,19 @@ public class JackfruitDemo { public static void main(String[] args) { + /*- // this factory is built by the annotation processor after reading // DemoClass DemoClassFactory factory = new DemoClassFactory(); - + // get an example config object DemoClass template = factory.getTemplate(); - - /*- + */ + // or if you prefer, use an interface DemoInterfaceFactory factory = new DemoInterfaceFactory(); DemoInterface template = factory.getTemplate(); - */ - + // generate an Apache PropertiesConfiguration object. This can be written out to // a file PropertiesConfiguration config = diff --git a/jackfruit/pom.xml b/jackfruit/pom.xml index 54db0b9..9cc1328 100644 --- a/jackfruit/pom.xml +++ b/jackfruit/pom.xml @@ -55,11 +55,6 @@ log4j-api 2.18.0 - - org.apache.commons - commons-text - 1.8 - org.apache.logging.log4j log4j-core diff --git a/jackfruit/src/main/java/jackfruit/processor/ConfigProcessor.java b/jackfruit/src/main/java/jackfruit/processor/ConfigProcessor.java index 729da01..c58deca 100644 --- a/jackfruit/src/main/java/jackfruit/processor/ConfigProcessor.java +++ b/jackfruit/src/main/java/jackfruit/processor/ConfigProcessor.java @@ -31,7 +31,6 @@ import javax.lang.model.util.Types; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; import org.apache.commons.configuration2.Configuration; -import org.apache.commons.text.WordUtils; import com.google.auto.service.AutoService; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.JavaFile; @@ -69,8 +68,10 @@ public class ConfigProcessor extends AbstractProcessor { Messager messager = processingEnv.getMessager(); + // find interfaces or abstract classes with the Jackfruit annotation List annotatedElements = roundEnv.getElementsAnnotatedWith(Jackfruit.class).stream() - .filter(e -> e.getKind() == ElementKind.INTERFACE || e.getKind() == ElementKind.CLASS) + .filter(e -> e.getKind() == ElementKind.INTERFACE + || (e.getKind() == ElementKind.CLASS && e.getModifiers().contains(Modifier.ABSTRACT))) .collect(Collectors.toList()); for (Element element : annotatedElements) { @@ -338,8 +339,7 @@ public class ConfigProcessor extends AbstractProcessor { 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($S, $T.wrap($L, 80))", layout, key, - WordUtils.class, commentName); + methodBuilder.addStatement("$N.setComment($S, $L)", layout, key, commentName); } } @@ -382,7 +382,8 @@ public class ConfigProcessor extends AbstractProcessor { String listName = method.getSimpleName() + "List"; builder.addStatement("$T " + listName + " = new $T()", listType, arrayListType); - builder.addStatement("String [] parts = $S.split($S)", bundle.defaultValue(), "\\s+"); + 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()) {