Merge branch '6-string-stuff' into 'main'

Resolve "String stuff"

Closes #6

See merge request nairah1/jackfruit!6
This commit is contained in:
Hari Nair
2022-09-24 16:04:53 +00:00
6 changed files with 72 additions and 41 deletions

View File

@@ -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<Double> doubles();
@Comment("List of RandomClass")
@DefaultValue("obj1 obj2")
@DefaultValue("""
obj1
obj2
obj3 obj4
""")
@ParserClass(SomeRandomClassParser.class)
List<SomeRandomClass> 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<SomeRandomClass> 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

View File

@@ -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<Double> doubles();
@Comment("List of RandomClass")
@DefaultValue("obj1 obj2")
@DefaultValue("""
obj1
obj2
obj3 obj4
""")
@ParserClass(SomeRandomClassParser.class)
public abstract List<SomeRandomClass> 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");
}
}

View File

@@ -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<Double> doubles();
@Comment("List of RandomClass")
@DefaultValue("obj1 obj2")
@DefaultValue("""
obj1
obj2
obj3 obj4
""")
@ParserClass(SomeRandomClassParser.class)
List<SomeRandomClass> randoms();

View File

@@ -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 =

View File

@@ -55,11 +55,6 @@
<artifactId>log4j-api</artifactId>
<version>2.18.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>

View File

@@ -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<Element> 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()) {