1 publish to maven central (#2)

* pom updates

* update artifactId, add gpg plugin

* use oss.sonatype.org

* update groupId in README

* update plugins

* use nexus-staging-maven-plugin

* add javadoc

* javadoc cleanup

* update versioning

* javadoc cleanup

* Update demo

---------

Co-authored-by: Hari Nair <hari@alumni.caltech.edu>
This commit is contained in:
Hari Nair
2023-09-05 17:16:54 -04:00
committed by GitHub
parent fef7f752a7
commit fa4b36f2f5
25 changed files with 521 additions and 374 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
.idea
/target

View File

@@ -10,14 +10,14 @@ Include Jackfruit in your project with the following POM:
```
<dependency>
<groupId>edu.jhuapl.ses.srn</groupId>
<groupId>edu.jhuapl.ses</groupId>
<artifactId>jackfruit</artifactId>
<version>$VERSION</version>
<type>pom</type>
</dependency>
```
Find the latest version at [Surfshop](http://surfshop:8082/ui/repos/tree/General/libs-snapshot-local/edu/jhuapl/ses/srn/jackfruit/).
Find the latest version at [Maven Central](https://central.sonatype.com/).
The annotation processor runs on any interface or abstract class annotated with `@Jackfruit`
```

View File

@@ -3,9 +3,10 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>jackfruit-parent</artifactId>
<groupId>edu.jhuapl.ses.srn</groupId>
<groupId>edu.jhuapl.ses</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
@@ -29,7 +30,7 @@
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>edu.jhuapl.ses.srn</groupId>
<groupId>edu.jhuapl.ses</groupId>
<artifactId>jackfruit</artifactId>
<version>${project.parent.version}</version>
</dependency>

View File

@@ -9,9 +9,9 @@ package crucible.crust.logging;
* Licensed 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.
@@ -37,16 +37,16 @@ import org.apache.logging.log4j.core.layout.PatternLayout;
/**
* A simple configuration class.
* <p>
* Default settings:
* <ul>
* <li>Pattern is "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%c{1}:%L] %msg%n%throwable"<br>
* (e.g. 2021-11-09 19:32:37.119 INFO [LoggingTest:25] Level INFO)</li>
* <li>Log level is {@link Level#INFO}</li>
* </ul>
*
* @author nairah1
*
* <p>Default settings:
*
* <ul>
* <li>Pattern is "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%c{1}:%L] %msg%n%throwable"<br>
* (e.g. 2021-11-09 19:32:37.119 INFO [LoggingTest:25] Level INFO)
* <li>Log level is {@link Level#INFO}
* </ul>
*
* @author Hari.Nair@jhuapl.edu
*/
public class Log4j2Configurator {
@@ -56,10 +56,9 @@ public class Log4j2Configurator {
private static Log4j2Configurator instance = null;
/**
*
* @return an instance of this singleton class.
*/
synchronized public static Log4j2Configurator getInstance() {
public static synchronized Log4j2Configurator getInstance() {
if (instance == null) {
instance = new Log4j2Configurator();
}
@@ -69,15 +68,17 @@ public class Log4j2Configurator {
private Log4j2Configurator() {
final LoggerContext loggerContext = LoggerContext.getContext(false);
final Configuration config = loggerContext.getConfiguration();
layout = PatternLayout.newBuilder().withPattern(DefaultConfiguration.DEFAULT_PATTERN)
.withConfiguration(config).build();
layout =
PatternLayout.newBuilder()
.withPattern(DefaultConfiguration.DEFAULT_PATTERN)
.withConfiguration(config)
.build();
fileAppenders = new HashMap<>();
setPattern("%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%c{1}:%L] %msg%n%throwable");
setLevel(Level.INFO);
}
/**
*
* @return a map of logger names to {@link LoggerConfig}
*/
private Map<String, LoggerConfig> getLoggerMap() {
@@ -85,22 +86,25 @@ public class Log4j2Configurator {
final Configuration config = loggerContext.getConfiguration();
Map<String, LoggerConfig> loggerMap = new HashMap<>(config.getLoggers());
loggerMap.put(LogManager.getRootLogger().getName(),
loggerMap.put(
LogManager.getRootLogger().getName(),
config.getLoggerConfig(LogManager.getRootLogger().getName()));
return Collections.unmodifiableMap(loggerMap);
}
/**
*
*
* @param filename Append log to named file, or create it if it doesn't exist.
*/
public void addFile(String filename) {
final LoggerContext loggerContext = LoggerContext.getContext(false);
Map<String, LoggerConfig> loggerMap = getLoggerMap();
FileAppender appender = FileAppender.newBuilder().setName(filename).withFileName(filename)
.setLayout(layout).build();
FileAppender appender =
FileAppender.newBuilder()
.setName(filename)
.withFileName(filename)
.setLayout(layout)
.build();
appender.start();
for (String loggerName : loggerMap.keySet()) {
@@ -112,8 +116,6 @@ public class Log4j2Configurator {
}
/**
*
*
* @param filename Stop logging to named file.
*/
public void removeFile(String filename) {
@@ -132,7 +134,6 @@ public class Log4j2Configurator {
}
/**
*
* @param pattern layout pattern for all {@link ConsoleAppender} and {@link FileAppender} objects.
*/
public void setPattern(String pattern) {
@@ -152,11 +153,20 @@ public class Log4j2Configurator {
// there should be a better way to do this - a toBuilder() method on the appender would be
// really useful
if (oldAppender instanceof ConsoleAppender) {
newAppender = ConsoleAppender.newBuilder().setName(appenderName).setConfiguration(config)
.setLayout(layout).build();
newAppender =
ConsoleAppender.newBuilder()
.setName(appenderName)
.setConfiguration(config)
.setLayout(layout)
.build();
} else if (oldAppender instanceof FileAppender) {
newAppender = FileAppender.newBuilder().setName(appenderName).setConfiguration(config)
.withFileName(((FileAppender) oldAppender).getFileName()).setLayout(layout).build();
newAppender =
FileAppender.newBuilder()
.setName(appenderName)
.setConfiguration(config)
.withFileName(((FileAppender) oldAppender).getFileName())
.setLayout(layout)
.build();
}
if (newAppender != null) {
newAppender.start();
@@ -169,15 +179,15 @@ public class Log4j2Configurator {
}
/**
* Sets the levels of <code>parentLogger</code> and all 'child' loggers to the given
* <code>level</code>. This is simply a call to
*
* Sets the levels of <code>parentLogger</code> and all 'child' loggers to the given <code>level
* </code>. This is simply a call to
*
* <pre>
* Configurator.setAllLevels(parentLogger, level)
* </pre>
*
* @param parentLogger
* @param level
*
* @param parentLogger name of parent logger
* @param level logging level
*/
public void setLevel(String parentLogger, Level level) {
Configurator.setAllLevels(parentLogger, level);
@@ -185,15 +195,14 @@ public class Log4j2Configurator {
/**
* Set all logger levels. This is simply a call to
*
*
* <pre>
* setLevel(LogManager.getRootLogger().getName(), level)
* </pre>
*
* @param level
*
* @param level logging level
*/
public void setLevel(Level level) {
setLevel(LogManager.getRootLogger().getName(), level);
}
}

View File

@@ -9,9 +9,9 @@ package jackfruit.demo;
* Licensed 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.
@@ -20,43 +20,42 @@ package jackfruit.demo;
* #L%
*/
import java.util.List;
import jackfruit.annotations.Comment;
import jackfruit.annotations.DefaultValue;
import jackfruit.annotations.Jackfruit;
import jackfruit.annotations.ParserClass;
import java.util.List;
/**
* &#x0040;Jackfruit on abstract class
* <ul>
* <li>prefix is optional</li>
* </ul>
* Method annotations:
* <ul>
* <li>&#x0040;Key
* <ul>
* <li>If omitted value is method name</li>
* </ul>
* </li>
* <li>&#x0040;Comment
* <ul>
* <li>Optional</li>
* </ul>
* </li>
* <li>&#x0040;DefaultValue
* <ul>
* <li>Required, String value</li>
* </ul>
* </li>
* <li>&#x0040;Parser
* <ul>
* <li>Optional, name of class to create object from String using its fromString() method</li>
* </ul>
* <p>
* Inspired by <a href="http://owner.aeonbits.org/">owner</a>.
*
* @author nairah1
*
* <ul>
* <li>prefix is optional
* </ul>
*
* Method annotations:
*
* <ul>
* <li>&#x0040;Key
* <ul>
* <li>If omitted value is method name
* </ul>
* <li>&#x0040;Comment
* <ul>
* <li>Optional
* </ul>
* <li>&#x0040;DefaultValue
* <ul>
* <li>Required, String value
* </ul>
* <li>&#x0040;Parser
* <ul>
* <li>Optional, name of class to create object from String using its fromString() method
* </ul>
* <p>Inspired by <a href="http://owner.aeonbits.org/">owner</a>.
* </ul>
*
* @author Hari.Nair@jhuapl.edu
*/
@Jackfruit(prefix = "prefix")
public abstract class DemoClass extends DemoSuperClass {
@@ -66,11 +65,13 @@ public abstract class DemoClass extends DemoSuperClass {
@Override
public abstract 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.")
@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();
@Comment("""
@Comment(
"""
This is a multiline
java text block.
@@ -100,5 +101,4 @@ public abstract class DemoClass extends DemoSuperClass {
public void noAnnotationsOnThisMethod() {
System.out.println("This method was not processed since it has no DefaultValue annotation");
}
}

View File

@@ -9,9 +9,9 @@ package jackfruit.demo;
* Licensed 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.
@@ -20,46 +20,44 @@ package jackfruit.demo;
* #L%
*/
import java.util.List;
import jackfruit.annotations.Comment;
import jackfruit.annotations.DefaultValue;
import jackfruit.annotations.Jackfruit;
import jackfruit.annotations.Key;
import jackfruit.annotations.ParserClass;
import java.util.List;
/**
* &#x0040;Jackfruit on interface
* <ul>
* <li>prefix is optional</li>
* </ul>
* Method annotations:
* <ul>
* <li>&#x0040;Key
* <ul>
* <li>If omitted value is method name</li>
* </ul>
* </li>
* <li>&#x0040;Comment
* <ul>
* <li>Optional</li>
* </ul>
* </li>
* <li>&#x0040;DefaultValue
* <ul>
* <li>Required, String value</li>
* </ul>
* </li>
* <li>&#x0040;Parser
* <ul>
* <li>Optional, name of class to create object from String using its fromString() method</li>
* </ul>
* <p>
* Inspired by <a href="http://owner.aeonbits.org/">owner</a>.
*
* @author nairah1
*
* <ul>
* <li>prefix is optional
* </ul>
*
* Method annotations:
*
* <ul>
* <li>&#x0040;Key
* <ul>
* <li>If omitted value is method name
* </ul>
* <li>&#x0040;Comment
* <ul>
* <li>Optional
* </ul>
* <li>&#x0040;DefaultValue
* <ul>
* <li>Required, String value
* </ul>
* <li>&#x0040;Parser
* <ul>
* <li>Optional, name of class to create object from String using its fromString() method
* </ul>
* <p>Inspired by <a href="http://owner.aeonbits.org/">owner</a>.
* </ul>
*
* @author Hari.Nair@jhuapl.edu
*/
@Jackfruit(prefix = "prefix")
public interface DemoInterface {
@@ -69,11 +67,13 @@ 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 but that's really up to you.")
@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();
@Comment("""
@Comment(
"""
This is a multiline
java text block.
@@ -82,7 +82,8 @@ public interface DemoInterface {
@DefaultValue("Default String")
String StringMethod();
@Comment("This string is serialized into an object\n\tThis comment contains a newline character, and this line starts with a tab.")
@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)
SomeRandomClass randomClass();
@@ -95,10 +96,9 @@ public interface DemoInterface {
@DefaultValue("""
obj1
obj2
obj3 obj4
""")
@ParserClass(SomeRandomClassParser.class)
List<SomeRandomClass> randoms();
}

View File

@@ -9,9 +9,9 @@ package jackfruit.demo;
* Licensed 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.
@@ -38,8 +38,9 @@ public abstract class DemoSuperClass extends DemoSuperSuperClass {
@DefaultValue("2")
@Override
public abstract int intMethod();
@Comment("This string is serialized into an object\n\tThis comment contains a newline character, and this line starts with a tab.")
@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 abstract SomeRandomClass randomClass();

View File

@@ -9,9 +9,9 @@ package jackfruit.demo;
* Licensed 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.
@@ -31,7 +31,7 @@ public abstract class DemoSuperSuperClass {
@Comment("from DemoSuperSuperClass")
@DefaultValue("-3")
public abstract int inherited2();
@Comment("from DemoSuperSuperClass")
@DefaultValue("-2")
public abstract int inherited();

View File

@@ -9,9 +9,9 @@ package jackfruit.demo;
* Licensed 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.
@@ -23,6 +23,7 @@ package jackfruit.demo;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.PropertiesConfigurationLayout;
@@ -89,14 +90,21 @@ public class JackfruitDemo {
System.out.println("random.toUpperCase() = " + random.toUpperCase());
// create a new factory with a different prefix, but same parameters
System.out.println();
System.out.println(
"Create a config with anotherPrefix and retrieve randoms. This will throw an exception.");
factory = new DemoClassFactory("anotherPrefix");
template = factory.fromConfig(config);
// this will not find anything, since template was created from the original config, which
// uses the original prefix.
randoms = template.randoms();
// this will throw an exception since anotherPrefix.randoms is not one of the keys in the
// configuration.
try {
randoms = template.randoms();
} catch (Exception e) {
System.out.println("Caught expected RuntimeException:\n" + e.getLocalizedMessage());
randoms = new ArrayList<>();
}
for (SomeRandomClass random : randoms)
System.out.println("random.toUpperCase() = " + random.toUpperCase());
}
}

View File

@@ -9,9 +9,9 @@ package jackfruit.demo;
* Licensed 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.
@@ -35,5 +35,4 @@ public class SomeRandomClass {
public String toUpperCase() {
return internalString.toUpperCase();
}
}

View File

@@ -39,9 +39,8 @@ import org.junit.Test;
/**
* From <a href=
* "https://stackoverflow.com/questions/21427301/debugging-annotation-processors-in-eclipse">https://stackoverflow.com/questions/21427301/debugging-annotation-processors-in-eclipse</a>
*
* @author nairah1
*
* @author Hari.Nair@jhuapl.edu
*/
public class TestProcessor {

View File

@@ -3,16 +3,16 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>jackfruit-parent</artifactId>
<groupId>edu.jhuapl.ses.srn</groupId>
<groupId>edu.jhuapl.ses</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>jackfruit</artifactId>
<name>jackfruit</name>
<description>Java Annotations Configuration library</description>
<!-- Specifies organization -->
<organization>
@@ -26,7 +26,7 @@
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<auto-service.version>1.1.0</auto-service.version>
<auto-service.version>1.1.1</auto-service.version>
<commons-configuration2.version>2.9.0</commons-configuration2.version>
<log4j-version>2.20.0</log4j-version>
<immutables.version>2.9.3</immutables.version>
@@ -84,6 +84,9 @@
</goals>
<configuration>
<executable>${project.basedir}/src/main/bash/createVersionFile.bash</executable>
<arguments>
<argument>${project.version}</argument>
</arguments>
</configuration>
</execution>
</executions>

View File

@@ -7,22 +7,10 @@ srcFile="../java/jackfruit/JackfruitVersion.java"
cd "$(dirname "$0")"
version=$1
date=$(date -u +"%y.%m.%d")
rev=$(git rev-parse --verify --short=8 HEAD)
if [ $? -gt 0 ]; then
lastCommit=$(date -u +"%y.%m.%d")
rev="UNVERSIONED"
else
lastCommit=$(git log -1 --format=%cd --date=format:%y.%m.%d)
if [[ $(git diff --stat) != '' ]]; then
if [[ $(git status -s | grep -v pom.xml | grep -v pom.bak | grep -v .m2 | grep -v $srcFile) != '' ]]; then
rev=${rev}M
fi
fi
fi
mkdir -p "$(dirname "$srcFile")"
touch $srcFile
@@ -32,9 +20,7 @@ cat <<EOF > $srcFile
package jackfruit;
public class JackfruitVersion {
public final static String lastCommit = "$lastCommit";
// an M at the end of gitRevision means this was built from a "dirty" git repository
public final static String rev = "$rev";
public final static String version = "$version";
public final static String packageName = "$package";
public final static String dateString = "$date";
}

View File

@@ -2,10 +2,8 @@
package jackfruit;
public class JackfruitVersion {
public final static String lastCommit = "23.06.04";
// an M at the end of gitRevision means this was built from a "dirty" git repository
public final static String rev = "72908621";
public final static String version = "1.0-SNAPSHOT";
public final static String packageName = "jackfruit";
public final static String dateString = "23.06.04";
public final static String dateString = "23.09.02";
}

View File

@@ -28,8 +28,8 @@ import java.lang.annotation.Target;
/**
* The Comment annotation specifies the comment that appears in the configuration file above the
* parameter itself.
*
* @author nairah1
*
* @author Hari.Nair@jhuapl.edu
*
*/
@Retention(RetentionPolicy.SOURCE)

View File

@@ -32,8 +32,8 @@ import java.lang.annotation.Target;
* Strings and primitives (and their corresponding wrapper types) are read natively. Other objects
* will need to use the {@link ParserClass} annotation to specify a class which implements the
* {@link Parser} interface to convert the object to and from a String.
*
* @author nairah1
*
* @author Hari.Nair@jhuapl.edu
*
*/
@Retention(RetentionPolicy.SOURCE)

View File

@@ -9,9 +9,9 @@ package jackfruit.annotations;
* Licensed 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.
@@ -27,16 +27,15 @@ import java.lang.annotation.Target;
/**
* Use this annotation 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
* an optional "prefix" argument that can be used to add a prefix to all the configuration keys
* created by the processor.
* <p>
* For example: <br>
* &#x0040;Jackfruit(prefix = "myPrefix")
* <p>
* Inspired by <a href="http://owner.aeonbits.org/">owner</a>.
*
* @author nairah1
*
* <p>For example: <br>
* &#x0040;Jackfruit(prefix = "myPrefix")
*
* <p>Inspired by <a href="http://owner.aeonbits.org/">owner</a>.
*
* @author Hari.Nair@jhuapl.edu
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)

View File

@@ -9,9 +9,9 @@ package jackfruit.annotations;
* Licensed 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.
@@ -28,9 +28,8 @@ import java.lang.annotation.Target;
/**
* The Key annotation can be used to specify the name for the configuration key. The default is to
* use the name of the method.
*
* @author nairah1
*
* @author Hari.Nair@jhuapl.edu
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)

View File

@@ -9,9 +9,9 @@ package jackfruit.annotations;
* Licensed 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.
@@ -22,10 +22,9 @@ package jackfruit.annotations;
/**
* Interface to serialize/deserialize a string to a custom class.
*
* @author nairah1
*
* @param <T>
* @author Hari.Nair@jhuapl.edu
* @param <T> Type to be serialized/deserialized
*/
public interface Parser<T> {
T fromString(String s);

View File

@@ -9,9 +9,9 @@ package jackfruit.annotations;
* Licensed 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.
@@ -28,9 +28,8 @@ import java.lang.annotation.Target;
/**
* The ParserClass annotation specifies a class which implements the {@link Parser} interface to
* convert an object to and from a String.
*
* @author nairah1
*
* @author Hari.Nair@jhuapl.edu
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)

View File

@@ -9,9 +9,9 @@ package jackfruit.processor;
* Licensed 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.
@@ -29,7 +29,7 @@ import org.immutables.value.Value;
/**
* Holds annotation information and other metadata about an annotated method.
*
* @author nairah1
* @author Hari.Nair@jhuapl.edu
*/
@Value.Immutable
public abstract class AnnotationBundle {
@@ -65,7 +65,7 @@ public abstract class AnnotationBundle {
/**
* @return If this configuration parameter is not a string or primitive/boxed type, this class
* will convert the string to the proper object and vice versa. This class must implement
* {@link Parser}.
* {@link jackfruit.annotations.Parser}.
*/
public abstract Optional<TypeMirror> parserClass();
}

View File

@@ -9,9 +9,9 @@ package jackfruit.processor;
* Licensed 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.
@@ -28,8 +28,8 @@ import org.apache.commons.configuration2.PropertiesConfigurationLayout;
* This interface converts instances of annotated interfaces of type T to Apache Commons
* Configuration files and vice versa.
*
* @author nairah1
* @param <T>
* @author Hari.Nair@jhuapl.edu
* @param <T> configuration class with annotations
*/
public interface ConfigFactory<T> {

View File

@@ -9,9 +9,9 @@ package jackfruit.processor;
* Licensed 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.
@@ -20,6 +20,22 @@ package jackfruit.processor;
* #L%
*/
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;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
@@ -56,36 +72,20 @@ 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
*
* <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><a href=
* "https://hannesdorfmann.com/annotation-processing/annotationprocessing101/">https://hannesdorfmann.com/annotation-processing/annotationprocessing101/</a>
* <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>
* </ul>
*
* @author Hari.Nair@jhuapl.edu
*/
@SupportedSourceVersion(SourceVersion.RELEASE_17)
@SupportedAnnotationTypes("jackfruit.annotations.Jackfruit")
@@ -107,10 +107,14 @@ public class ConfigProcessor extends AbstractProcessor {
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());
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) {
@@ -119,16 +123,16 @@ public class ConfigProcessor extends AbstractProcessor {
Jackfruit configParams = annotatedType.getAnnotation(Jackfruit.class);
String prefix = configParams.prefix().strip();
if (prefix.length() > 0 && !prefix.endsWith("."))
prefix += ".";
if (!prefix.isEmpty() && !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);
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());
@@ -136,16 +140,21 @@ public class ConfigProcessor extends AbstractProcessor {
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();
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 built %s\"", JackfruitVersion.version, JackfruitVersion.dateString))
.build();
TypeSpec.Builder classBuilder =
TypeSpec.classBuilder(factoryName).addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addSuperinterface(ptn).addAnnotation(generated);
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")
@@ -185,8 +194,8 @@ public class ConfigProcessor extends AbstractProcessor {
&& e instanceof ExecutableElement ex) {
enclosedMethods.put(ex.getSimpleName(), ex);
AnnotationBundle defaultValues = defaultAnnotationsMap.get(ex.getSimpleName());
defaultAnnotationsMap.put(ex.getSimpleName(),
buildAnnotationBundle(ex, defaultValues));
defaultAnnotationsMap.put(
ex.getSimpleName(), buildAnnotationBundle(ex, defaultValues));
}
}
}
@@ -201,28 +210,33 @@ public class ConfigProcessor extends AbstractProcessor {
// 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();
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)
MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, prefixMemberName)
.beginControlFlow("if ($N == null)", prefixMemberName)
.addStatement("$N = \"\"", prefixMemberName).endControlFlow()
.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();
.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.isDefault()) continue;
if (m.getName().equals("toConfig")) {
MethodSpec toConfig = buildToConfig(tvn, m, annotationsMap, prefixMemberName);
@@ -238,7 +252,6 @@ public class ConfigProcessor extends AbstractProcessor {
MethodSpec fromConfig = buildFromConfig(tvn, m, annotationsMap, prefixMemberName);
methods.add(fromConfig);
}
}
methods.addAll(buildWithMethods(tvn, annotationsMap, prefixMemberName));
@@ -254,8 +267,8 @@ public class ConfigProcessor extends AbstractProcessor {
try (PrintWriter pw = new PrintWriter(jfo.openWriter())) {
javaFile.writeTo(pw);
}
messager.printMessage(Diagnostic.Kind.NOTE,
String.format("wrote %s", javaFile.toJavaFileObject().toUri()));
messager.printMessage(
Diagnostic.Kind.NOTE, String.format("wrote %s", javaFile.toJavaFileObject().toUri()));
}
} catch (IOException e1) {
messager.printMessage(Diagnostic.Kind.ERROR, e1.getLocalizedMessage());
@@ -263,17 +276,15 @@ public class ConfigProcessor extends AbstractProcessor {
}
}
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) {
private AnnotationBundle buildAnnotationBundle(
ExecutableElement e, AnnotationBundle defaultValues) {
ImmutableAnnotationBundle.Builder builder = ImmutableAnnotationBundle.builder();
builder.key(e.getSimpleName().toString());
@@ -300,19 +311,22 @@ public class ConfigProcessor extends AbstractProcessor {
} 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()));
processingEnv
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
String.format(
"Unsupported kind %s for type %s!",
erasure.getKind().toString(), erasure));
}
builder.addAllTypeArgs(typeArgs);
List<Annotation> methodAnnotations = new ArrayList<>();
for (var a : supportedMethodAnnotations)
methodAnnotations.add(e.getAnnotation(a));
for (var a : supportedMethodAnnotations) methodAnnotations.add(e.getAnnotation(a));
for (Annotation annotation : methodAnnotations) {
if (annotation == null)
continue;
if (annotation == null) continue;
if (annotation instanceof Key) {
builder.key(((Key) annotation).value());
@@ -340,34 +354,45 @@ public class ConfigProcessor extends AbstractProcessor {
AnnotationBundle bundle = builder.build();
if (ConfigProcessorUtils.isList(bundle.erasure(), processingEnv)
&& bundle.typeArgs().size() == 0)
messager.printMessage(Diagnostic.Kind.ERROR,
&& bundle.typeArgs().isEmpty())
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 prefixMemberName
* @return
*/
private MethodSpec buildToConfig(TypeVariableName tvn, Method m,
Map<ExecutableElement, AnnotationBundle> annotationsMap, String prefixMemberName) {
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();
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());
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()",
methodBuilder.addStatement(
"$T config = new $T()",
org.apache.commons.configuration2.PropertiesConfiguration.class,
org.apache.commons.configuration2.PropertiesConfiguration.class);
methodBuilder.addStatement("config.setLayout($N)", layout);
@@ -377,8 +402,8 @@ public class ConfigProcessor extends AbstractProcessor {
AnnotationBundle ab = annotationsMap.get(method);
String key = ab.key();
if (needBlank) {
methodBuilder.addStatement("$N.setBlankLinesBefore($N + $S, 1)", layout, prefixMemberName,
key);
methodBuilder.addStatement(
"$N.setBlankLinesBefore($N + $S, 1)", layout, prefixMemberName, key);
needBlank = false;
}
@@ -405,49 +430,54 @@ public class ConfigProcessor extends AbstractProcessor {
} else {
TypeMirror typeArg = ab.typeArgs().get(0);
if (ConfigProcessorUtils.isByte(typeArg, processingEnv))
methodBuilder.addStatement("$L.add($T.toString(element))", listName,
java.lang.Byte.class);
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);
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);
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);
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);
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);
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);
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);
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,
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) {
if (!ab.comment().isEmpty()) {
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.addStatement(
"$N.setComment($N + $S, $L)", layout, prefixMemberName, key, commentName);
}
}
@@ -458,27 +488,33 @@ public class ConfigProcessor extends AbstractProcessor {
/**
* 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) {
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);
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());
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;
@@ -498,8 +534,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(),
"[\\n\\r\\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()) {
@@ -534,9 +570,13 @@ public class ConfigProcessor extends AbstractProcessor {
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()));
if (bundle.defaultValue().trim().isEmpty()) {
processingEnv
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
String.format(
"Default value on method %s is blank!", method.getSimpleName()));
}
builder.addStatement("return $L", bundle.defaultValue());
}
@@ -551,31 +591,40 @@ public class ConfigProcessor extends AbstractProcessor {
/**
* 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) {
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");
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());
MethodSpec.Builder builder =
MethodSpec.methodBuilder(method.getSimpleName().toString())
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.returns(TypeName.get(method.getReturnType()))
.addJavadoc(bundle.comment());
builder.addStatement("String key = $N + $S", prefix, bundle.key());
builder
.beginControlFlow("if (!config.containsKey(key))")
.addStatement("throw new $T($S + key)", RuntimeException.class, "No such key")
.addStatement("throw new $T($S + key)", RuntimeException.class, "No such key ")
.endControlFlow();
TypeMirror parser = null;
@@ -642,8 +691,11 @@ public class ConfigProcessor extends AbstractProcessor {
} else if (ConfigProcessorUtils.isString(bundle.erasure(), processingEnv)) {
builder.addStatement("return config.getString(key)");
} else {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Can't handle return type " + m.getReturnType().getCanonicalName());
processingEnv
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"Can't handle return type " + m.getReturnType().getCanonicalName());
}
}
}
@@ -656,14 +708,16 @@ public class ConfigProcessor extends AbstractProcessor {
/**
* 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) {
private List<MethodSpec> buildWithMethods(
TypeVariableName tvn,
Map<ExecutableElement, AnnotationBundle> annotationsMap,
String prefixMemberName) {
List<MethodSpec> withMethods = new ArrayList<>();
@@ -671,14 +725,17 @@ public class ConfigProcessor extends AbstractProcessor {
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));
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);
MethodSpec.Builder builder =
MethodSpec.methodBuilder(camelCase)
.addModifiers(Modifier.PUBLIC)
.returns(propertiesConfigurationClass);
builder.addJavadoc("Replace the value of " + methodName);
builder.addParameter(ps);
builder.addParameter(
@@ -688,8 +745,10 @@ public class ConfigProcessor extends AbstractProcessor {
withMethods.add(builder.build());
// method with PropertiesConfiguration passed in as an argument
builder = MethodSpec.methodBuilder(camelCase).addModifiers(Modifier.PUBLIC)
.returns(propertiesConfigurationClass);
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(
@@ -742,8 +801,11 @@ public class ConfigProcessor extends AbstractProcessor {
} else {
if (ab.parserClass().isPresent()) {
// store the serialized string as the property
builder.addStatement("config.setProperty($N + $S, $L.toString(replaceValue))",
prefixMemberName, key, parserName);
builder.addStatement(
"config.setProperty($N + $S, $L.toString(replaceValue))",
prefixMemberName,
key,
parserName);
} else {
builder.addStatement("config.setProperty($N + $S, replaceValue)", prefixMemberName, key);
}
@@ -756,5 +818,4 @@ public class ConfigProcessor extends AbstractProcessor {
return withMethods;
}
}

View File

@@ -9,9 +9,9 @@ package jackfruit.processor;
* Licensed 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.
@@ -30,7 +30,11 @@ import javax.tools.Diagnostic;
public class ConfigProcessorUtils {
/**
* @param processingEnv
*
* @param typeMirror the return type without any parameters (e.g. List rather than
* * List&lt;String&gt;)
* @param processingEnv Processing environment providing by the tool framework, from {@link
* javax.annotation.processing.AbstractProcessor}
* @return true if this annotated member returns a {@link List}
*/
public static boolean isList(TypeMirror typeMirror, ProcessingEnvironment processingEnv) {
@@ -38,9 +42,10 @@ public class ConfigProcessorUtils {
}
/**
* @param typeMirror either {@link AnnotationBundle#erasure()} for the return value, or an element of {@link
* AnnotationBundle#typeArgs()} for a parameterized type
* @param processingEnv
* @param typeMirror either {@link AnnotationBundle#erasure()} for the return value, or an element
* of {@link AnnotationBundle#typeArgs()} for a parameterized type
* @param processingEnv Processing environment providing by the tool framework, from {@link
* javax.annotation.processing.AbstractProcessor}
* @return true if this annotated member returns a {@link Boolean} or primitive boolean
*/
public static boolean isBoolean(TypeMirror typeMirror, ProcessingEnvironment processingEnv) {
@@ -49,9 +54,10 @@ public class ConfigProcessorUtils {
}
/**
* @param typeMirror either {@link AnnotationBundle#erasure()} for the return value, or an element of {@link
* AnnotationBundle#typeArgs()} for a parameterized type
* @param processingEnv
* @param typeMirror either {@link AnnotationBundle#erasure()} for the return value, or an element
* of {@link AnnotationBundle#typeArgs()} for a parameterized type
* @param processingEnv Processing environment providing by the tool framework, from {@link
* javax.annotation.processing.AbstractProcessor}
* @return true if this annotated member returns a {@link Byte} or primitive byte
*/
public static boolean isByte(TypeMirror typeMirror, ProcessingEnvironment processingEnv) {
@@ -60,9 +66,10 @@ public class ConfigProcessorUtils {
}
/**
* @param typeMirror either {@link AnnotationBundle#erasure()} for the return value, or an element of {@link
* AnnotationBundle#typeArgs()} for a parameterized type
* @param processingEnv
* @param typeMirror either {@link AnnotationBundle#erasure()} for the return value, or an element
* of {@link AnnotationBundle#typeArgs()} for a parameterized type
* @param processingEnv Processing environment providing by the tool framework, from {@link
* javax.annotation.processing.AbstractProcessor}
* @return true if this annotated member returns a {@link Double} or primitive double
*/
public static boolean isDouble(TypeMirror typeMirror, ProcessingEnvironment processingEnv) {
@@ -71,9 +78,10 @@ public class ConfigProcessorUtils {
}
/**
* @param typeMirror either {@link AnnotationBundle#erasure()} for the return value, or an element of {@link
* AnnotationBundle#typeArgs()} for a parameterized type
* @param processingEnv
* @param typeMirror either {@link AnnotationBundle#erasure()} for the return value, or an element
* of {@link AnnotationBundle#typeArgs()} for a parameterized type
* @param processingEnv Processing environment providing by the tool framework, from {@link
* javax.annotation.processing.AbstractProcessor}
* @return true if this annotated member returns a {@link Float} or primitive float
*/
public static boolean isFloat(TypeMirror typeMirror, ProcessingEnvironment processingEnv) {
@@ -82,9 +90,10 @@ public class ConfigProcessorUtils {
}
/**
* @param typeMirror either {@link AnnotationBundle#erasure()} for the return value, or an element of {@link
* AnnotationBundle#typeArgs()} for a parameterized type
* @param processingEnv
* @param typeMirror either {@link AnnotationBundle#erasure()} for the return value, or an element
* of {@link AnnotationBundle#typeArgs()} for a parameterized type
* @param processingEnv Processing environment providing by the tool framework, from {@link
* javax.annotation.processing.AbstractProcessor}
* @return true if this annotated member returns a {@link Integer} or primitive int
*/
public static boolean isInteger(TypeMirror typeMirror, ProcessingEnvironment processingEnv) {
@@ -93,9 +102,10 @@ public class ConfigProcessorUtils {
}
/**
* @param typeMirror either {@link AnnotationBundle#erasure()} for the return value, or an element of {@link
* AnnotationBundle#typeArgs()} for a parameterized type
* @param processingEnv
* @param typeMirror either {@link AnnotationBundle#erasure()} for the return value, or an element
* of {@link AnnotationBundle#typeArgs()} for a parameterized type
* @param processingEnv Processing environment providing by the tool framework, from {@link
* javax.annotation.processing.AbstractProcessor}
* @return true if this annotated member returns a {@link Long} or primitive long
*/
public static boolean isLong(TypeMirror typeMirror, ProcessingEnvironment processingEnv) {
@@ -104,9 +114,10 @@ public class ConfigProcessorUtils {
}
/**
* @param typeMirror either {@link AnnotationBundle#erasure()} for the return value, or an element of {@link
* AnnotationBundle#typeArgs()} for a parameterized type
* @param processingEnv
* @param typeMirror either {@link AnnotationBundle#erasure()} for the return value, or an element
* of {@link AnnotationBundle#typeArgs()} for a parameterized type
* @param processingEnv Processing environment providing by the tool framework, from {@link
* javax.annotation.processing.AbstractProcessor}
* @return true if this annotated member returns a {@link Short} or primitive float
*/
public static boolean isShort(TypeMirror typeMirror, ProcessingEnvironment processingEnv) {
@@ -115,9 +126,10 @@ public class ConfigProcessorUtils {
}
/**
* @param typeMirror either {@link AnnotationBundle#erasure()} for the return value, or an element of {@link
* AnnotationBundle#typeArgs()} for a parameterized type
* @param processingEnv
* @param typeMirror either {@link AnnotationBundle#erasure()} for the return value, or an element
* of {@link AnnotationBundle#typeArgs()} for a parameterized type
* @param processingEnv Processing environment providing by the tool framework, from {@link
* javax.annotation.processing.AbstractProcessor}
* @return true if this annotated member returns a {@link String}
*/
public static boolean isString(TypeMirror typeMirror, ProcessingEnvironment processingEnv) {

105
pom.xml
View File

@@ -3,16 +3,20 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>edu.jhuapl.ses.srn</groupId>
<groupId>edu.jhuapl.ses</groupId>
<artifactId>jackfruit-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>jackfruit-parent</name>
<description>Jackfruit processes annotations on Java interfaces and abstract classes to generate code that can read and write Apache Configuration files.</description>
<url>https://github.com/JHUAPL/Jackfruit</url>
<inceptionYear>2023</inceptionYear>
<organization>
<name>Johns Hopkins University Applied Physics Laboratory</name>
<url>http://jhuapl.edu</url>
<url>https://www.jhuapl.edu/</url>
</organization>
<licenses>
@@ -22,14 +26,31 @@
</license>
</licenses>
<!-- publish to surfshop -->
<developers>
<developer>
<id>HariNairJHUAPL</id>
<name>Hari Nair</name>
<email>Hari.Nair@jhuapl.edu</email>
<organization>JHUAPL</organization>
<organizationUrl>https://www.jhuapl.edu/</organizationUrl>
</developer>
</developers>
<scm>
<connection>scm:git:https://github.com/JHUAPL/Jackfruit.git</connection>
<url>https://github.com/JHUAPL/Jackfruit.git</url>
<developerConnection>scm:git:https://github.com/JHUAPL/Jackfruit.git</developerConnection>
<tag>HEAD</tag>
</scm>
<!-- publish to maven central -->
<distributionManagement>
<repository>
<id>central</id>
<name>surfshop-snapshots</name>
<url>http://surfshop.jhuapl.edu:8081/artifactory/libs-snapshot-local</url>
</repository>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
@@ -45,8 +66,8 @@
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
@@ -56,7 +77,7 @@
https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.2.0</version>
<version>3.3.1</version>
</plugin>
<!-- default lifecycle, jar packaging: see
https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
@@ -73,7 +94,7 @@
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.0</version>
<version>3.1.2</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
@@ -85,14 +106,28 @@
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
<version>3.1.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- site lifecycle, see
https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>4.0.0-M8</version>
<version>4.0.0-M9</version>
<configuration>
<skip>${maven-site-plugin.skip}</skip>
</configuration>
@@ -100,13 +135,13 @@
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.4.4</version>
<version>3.4.5</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>2.0.1</version>
<version>2.2.0</version>
<configuration>
<licenseName>apache_v2</licenseName>
</configuration>
@@ -139,10 +174,15 @@
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.3.0</version>
<version>3.4.0</version>
<executions>
<execution>
<id>enforce-maven</id>
@@ -160,6 +200,39 @@
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
<configuration>
<gpgArguments>
<arg>--pinentry-mode</arg>
<arg>loopback</arg>
</gpgArguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.13</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>