diff --git a/.gitignore b/.gitignore
index 9d8282e9..7834853e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,5 @@ release.properties
#IntelliJ Project directory
.idea/
+**.iml
+.java-version
diff --git a/.travis.yml b/.travis.yml
index 5bd06656..5eeabe77 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,11 @@
+dist: trusty
language: java
jdk:
- oraclejdk8
- openjdk8
+branches:
+ except:
+ - logo_submission
script: "mvn cobertura:cobertura"
diff --git a/README.md b/README.md
index 924c0b05..ec19ce54 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,24 @@
-[](https://travis-ci.org/eeverman/andhow)
+[](https://travis-ci.com/github/eeverman/andhow)
[](https://codecov.io/gh/eeverman/andhow)
[](https://www.javadoc.io/doc/org.yarnandtail/andhow)
+
+
+## New Release: 0.4.1, June 2, 2021 ([notes](https://github.com/eeverman/andhow/releases/tag/andhow-0.4.1)).
+
+
+This larger update fixes several issues due to newer JVMs and the IntelliJ IDE.
+Special thanks to first time contributor [Vicky Ronnen](https://github.com/VickyRonnen) for fixing Issue [497](https://github.com/eeverman/andhow/issues/497) -
+This bug made AndHow unusable for anyone using newer versions of IntelliJ.
+
+

-AndHow! strong.valid.simple.AppConfiguration
-======
+## AndHow! strong.valid.simple.AppConfiguration
AndHow is an easy to use configuration framework with strong typing and detailed
validation for web apps, command line or any application environment.
_**Learn more at the [AndHow main site](https://sites.google.com/view/andhow)**_
-Key Features
---------------
+## Key Features
* **Strong Typing**
* **Detailed validation**
* **Simple to use**
@@ -19,58 +27,57 @@ Key Features
* **Loads values from multiple sources (JNDI, env vars, prop files, etc)**
* **Generates configuration sample file based on application properties**
-Questions / Discussion / Contact
---------------
+## Questions / Discussion / Contact
[Join the discussion](https://sites.google.com/view/andhow/join-discussion)
on the [user forum](https://groups.google.com/d/forum/andhowuser)
or the *Slack* group (See details on the
[Join](https://sites.google.com/view/andhow/join-discussion) page).
-Use it via Maven (available on Maven Central)
---------------
+## Use it via Maven (available on Maven Central)
```xml
org.yarnandtailandhow
- 0.4.0
+ 0.4.1
```
-**AndHow can be used in projects with Java 8 and above, however, Java 9 and above have [some restrictions](https://sites.google.com/view/andhow/user-guide/java9)**
+**AndHow can be used in projects with Java 8 - Java 15. Work to support Java 16 is underway and will be the next major release. There are [some considerations](https://sites.google.com/view/andhow/user-guide/java9-and-above) for Java 9 and above if your project uses Jigsaw Modules.**
-Complete Usage Example
---------------
+## Complete Usage Example
_**More usage examples and documentation
are available at the [AndHow main site](https://sites.google.com/view/andhow)**_
```java
package org.simple;
+import org.yarnandtail.andhow.AndHow;
import org.yarnandtail.andhow.property.*;
public class GettingStarted {
-
+
//1
- final static IntProp COUNT_DOWN_START = IntProp.builder().mustBeNonNull()
- .desc("Start the countdown from this number")
- .mustBeGreaterThanOrEqualTo(1).defaultValue(2).build();
-
+ public final static IntProp COUNT_DOWN_START = IntProp.builder().mustBeNonNull()
+ .mustBeGreaterThanOrEqualTo(1).defaultValue(3).build();
+
private final static StrProp LAUNCH_CMD = StrProp.builder().mustBeNonNull()
- .desc("What to say when its time to launch")
- .mustMatchRegex(".*Go.*").defaultValue("GoGoGo!").build();
-
+ .desc("What to say when its time to launch")
+ .mustMatchRegex(".*Go.*").defaultValue("Go-Go-Go!").build();
+
public String launch() {
String launch = "";
-
+
//2
for (int i = COUNT_DOWN_START.getValue(); i >= 1; i--) {
launch = launch += i + "...";
}
-
+
return launch + LAUNCH_CMD.getValue();
}
-
+
public static void main(String[] args) {
- GettingStarted gs = new GettingStarted();
- System.out.println(gs.launch());
+ AndHow.findConfig().setCmdLineArgs(args); //3 Optional
+
+ System.out.println( new GettingStarted().launch() );
+ System.out.println( LAUNCH_CMD.getValue().replace("Go", "Gone") );
}
}
```
@@ -78,42 +85,65 @@ public class GettingStarted {
Properties must be `final static`, but may be `private` or any other scope.
`builder` methods simplify adding validation, description, defaults and
other metadata.
-Properties are strongly typed, so default values and validation are specific to
-the type, for instance, `StrProp` has regex validation rules for `String`s
-while the `IntProp` has greater-than and less-than rules available.
+Properties are strongly typed, so default values and validation are type specific, e.g.,
+`StrProp` has Regex validation while the `IntProp` has GreaterThan / LessThan rules available.
### Section //2 : Using AndHow Properties
AndHow Properties are used just like static final constants with an added
-`.getValue()` on the end to fetch the value.
-Strong typing means that calling `COUNT_DOWN_START.getValue()`
+`.getValue()` tacked on. Strong typing means that calling `COUNT_DOWN_START.getValue()`
returns an `Integer` while calling `LAUNCH_CMD.getValue()` returns a `String`.
+An AndHow Property (and its value) can be accessed anywhere it is visible.
+`COUNT_DOWN_START` is public in a public class, so it could be used anywhere, while
+`LAUNCH_CMD` is private.
+AndHow Properties are always `static`, so they can be accessed in both static
+and instance methods, just like this example shows.
+
+### Section //3 : Accepting Command Line Arguments
+If an application needs command line arguments (CLAs), just pass them to AndHow
+at startup as this example shows. Properties are referred to using 'dot notation', e.g.:
+```
+java -jar GettingStarted.jar org.simple.GettingStarted.LAUNCH_CMD=GoManGo
+```
+If you don't need to accept CLA's, you can leave line `//3` out -
+AndHow will initialize and startup without any explicit _init_ method when
+the first Property is accessed.
+
### How do I actually configure some values?
We're getting there.
The example has defaults for each property so with no other configuration available,
-the main method uses the defaults and prints: `3...2...1...GoGoGo!`
-Things are more interesting if the default values are removed from the code above.
+the main method uses the defaults and prints:
+```
+3...2...1...Go-Go-Go!
+Gone-Gone-Gone!
+```
+Things are more interesting if the default values are removed from the code above:
+```java
+public final static IntProp COUNT_DOWN_START = IntProp.builder().mustBeNonNull()
+ .mustBeGreaterThanOrEqualTo(1).build(); //default removed
+
+private final static StrProp LAUNCH_CMD = StrProp.builder().mustBeNonNull()
+ .mustMatchRegex(".*Go.*").build(); //default removed
+```
Both properties must be non-null, so removing the defaults causes the validation
-rules to be violated at startup. Here is an excerpt of the console output when that happens:
+rules to be violated at startup. Here is an excerpt from the console when that happens:
```
========================================================================
Drat! There were AndHow startup errors.
Sample configuration files will be written to: '/some_local_tmp_directory/andhow-samples/'
========================================================================
-REQUIRMENT PROBLEMS - When a required property is not provided
-Detailed list of Requirements Problems:
-Property org.simple.GettingStarted.COUNT_DOWN_START: This Property must be non-null - It must have a non-null default or be loaded by one of the loaders to a non-null value
-Property org.simple.GettingStarted.LAUNCH_CMD: This Property must be non-null - It must have a non-null default or be loaded by one of the loaders to a non-null value
+Property org.simple.GettingStarted.COUNT_DOWN_START: This Property must be non-null
+Property org.simple.GettingStarted.LAUNCH_CMD: This Property must be non-null
========================================================================
```
-**Validation happens at startup and happens for all properties in the entire application.**
-Properties, even those defined in 3rd party jars, are discovered and values for
+**AndHow does validation at startup for all properties in the entire application.**
+Properties, _even those defined in 3rd party jars_, are discovered and values for
them are loaded and validated.
-If that fails (as it did above), AndHow throws a RuntimeException to stop
+If validation fails (as it did above), AndHow throws a RuntimeException to stop
application startup and uses property metadata to generate specific error
messages and (helpfully) sample configuration files.
-Here is an excerpt of the Java Properties file created when the example code failed validation:
+Here is an excerpt of the Java Properties file created when the code above failed validation:
```
# ######################################################################
# Property Group org.simple.GettingStarted
@@ -129,15 +159,15 @@ org.simple.GettingStarted.LAUNCH_CMD = [String]
AndHow uses all of the provided metadata to create a detailed and well commented
configuration file for your project.
Insert some real values into that file and place it on your classpath at
-/andhow.properties and it will automatically be discovered and loaded at startup.
-By default, AndHow automatically discovers and loads configuration from seven common sources.
+`/andhow.properties` and it will automatically be discovered and loaded at startup.
+By default, AndHow discovers and loads configuration from seven common sources.
The default list of configuration loading, in order, is:
1. Fixed values (explicitly set in code for AndHow to use)
2. String[] arguments from the static void main() method
-3. System Properties
+3. System Properties _(Like the one auto-generated above)_
4. Environmental Variables
5. JNDI
-6. Java properties file on the filesystem (path specified as an AndHow property)
+6. Java properties file on the filesystem (path must be specified)
7. Java properties file on the classpath (defaults to /andhow.properties)
Property values are set on a first-win basis, so if a property is set as fixed value,
@@ -147,3 +177,4 @@ Values passed to the main method take precedence over system properties as so on
_**For more examples and documentation, visit the [AndHow main site](https://sites.google.com/view/andhow)**_
_**&?!**_
+
diff --git a/andhow-annotation-processor/pom.xml b/andhow-annotation-processor/pom.xml
index c7824875..f472f676 100644
--- a/andhow-annotation-processor/pom.xml
+++ b/andhow-annotation-processor/pom.xml
@@ -3,7 +3,7 @@
org.yarnandtailandhow-parent
- 0.4.1-SNAPSHOT
+ 0.4.2-SNAPSHOTandhow-annotation-processor
@@ -38,10 +38,6 @@
-
- junit
- junit
- com.google.testing.compilecompile-testing
diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowCompileException.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowCompileException.java
new file mode 100644
index 00000000..3f0b1c5a
--- /dev/null
+++ b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowCompileException.java
@@ -0,0 +1,80 @@
+package org.yarnandtail.andhow.compile;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Exception thrown by the AndHowCompileProcessor for any type of problem
+ * during the compile.
+ *
+ * This exception can accumulate a list of Problems so that all problems can
+ * be found in a single pass and for easier testing (this exception can be
+ * inspected to determine the exact problem).
+ *
+ * @author ericeverman
+ */
+public class AndHowCompileException extends RuntimeException {
+
+ public static final String DEFAULT_MSG = "The AndHowCompileProcessor found a problem"
+ + " during compilation and threw a fatal exception - "
+ + "See the error details listed nearby.";
+
+ private final String msg;
+ private final List problems;
+ private final Throwable cause;
+
+
+ /**
+ * Instance when there are one or more AndHow domain 'problems' with the
+ * code being compiled.
+ *
+ * Examples would include Properties that are not static final
+ * or too many init classes on the classpath.
+ *
+ * @param problems A list of problems found during compilation. This list
+ * instance is kept, so no modifications to the list should be made by the
+ * caller.
+ */
+ public AndHowCompileException(List problems) {
+
+ cause = null;
+ msg = DEFAULT_MSG;
+
+ this.problems = (problems != null) ? problems : Collections.emptyList();
+ }
+
+ /**
+ * Instance when there is some unexpected, non-AndHow related exception.
+ *
+ * Example: Unwritable file system.
+ *
+ * @param message AndHow context description
+ * @param cause Error caught that caused the issue
+ */
+ public AndHowCompileException(String message, Throwable cause) {
+ msg = message;
+ this.cause = cause;
+ this.problems = Collections.emptyList();
+ }
+
+ @Override
+ public synchronized Throwable getCause() {
+ return (cause != null) ? cause : super.getCause();
+ }
+
+
+ @Override
+ public String getMessage() {
+ return msg;
+ }
+
+ /**
+ * The list of AndHow CompileProblems discovered, if any.
+ *
+ * @return A non-null, by possibly empty list. Do not modify the returned list.
+ */
+ public List getProblems() {
+ return problems;
+ }
+
+}
diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor.java
index 2838edde..ea18e9a2 100644
--- a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor.java
+++ b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor.java
@@ -1,105 +1,149 @@
package org.yarnandtail.andhow.compile;
-import java.util.logging.Level;
import org.yarnandtail.andhow.service.PropertyRegistrationList;
-import org.yarnandtail.andhow.service.PropertyRegistration;
-import com.sun.source.util.Trees;
import java.io.*;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
import java.util.*;
import javax.annotation.processing.*;
+import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
-
import javax.tools.FileObject;
import org.yarnandtail.andhow.AndHowInit;
import org.yarnandtail.andhow.api.Property;
import org.yarnandtail.andhow.service.*;
-import org.yarnandtail.andhow.util.AndHowLog;
+import static javax.tools.Diagnostic.*;
import static javax.tools.StandardLocation.CLASS_OUTPUT;
+import org.yarnandtail.andhow.util.TextUtil;
/**
+ * This is the central AndHow compilation class, an Annotation Processor.
*
- * Note: check to ensure that Props are not referenced in static init blocks b/c
- * we may need to load the class (and run its init) before andHow init can
- * complete, causing a circular init loop.
- *
- * @author ericeverman
+ * An annotation processor nominally reads annotations as classes are compiled.
+ * This class is annotated {@code SupportedAnnotationTypes("*")}, allowing it to
+ * 'see' all classes as they are compiled. This class then delegates to a
+ * 'scanner' class that does deep inspection on compiled code, looking for
+ * AndHow Properties.
+ *
+ * When an AndHow Property is found in a class, a new {@code PropertyRegistrar}
+ * class is created, which contains the list of Properties in that class.
+ * There is a one-to-one correspondence between user classes that contain
+ * AndHow Properties and auto-created {@code PropertyRegistrar} classes.
+ *
+ * At runtime, AndHow will use the {@code ServiceLoader} to discover all instances
+ * of {@code PropertyRegistrar} on the classpath, thus finding all AndHow
+ * Property containing classes.
*/
@SupportedAnnotationTypes("*")
public class AndHowCompileProcessor extends AbstractProcessor {
- private static final AndHowLog LOG = AndHowLog.getLogger(AndHowCompileProcessor.class);
-
+
private static final String INIT_CLASS_NAME = AndHowInit.class.getCanonicalName();
private static final String TEST_INIT_CLASS_NAME = "org.yarnandtail.andhow.AndHowTestInit";
-
+
private static final String SERVICES_PACKAGE = "";
-
+
private static final String SERVICE_REGISTRY_META_DIR = "META-INF/services/";
-
+
//Static to insure all generated classes have the same timestamp
private static Calendar runDate;
- private Trees trees;
-
private final List registrars = new ArrayList();
-
+
private final List initClasses = new ArrayList(); //List of init classes (should only ever be 1)
private final List testInitClasses = new ArrayList(); //List of test init classes (should only ever be 1)
+ private final List problems = new ArrayList(); //List of problems found. >0== RuntimeException
+
+ /**
+ * A no-arg constructor is required.
+ */
public AndHowCompileProcessor() {
- //required by Processor API
+ //used to ensure all metadata files have the same date
runDate = new GregorianCalendar();
}
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ //Unwrap the IntelliJ ProcessingEnvironment if needed
+ super.init(unwrap(processingEnv));
+ }
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ //Only scanning for declaration of AndHow Properties, so should
+ //be immune to most new language constructs.
+ return SourceVersion.latestSupported();
+ }
+
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
- boolean isLastRound = roundEnv.processingOver();
-
Filer filer = this.processingEnv.getFiler();
+ Messager log = this.processingEnv.getMessager();
+ boolean isLastRound = roundEnv.processingOver();
if (isLastRound) {
- LOG.debug("Final round of annotation processing. Total root element count: {0}", roundEnv.getRootElements().size());
+ debug(log, "Final round of annotation processing. Total root element count: {}",
+ roundEnv.getRootElements().size());
- try {
- if (initClasses.size() == 1) {
- LOG.info("Found exactly 1 {0} class: {1}", INIT_CLASS_NAME, initClasses.get(0).fullClassName);
- writeServiceFile(filer, AndHowInit.class.getCanonicalName(), initClasses);
+ if (initClasses.size() > 1) {
+ problems.add(new CompileProblem.TooManyInitClasses(
+ INIT_CLASS_NAME, initClasses));
+ }
+
+ if (testInitClasses.size() > 1) {
+ problems.add(new CompileProblem.TooManyInitClasses(
+ TEST_INIT_CLASS_NAME, testInitClasses));
+ }
- } else if (initClasses.size() > 1) {
- TooManyInitClassesException err =
- new TooManyInitClassesException(INIT_CLASS_NAME, initClasses);
+ if (problems.isEmpty()) {
+ try {
+ if (initClasses.size() == 1) {
- err.writeDetails(LOG);
- throw err;
- }
+ debug(log, "Found exactly 1 {} class: {}",
+ INIT_CLASS_NAME, initClasses.get(0).fullClassName);
- if (testInitClasses.size() == 1) {
+ writeServiceFile(filer, AndHowInit.class.getCanonicalName(), initClasses);
- LOG.info("Found exactly 1 {0} class: {1}", TEST_INIT_CLASS_NAME, testInitClasses.get(0).fullClassName);
- writeServiceFile(filer, TEST_INIT_CLASS_NAME, testInitClasses);
+ }
- } else if (testInitClasses.size() > 1) {
- TooManyInitClassesException err =
- new TooManyInitClassesException(TEST_INIT_CLASS_NAME, testInitClasses);
+ if (testInitClasses.size() == 1) {
+ debug(log, "Found exactly 1 {} class: {}",
+ TEST_INIT_CLASS_NAME, testInitClasses.get(0).fullClassName);
- err.writeDetails(LOG);
- throw err;
- }
+ writeServiceFile(filer, TEST_INIT_CLASS_NAME, testInitClasses);
- if (registrars != null && registrars.size() > 0) {
- writeServiceFile(filer, PropertyRegistrar.class.getCanonicalName(), registrars);
+ }
+
+ if (registrars.size() > 0) {
+ writeServiceFile(filer, PropertyRegistrar.class.getCanonicalName(), registrars);
+ }
+
+ } catch (IOException e) {
+ throw new AndHowCompileException("Exception while trying to write generated files", e);
}
- } catch (IOException e) {
- throw new RuntimeException("Exception while trying to write generated files", e);
+ } else {
+ error(log, "AndHow Property definition or Init class errors "
+ + "prevented compilation. Each of the following errors "
+ + "must be fixed before compilation is possible.");
+ error(log, "AndHow errors discovered: {}", problems.size());
+
+ for (CompileProblem err : problems) {
+ error(log, err.getFullMessage());
+ }
+
+ throw new AndHowCompileException(problems);
}
-
+
} else {
- LOG.trace("Another round of annotation processing. Current root element count: {0}", roundEnv.getRootElements().size());
+ debug(log, "Another round of annotation processing. "
+ + "Current root element count: {}", roundEnv.getRootElements().size());
//
@@ -110,7 +154,7 @@ public boolean process(Set extends TypeElement> annotations, RoundEnvironment
TypeElement te = (TypeElement) e;
AndHowElementScanner7 st = new AndHowElementScanner7(
- this.processingEnv,
+ this.processingEnv,
Property.class.getCanonicalName(),
INIT_CLASS_NAME,
TEST_INIT_CLASS_NAME);
@@ -125,37 +169,23 @@ public boolean process(Set extends TypeElement> annotations, RoundEnvironment
if (ret.hasRegistrations()) {
- LOG.debug("Found {0} AndHow Properties in class {1} ", ret.getRegistrations().size(), ret.getRootCanonicalName());
+ debug(log, "Found {} AndHow Properties in class {} ",
+ ret.getRegistrations().size(), ret.getRootCanonicalName());
+
PropertyRegistrarClassGenerator gen = new PropertyRegistrarClassGenerator(ret, AndHowCompileProcessor.class, runDate);
registrars.add(new CauseEffect(gen.buildGeneratedClassFullName(), te));
PropertyRegistrationList regs = ret.getRegistrations();
- if (LOG.isLoggable(Level.FINEST)) {
- for (PropertyRegistration p : ret.getRegistrations()) {
- LOG.trace("Found AndHow Property ''{0}'' in root class ''{1}'', immediate parent is ''{2}''",
- p.getCanonicalPropertyName(), p.getCanonicalRootName(), p.getJavaCanonicalParentName());
- }
- }
-
try {
writeClassFile(filer, gen, e);
- LOG.trace("Wrote new generated class file " + gen.buildGeneratedClassSimpleName());
+ debug(log, "Wrote new generated class file {}", gen.buildGeneratedClassSimpleName());
} catch (Exception ex) {
- LOG.error("Unable to write generated classfile '" + gen.buildGeneratedClassFullName() + "'", ex);
+ error(log, "Unable to write generated classfile '" + gen.buildGeneratedClassFullName() + "'", ex);
throw new RuntimeException(ex);
}
}
- if (ret.getErrors().size() > 0) {
- LOG.error(
- "AndHow Property definition errors prevented compilation to complete. " +
- "Each of the following errors must be fixed before compilation is possible.");
- for (String err : ret.getErrors()) {
- LOG.error("AndHow Property Error: {0}", err);
- }
-
- throw new RuntimeException("AndHowCompileProcessor threw a fatal exception - See error log for details.");
- }
+ problems.addAll(ret.getProblems());
}
}
@@ -164,28 +194,47 @@ public boolean process(Set extends TypeElement> annotations, RoundEnvironment
}
- public void writeClassFile(Filer filer, PropertyRegistrarClassGenerator generator, Element causingElement) throws Exception {
+ /**
+ * Writes a new class implementing the {@code PropertyRegistrar} interface.
+ *
+ * The new class directly corresponds to a user classes containing AndHow
+ * Properties and will contain meta data about the properties.
+ *
+ * @param filer The javac file system representation for writing files.
+ * @param generator AndHow class capable of generating source code for this
+ * {@code PropertyRegistrar} class.
+ * @param causingElement A javac Element, which generically refers to any
+ * piece of source code such as a keyword, class name, etc.. When a file
+ * is written to the filer, a {@code causingElement} is recorded as metadata
+ * so there is an association between the file and the reason it was written.
+ * Likely this is normally used to associate source code line numbers with
+ * generated code.
+ *
+ * @throws Exception If unable to write (out of disc space?)
+ */
+ public void writeClassFile(Filer filer,
+ PropertyRegistrarClassGenerator generator, Element causingElement) throws Exception {
String classContent = generator.generateSource();
- FileObject classFile = filer.createSourceFile(generator.buildGeneratedClassFullName(), causingElement);
+ FileObject classFile = filer.createSourceFile(
+ generator.buildGeneratedClassFullName(), causingElement);
try (Writer writer = classFile.openWriter()) {
writer.write(classContent);
- }
+ }
}
-
- protected void writeServiceFile(Filer filer,
- String fullyQualifiedServiceInterfaceName,
- List implementingClasses) throws IOException {
-
+
+ protected void writeServiceFile(Filer filer,
+ String fullyQualifiedServiceInterfaceName, List implementingClasses) throws IOException {
+
//Get a unique causing elements
HashSet set = new HashSet();
for (CauseEffect ce : implementingClasses) {
set.add(ce.causeElement);
}
-
-
+
+
//The CLASS_OUTPUT location is used instead of SOURCE_OUTPUT because it
//seems that non-Java files are not copied over from the SOURCE_OUTPUT
//location.
@@ -199,21 +248,81 @@ protected void writeServiceFile(Filer filer,
writer.write(System.lineSeparator());
}
}
-
+
+ }
+
+ /**
+ * Logs a debug message using the javac standard Messager system.
+ *
+ * @param log The Message instance to use
+ * @param pattern String pattern with curly variable replacement like this: {}
+ * @param args Arguments to put into the {}'s, in order.
+ */
+ void debug(Messager log, String pattern, Object... args) {
+ log.printMessage(Kind.NOTE, TextUtil.format(pattern, args));
+ }
+
+ /**
+ * Logs an error message using the javac standard Messager system.
+ *
+ * @param log The Message instance to use
+ * @param pattern String pattern with curly variable replacement like this: {}
+ * @param args Arguments to put into the {}'s, in order.
+ */
+ void error(Messager log, String pattern, Object... args) {
+ log.printMessage(Kind.ERROR, TextUtil.format(pattern, args));
}
-
+
/**
- * Match up a causal Element w/ the Class name that will be registered in
+ * Match up a causal Element (Basically the compiler representation of a
+ * class to be compiled) w/ the Class name that will be registered in
* a service registry.
+ *
+ * When the AnnotationProcessor writes a new file to the Filer, it wants
+ * a causal Element to associate with it, apparently this info could be
+ * used for reporting or something.
*/
protected static class CauseEffect {
String fullClassName;
Element causeElement;
-
+
public CauseEffect(String fullClassName, Element causeElement) {
this.fullClassName = fullClassName;
this.causeElement = causeElement;
}
}
+ /**
+ * With the introduction of IntelliJ Idea 2020.3 release the ProcessingEnvironment
+ * is not of type com.sun.tools.javac.processing.JavacProcessingEnvironment
+ * but java.lang.reflect.Proxy.
+ * The com.sun.source.util.Trees.instance() throws an IllegalArgumentException when the proxied processingEnv is passed.
+ *
+ * @author Vicky Ronnen (vicky.ronnen@upcmail.nl or v.ronnen@gmail.com)
+ * @link https://youtrack.jetbrains.com/issue/IDEA-256707
+ * @param processingEnv possible proxied
+ * @return ProcessingEnvironment unwrapped from the proxy if proxied or the original processingEnv
+ */
+ private ProcessingEnvironment unwrap(ProcessingEnvironment processingEnv) {
+ if (Proxy.isProxyClass(processingEnv.getClass())) {
+ InvocationHandler invocationHandler = Proxy.getInvocationHandler(processingEnv);
+ try {
+ Field field = invocationHandler.getClass().getDeclaredField("val$delegateTo");
+ field.setAccessible(true);
+ Object o = field.get(invocationHandler);
+ if (o instanceof ProcessingEnvironment) {
+ return (ProcessingEnvironment) o;
+ } else {
+ processingEnv.getMessager().printMessage(Kind.ERROR, "got " + o.getClass() + " expected instanceof com.sun.tools.javac.processing.JavacProcessingEnvironment");
+ return null;
+ }
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ processingEnv.getMessager().printMessage(Kind.ERROR, e.getMessage());
+ return null;
+ }
+ } else {
+ return processingEnv;
+ }
+ }
+
}
diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowElementScanner7.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowElementScanner7.java
index fed4c76e..9fcad8d8 100644
--- a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowElementScanner7.java
+++ b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowElementScanner7.java
@@ -78,9 +78,9 @@ public CompileUnit visitVariable(VariableElement e, String p) {
ts.scan(trees.getPath(e), marker);
if (marker.isNewProperty()) {
compileUnit.addProperty(
- new SimpleVariable(e.getSimpleName().toString(),
+ e.getSimpleName().toString(),
e.getModifiers().contains(Modifier.STATIC),
- e.getModifiers().contains(Modifier.FINAL))
+ e.getModifiers().contains(Modifier.FINAL)
);
if (LOG.isLoggable(Level.FINE)) {
diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileProblem.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileProblem.java
new file mode 100644
index 00000000..2ac5d68e
--- /dev/null
+++ b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileProblem.java
@@ -0,0 +1,253 @@
+package org.yarnandtail.andhow.compile;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.yarnandtail.andhow.util.TextUtil;
+
+/**
+ * Implementation represent specific problems with AndHow Property usage or
+ * initiation, discovered during compilation.
+ */
+public abstract class CompileProblem {
+
+ final String groupName;
+ final String propName;
+ final boolean propProblem;
+
+ /**
+ * Base constructor for Property related problems.
+ *
+ * @param groupName Full name of the class containing the {@code Property}.
+ * If an inner class, should be the complete name w/ the inner class.
+ * @param propName The declared name of the {@code Property} w/ the problem.
+ */
+ CompileProblem(String groupName, String propName) {
+ this.groupName = groupName;
+ this.propName = propName;
+ propProblem = true;
+ }
+
+ /**
+ * Base constructor for non-Property related problems, such as Initiation
+ * errors.
+ */
+ CompileProblem() {
+ groupName = null;
+ propName = null;
+ propProblem = false;
+ }
+
+ /**
+ * Full name of the class containing the {@code Property}.
+ * In the case of an inner class, this should be the complete name including
+ * that inner class.
+ *
+ * @return May be null if this is not a property related problem.
+ */
+ public String getGroupName() {
+ return groupName;
+ }
+
+ /**
+ * The declared name of the {@code Property} that has the problem.
+ *
+ * @return May be null if this is not a property related problem.
+ */
+ public String getPropertyName() {
+ return propName;
+ }
+
+ /**
+ * If true, the problem is related to a specific {@code Property}.
+ * @return
+ */
+ public boolean isPropertyProblem() {
+ return propProblem;
+ }
+
+ /**
+ * Logical equals implementation based on equality of fields.
+ *
+ * This is only used for comparisons during testing.
+ *
+ * @param o Compared to me.
+ * @return True if logically equal.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o != null && this.getClass().isInstance(o)) {
+ CompileProblem cp = (CompileProblem)o;
+
+ return
+ this.groupName.equals(cp.groupName) &&
+ this.propName.equals(cp.propName) &&
+ this.propProblem == cp.propProblem;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * The complete message that will be logged during compilation.
+ *
+ * Intended to be user readable w/ enough info for the user to understand
+ * and find the issue.
+ *
+ * @return A String explanation of the problem.
+ */
+ public abstract String getFullMessage();
+
+ /**
+ * An AndHow Property is missing the {@code static} modifier.
+ */
+ static class PropMissingStatic extends CompileProblem {
+
+ /**
+ * New instance.
+ *
+ * @param groupName Full name of the class containing the {@code Property}.
+ * If an inner class, should be the complete name w/ the inner class.
+ * @param propName The declared name of the {@code Property} w/ the problem.
+ */
+ public PropMissingStatic(String groupName, String propName) {
+ super(groupName, propName);
+ }
+
+ @Override
+ public String getFullMessage() {
+ return TextUtil.format(
+ "The AndHow Property '{}' in the class '{}' must be declared as static.",
+ groupName, propName);
+ }
+ }
+
+ /**
+ * An AndHow Property is missing the {@code final} modifier.
+ */
+ static class PropMissingFinal extends CompileProblem {
+
+ /**
+ * New instance.
+ *
+ * @param groupName Full name of the class containing the {@code Property}.
+ * If an inner class, should be the complete name w/ the inner class.
+ * @param propName The declared name of the {@code Property} w/ the problem.
+ */
+ public PropMissingFinal(String groupName, String propName) {
+ super(groupName, propName);
+ }
+
+ @Override
+ public String getFullMessage() {
+ return TextUtil.format(
+ "The AndHow Property '{}' in the class '{}' must be declared as final.",
+ groupName, propName);
+ }
+ }
+
+ /**
+ * An AndHow Property is missing the {@code static} and {@code final} modifiers.
+ */
+ static class PropMissingStaticFinal extends CompileProblem {
+
+ /**
+ * New instance.
+ *
+ * @param groupName Full name of the class containing the {@code Property}.
+ * If an inner class, should be the complete name w/ the inner class.
+ * @param propName The declared name of the {@code Property} w/ the problem.
+ */
+ public PropMissingStaticFinal(String groupName, String propName) {
+ super(groupName, propName);
+ }
+
+ @Override
+ public String getFullMessage() {
+ return TextUtil.format(
+ "The AndHow Property '{}' in the class '{}' must be declared as final.",
+ groupName, propName);
+ }
+ }
+
+ /**
+ * More than a single Init class was found on the classpath.
+ */
+ static class TooManyInitClasses extends CompileProblem {
+ private final List _instances = new ArrayList();
+ private final String _fullInitClassName;
+
+ /**
+ * New instance.
+ *
+ * @param fullInitClassName The Init interface name
+ * @param instances A list of instances that implement the interface.
+ */
+ public TooManyInitClasses(
+ String fullInitClassName,
+ List instances) {
+
+ if (instances != null) this._instances.addAll(instances);
+ this._fullInitClassName = fullInitClassName;
+
+ }
+
+ /**
+ * A list of full class names that implement the InitClassName interface.
+ *
+ * @return A List of class names. Never null.
+ */
+ public List getInstanceNames() {
+ List names = new ArrayList();
+
+ for (AndHowCompileProcessor.CauseEffect ce : _instances) {
+ names.add(ce.fullClassName);
+ }
+
+ return names;
+ }
+
+ /**
+ * The Init interface name.
+ *
+ * @return A class name.
+ */
+ public String getInitClassName() {
+ return _fullInitClassName;
+ }
+
+ @Override
+ public String getFullMessage() {
+ String impList = _instances.stream()
+ .map(i -> i.fullClassName).collect(Collectors.joining( ", " ));
+
+ return TextUtil.format("Multiple ({}) implementations of {} were found, "
+ + "but only one is allowed. Implementations found: {}",
+ String.valueOf(_instances.size()),
+ _fullInitClassName, impList);
+ }
+
+ /**
+ * Logical equals implementation based on equality of fields.
+ *
+ * This is only used for comparisons during testing.
+ *
+ * @param o Compared to me.
+ * @return True if logically equal.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o != null && this.getClass().isInstance(o)) {
+ TooManyInitClasses cp = (TooManyInitClasses)o;
+
+ return
+ this._instances.containsAll(cp._instances) &&
+ cp._instances.containsAll(this._instances) &&
+ this._fullInitClassName.equals(cp._fullInitClassName);
+ } else {
+ return false;
+ }
+ }
+ }
+
+}
diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileUnit.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileUnit.java
index 0b470a3c..6c262709 100644
--- a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileUnit.java
+++ b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileUnit.java
@@ -32,7 +32,7 @@ public class CompileUnit {
private final String classCanonName;
private PropertyRegistrationList registrations; //late init
- private List errors; //late init
+ private List errors; //late init
private boolean initClass; //True if an AndHowInit instance (and not AndHowTestInit)
private boolean testInitClass; //True if an AndHowTestInit instance
@@ -101,45 +101,42 @@ public SimpleType popType() {
* If modifiers are invalid, an error will be recorded rather than a
* Property.
*
- * @param variableElement A SimpleType representing a variable to which an
- * AndHow property is constructed and assigned to.
+ * @param name The name of the variable the Property is assigned to.
+ * @param _static Does the variable has the static modifier?
+ * @param _final Is the variable declared as static?
* @return True if the property could be added, false if an error was
* recorded instead.
*/
- public boolean addProperty(SimpleVariable variableElement) {
+ public boolean addProperty(String name, boolean _static, boolean _final) {
- if (variableElement.isStatic() && variableElement.isFinal()) {
+ if (_static && _final) {
if (registrations == null) {
registrations = new PropertyRegistrationList(classCanonName);
}
- registrations.add(variableElement.getName(), getInnerPathNames());
+ registrations.add(name, getInnerPathNames());
return true;
} else {
- addPropertyError(variableElement.getName(), "New AndHow Properties must be assigned to a static final field.");
+
+ if (errors == null) {
+ errors = new ArrayList();
+ }
+
+ String parentName = NameUtil.getJavaName(classCanonName, this.getInnerPathNames());
+
+ if (_static) {
+ errors.add(new CompileProblem.PropMissingFinal(parentName, name));
+ } else if (_final) {
+ errors.add(new CompileProblem.PropMissingStatic(parentName, name));
+ } else {
+ errors.add(new CompileProblem.PropMissingStaticFinal(parentName, name));
+ }
+
return false;
}
}
- /**
- * Register an AndHow Property declaration in the current scope - either
- * directly in the the top level class or the recorded path to an inner
- * class.
- *
- * If modifiers are invalid, an error will be recorded rather than a
- * Property.
- *
- * @param name The name of the variable the Property is assigned to.
- * @param _static Does the variable has the static modifier?
- * @param _final Is the variable declared as static?
- * @return True if the property could be added, false if an error was
- * recorded instead.
- */
- public boolean addProperty(String name, boolean _static, boolean _final) {
- return addProperty(new SimpleVariable(name, _static, _final));
- }
-
/**
* Return the state of inner class nesting from the outermost to the
* innermost.
@@ -147,7 +144,7 @@ public boolean addProperty(String name, boolean _static, boolean _final) {
* If the current state is at the root of the top level class, an empty list
* is returned.
*
- * @see getInnerPathNames() for just the names of the nested inner classes.
+ * @see #getInnerPathNames() for just the names of the nested inner classes.
* @return
*/
public List getInnerPath() {
@@ -193,17 +190,6 @@ public List getInnerPathNames() {
return pathNames;
}
- public void addPropertyError(String propName, String msg) {
-
- if (errors == null) {
- errors = new ArrayList();
- }
-
- String parentName = NameUtil.getJavaName(classCanonName, this.getInnerPathNames());
-
- errors.add("The AndHow Property '" + propName + "' in " + parentName + " is invalid: " + msg);
- }
-
/**
* The fully qualified name of the top level class this CompileUnit is for.
*
@@ -263,16 +249,16 @@ public PropertyRegistrationList getRegistrations() {
*
* @return
*/
- public List getErrors() {
+ public List getProblems() {
if (errors != null) {
return errors;
} else {
- return Collections.EMPTY_LIST;
+ return Collections.emptyList();
}
}
/**
- * Returns true if the getErrors() list would be non-empty.
+ * Returns true if the getProblems() list would be non-empty.
*
* @return
*/
diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileUtil.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileUtil.java
new file mode 100644
index 00000000..9ea3e415
--- /dev/null
+++ b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileUtil.java
@@ -0,0 +1,70 @@
+package org.yarnandtail.andhow.compile;
+
+/**
+ * Utilities for the AndHow AnnotationProcessor.
+ */
+public class CompileUtil {
+
+ /**
+ * Determine the correct 'Generated' annotation class name based on the current Java runtime.
+ *
+ * This method fetches the version of the current runtime via getMajorJavaVersion() and
+ * uses that to call getGeneratedAnnotationClassName(int).
+ *
+ * @return A the fully qualified class name of the Generated annotation.
+ */
+ public static String getGeneratedAnnotationClassName() {
+ return getGeneratedAnnotationClassName(getMajorJavaVersion(System.getProperty("java.version")));
+ }
+
+ /**
+ * Determine the correct 'Generated' annotation class name based on the Java major version.
+ * Java 8 uses the @javax.annotation.Generated annotation to mark a generated class.
+ * Java 9 and beyond uses @javax.annotation.processing.Generated
+ *
+ * Both annotations are SOURCE level retention, so they are only present in the source code and no
+ * record of them is compiled into the binary. Thus, the determination of which one to use is
+ * based only on the Java version used to compile, not the -source or -target settings.
+ *
+ * @param javaMajorVersion The Java version in integer form. Use '8' for 1.8.
+ * @return A the fully qualified class name of the Generated annotation.
+ */
+ public static String getGeneratedAnnotationClassName(int javaMajorVersion) {
+ if (javaMajorVersion < 9) {
+ return "javax.annotation.Generated";
+ } else {
+ return "javax.annotation.processing.Generated";
+ }
+ }
+
+ /**
+ * Determine the major version of the Java runtime based on a version string.
+ *
+ * All versions are integers, thus version `1.8` returns `8`.
+ * Java 10 introduces the Runtime.version(), which would remove the need for this method,
+ * however, at the moment the code is still Java 8 compatable.
+ *
+ * @param versionString As returned from SystemProperties.getProperty("java.version")
+ * @return
+ */
+ public static int getMajorJavaVersion(String versionString) {
+ String[] versionParts = versionString.split("[\\.\\-_]", 3);
+
+ try {
+ if ("1".equals(versionParts[0])) {
+ //Old style 1.x format
+ return Integer.parseInt(versionParts[1]);
+ } else {
+ return Integer.parseInt(versionParts[0]);
+ }
+
+ } catch (NumberFormatException e) {
+ throw new RuntimeException(
+ "AndHow couldn't parse '" + versionString + "' as a 'java.version' string in System.properties. " +
+ "Is this a non-standard JDK? ", e
+ );
+ }
+
+
+ }
+}
diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/PropertyRegistrarClassGenerator.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/PropertyRegistrarClassGenerator.java
index 5e50a83b..8cb598bb 100644
--- a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/PropertyRegistrarClassGenerator.java
+++ b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/PropertyRegistrarClassGenerator.java
@@ -24,7 +24,8 @@ public class PropertyRegistrarClassGenerator {
* Create a new instance w all info needed to generateSource a PropertyRegistrar file.
*
* @param compUnit CompileUnit instance w/ all needed class and property info
- * @param generatingClass The class (likely an AnnotationProcessor) that will be annotated as the generator
+ * @param generatingClass The class of our AnnotationProcessor to be listed as the generator in the
+ * Generated annotation.
* @param runDate The Calendar date-time of the run, used for annotation.
* Passed in so all generated files can have the same timestamp.
*/
@@ -42,6 +43,7 @@ public String getTemplatePath() {
public String getTemplate() throws Exception {
return IOUtil.getUTF8ResourceAsString(getTemplatePath());
}
+
public String generateSource() throws Exception {
@@ -50,11 +52,14 @@ public String generateSource() throws Exception {
String source = String.format(template,
buildPackageString(),
- compUnit.getRootCanonicalName(), compUnit.getRootSimpleName(),
+ compUnit.getRootCanonicalName(),
+ compUnit.getRootSimpleName(),
buildGeneratedClassSimpleName(),
PropertyRegistrar.class.getCanonicalName(),
- generatingClass.getCanonicalName(), buildRunDateString(),
- buildRegistrationAddsString()
+ generatingClass.getCanonicalName(),
+ buildRunDateString(),
+ buildRegistrationAddsString(),
+ CompileUtil.getGeneratedAnnotationClassName()
);
return source;
diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/SimpleVariable.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/SimpleVariable.java
deleted file mode 100644
index 204cfe14..00000000
--- a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/SimpleVariable.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.yarnandtail.andhow.compile;
-
-/**
- * Compile time representation of a variable w/ just enough information
- * to decide if it is a valid variable for an AndHow Property to be constructed
- * and assigned to.
- */
-public class SimpleVariable {
-
- private final String name;
- private final boolean _static;
- private final boolean _final;
-
- /**
- *
- * @param name Name of this variable
- * @param _static Is this var marked as static?
- * @param _final Is this var marked as final?
- */
- public SimpleVariable(String name, boolean _static, boolean _final) {
- this.name = name;
- this._static = _static;
- this._final = _final;
- }
-
- public String getName() {
- return name;
- }
-
- public boolean isStatic() {
- return _static;
- }
-
- public boolean isFinal() {
- return _final;
- }
-
-
-}
diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/TooManyInitClassesException.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/TooManyInitClassesException.java
deleted file mode 100644
index 5604ed89..00000000
--- a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/TooManyInitClassesException.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.yarnandtail.andhow.compile;
-
-import java.util.ArrayList;
-import java.util.List;
-import org.yarnandtail.andhow.compile.AndHowCompileProcessor.CauseEffect;
-import org.yarnandtail.andhow.util.AndHowLog;
-
-/**
- *
- * @author ericeverman
- */
-public class TooManyInitClassesException extends RuntimeException {
- protected List _instances = new ArrayList();
- protected String _fullInitClassName;
-
- public TooManyInitClassesException(
- String fullInitClassName,
- List instances) {
- if (instances != null) this._instances.addAll(instances);
- this._fullInitClassName = fullInitClassName;
- }
-
- public List getInstanceNames() {
- List names = new ArrayList();
-
- for (CauseEffect ce : _instances) {
- names.add(ce.fullClassName);
- }
-
- return names;
- }
-
- public void writeDetails(AndHowLog log) {
- log.error("Multiple ({0}) {1} implementation classes were found, but only "
- + "one is allowed. List follows:",
- Integer.valueOf(_instances.size()).toString(), _fullInitClassName);
-
- for (String name : getInstanceNames()) {
- log.error("\t* " + name);
- }
- }
-
- @Override
- public String getMessage() {
- return "Multiple " + _fullInitClassName + " implementations were found - "
- + "only one is allowed. See System.err for complete list";
- }
-
-
-}
diff --git a/andhow-annotation-processor/src/main/resources/org/yarnandtail/andhow/compile/PropertyRegistrarClassGenerator_Template.txt b/andhow-annotation-processor/src/main/resources/org/yarnandtail/andhow/compile/PropertyRegistrarClassGenerator_Template.txt
index 9af12a1c..a5833bd8 100644
--- a/andhow-annotation-processor/src/main/resources/org/yarnandtail/andhow/compile/PropertyRegistrarClassGenerator_Template.txt
+++ b/andhow-annotation-processor/src/main/resources/org/yarnandtail/andhow/compile/PropertyRegistrarClassGenerator_Template.txt
@@ -4,12 +4,15 @@ import org.yarnandtail.andhow.service.AbstractPropertyRegistrar;
import org.yarnandtail.andhow.service.PropertyRegistrationList;
/*
-Java9 places 'Generated' in a module that needs to be separate included in a build
-or brought in as a dependency. As a result, just using a comment instead.
-@javax.annotation.Generated(
+AndHow generated class that is discovered via the Service Provider API to
+register a proxied class as having AndHow properties. These properties
+can then be auto-discovered and assigned values at startup.
+See: https://github.com/eeverman/andhow
+*/
+@%9$s(
value="%6$s",
date="%7$s",
- comments="Proxy for %2$s registered as a service provider in META-INF/services/%5$s") */
+ comments="Proxy for %2$s registered as a service provider in META-INF/services/%5$s")
public class %4$s extends AbstractPropertyRegistrar {
@Override
diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileExceptionTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileExceptionTest.java
new file mode 100644
index 00000000..adca2e95
--- /dev/null
+++ b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileExceptionTest.java
@@ -0,0 +1,51 @@
+package org.yarnandtail.andhow.compile;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ *
+ * @author ericeverman
+ */
+public class AndHowCompileExceptionTest {
+
+ @Test
+ public void testAllForProblemConstructor() {
+
+ //Setup
+ List problems = new ArrayList();
+ CompileProblem.PropMissingStaticFinal prob1 = new CompileProblem.PropMissingStaticFinal("g1", "p1");
+ CompileProblem.PropMissingStaticFinal prob2 = new CompileProblem.PropMissingStaticFinal("g1", "p1");
+ problems.clear();
+ problems.add(prob1);
+ problems.add(prob2);
+
+
+ AndHowCompileException ahce = new AndHowCompileException(problems);
+ assertNull(ahce.getCause());
+ assertEquals(AndHowCompileException.DEFAULT_MSG, ahce.getMessage());
+ assertTrue(problems.containsAll(ahce.getProblems()));
+ assertTrue(ahce.getProblems().containsAll(problems));
+ }
+
+ @Test
+ public void testNullProblemsShouldNotBombr() {
+
+ AndHowCompileException ahce = new AndHowCompileException(null);
+ assertEquals(0, ahce.getProblems().size());
+ }
+
+ @Test
+ public void testAllForMessageThrowableConstructor() {
+
+ final String MSG = "ABC";
+ final Exception E = new Exception();
+
+ AndHowCompileException ahce = new AndHowCompileException(MSG, E);
+ assertEquals(E, ahce.getCause());
+ assertEquals(MSG, ahce.getMessage());
+ assertEquals(0, ahce.getProblems().size());
+ }
+}
diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessorTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessorTest.java
deleted file mode 100644
index 2e64408d..00000000
--- a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessorTest.java
+++ /dev/null
@@ -1,260 +0,0 @@
-package org.yarnandtail.andhow.compile;
-
-import org.yarnandtail.compile.*;
-import org.yarnandtail.andhow.service.PropertyRegistrar;
-import org.yarnandtail.andhow.service.PropertyRegistration;
-import java.io.*;
-import java.nio.charset.Charset;
-import java.util.*;
-import javax.tools.*;
-import org.junit.Test;
-import org.yarnandtail.andhow.util.IOUtil;
-
-import static org.junit.Assert.*;
-
-/**
- * A lot of this code was borrowed from here:
- * https://gist.github.com/johncarl81/46306590cbdde5a3003f
- * @author ericeverman
- */
-public class AndHowCompileProcessorTest {
-
- //
- //Names of classes in the resources directory, used for compile testing of
- //initiation self discovery. The 'A' class extends the 'Abstract' one for each.
- protected static final String AndHowInitAbstract_NAME = "org.yarnandtail.andhow.compile.AndHowInitAbstract";
- protected static final String AndHowInitA_NAME = "org.yarnandtail.andhow.compile.AndHowInitA";
- protected static final String AndHowInitB_NAME = "org.yarnandtail.andhow.compile.AndHowInitB";
- protected static final String AndHowTestInitAbstract_NAME = "org.yarnandtail.andhow.compile.AndHowTestInitAbstract";
- protected static final String AndHowTestInitA_NAME = "org.yarnandtail.andhow.compile.AndHowTestInitA";
- protected static final String AndHowTestInitB_NAME = "org.yarnandtail.andhow.compile.AndHowTestInitB";
-
-
- @Test
- public void testCompileAnnotationProcessorOutput() throws Exception {
-
- final String CLASS_PACKAGE = "org.yarnandtail.andhow.compile";
- final String CLASS_NAME = CLASS_PACKAGE + ".PropertySample";
- final String CLASS_SOURCE_PATH = "/" + CLASS_NAME.replace(".", "/") + ".java";
- final String GEN_CLASS_NAME = CLASS_PACKAGE + ".$PropertySample_AndHowProps";
-
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- final MemoryFileManager manager = new MemoryFileManager(compiler);
- TestClassLoader loader = new TestClassLoader(manager);
-
- List options=new ArrayList();
- //options.add("-verbose");
-
-
- Set input = new HashSet();
- String classContent = IOUtil.getUTF8ResourceAsString(CLASS_SOURCE_PATH);
- input.add(new TestSource(CLASS_NAME, JavaFileObject.Kind.SOURCE, classContent));
-
- JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, input);
- task.setProcessors(Collections.singleton(new AndHowCompileProcessor()));
- task.call();
-
- Object genClass = loader.loadClass(GEN_CLASS_NAME).newInstance();
- String genSvsFile = IOUtil.toString(loader.getResourceAsStream("/META-INF/services/org.yarnandtail.andhow.service.PropertyRegistrar"), Charset.forName("UTF-8"));
-
- assertNotNull(genClass);
-
- PropertyRegistrar registrar = (PropertyRegistrar)genClass;
-
- //final String ROOT = "org.yarnandtail.andhow.compile.PropertySample";
-
- assertEquals(CLASS_NAME, registrar.getRootCanonicalName());
- List propRegs = registrar.getRegistrationList();
-
- assertEquals(CLASS_NAME + ".STRING", propRegs.get(0).getCanonicalPropertyName());
- assertEquals(CLASS_NAME + ".STRING_PUB", propRegs.get(1).getCanonicalPropertyName());
- assertEquals(CLASS_NAME + ".PC.STRING", propRegs.get(2).getCanonicalPropertyName());
- assertEquals(CLASS_NAME + ".PC.STRING_PUB", propRegs.get(3).getCanonicalPropertyName());
- assertEquals(CLASS_NAME + ".PC.PC_PC.STRING", propRegs.get(4).getCanonicalPropertyName());
- assertEquals(CLASS_NAME + ".PC.PC_PI.STRING", propRegs.get(5).getCanonicalPropertyName());
-
- assertEquals(CLASS_NAME + ".PI.STRING", propRegs.get(6).getCanonicalPropertyName());
- assertEquals(CLASS_NAME + ".PI.PI_DC.STRING", propRegs.get(7).getCanonicalPropertyName());
- assertEquals(CLASS_NAME + ".PI.PI_DC.STRING_PUB", propRegs.get(8).getCanonicalPropertyName());
- assertEquals(CLASS_NAME + ".PI.PI_DI.STRING", propRegs.get(9).getCanonicalPropertyName());
- assertEquals(CLASS_NAME + ".PI.PI_DI.STRING_PUB", propRegs.get(10).getCanonicalPropertyName());
-
- //
- //Test the registration file
- assertNotNull(genSvsFile);
- assertEquals(GEN_CLASS_NAME, genSvsFile.trim());
- }
-
-
- @Test
- public void testServiceRegistrationOfOneProdAndOneTestInit() throws Exception {
-
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- final MemoryFileManager manager = new MemoryFileManager(compiler);
- TestClassLoader loader = new TestClassLoader(manager);
-
- List options=new ArrayList();
- //options.add("-verbose");
-
-
- Set input = new HashSet();
- input.add(new TestSource(AndHowInitAbstract_NAME)); //abstract should be ignored
- input.add(new TestSource(AndHowInitA_NAME));
- input.add(new TestSource(AndHowTestInitAbstract_NAME));
- input.add(new TestSource(AndHowTestInitA_NAME));
-
- JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, input);
- task.setProcessors(Collections.singleton(new AndHowCompileProcessor()));
- task.call();
-
- String prodInitSvs = IOUtil.toString(loader.getResourceAsStream("/META-INF/services/org.yarnandtail.andhow.AndHowInit"), Charset.forName("UTF-8"));
- String testInitSvs = IOUtil.toString(loader.getResourceAsStream("/META-INF/services/org.yarnandtail.andhow.AndHowTestInit"), Charset.forName("UTF-8"));
-
-
- //
- //Test the initiation files
- assertNotNull(prodInitSvs);
- assertEquals(AndHowInitA_NAME, prodInitSvs.trim());
- assertNotNull(testInitSvs);
- assertEquals(AndHowTestInitA_NAME, testInitSvs.trim());
- }
-
-
- @Test
- public void testServiceRegistrationOfOneProdInit() throws Exception {
-
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- final MemoryFileManager manager = new MemoryFileManager(compiler);
- TestClassLoader loader = new TestClassLoader(manager);
-
- List options=new ArrayList();
- //options.add("-verbose");
-
-
- Set input = new HashSet();
- input.add(new TestSource(AndHowInitAbstract_NAME));
- input.add(new TestSource(AndHowInitA_NAME));
-
- JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, input);
- task.setProcessors(Collections.singleton(new AndHowCompileProcessor()));
- task.call();
-
- String prodInitSvs = IOUtil.toString(loader.getResourceAsStream("/META-INF/services/org.yarnandtail.andhow.AndHowInit"), Charset.forName("UTF-8"));
-
- //
- //Test the initiation files
- assertNotNull(prodInitSvs);
- assertEquals(AndHowInitA_NAME, prodInitSvs.trim());
- }
-
- @Test
- public void testServiceRegistrationOfOneTestInit() throws Exception {
-
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- final MemoryFileManager manager = new MemoryFileManager(compiler);
- TestClassLoader loader = new TestClassLoader(manager);
-
- List options=new ArrayList();
- //options.add("-verbose");
-
-
- Set input = new HashSet();
- input.add(new TestSource(AndHowTestInitAbstract_NAME));
- input.add(new TestSource(AndHowTestInitA_NAME));
-
- JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, input);
- task.setProcessors(Collections.singleton(new AndHowCompileProcessor()));
- task.call();
-
- String testInitSvs = IOUtil.toString(loader.getResourceAsStream("/META-INF/services/org.yarnandtail.andhow.AndHowTestInit"), Charset.forName("UTF-8"));
-
-
- //
- //Test the initiation files
- assertNotNull(testInitSvs);
- assertEquals(AndHowTestInitA_NAME, testInitSvs.trim());
- }
-
-
- @Test
- public void testServiceRegistrationOfAndHowInitWithTooManyProdInstances() throws Exception {
-
- try {
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- final MemoryFileManager manager = new MemoryFileManager(compiler);
- TestClassLoader loader = new TestClassLoader(manager);
-
- List options=new ArrayList();
- //options.add("-verbose");
-
-
- Set input = new HashSet();
- input.add(new TestSource(AndHowInitAbstract_NAME));
- input.add(new TestSource(AndHowInitA_NAME));
- input.add(new TestSource(AndHowInitB_NAME));
-
- JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, input);
- task.setProcessors(Collections.singleton(new AndHowCompileProcessor()));
- task.call();
-
- } catch (RuntimeException e) {
- TooManyInitClassesException tmi = null;
-
- if (e instanceof TooManyInitClassesException) {
- tmi = (TooManyInitClassesException)e;
- } else if (e.getCause() != null && e.getCause() instanceof TooManyInitClassesException) {
- tmi = (TooManyInitClassesException) e.getCause();
- }
-
- if (tmi != null) {
- assertEquals(2, tmi.getInstanceNames().size());
- assertTrue(tmi.getInstanceNames().contains(AndHowInitA_NAME));
- assertTrue(tmi.getInstanceNames().contains(AndHowInitB_NAME));
- } else {
- fail("Expecting the exception to be TooManyInitClassesException or caused by it.");
- }
- }
- }
-
-
- @Test
- public void testServiceRegistrationOfAndHowInitWithTooManyTestInstances() throws Exception {
-
- try {
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- final MemoryFileManager manager = new MemoryFileManager(compiler);
- TestClassLoader loader = new TestClassLoader(manager);
-
- List options=new ArrayList();
- //options.add("-verbose");
-
-
- Set input = new HashSet();
- input.add(new TestSource(AndHowTestInitAbstract_NAME));
- input.add(new TestSource(AndHowTestInitA_NAME));
- input.add(new TestSource(AndHowTestInitB_NAME));
-
- JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, input);
- task.setProcessors(Collections.singleton(new AndHowCompileProcessor()));
- task.call();
-
- } catch (RuntimeException e) {
- TooManyInitClassesException tmi = null;
-
- if (e instanceof TooManyInitClassesException) {
- tmi = (TooManyInitClassesException)e;
- } else if (e.getCause() != null && e.getCause() instanceof TooManyInitClassesException) {
- tmi = (TooManyInitClassesException) e.getCause();
- }
-
- if (tmi != null) {
- assertEquals(2, tmi.getInstanceNames().size());
- assertTrue(tmi.getInstanceNames().contains(AndHowTestInitA_NAME));
- assertTrue(tmi.getInstanceNames().contains(AndHowTestInitB_NAME));
- } else {
- fail("Expecting the exception to be TooManyInitClassesException or caused by it.");
- }
- }
- }
-
-}
diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor_InitTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor_InitTest.java
new file mode 100644
index 00000000..aa806fc4
--- /dev/null
+++ b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor_InitTest.java
@@ -0,0 +1,261 @@
+package org.yarnandtail.andhow.compile;
+
+import org.yarnandtail.compile.*;
+import static org.yarnandtail.andhow.compile.CompileProblem.*;
+import java.nio.charset.Charset;
+import java.util.*;
+import javax.tools.*;
+import javax.tools.Diagnostic.Kind;
+import org.junit.jupiter.api.Test;
+import org.yarnandtail.andhow.util.IOUtil;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * A lot of this code was borrowed from here:
+ * https://gist.github.com/johncarl81/46306590cbdde5a3003f
+ * @author ericeverman
+ */
+public class AndHowCompileProcessor_InitTest extends AndHowCompileProcessorTestBase {
+
+ static final String pkg = AndHowCompileProcessor_PropertyTest.class.getPackage().getName();
+
+ //
+ //Names of classes in the resources directory, used for compile testing of
+ //initiation self discovery. The 'A' class extends the 'Abstract' one for each.
+ protected static final String AndHowInitAbstract_NAME = "AndHowInitAbstract";
+ protected static final String AndHowInitA_NAME = "AndHowInitA";
+ protected static final String AndHowInitB_NAME = "AndHowInitB";
+ protected static final String AndHowTestInitAbstract_NAME = "AndHowTestInitAbstract";
+ protected static final String AndHowTestInitA_NAME = "AndHowTestInitA";
+ protected static final String AndHowTestInitB_NAME = "AndHowTestInitB";
+
+ @Test
+ public void testServiceRegistrationOfOneProdAndOneTestInit() throws Exception {
+
+ List options=new ArrayList();
+ //options.add("-verbose");
+
+ sources.add(buildTestSource(pkg, AndHowInitAbstract_NAME)); //Abstract should be igored
+ sources.add(buildTestSource(pkg, AndHowInitA_NAME));
+ sources.add(buildTestSource(pkg, AndHowTestInitAbstract_NAME));
+ sources.add(buildTestSource(pkg, AndHowTestInitA_NAME));
+
+ JavaCompiler.CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, sources);
+ task.setProcessors(Collections.singleton(new AndHowCompileProcessor()));
+ task.call();
+
+ String prodInitSvs = IOUtil.toString(loader.getResourceAsStream(INIT_SVS_PATH), Charset.forName("UTF-8"));
+ String testInitSvs = IOUtil.toString(loader.getResourceAsStream(TEST_INIT_SVS_PATH), Charset.forName("UTF-8"));
+
+ assertEquals(0,
+ diagnostics.getDiagnostics().stream().filter(
+ d -> d.getKind().equals(Kind.ERROR) || d.getKind().equals(Kind.WARNING)
+ ).count(),
+ "Should be no warn/errors");
+
+ //
+ //Test the initiation files
+ assertNotNull(prodInitSvs);
+ assertEquals(fullName(pkg, AndHowInitA_NAME), prodInitSvs.trim());
+ assertNotNull(testInitSvs);
+ assertEquals(fullName(pkg, AndHowTestInitA_NAME), testInitSvs.trim());
+ }
+
+
+ @Test
+ public void testServiceRegistrationOfOneProdInit() throws Exception {
+
+ List options=new ArrayList();
+ //options.add("-verbose");
+
+ sources.add(buildTestSource(pkg, AndHowInitAbstract_NAME));
+ sources.add(buildTestSource(pkg, AndHowInitA_NAME));
+
+ JavaCompiler.CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, sources);
+ task.setProcessors(Collections.singleton(new AndHowCompileProcessor()));
+ task.call();
+
+ String prodInitSvs = IOUtil.toString(loader.getResourceAsStream(INIT_SVS_PATH), Charset.forName("UTF-8"));
+
+ assertEquals(0,
+ diagnostics.getDiagnostics().stream().filter(
+ d -> d.getKind().equals(Kind.ERROR) || d.getKind().equals(Kind.WARNING)
+ ).count(),
+ "Should be no warn/errors");
+
+ //
+ //Test the initiation files
+ assertNotNull(prodInitSvs);
+ assertEquals(fullName(pkg, AndHowInitA_NAME), prodInitSvs.trim());
+ }
+
+ @Test
+ public void testServiceRegistrationOfOneTestInit() throws Exception {
+
+ List options=new ArrayList();
+ //options.add("-verbose");
+
+ sources.add(buildTestSource(pkg, AndHowTestInitAbstract_NAME));
+ sources.add(buildTestSource(pkg, AndHowTestInitA_NAME));
+
+ JavaCompiler.CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, sources);
+ task.setProcessors(Collections.singleton(new AndHowCompileProcessor()));
+ task.call();
+
+ String testInitSvs = IOUtil.toString(loader.getResourceAsStream(TEST_INIT_SVS_PATH), Charset.forName("UTF-8"));
+
+ assertEquals(0,
+ diagnostics.getDiagnostics().stream().filter(
+ d -> d.getKind().equals(Kind.ERROR) || d.getKind().equals(Kind.WARNING)
+ ).count(),
+ "Should be no warn/errors");
+
+
+ //
+ //Test the initiation files
+ assertNotNull(testInitSvs);
+ assertEquals(fullName(pkg, AndHowTestInitA_NAME), testInitSvs.trim());
+ }
+
+
+ @Test
+ public void testServiceRegistrationOfAndHowInitWithTooManyProdInstances() throws Exception {
+
+ try {
+
+ List options=new ArrayList();
+ //options.add("-verbose");
+
+ sources.add(buildTestSource(pkg, AndHowInitAbstract_NAME));
+ sources.add(buildTestSource(pkg, AndHowInitA_NAME));
+ sources.add(buildTestSource(pkg, AndHowInitB_NAME));
+
+ JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, sources);
+ task.setProcessors(Collections.singleton(new AndHowCompileProcessor()));
+ task.call();
+
+ fail("Should have thrown an exception");
+
+ } catch (RuntimeException e) {
+
+ assertNotNull(e.getCause());
+ assertTrue(e.getCause() instanceof AndHowCompileException);
+
+ AndHowCompileException ce = (AndHowCompileException) e.getCause();
+
+ assertEquals(1, ce.getProblems().size());
+ assertTrue(ce.getProblems().get(0) instanceof TooManyInitClasses);
+
+ TooManyInitClasses tmi = (TooManyInitClasses) ce.getProblems().get(0);
+
+ assertEquals(2, tmi.getInstanceNames().size());
+ assertTrue(tmi.getInstanceNames().contains(fullName(pkg, AndHowInitA_NAME)));
+ assertTrue(tmi.getInstanceNames().contains(fullName(pkg, AndHowInitB_NAME)));
+ }
+ }
+
+
+ @Test
+ public void testServiceRegistrationOfAndHowInitWithTooManyTestInstances() throws Exception {
+
+ try {
+ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+ final MemoryFileManager manager = new MemoryFileManager(compiler);
+ TestClassLoader loader = new TestClassLoader(manager);
+
+ List options=new ArrayList();
+ //options.add("-verbose");
+
+ sources.add(buildTestSource(pkg, AndHowTestInitAbstract_NAME));
+ sources.add(buildTestSource(pkg, AndHowTestInitA_NAME));
+ sources.add(buildTestSource(pkg, AndHowTestInitB_NAME));
+
+ JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, sources);
+ task.setProcessors(Collections.singleton(new AndHowCompileProcessor()));
+ task.call();
+
+ fail("Should have thrown an exception");
+
+ } catch (RuntimeException e) {
+
+ assertNotNull(e.getCause());
+ assertTrue(e.getCause() instanceof AndHowCompileException);
+
+ AndHowCompileException ce = (AndHowCompileException) e.getCause();
+
+ assertEquals(1, ce.getProblems().size());
+ assertTrue(ce.getProblems().get(0) instanceof TooManyInitClasses);
+
+ TooManyInitClasses tmi = (TooManyInitClasses) ce.getProblems().get(0);
+
+ assertEquals(2, tmi.getInstanceNames().size());
+ assertTrue(tmi.getInstanceNames().contains(fullName(pkg, AndHowTestInitA_NAME)));
+ assertTrue(tmi.getInstanceNames().contains(fullName(pkg, AndHowTestInitB_NAME)));
+
+ }
+ }
+
+
+ @Test
+ public void testServiceRegistrationOfAndHowInitWithTooManyInstAndBadProperties() throws Exception {
+
+ try {
+
+ List options=new ArrayList();
+ //options.add("-verbose");
+
+ sources.add(buildTestSource(pkg, AndHowInitAbstract_NAME));
+ sources.add(buildTestSource(pkg, AndHowInitA_NAME));
+ sources.add(buildTestSource(pkg, AndHowInitB_NAME));
+ sources.add(buildTestSource(pkg, AndHowTestInitAbstract_NAME));
+ sources.add(buildTestSource(pkg, AndHowTestInitA_NAME));
+ sources.add(buildTestSource(pkg, AndHowTestInitB_NAME));
+ sources.add(buildTestSource(pkg, "BadProps_1"));
+ sources.add(buildTestSource(pkg, "BadProps_2"));
+
+ JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, sources);
+ task.setProcessors(Collections.singleton(new AndHowCompileProcessor()));
+ task.call();
+
+ fail("Should have thrown an exception");
+
+ } catch (RuntimeException e) {
+
+ assertNotNull(e.getCause());
+ assertTrue(e.getCause() instanceof AndHowCompileException);
+
+ AndHowCompileException ace = (AndHowCompileException) e.getCause();
+
+ String CLASSNAME = "org.yarnandtail.andhow.compile.BadProps_1";
+ String INNER_CLASSNAME = CLASSNAME + "$INNER_CLASS";
+
+ //Expected CompileProblems
+ //All problems for both classes should be reported in one exception
+ CompileProblem prob1 = new PropMissingFinal(CLASSNAME, "STR_1");
+ CompileProblem prob2 = new PropMissingStatic(CLASSNAME, "STR_2");
+ CompileProblem prob3 = new PropMissingStaticFinal(CLASSNAME, "STR_3");
+ CompileProblem prob4 = new PropMissingFinal(INNER_CLASSNAME, "STR_1");
+ CompileProblem prob5 = new PropMissingStatic(INNER_CLASSNAME, "STR_2");
+ CompileProblem prob6 = new PropMissingStaticFinal(INNER_CLASSNAME, "STR_3");
+
+ //In 2nd class
+ CompileProblem prob7 = new PropMissingFinal(
+ "org.yarnandtail.andhow.compile.BadProps_2", "STR_1");
+
+
+ assertEquals(9, ace.getProblems().size());
+ assertEquals(2,
+ ace.getProblems().stream().filter(i -> i instanceof TooManyInitClasses).count()
+ );
+ assertTrue(ace.getProblems().contains(prob1));
+ assertTrue(ace.getProblems().contains(prob2));
+ assertTrue(ace.getProblems().contains(prob3));
+ assertTrue(ace.getProblems().contains(prob4));
+ assertTrue(ace.getProblems().contains(prob5));
+ assertTrue(ace.getProblems().contains(prob6));
+ assertTrue(ace.getProblems().contains(prob7));
+ }
+ }
+
+}
diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor_PropertyTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor_PropertyTest.java
new file mode 100644
index 00000000..79f488d9
--- /dev/null
+++ b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor_PropertyTest.java
@@ -0,0 +1,178 @@
+package org.yarnandtail.andhow.compile;
+
+import java.nio.charset.Charset;
+import java.util.*;
+import javax.tools.*;
+import static org.junit.jupiter.api.Assertions.*;
+import org.junit.jupiter.api.Test;
+import org.yarnandtail.andhow.compile.CompileProblem.PropMissingFinal;
+import org.yarnandtail.andhow.compile.CompileProblem.PropMissingStatic;
+import org.yarnandtail.andhow.compile.CompileProblem.PropMissingStaticFinal;
+import org.yarnandtail.andhow.service.*;
+import org.yarnandtail.andhow.util.IOUtil;
+import org.yarnandtail.compile.AndHowCompileProcessorTestBase;
+
+/**
+ * A lot of this code was borrowed from here:
+ * https://gist.github.com/johncarl81/46306590cbdde5a3003f
+ * @author ericeverman
+ */
+public class AndHowCompileProcessor_PropertyTest extends AndHowCompileProcessorTestBase {
+
+ /** Shortcut to this package */
+ static final String pkg = AndHowCompileProcessor_PropertyTest.class.getPackage().getName();
+
+
+
+ @Test
+ public void testComplexNestedPropertySampleClass() throws Exception {
+
+ String classSimpleName = "PropertySample";
+
+ List options=new ArrayList();
+ //options.add("-verbose");
+
+ sources.add(buildTestSource(pkg, classSimpleName));
+
+ JavaCompiler.CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, sources);
+ task.setProcessors(Collections.singleton(new AndHowCompileProcessor()));
+ task.call();
+
+ Object genClass = loader.loadClass(genName(pkg, classSimpleName)).newInstance();
+ String genSvsFile = IOUtil.toString(
+ loader.getResourceAsStream(REGISTRAR_SVS_PATH), Charset.forName("UTF-8"));
+
+ assertEquals(0,
+ diagnostics.getDiagnostics().stream().filter(
+ d -> d.getKind().equals(Diagnostic.Kind.ERROR) || d.getKind().equals(Diagnostic.Kind.WARNING)
+ ).count(),
+ "Should be no warn/errors");
+
+ assertNotNull(genClass);
+
+ PropertyRegistrar registrar = (PropertyRegistrar)genClass;
+
+ //final String ROOT = "org.yarnandtail.andhow.compile.PropertySample";
+
+ assertEquals(fullName(pkg, classSimpleName), registrar.getRootCanonicalName());
+ List propRegs = registrar.getRegistrationList();
+
+ assertEquals(fullName(pkg, classSimpleName) + ".STRING", propRegs.get(0).getCanonicalPropertyName());
+ assertEquals(fullName(pkg, classSimpleName) + ".STRING_PUB", propRegs.get(1).getCanonicalPropertyName());
+ assertEquals(fullName(pkg, classSimpleName) + ".PC.STRING", propRegs.get(2).getCanonicalPropertyName());
+ assertEquals(fullName(pkg, classSimpleName) + ".PC.STRING_PUB", propRegs.get(3).getCanonicalPropertyName());
+ assertEquals(fullName(pkg, classSimpleName) + ".PC.PC_PC.STRING", propRegs.get(4).getCanonicalPropertyName());
+ assertEquals(fullName(pkg, classSimpleName) + ".PC.PC_PI.STRING", propRegs.get(5).getCanonicalPropertyName());
+
+ assertEquals(fullName(pkg, classSimpleName) + ".PI.STRING", propRegs.get(6).getCanonicalPropertyName());
+ assertEquals(fullName(pkg, classSimpleName) + ".PI.PI_DC.STRING", propRegs.get(7).getCanonicalPropertyName());
+ assertEquals(fullName(pkg, classSimpleName) + ".PI.PI_DC.STRING_PUB", propRegs.get(8).getCanonicalPropertyName());
+ assertEquals(fullName(pkg, classSimpleName) + ".PI.PI_DI.STRING", propRegs.get(9).getCanonicalPropertyName());
+ assertEquals(fullName(pkg, classSimpleName) + ".PI.PI_DI.STRING_PUB", propRegs.get(10).getCanonicalPropertyName());
+
+ //
+ //Test the registration file
+ assertNotNull(genSvsFile);
+ assertEquals(genName(pkg, classSimpleName), genSvsFile.trim());
+ }
+
+
+ @Test
+ public void testSimpleHappyPathClass() throws Exception {
+
+ String classSimpleName = "HappyPathProps";
+
+ List options=new ArrayList();
+ //options.add("-verbose");
+
+ sources.add(buildTestSource(pkg, classSimpleName));
+
+ JavaCompiler.CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, sources);
+ task.setProcessors(Collections.singleton(new AndHowCompileProcessor()));
+ task.call();
+
+ Object genClass = loader.loadClass(genName(pkg, classSimpleName)).newInstance();
+ String genSvsFile = IOUtil.toString(
+ loader.getResourceAsStream(REGISTRAR_SVS_PATH), Charset.forName("UTF-8"));
+
+ assertEquals(0,
+ diagnostics.getDiagnostics().stream().filter(
+ d -> d.getKind().equals(Diagnostic.Kind.ERROR) || d.getKind().equals(Diagnostic.Kind.WARNING)
+ ).count(),
+ "Should be no warn/errors");
+
+ assertNotNull(genClass);
+
+ PropertyRegistrar registrar = (PropertyRegistrar)genClass;
+
+ //final String ROOT = "org.yarnandtail.andhow.compile.PropertySample";
+
+ assertEquals(fullName(pkg, classSimpleName), registrar.getRootCanonicalName());
+ List propRegs = registrar.getRegistrationList();
+
+ assertEquals(fullName(pkg, classSimpleName) + ".STR_1", propRegs.get(0).getCanonicalPropertyName());
+ assertEquals(fullName(pkg, classSimpleName) + ".STR_2", propRegs.get(1).getCanonicalPropertyName());
+ assertEquals(fullName(pkg, classSimpleName) + ".INNER.STR_1", propRegs.get(2).getCanonicalPropertyName());
+ assertEquals(fullName(pkg, classSimpleName) + ".INNER.STR_2", propRegs.get(3).getCanonicalPropertyName());
+
+ //
+ //Test the registration file
+ assertNotNull(genSvsFile);
+ assertEquals(genName(pkg, classSimpleName), genSvsFile.trim());
+ }
+
+
+ @Test
+ public void testMissingStaticAndFinalModifiersOnProperties() throws Exception {
+
+ List options=new ArrayList();
+ //options.add("-verbose");
+
+ sources.add(buildTestSource(pkg, "BadProps_1"));
+ sources.add(buildTestSource(pkg, "BadProps_2"));
+
+ JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, sources);
+ task.setProcessors(Collections.singleton(new AndHowCompileProcessor()));
+
+ try {
+ task.call();
+ fail("This should have thrown an exception");
+ } catch (RuntimeException e) {
+
+ assertTrue(e.getCause() instanceof AndHowCompileException);
+
+ AndHowCompileException ace = (AndHowCompileException) e.getCause();
+
+ String CLASSNAME = "org.yarnandtail.andhow.compile.BadProps_1";
+ String INNER_CLASSNAME = CLASSNAME + "$INNER_CLASS";
+
+ //Expected CompileProblems
+ //All problems for both classes should be reported in one exception
+ CompileProblem prob1 = new PropMissingFinal(CLASSNAME, "STR_1");
+ CompileProblem prob2 = new PropMissingStatic(CLASSNAME, "STR_2");
+ CompileProblem prob3 = new PropMissingStaticFinal(CLASSNAME, "STR_3");
+ CompileProblem prob4 = new PropMissingFinal(INNER_CLASSNAME, "STR_1");
+ CompileProblem prob5 = new PropMissingStatic(INNER_CLASSNAME, "STR_2");
+ CompileProblem prob6 = new PropMissingStaticFinal(INNER_CLASSNAME, "STR_3");
+
+ //In 2nd class
+ CompileProblem prob7 = new PropMissingFinal(
+ "org.yarnandtail.andhow.compile.BadProps_2", "STR_1");
+
+ assertEquals(7, ace.getProblems().size());
+ assertTrue(ace.getProblems().contains(prob1));
+ assertTrue(ace.getProblems().contains(prob2));
+ assertTrue(ace.getProblems().contains(prob3));
+ assertTrue(ace.getProblems().contains(prob4));
+ assertTrue(ace.getProblems().contains(prob5));
+ assertTrue(ace.getProblems().contains(prob6));
+ assertTrue(ace.getProblems().contains(prob7));
+
+ } catch (Throwable t) {
+ fail("This should have thrown an AndHowCompileException");
+ }
+
+ }
+
+
+}
diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileProblemTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileProblemTest.java
new file mode 100644
index 00000000..2fa41e18
--- /dev/null
+++ b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileProblemTest.java
@@ -0,0 +1,135 @@
+/*
+ */
+package org.yarnandtail.andhow.compile;
+
+import java.util.ArrayList;
+import javax.lang.model.element.Element;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.yarnandtail.andhow.compile.CompileProblem.*;
+import static org.mockito.Mockito.*;
+import org.yarnandtail.andhow.compile.AndHowCompileProcessor.CauseEffect;
+
+/**
+ *
+ * @author ericeverman
+ */
+public class CompileProblemTest {
+
+ public CompileProblemTest() {
+ }
+
+
+ @Test
+ public void testBasicPropsOfPropMissingStatic() {
+ PropMissingStatic prob1 = new PropMissingStatic("g1", "p1");
+ PropMissingStatic prob2 = new PropMissingStatic("g1", "p1");
+ PropMissingStatic prob3 = new PropMissingStatic("Xg1", "Xp1");
+
+ assertTrue(prob1.isPropertyProblem());
+ assertEquals("g1", prob1.getGroupName());
+ assertEquals("p1", prob1.getPropertyName());
+ assertTrue(prob1.getFullMessage().contains("g1"));
+ assertTrue(prob1.getFullMessage().contains("p1"));
+
+ assertEquals(prob1, prob2);
+ assertNotEquals(prob1, prob3);
+ assertNotEquals(prob2, prob3);
+ }
+
+ @Test
+ public void testBasicPropsOfPropMissingFinal() {
+ PropMissingFinal prob1 = new PropMissingFinal("g1", "p1");
+ PropMissingFinal prob2 = new PropMissingFinal("g1", "p1");
+ PropMissingFinal prob3 = new PropMissingFinal("Xg1", "Xp1");
+
+ assertTrue(prob1.isPropertyProblem());
+ assertEquals("g1", prob1.getGroupName());
+ assertEquals("p1", prob1.getPropertyName());
+ assertTrue(prob1.getFullMessage().contains("g1"));
+ assertTrue(prob1.getFullMessage().contains("p1"));
+
+ assertEquals(prob1, prob2);
+ assertNotEquals(prob1, prob3);
+ assertNotEquals(prob2, prob3);
+ }
+
+ @Test
+ public void testBasicPropsOfPropMissingStaticFinal() {
+ PropMissingStaticFinal prob1 = new PropMissingStaticFinal("g1", "p1");
+ PropMissingStaticFinal prob2 = new PropMissingStaticFinal("g1", "p1");
+ PropMissingStaticFinal prob3 = new PropMissingStaticFinal("Xg1", "Xp1");
+
+ assertTrue(prob1.isPropertyProblem());
+ assertEquals("g1", prob1.getGroupName());
+ assertEquals("p1", prob1.getPropertyName());
+ assertTrue(prob1.getFullMessage().contains("g1"));
+ assertTrue(prob1.getFullMessage().contains("p1"));
+
+ assertEquals(prob1, prob2);
+ assertNotEquals(prob1, prob3);
+ assertNotEquals(prob2, prob3);
+ }
+
+ @Test
+ public void testTooManyInitClasses() {
+ Element element1 = mock(Element.class);
+ Element element2 = mock(Element.class);
+
+ CauseEffect ce1 = new CauseEffect("org.MyClass1", element1);
+ CauseEffect ce2 = new CauseEffect("org.MyClass2", element2);
+ ArrayList ces1 = new ArrayList();
+ ces1.add(ce1);
+ ces1.add(ce2);
+
+ final String INIT_NAME = "SOME_INTERFACE_NAME";
+
+ TooManyInitClasses tmi1a = new TooManyInitClasses(INIT_NAME, ces1);
+ TooManyInitClasses tmi1null = new TooManyInitClasses(INIT_NAME, null);
+
+ assertFalse(tmi1a.isPropertyProblem());
+ assertEquals(INIT_NAME, tmi1a.getInitClassName());
+ assertTrue(tmi1a.getFullMessage().contains(INIT_NAME));
+ assertTrue(tmi1a.getFullMessage().contains(ce1.fullClassName));
+ assertTrue(tmi1a.getFullMessage().contains(ce2.fullClassName));
+ assertEquals(2, tmi1a.getInstanceNames().size());
+ assertTrue(tmi1a.getInstanceNames().contains(ce1.fullClassName));
+ assertTrue(tmi1a.getInstanceNames().contains(ce2.fullClassName));
+ assertEquals(0, tmi1null.getInstanceNames().size());
+
+ TooManyInitClasses tmi1b = new TooManyInitClasses(INIT_NAME, ces1);
+
+ ArrayList ces2 = new ArrayList();
+ ces2.add(ce1);
+ TooManyInitClasses tmi2 = new TooManyInitClasses(INIT_NAME, ces2);
+
+ assertEquals(tmi1a, tmi1b);
+ assertEquals(tmi1b, tmi1a);
+ assertNotEquals(tmi1a, tmi1null);
+ assertNotEquals(tmi1a, null);
+ assertNotEquals(tmi1a, new PropMissingFinal("g1", "p1"));
+ assertNotEquals(tmi1a, tmi2);
+ assertNotEquals(tmi2, tmi1a);
+ }
+
+
+ @Test
+ public void testEqualsAcrossTypes() {
+ Integer i1 = 1;
+ CompileProblem prob1 = new PropMissingStatic("g1", "p1");
+ CompileProblem prob2 = new PropMissingFinal("g1", "p1");
+ CompileProblem prob3 = new PropMissingStaticFinal("g1", "p1");
+
+ //Just checking
+ assertEquals(prob1, prob1);
+ assertEquals(prob2, prob2);
+ assertEquals(prob3, prob3);
+
+ assertNotEquals(prob1, null);
+ assertNotEquals(prob1, i1);
+ assertNotEquals(prob1, prob2);
+ assertNotEquals(prob1, prob3);
+ assertNotEquals(prob2, prob3);
+ }
+
+}
diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileUnitTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileUnitTest.java
index 2e474f9a..cbde599d 100644
--- a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileUnitTest.java
+++ b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileUnitTest.java
@@ -1,9 +1,9 @@
package org.yarnandtail.andhow.compile;
import org.yarnandtail.andhow.service.PropertyRegistrationList;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
/**
*
@@ -32,12 +32,12 @@ public void testHappyPath() {
CompileUnit cu = new CompileUnit(ROOT_QUAL_NAME);
//root prop
- cu.addProperty(new SimpleVariable(PROP1_NAME, true, true)); //0
+ cu.addProperty(PROP1_NAME, true, true); //0
{
//1st inner class
cu.pushType(INNER1_SIMP_NAME, true);
- cu.addProperty(new SimpleVariable(PROP1_NAME, true, true)); //1
+ cu.addProperty(PROP1_NAME, true, true); //1
{
//2nd inner class
@@ -51,8 +51,8 @@ public void testHappyPath() {
assertEquals(INNER2_SIMP_NAME, cu.getInnerPathNames().get(1));
//
- cu.addProperty(new SimpleVariable(PROP1_NAME, true, true)); //2
- cu.addProperty(new SimpleVariable(PROP2_NAME, true, true)); //3
+ cu.addProperty(PROP1_NAME, true, true); //2
+ cu.addProperty(PROP2_NAME, true, true); //3
cu.popType();
//Check we have the right inner path remaining
@@ -61,12 +61,12 @@ public void testHappyPath() {
}
//one more at 1st inner level
- cu.addProperty(new SimpleVariable(PROP2_NAME, true, true)); //4
+ cu.addProperty(PROP2_NAME, true, true); //4
cu.popType();
}
//one more at root level
- cu.addProperty(new SimpleVariable(PROP2_NAME, true, true)); //5
+ cu.addProperty(PROP2_NAME, true, true); //5
PropertyRegistrationList list = cu.getRegistrations();
diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileUtilTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileUtilTest.java
new file mode 100644
index 00000000..4d0c64bf
--- /dev/null
+++ b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileUtilTest.java
@@ -0,0 +1,47 @@
+package org.yarnandtail.andhow.compile;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class CompileUtilTest {
+
+ @Test
+ public void getGeneratedAnnotationClassNameTest() {
+ assertEquals("javax.annotation.Generated", CompileUtil.getGeneratedAnnotationClassName(8));
+ assertEquals("javax.annotation.processing.Generated", CompileUtil.getGeneratedAnnotationClassName(9));
+ }
+
+ @Test
+ public void getMajorJavaVersionHappyTest() {
+
+ assertEquals(8, CompileUtil.getMajorJavaVersion("1.8"));
+ assertEquals(8, CompileUtil.getMajorJavaVersion("1.8.434-be"));
+ assertEquals(8, CompileUtil.getMajorJavaVersion("1.8.0_152"));
+
+ assertEquals(9, CompileUtil.getMajorJavaVersion("9"));
+ assertEquals(9, CompileUtil.getMajorJavaVersion("9.0.1"));
+
+ //Weird stuff still ok
+ assertEquals(9, CompileUtil.getMajorJavaVersion("9-prerelease"));
+ assertEquals(9, CompileUtil.getMajorJavaVersion("9_prerelease"));
+
+ assertEquals(11, CompileUtil.getMajorJavaVersion("11"));
+ assertEquals(11, CompileUtil.getMajorJavaVersion("11.0"));
+ assertEquals(11, CompileUtil.getMajorJavaVersion("11.1"));
+ assertEquals(11, CompileUtil.getMajorJavaVersion("11.0.1"));
+ assertEquals(11, CompileUtil.getMajorJavaVersion("11.1-b53"));
+ }
+
+ @Test
+ public void getMajorJavaVersionExceptionTest() {
+
+ try {
+ CompileUtil.getMajorJavaVersion("WeirdVersion");
+ fail("This should have thrown an exception");
+ } catch (Exception e) {
+ //Expected
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/PropertyRegistrarClassGeneratorTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/PropertyRegistrarClassGeneratorTest.java
index fb8cf10b..9f845aa3 100644
--- a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/PropertyRegistrarClassGeneratorTest.java
+++ b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/PropertyRegistrarClassGeneratorTest.java
@@ -3,13 +3,13 @@
import com.google.testing.compile.Compilation;
import com.google.testing.compile.JavaFileObjects;
import java.util.*;
-import javax.tools.JavaFileObject;
-import org.junit.Test;
-import org.junit.Before;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import static com.google.testing.compile.CompilationSubject.assertThat;
import static com.google.testing.compile.Compiler.javac;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
/**
*
@@ -28,7 +28,7 @@ public class PropertyRegistrarClassGeneratorTest {
private GregorianCalendar runDate;
- @Before
+ @BeforeEach
public void initEach() {
runDate = new GregorianCalendar();
//runDate.setTimeZone(TimeZone.getTimeZone(ZoneId.of("GMT-6")));
@@ -143,7 +143,7 @@ public void testBuildRegistrationAddsString_Complex() {
assertEquals("list.add(\"" + PROP1_NAME + "\", \"" + INNER1_SIMP_NAME + "\", \"" + INNER2_SIMP_NAME + "\");", eachAdds[3]);
assertEquals("list.add(\"" + PROP2_NAME + "\");", eachAdds[4]); //No inner path b/c in inherits from above
}
-
+
/**
* Basic gross test that the generated source is compilable
*/
@@ -160,6 +160,17 @@ public void testGenerate_simple() throws Exception {
assertThat(compilation).succeeded();
+ //Check the source file
+
+ //This method is separately tested, so lets trust it works correctly
+ int javaVersion = CompileUtil.getMajorJavaVersion(System.getProperty("java.version"));
+
+ if (javaVersion < 9) {
+ assertTrue(sourceStr.contains("@javax.annotation.Generated("));
+ } else {
+ assertTrue(sourceStr.contains("@javax.annotation.processing.Generated("));
+ }
+
}
/**
@@ -204,7 +215,7 @@ public CompileUnit simpleCompileUnit() {
CompileUnit cu = new CompileUnit(ROOT_QUAL_NAME);
//root prop
- cu.addProperty(new SimpleVariable(PROP1_NAME, true, true));
+ cu.addProperty(PROP1_NAME, true, true);
cu.addProperty(PROP2_NAME, true, true);
return cu;
@@ -215,7 +226,7 @@ public CompileUnit simpleCompileUnit_DefaultPkg() {
CompileUnit cu = new CompileUnit(ROOT_SIMPLE_NAME);
//root prop
- cu.addProperty(new SimpleVariable(PROP1_NAME, true, true));
+ cu.addProperty(PROP1_NAME, true, true);
cu.addProperty(PROP2_NAME, true, true);
return cu;
@@ -229,18 +240,18 @@ public CompileUnit complexCompileUnit() {
CompileUnit cu = new CompileUnit(ROOT_QUAL_NAME);
//root prop
- cu.addProperty(new SimpleVariable(PROP1_NAME, true, true));
+ cu.addProperty(PROP1_NAME, true, true);
{
//1st inner class
cu.pushType(INNER1_SIMP_NAME, true);
- cu.addProperty(new SimpleVariable(PROP1_NAME, true, true));
+ cu.addProperty(PROP1_NAME, true, true);
{
//2nd inner class
cu.pushType(INNER2_SIMP_NAME, true);
- cu.addProperty(new SimpleVariable(PROP1_NAME, true, true));
- cu.addProperty(new SimpleVariable(PROP2_NAME, true, true));
+ cu.addProperty(PROP1_NAME, true, true);
+ cu.addProperty(PROP2_NAME, true, true);
cu.popType();
}
diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/__PropertySample_PropertyRegistrationForClassTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/__PropertySample_PropertyRegistrationForClassTest.java
index af159b8b..ca8008e8 100644
--- a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/__PropertySample_PropertyRegistrationForClassTest.java
+++ b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/__PropertySample_PropertyRegistrationForClassTest.java
@@ -3,9 +3,9 @@
import org.yarnandtail.andhow.service.PropertyRegistrar;
import org.yarnandtail.andhow.service.PropertyRegistration;
import java.util.List;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
/**
*
diff --git a/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/BadProps_1.java b/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/BadProps_1.java
new file mode 100644
index 00000000..56cc8efa
--- /dev/null
+++ b/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/BadProps_1.java
@@ -0,0 +1,27 @@
+package org.yarnandtail.andhow.compile;
+
+import org.yarnandtail.andhow.property.StrProp;
+
+/**
+ * A class that should have compile problems
+ *
+ * @author ericeverman
+ */
+public class BadProps_1 {
+
+ private static StrProp STR_1 = StrProp.builder().build(); //---Should be final!!!
+ public final StrProp STR_2 = StrProp.builder().build(); //---Should be static!!!
+ StrProp STR_3 = StrProp.builder().build(); //---Should be static final!!!
+
+ private static interface INNER {
+ StrProp STR_1 = StrProp.builder().build(); //static final is assumed
+ static final StrProp STR_2 = StrProp.builder().build();
+ }
+
+ private static class INNER_CLASS {
+ private static StrProp STR_1 = StrProp.builder().build(); //---Should be final!!!
+ public final StrProp STR_2 = StrProp.builder().build(); //---Should be static!!!
+ StrProp STR_3 = StrProp.builder().build(); //---Should be static final!!!
+ }
+
+}
diff --git a/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/BadProps_2.java b/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/BadProps_2.java
new file mode 100644
index 00000000..0fe8e6fa
--- /dev/null
+++ b/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/BadProps_2.java
@@ -0,0 +1,14 @@
+package org.yarnandtail.andhow.compile;
+
+import org.yarnandtail.andhow.property.StrProp;
+
+/**
+ * A class that should have compile problems
+ *
+ * @author ericeverman
+ */
+public class BadProps_2 {
+
+ private static StrProp STR_1 = StrProp.builder().build(); //---Should be final!!!
+
+}
diff --git a/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/HappyPathProps.java b/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/HappyPathProps.java
new file mode 100644
index 00000000..c8c4349a
--- /dev/null
+++ b/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/HappyPathProps.java
@@ -0,0 +1,21 @@
+package org.yarnandtail.andhow.compile;
+
+import org.yarnandtail.andhow.property.StrProp;
+
+/**
+ * A class that should compile just fine w/ AndHow's Annotation compiler
+ *
+ * @author ericeverman
+ */
+public class HappyPathProps {
+
+ private static final StrProp STR_1 = StrProp.builder().build();
+ public static final StrProp STR_2 = StrProp.builder().build();
+
+ private static interface INNER {
+
+ static final StrProp STR_1 = StrProp.builder().build();
+ static final StrProp STR_2 = StrProp.builder().build();
+ }
+
+}
diff --git a/andhow-core/pom.xml b/andhow-core/pom.xml
index 74647c66..7dd5188b 100644
--- a/andhow-core/pom.xml
+++ b/andhow-core/pom.xml
@@ -3,7 +3,7 @@
org.yarnandtailandhow-parent
- 0.4.1-SNAPSHOT
+ 0.4.2-SNAPSHOTandhow-core
@@ -14,12 +14,7 @@
-
-
- junit
- junit
- org.springframeworkspring-test
@@ -29,5 +24,4 @@
commons-io
-
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/AndHow.java b/andhow-core/src/main/java/org/yarnandtail/andhow/AndHow.java
index 9de90cbd..ed1390cb 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/AndHow.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/AndHow.java
@@ -2,8 +2,10 @@
import java.lang.reflect.Field;
import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.yarnandtail.andhow.api.*;
import org.yarnandtail.andhow.internal.AndHowCore;
+import org.yarnandtail.andhow.internal.ConstructionProblem;
import org.yarnandtail.andhow.util.AndHowUtil;
/**
@@ -41,9 +43,19 @@ public class AndHow implements StaticPropertyConfiguration, ValidatedValues {
private static final Object LOCK = new Object();
private volatile AndHowCore core;
+
+ /** Stack trace and time of startup */
+ private static volatile Initialization initialization;
+
+ /**
+ * True only during the instance(AndHowConfiguration) method to detect
+ * re-entrant initialization
+ */
+ private static AtomicBoolean initializing = new AtomicBoolean(false);
private AndHow(AndHowConfiguration config) throws AppFatalException {
synchronized (LOCK) {
+
core = new AndHowCore(
config.getNamingStrategy(),
config.buildLoaders(),
@@ -117,34 +129,67 @@ public static AndHow instance(AndHowConfiguration config) throws AppFatalExcepti
} else {
if (singleInstance == null) {
-
- singleInstance = new AndHow(config);
-
+
+ if (! initializing.get()) {
+
+ try {
+
+ initializing.getAndSet(true); //Block re-entrant initialization
+ initialization = new Initialization(); //Record initialization time & place
+ singleInstance = new AndHow(config); //Build new instance
+
+ } finally {
+ initializing.getAndSet(false); //Done w/ init regardless of possible error
+ }
+
+ } else {
+
+ throw new AppFatalException(
+ new ConstructionProblem.InitiationLoopException(initialization, new Initialization()));
+ }
+
} else if (singleInstance.core == null) {
/* This is a concession for testing. During testing the
core is deleted to force AndHow to reload. Its really an
invalid state (instance and core should be null/non-null
together, but its handled here to simplify testing. */
- try {
-
- AndHowCore newCore = new AndHowCore(
- config.getNamingStrategy(),
- config.buildLoaders(),
- config.getRegisteredGroups());
- Field coreField = AndHow.class.getDeclaredField("core");
- coreField.setAccessible(true);
- coreField.set(singleInstance, newCore);
-
- } catch (Exception ex) {
- if (ex instanceof AppFatalException) {
- throw (AppFatalException) ex;
- } else {
- throwFatal("", ex);
+
+ if (! initializing.get()) {
+
+ try {
+
+ initializing.getAndSet(true); //Block re-entrant initialization
+ initialization = new Initialization(); //Record initialization time & place
+
+ AndHowCore newCore = new AndHowCore(
+ config.getNamingStrategy(),
+ config.buildLoaders(),
+ config.getRegisteredGroups());
+ Field coreField = AndHow.class.getDeclaredField("core");
+ coreField.setAccessible(true);
+ coreField.set(singleInstance, newCore);
+
+ } catch (Exception ex) {
+
+ if (ex instanceof AppFatalException) {
+ throw (AppFatalException) ex;
+ } else {
+ throwFatal("", ex);
+ }
+ } finally {
+ initializing.getAndSet(false); //Done w/ init regardless of possible error
}
+
+ } else {
+
+ throw new AppFatalException(
+ new ConstructionProblem.InitiationLoopException(initialization, new Initialization()));
+
}
}
+
return singleInstance;
}
@@ -160,6 +205,28 @@ invalid state (instance and core should be null/non-null
public static boolean isInitialize() {
return singleInstance != null && singleInstance.core != null;
}
+
+ /**
+ * Get the stacktrace of where AndHow was initialized.
+ *
+ * This can be useful for debugging InitiationLoopException errors or
+ * errors caused by trying to initialize AndHow when it is already initialized.
+ * This stacktrace identifies the point in code that caused the initial
+ * AndHow initialization, prior to the error. The reported exception will
+ * point to the place where AndHow entered a loop during its construction
+ * or application code attempted to re-initialize AndHow.
+ *
+ * @return A stacktrace if it is available (some JVMs may not provide one)
+ * or an empty stacktrace array if it is not available or AndHow is not
+ * yet initialized.
+ */
+ public static StackTraceElement[] getInitializationTrace() {
+ if (initialization != null) {
+ return initialization.getStackTrace();
+ } else {
+ return new StackTraceElement[0];
+ }
+ }
//
//PropertyValues Interface
@@ -219,5 +286,31 @@ private static void throwFatal(String message, Throwable throwable) {
throw afe;
}
}
+
+
+ /**
+ * Encapsilate when and where AndHow was initialized.
+ *
+ * Useful for debugging re-entrant startups or uncontrolled startup conditions.
+ */
+ public static class Initialization {
+ private StackTraceElement[] stackTrace;
+ private long timeStamp;
+
+ public Initialization() {
+ timeStamp = System.currentTimeMillis();
+ StackTraceElement[] ste = new Exception().getStackTrace();
+ stackTrace = Arrays.copyOfRange(ste, 1, ste.length - 1);
+ }
+
+ public StackTraceElement[] getStackTrace() {
+ return stackTrace;
+ }
+
+ public long getTimeStamp() {
+ return timeStamp;
+ }
+
+ }
}
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/AndHowConfiguration.java b/andhow-core/src/main/java/org/yarnandtail/andhow/AndHowConfiguration.java
index c8dfa6dc..8c74ddee 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/AndHowConfiguration.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/AndHowConfiguration.java
@@ -2,6 +2,7 @@
import java.util.List;
import org.yarnandtail.andhow.api.*;
+import org.yarnandtail.andhow.property.StrProp;
/**
*
@@ -30,6 +31,10 @@ public interface AndHowConfiguration {
* prior to any other loader. Since the first loaded value for a Property
* 'wins', this effectively fixes the value and makes it non-configurable.
*
+ * Values specified by the two addFixedValue methods will
+ * through a DuplicatePropertyLoaderProblem if they refer to
+ * the same Property.
+ *
* @param The type of Property and value
* @param property The property to set a value for
* @param value The value to set.
@@ -38,15 +43,216 @@ public interface AndHowConfiguration {
C addFixedValue(Property property, T value);
/**
- * Removes a PropertyValue from the list of fixed values.
+ * Removes a Property value set only via addFixedValue(Property, T value)
+ * from the list of fixed values.
*
- * It is not an error to attempt to remove a property that is not in the
- * current fixed value list.
+ * It is not an error to attempt to remove a property that is not in this fixed value list.
*
* @param property A non-null property.
* @return
*/
C removeFixedValue(Property> property);
-
+
+ /**
+ * Sets a fixed, non-configurable value for a named Property.
+ *
+ * Property values set in this way use the FixedValueLoader to load values
+ * prior to any other loader. Since the first loaded value for a Property
+ * 'wins', this effectively fixes the value and makes it non-configurable.
+ *
+ * Values specified by the two addFixedValue methods will
+ * through a DuplicatePropertyLoaderProblem if they refer to
+ * the same Property.
+ *
+ * @param name The canonical or alias name of Property, which is trimmed to null.
+ * @param value The Object value to set, which must match the type of the Property.
+ * @return
+ */
+ C addFixedValue(String name, Object value);
+
+ /**
+ * Removes a Property value set only via addFixedValue(String name, Object value)
+ * from the list of fixed values.
+ *
+ * Note that to successfully remove a fixed value from this list, the name must exactly
+ * match the name used to set the property via addFixedValue(String, Object). Since
+ * Properties can have aliases, you must know the exact name to set the property.
+ *
+ * It is not an error to attempt to remove a property that is not in this fixed value list,
+ * or to attempt to remove a property value that does not exist - these are just no-ops.
+ *
+ * @param propertyNameOrAlias The name or alias of a property.
+ * @return
+ */
+ C removeFixedValue(String propertyNameOrAlias);
+
+ /**
+ * Sets the classpath path to a properties file for the
+ * {@Code StdPropFileOnClasspathLoader} to read and load from.
+ *
+ * If no path is specified via one of the two {@Code setClasspathPropFilePath} methods,
+ * the default classpath of '/andhow.properties' is used.
+ *
+ * As per Java convention, a path on the classpath can use dots or slashes to separate packages.
+ * However, if the file name itself contains dots, then the path must start with a slash and use
+ * slashes to separate packages.
+ *
+ * Valid Examples:
+ *
+ *
/andhow.property - The default. The file is at the root and the name contains a dot
+ *
/org/ngo/config.props - Similar to above, but in the org.ngo package, the file name is 'config.props'
+ *
org.ngo.props - The package is org.ngo, the file name is 'props'.
+ * There are no dots in the file name, so its OK to use dots to separate the packages
+ *
+ *
+ * @param classpathPropFilePathString
+ * @return
+ */
+ C setClasspathPropFilePath(String classpathPropFilePathString);
+
+ /**
+ * Sets the classpath path via a StrProp (a Property of String type) to a
+ * properties file for the {@Code StdPropFileOnClasspathLoader} to read and load from.
+ *
+ * If no path is specified via one of the two {@Code setClasspathPropFilePath} methods,
+ * the default classpath of '/andhow.properties' is used.
+ *
+ * As per Java convention, a path on the classpath can use dots or slashes to separate packages.
+ * However, if the file name itself contains dots, then the path must start with a slash and use
+ * slashes to separate packages. Its common to have a '.properties' extension on the properties
+ * file, so its good practice to add a validation rule to the StrProp used here to ensure it
+ * {@Code mustStartWith("/")}.
+ *
+ * Valid Examples of configured values:
+ *
+ *
/andhow.property - The default. The file is at the root and the name contains a dot
+ *
/org/ngo/config.props - Similar to above, but in the org.ngo package, the file name is 'config.props'
+ *
org.ngo.props - The package is org.ngo, the file name is 'props'.
+ * There are no dots in the file name, so its OK to use dots to separate the packages
+ *
+ *
+ * @param classpathPropFilePathProperty
+ * @return
+ */
+ C setClasspathPropFilePath(StrProp classpathPropFilePathProperty);
+
+ /**
+ * If called to set this to 'required', a classpath properties file must
+ * exist and be readable. This flag is used by the {@Code StdPropFileOnClasspathLoader}.
+ *
+ * Since the {@Code StdPropFileOnClasspathLoader} has a default property file name,
+ * {@Code /andhow.properties}, setting this to 'required' means that either that
+ * default file name or another that you configure instead must exist.
+ *
+ * @See setClasspathPropFilePath methods for details on using a non-default
+ * classpath properties file.
+ *
+ * A RuntimeException will be thrown if this is set to 'required' and there
+ * is no classpath properties file that can be read.
+ *
+ * This is NOT set by default, allowing the properties file to be optional.
+ *
+ * @return
+ */
+ C classpathPropertiesRequired();
+
+ /**
+ * Sets the properties file on the classpath to be optional, the default.
+ *
+ * @See classpathPropertiesRequired
+ *
+ * @return
+ */
+ C classpathPropertiesNotRequired();
+
+ /**
+ * Sets the filesystem path via a StrProp (a Property of String type) to a
+ * properties file for the StdPropFileOnFilesystemLoader to load.
+ *
+ * If no property is set to specify a path, or a property is set by has no
+ * value, this loader won't be used. If the property is specified but the
+ * specified file is missing, an error will be thrown based on the
+ * filesystemPropFileRequired flag.
+ *
+ * Paths should generally be absolute and correctly formed for the host
+ * environment.
+ *
+ * @param filesystemPropFilePath
+ * @return
+ */
+ C setFilesystemPropFilePath(StrProp filesystemPropFilePath);
+
+ /**
+ * If called to set this to 'required', a non-null configured value for the
+ * filesystem properties file must point to an existing, readable properties
+ * file. This flag is used by the {@Code StdPropFileOnFilesystemLoader}.
+ *
+ * A RuntimeException will be thrown if this is set to 'required' and there
+ * is a path specified which points to a file that does not exist.
+ * Configuring a filesystem path is a two step process:
+ *
First, a StrProp Property must be specified for this configuration
+ * via the {@Code setFilesystemPropFilePath} method
+ *
Then, a value must be configured for in an any way that AndHow
+ * reads and loads values, such as environment vars, system properties, etc..
+ *
+ * If and non-null value is configured, its doesn't point to a readable properties
+ * file, AND this required flag is set, a RuntimeException will be thrown at startup.
+ *
+ * This is NOT set by default, allowing the properties file to be optional.
+ *
+ * @return
+ */
+ C filesystemPropFileRequired();
+
+ /**
+ * Sets the properties file on the filesystem to be optional, the default.
+ *
+ * @See setFilesystemPropFilePath
+ *
+ * @return
+ */
+ C filesystemPropFileNotRequired();
+
+ //
+ //Loader related
+
+ /**
+ * The default list of standard loaders, as a list of Classes that implement
+ * {@Code StandardLoader}
+ *
+ * The returned list is disconnected from the actual list of loaders - it is
+ * intended to be a starting point for applications that want to modify the
+ * list, then call setStandardLoaders().
+ *
+ * Unlike other methods of this class, it does not fluently return a method
+ * to itself, so your code will need a AndHowConfiguration instance reference to
+ * use it, eg:
+ *
{@Code
+ * public class MyAppInitiation implements AndHowInit {
+ * @Override
+ * public AndHowConfiguration getConfiguration() {
+ * AndHowConfiguration config = AndHow.findConfig();
+ * List> sll = config.getDefaultLoaderList();
+ * ...do some rearranging of the list...
+ *
+ * config.setStandardLoaders(sll) ...and go on to call other methods on config...
+ * }
+ * }
+ * }
+ *
+ * Note: AndHow version up to and including 0.4.1 had this method as a static
+ * method.
+ * @return
+ */
+ List> getDefaultLoaderList();
+
+ C setStandardLoaders(List> newStandardLoaders);
+
+ C setStandardLoaders(Class extends StandardLoader>... newStandardLoaders);
+
+ C insertLoaderBefore(Class extends StandardLoader> insertBeforeThisLoader, Loader loaderToInsert);
+
+ C insertLoaderAfter(Class extends StandardLoader> insertAfterThisLoader, Loader loaderToInsert);
+
void build();
}
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/BaseConfig.java b/andhow-core/src/main/java/org/yarnandtail/andhow/BaseConfig.java
index 227fbf81..92600621 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/BaseConfig.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/BaseConfig.java
@@ -3,6 +3,7 @@
import java.lang.reflect.*;
import java.util.*;
import org.yarnandtail.andhow.api.*;
+import org.yarnandtail.andhow.load.KeyObjectPair;
import org.yarnandtail.andhow.load.std.*;
import org.yarnandtail.andhow.name.CaseInsensitiveNaming;
import org.yarnandtail.andhow.property.StrProp;
@@ -32,9 +33,14 @@ public abstract class BaseConfig> implements AndHowConfi
protected Map, List> insertBefore = new HashMap();
protected Map, List> insertAfter = new HashMap();
- //A list of hardcoded values used by the StdFixedValueLoader
+ //A list of hardcoded values used by the StdFixedValueLoader.
+ //Provided w/ live Property references
protected final List _fixedVals = new ArrayList();
+ //A list of hardcoded values used by the StdFixedValueLoader.
+ //Provided as key name (string) and value (object)
+ protected final List _fixedKeyObjectPairVals = new ArrayList();
+
//A list of command line arguments
protected final List _cmdLineArgs = new ArrayList();
@@ -71,6 +77,7 @@ public NamingStrategy getNamingStrategy() {
protected StdFixedValueLoader buildStdFixedValueLoader() {
StdFixedValueLoader loader = new StdFixedValueLoader();
loader.setPropertyValues(_fixedVals);
+ loader.setKeyObjectPairValues(_fixedKeyObjectPairVals);
return loader;
}
@@ -162,14 +169,9 @@ public List getRegisteredGroups() {
public void build() {
AndHow.instance(this);
}
-
- /**
- * The list of default loaders as a list.
- *
- * This is a disconnected list from any instance of the BaseConfig.
- * @return
- */
- public static List> getDefaultLoaderList() {
+
+ @Override
+ public List> getDefaultLoaderList() {
List> loaders = new ArrayList();
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/StdConfig.java b/andhow-core/src/main/java/org/yarnandtail/andhow/StdConfig.java
index b5c4a10c..d3f0c53e 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/StdConfig.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/StdConfig.java
@@ -2,6 +2,7 @@
import java.util.*;
import org.yarnandtail.andhow.api.*;
+import org.yarnandtail.andhow.load.KeyObjectPair;
import org.yarnandtail.andhow.property.StrProp;
import org.yarnandtail.andhow.util.TextUtil;
@@ -34,6 +35,7 @@ public S addFixedValue(Property property, T value) {
throw new IllegalArgumentException("The property cannot be null");
}
+ //simple check for duplicates doesn't consider KOP values
for (PropertyValue pv : _fixedVals) {
if (property.equals(pv.getProperty())) {
throw new IllegalArgumentException("A fixed value for this property has been assigned twice.");
@@ -48,20 +50,36 @@ public S addFixedValue(Property property, T value) {
@Override
public S removeFixedValue(Property> property) {
+ _fixedVals.removeIf(f -> f.getProperty().equals(property));
+ return (S) this;
+ }
- if (property == null) {
- throw new IllegalArgumentException("The property cannot be null");
- }
+ @Override
+ public S addFixedValue(final String propertyNameOrAlias, final Object value) {
- Iterator it = _fixedVals.iterator();
- while (it.hasNext()) {
- PropertyValue pv = it.next();
- if (property.equals(pv.getProperty())) {
- it.remove();
- break;
+ try {
+ KeyObjectPair kop = new KeyObjectPair(propertyNameOrAlias, value);
+
+ //Simple check for duplicates
+ if (_fixedKeyObjectPairVals.stream().map(k -> k.getName()).anyMatch(n -> n.equals(kop.getName()))) {
+ throw new IllegalArgumentException(
+ "A fixed value for the Property '" + kop.getName() + "' has been assigned twice.");
}
+
+ _fixedKeyObjectPairVals.add(kop);
+
+ return (S) this;
+
+ } catch (ParsingException e) {
+ throw new IllegalArgumentException(e);
}
+ }
+
+ @Override
+ public S removeFixedValue(final String propertyNameOrAlias) {
+ final String cleanName = TextUtil.trimToNull(propertyNameOrAlias);
+ _fixedKeyObjectPairVals.removeIf(k -> k.getName().equals(cleanName));
return (S) this;
}
@@ -75,20 +93,7 @@ public S setCmdLineArgs(String[] commandLineArgs) {
return (S) this;
}
- /**
- * Sets the classpath path to a properties file for the
- * StdPropFileOnClasspathLoader to load.
- *
- * If no path is specified via either a String or StrProp, the path
- * '/andhow.properties' is used.
- *
- * Paths should start with a forward slash and have packages delimited by
- * forward slashes. If the file name contains a dot, the path must
- * start with a forward slash.
- *
- * @param classpathPropFilePathString
- * @return
- */
+ @Override
public S setClasspathPropFilePath(String classpathPropFilePathString) {
classpathPropFilePathString = TextUtil.trimToNull(classpathPropFilePathString);
@@ -98,33 +103,21 @@ public S setClasspathPropFilePath(String classpathPropFilePathString) {
+ "be specified as both a String and StrProp");
}
- if (classpathPropFilePathString != null && !classpathPropFilePathString.startsWith("/")
- && (classpathPropFilePathString.endsWith(".properties") || classpathPropFilePathString.endsWith(".xml"))) {
-
- throw new IllegalArgumentException("The path to the property file on "
- + "the classpath should start with a '/' if the filename contains a dot.");
+ if (
+ classpathPropFilePathString != null &&
+ classpathPropFilePathString.contains(".") &&
+ !classpathPropFilePathString.startsWith("/")
+ ) {
+ throw new IllegalArgumentException("A path to a property file on the classpath "
+ + "must start with a '/' if the filename contains a dot.");
}
+
this.classpathPropFilePathStr = classpathPropFilePathString;
return (S) this;
}
- /**
- * Sets the classpath path via a StrProp (a Property of String type) to a
- * properties file for the StdPropFileOnClasspathLoader to load.
- *
- * If no path is specified via either a String or StrProp, the path
- * '/andhow.properties' is used.
- *
- * Paths should start with a forward slash and have packages delimited by
- * forward slashes. If the file name contains a dot, the path must
- * start with a forward slash. Thus, it is good practice to add a validation
- * rule to the StrProp used here to ensure it
- * mustStartWith("/").
- *
- * @param classpathPropFilePathProperty
- * @return
- */
+ @Override
public S setClasspathPropFilePath(StrProp classpathPropFilePathProperty) {
if (classpathPropFilePathStr != null && classpathPropFilePathProperty != null) {
@@ -137,73 +130,31 @@ public S setClasspathPropFilePath(StrProp classpathPropFilePathProperty) {
return (S) this;
}
- /**
- * If set, the properties file loaded by StdPropFileOnClasspathLoader must
- * be found and a RuntimeException will be thrown if it is not found.
- *
- * This is not set by default, allowing the properties file to be optional.
- *
- * @return
- */
+ @Override
public S classpathPropertiesRequired() {
_missingClasspathPropFileAProblem = true;
return (S) this;
}
- /**
- * If set, the properties file loaded by StdPropFileOnClasspathLoader is
- * optional and will not throw an error if it is not found.
- *
- * This is set by default, so there is no need to explicitly call it.
- *
- * @return
- */
+ @Override
public S classpathPropertiesNotRequired() {
_missingClasspathPropFileAProblem = false;
return (S) this;
}
- /**
- * Sets the filesystem path via a StrProp (a Property of String type) to a
- * properties file for the StdPropFileOnFilesystemLoader to load.
- *
- * If no property is set to specify a path, or a property is set by has no
- * value, this loader won't be used. If the property is specified but the
- * specified file is missing, an error will be thrown based on the
- * filesystemPropFileRequired flag.
- *
- * Paths should generally be absolute and correctly formed for the host
- * environment.
- *
- * @param filesystemPropFilePath
- * @return
- */
+ @Override
public S setFilesystemPropFilePath(StrProp filesystemPropFilePath) {
this.filesystemPropFilePathProp = filesystemPropFilePath;
return (S) this;
}
- /**
- * If set, the properties file loaded by StdPropFileOnFilesystemLoader must
- * be found and a RuntimeException will be thrown if it is not found.
- *
- * This is not set by default, allowing the properties file to be optional.
- *
- * @return
- */
+ @Override
public S filesystemPropFileRequired() {
_missingFilesystemPropFileAProblem = true;
return (S) this;
}
- /**
- * If set, the properties file loaded by StdPropFileOnFilesystemLoader is
- * optional and will not throw an error if it is not found.
- *
- * This is set by default, so there is no need to explicitly call it.
- *
- * @return
- */
+ @Override
public S filesystemPropFileNotRequired() {
_missingFilesystemPropFileAProblem = false;
return (S) this;
@@ -212,6 +163,7 @@ public S filesystemPropFileNotRequired() {
/**
* Allows system properties to be overridden.
*
+ * @deprecated
* @param properties
*/
public S setSystemProperties(Properties properties) {
@@ -220,16 +172,37 @@ public S setSystemProperties(Properties properties) {
}
/**
- * Allows the System environment to be overridden.
+ * Sets the System environment vars that AndHow will use to load Property values
+ * from for the {@Code StdEnvVarLoader} loader.
+ *
+ * If this method is not called or is called with a null Map, the actual env vars
+ * from {@Code System.getenv()} will be used. Calling this method with an empty
+ * Map will effectively prevent AndHow from receiving configuration from env vars.
+ *
+ * This does not actually change actual environment variables or what is
+ * returned from {@Code System.getenv()}. It only replaces what AndHow will see for env vars.
*
- * @param envProperties
+ * Note: There is no reason to use this method: Use one of the {@Code addFixedValue()}
+ * methods instead. Those methods are more clear, don't have to parse values, and
+ * (unlike this method) are not deprecated.
+ *
+ * @deprecated This method will be removed in a future release - it has no meaningful
+ * usage. Use the addFixedValue() methods instead.
+ * @param newEnvProperties
* @return
*/
- public S setEnvironmentProperties(Map envProperties) {
- this.envProperties = envProperties;
+ public S setEnvironmentProperties(Map newEnvProperties) {
+
+ if (newEnvProperties != null) {
+ this.envProperties = new HashMap<>();
+ this.envProperties.putAll(newEnvProperties);
+ } else {
+ this.envProperties = null;
+ }
return (S) this;
}
+ @Override
public S setStandardLoaders(List> newStandardLoaders) {
standardLoaders.clear();
@@ -238,6 +211,7 @@ public S setStandardLoaders(List> newStandardLoa
return (S) this;
}
+ @Override
public S setStandardLoaders(Class extends StandardLoader>... newStandardLoaders) {
standardLoaders.clear();
@@ -249,6 +223,7 @@ public S setStandardLoaders(Class extends StandardLoader>... newStandardLoader
return (S) this;
}
+ @Override
public S insertLoaderBefore(
Class extends StandardLoader> insertBeforeThisLoader, Loader loaderToInsert) {
@@ -263,6 +238,7 @@ public S insertLoaderBefore(
return (S) this;
}
+ @Override
public S insertLoaderAfter(
Class extends StandardLoader> insertAfterThisLoader, Loader loaderToInsert) {
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/api/AppFatalException.java b/andhow-core/src/main/java/org/yarnandtail/andhow/api/AppFatalException.java
index 9e1eb255..5e64c1d9 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/api/AppFatalException.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/api/AppFatalException.java
@@ -24,7 +24,7 @@ public class AppFatalException extends RuntimeException {
*/
public AppFatalException(String message, Throwable throwable) {
super(message, throwable);
- this.problems = new ProblemList();
+ this.problems = ProblemList.EMPTY_PROBLEM_LIST;
}
/**
@@ -34,7 +34,7 @@ public AppFatalException(String message, Throwable throwable) {
*/
public AppFatalException(String message) {
super(message);
- this.problems = new ProblemList();
+ this.problems = ProblemList.EMPTY_PROBLEM_LIST;
}
public AppFatalException(String message, ProblemList problems) {
@@ -58,6 +58,17 @@ public AppFatalException(String message, Problem problem) {
}
}
+ public AppFatalException(Problem problem) {
+ super(problem != null?problem.getFullMessage():"Unknown AndHow fatal exception");
+
+ if (problem != null) {
+ this.problems = new ProblemList();
+ this.problems.add(problem);
+ } else {
+ this.problems = ProblemList.EMPTY_PROBLEM_LIST;
+ }
+ }
+
public ProblemList getProblems() {
return problems;
}
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/api/ValidatedValuesWithContext.java b/andhow-core/src/main/java/org/yarnandtail/andhow/api/ValidatedValuesWithContext.java
index a51b6afc..855e80bc 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/api/ValidatedValuesWithContext.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/api/ValidatedValuesWithContext.java
@@ -3,18 +3,18 @@
import java.util.List;
/**
- * Extention of ValueMap that adds contextual information to the ValueMap.
+ * Extention of ValidatedValues that adds contextual information.
*
- * ValueMap has all the needed info to provide values for Proerties during runtime.
+ * ValidatedValues has all the needed info to provide values for Properties during runtime.
* This class provides more metadata, such as where a value was loaded from, if
* there are Problems encountered during value loading and which values were loaded
* by which Loader, etc..
*
- * During startup, a mutable version of ValueMapWithContext if incrementally loaded
+ * During startup, a mutable version of ValueMapWithContext is incrementally loaded
* with values and reported issues. After loading is complete, values are copied
- * to immutable versions of ValueMap and ValueMapWithContext. ValueMap is used
- * to fetch values as needed, ValueMapWithContext provides metadata on values if
- * needed.
+ * to an immutable ValidatedValues and ValidatedValuesWithContext.
+ * ValidatedValues is used to fetch values as needed, ValidatedValuesWithContext
+ * provides metadata on values if needed.
*
* @author eeverman
*/
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/internal/ConstructionProblem.java b/andhow-core/src/main/java/org/yarnandtail/andhow/internal/ConstructionProblem.java
index f3ce61f4..b0256988 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/internal/ConstructionProblem.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/internal/ConstructionProblem.java
@@ -319,4 +319,39 @@ public String getProblemDescription() {
AndHowInit.class.getCanonicalName(), joined);
}
}
+
+ public static class InitiationLoopException extends ConstructionProblem {
+ AndHow.Initialization originalInit;
+ AndHow.Initialization secondInit;
+
+ public InitiationLoopException(AndHow.Initialization originalInit, AndHow.Initialization secondInit) {
+ this.originalInit = originalInit;
+ this.secondInit = secondInit;
+ }
+
+ public AndHow.Initialization getOriginalInit() {
+ return originalInit;
+ }
+
+ public AndHow.Initialization getSecondInit() {
+ return secondInit;
+ }
+
+
+ @Override
+ public String getProblemDescription() {
+
+ return "AndHow detected a loop during initiation. "
+ + "Likely causes are: " + System.lineSeparator()
+ + "- Multiple places in application code where AndHow.instance(AndHowConfiguration) is called" + System.lineSeparator()
+ + "- static initiation blocks or static variables that are initiated refering to the value of an AndHow Property" + System.lineSeparator()
+ + "- AndHow Properties that refer to the value of other AndHow properties in their construction" + System.lineSeparator()
+ + "::The first line in the stack trace following this error referring to your application code is likely causing the initiation loop::";
+ }
+
+ @Override
+ public String getFullMessage() {
+ return getProblemDescription();
+ }
+ }
}
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/load/BaseLoader.java b/andhow-core/src/main/java/org/yarnandtail/andhow/load/BaseLoader.java
index 4cf3f92f..e27ed15f 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/load/BaseLoader.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/load/BaseLoader.java
@@ -31,7 +31,7 @@ public List getInstanceConfig() {
public SamplePrinter getConfigSamplePrinter() {
return null; //Each implementation needs to provide its own.
}
-
+
/**
* Util method to load a String to a property by name.
*
@@ -45,47 +45,62 @@ public SamplePrinter getConfigSamplePrinter() {
*/
protected void attemptToAdd(StaticPropertyConfigurationInternal appConfigDef, List values,
ProblemList loaderProblems, String key, String strValue) {
-
- key = TextUtil.trimToNull(key);
-
- if (key != null) {
-
- String effKey = appConfigDef.getNamingStrategy().toEffectiveName(key);
-
- Property prop = appConfigDef.getProperty(effKey);
- if (prop != null) {
-
- ValidatedValue pv = null;
-
- try {
- pv = createValue(appConfigDef, prop, strValue);
- } catch (ParsingException e) {
- loaderProblems.add(new LoaderProblem.StringConversionLoaderProblem(
- this, appConfigDef.getGroupForProperty(prop).getProxiedGroup(), prop, e.getProblemText()));
- }
-
- if (pv != null) {
- ValidatedValue dup = findDuplicateProperty(pv, values);
-
- if (dup == null) {
- values.add(pv);
- } else {
- loaderProblems.add(new DuplicatePropertyLoaderProblem(
- this, appConfigDef.getGroupForProperty(prop).getProxiedGroup(), prop));
- }
- }
-
- } else if (this instanceof ReadLoader) {
- ReadLoader rl = (ReadLoader)this;
- if (rl.isUnknownPropertyAProblem()) {
- loaderProblems.add(new UnknownPropertyLoaderProblem(this, key));
- }
+ Property prop = mapNametoProperty(appConfigDef, key);
+
+ if (prop != null) {
+
+ ValidatedValue validatedValue = null;
+
+ try {
+ validatedValue = createValue(appConfigDef, prop, strValue);
+ } catch (ParsingException e) {
+ loaderProblems.add(new LoaderProblem.StringConversionLoaderProblem(
+ this, appConfigDef.getGroupForProperty(prop).getProxiedGroup(), prop, e.getProblemText()));
}
+ attemptToAddIfNotDuplicate(appConfigDef, values, loaderProblems, validatedValue);
+
+ } else if (this instanceof ReadLoader) {
+ ReadLoader rl = (ReadLoader)this;
+ if (rl.isUnknownPropertyAProblem()) {
+ loaderProblems.add(new UnknownPropertyLoaderProblem(this, key));
+ }
}
+
+ }
+
+ /**
+ * Util method to load an Object value to a named Property.
+ *
+ * Intended for code-based loaders where Property names are specified with Object based keys.
+ * This would happen with hardcoded / in-code loaders, likely during testing, where property
+ * values can be specified as real objects, but the Properties themselves may not all be
+ * visible, so names are used instead of Property references.
+ *
+ * @param appConfigDef Used to look up the property name for find the actual property
+ * @param values List of PropertyValues to add to, which should be only the value of this loader.
+ * @param loaderProblems A list of Problems to add to if there is a loader related problem
+ * @param key The property name
+ * @param value The property value as an Object, already of the expected type for the Property.
+ */
+ protected void attemptToAdd(StaticPropertyConfigurationInternal appConfigDef, List values,
+ ProblemList loaderProblems, String key, Object value) {
+
+ Property prop = mapNametoProperty(appConfigDef, key);
+
+ if (prop != null) {
+
+ attemptToAdd(appConfigDef, values, loaderProblems, prop, value);
+
+ } else if (this instanceof ReadLoader) {
+ ReadLoader rl = (ReadLoader)this;
+ if (rl.isUnknownPropertyAProblem()) {
+ loaderProblems.add(new UnknownPropertyLoaderProblem(this, key));
+ }
+ }
+
}
-
/**
* Util method to attempt to load an object of an unknown type to a property.
@@ -98,23 +113,24 @@ protected void attemptToAdd(StaticPropertyConfigurationInternal appConfigDef, Li
* @param values List of PropertyValues to add to, which should be only the value of this loader.
* @param loaderProblems A list of LoaderProblems to add to if there is a loader related problem
* @param prop The Property to load to
- * @param value The Object to be loaded to this property
+ * @param value The Object to be loaded to this property. If a String and that does
+ * not match the Property type, parsing is attempted to convert it.
*/
protected void attemptToAdd(StaticPropertyConfigurationInternal appConfigDef, List values,
ProblemList loaderProblems, Property prop, Object value) {
if (prop != null) {
- ValidatedValue pv = null;
+ ValidatedValue validatedValue = null;
if (value.getClass().equals(prop.getValueType().getDestinationType())) {
- pv = new ValidatedValue(prop, value);
+ validatedValue = new ValidatedValue(prop, value);
} else if (value instanceof String) {
try {
- pv = createValue(appConfigDef, prop, value.toString());
+ validatedValue = createValue(appConfigDef, prop, value.toString());
} catch (ParsingException e) {
loaderProblems.add(new LoaderProblem.StringConversionLoaderProblem(
this, appConfigDef.getGroupForProperty(prop).getProxiedGroup(), prop, e.getProblemText()));
@@ -124,21 +140,38 @@ protected void attemptToAdd(StaticPropertyConfigurationInternal appConfigDef, Li
loaderProblems.add(
new ObjectConversionValueProblem(this, appConfigDef.getGroupForProperty(prop).getProxiedGroup(), prop, value));
}
-
- if (pv != null) {
-
- ValidatedValue dup = findDuplicateProperty(pv, values);
-
- if (dup == null) {
- values.add(pv);
- } else {
- loaderProblems.add(new DuplicatePropertyLoaderProblem(
- this, appConfigDef.getGroupForProperty(prop).getProxiedGroup(), prop));
- }
- }
+
+ attemptToAddIfNotDuplicate(appConfigDef, values, loaderProblems, validatedValue);
}
}
+
+ /**
+ * Adds the ValidatedValue to the VV list if it is not a duplicate.
+ * This is the actual 'load' moment of the Loader: Adding it to this list means
+ * that the value has been loaded.
+ *
+ * Loader subclasses should use the other attemptToAdd methods - this one is invoked by those.
+ *
+ * @param appConfigDef Used to look up the property name for find the actual property
+ * @param values List of PropertyValues to add to, which should be only the value of this loader.
+ * @param loaderProblems A list of Problems to add to if there is a loader related problem
+ * @param validatedValue The validated value to load, which may be null which is a no-op.
+ */
+ protected void attemptToAddIfNotDuplicate(StaticPropertyConfigurationInternal appConfigDef, List values,
+ ProblemList loaderProblems, ValidatedValue validatedValue) {
+
+ if (validatedValue != null) {
+ ValidatedValue dup = findDuplicateProperty(validatedValue, values);
+
+ if (dup == null) {
+ values.add(validatedValue);
+ } else {
+ loaderProblems.add(new DuplicatePropertyLoaderProblem(
+ this, appConfigDef.getGroupForProperty(validatedValue.getProperty()).getProxiedGroup(), validatedValue.getProperty()));
+ }
+ }
+ }
protected ValidatedValue findDuplicateProperty(ValidatedValue current, List values) {
for (ValidatedValue ref : values) {
@@ -173,6 +206,26 @@ protected ValidatedValue createValue(StaticPropertyConfigurationInternal app
return new ValidatedValue(prop, value);
}
+
+ /**
+ * Maps the passed Property name or alias to a Property or null if it cannot be found.
+ * @param appConfigDef The AppConfig
+ * @param name The name, which will be trimmed and converted to an effective name.
+ * @return The Property or null if it cannot be found.
+ */
+ protected Property mapNametoProperty(StaticPropertyConfigurationInternal appConfigDef, String name) {
+
+ name = TextUtil.trimToNull(name);
+
+ if (name != null) {
+
+ Property prop = appConfigDef.getProperty(name);
+ return prop;
+
+ } else {
+ return null;
+ }
+ }
@Override
public void releaseResources() {
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/load/FixedValueLoader.java b/andhow-core/src/main/java/org/yarnandtail/andhow/load/FixedValueLoader.java
index 6f940089..d9162bc5 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/load/FixedValueLoader.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/load/FixedValueLoader.java
@@ -7,12 +7,14 @@
/**
* A utility loader that is used internally to put fixed values into the effective
- * list of values.
+ * list of configured values.
*
* This loader does not trim incoming values for String type properties - they are
* assumed to already be in final form.
* This loader considers it a problem to be passed unrecognized properties
- * and will throw a RuntimeException if that happens.
+ * and will throw a RuntimeException if that happens, though this can be set
+ * false to allow fixed values set via name to be ignored if the name is not
+ * recognized.
*
* @author eeverman
*/
@@ -20,39 +22,75 @@ public class FixedValueLoader extends BaseLoader implements ReadLoader {
protected boolean unknownPropertyAProblem = true;
- protected List values = new ArrayList();
+ protected final List values = new ArrayList();
+
+ protected final List keyObjectPairValues = new ArrayList();
public FixedValueLoader() {
}
-
+
+ /**
+ * Set property values as PropertyValues, which require live
+ * references to each Property.
+ * Values set in this way are additive to properties set via
+ * setKopValues and duplicate properties between the
+ * two will nominally be considered duplicates.
+ *
+ * @param values
+ */
public void setPropertyValues(List values) {
if (values != null) {
this.values.addAll(values);
}
}
-
+
+ /**
+ * @deprecated This method duplicates functionality and will
+ * be removed. The logic of this method is slightly wrong as
+ * well (compared to the List version): As this method is currently
+ * implemente, an empty array will NOT erase all values. The
+ * Other List based methods would erase all values in that case.
+ * @param values
+ */
public void setPropertyValues(PropertyValue... values) {
if (values != null && values.length > 0) {
this.values.addAll(Arrays.asList(values));
}
}
-
+
+ /**
+ * Set property values as KeyObjectPair's.
+ * Values set in this way are additive to properties set via
+ * setPropertyValues and duplicate properties between the
+ * two will nominally be considered duplicates.
+ *
+ * @param values
+ */
+ public void setKeyObjectPairValues(List values) {
+ if (values != null) {
+ keyObjectPairValues.addAll(values);
+ }
+ }
+
@Override
public LoaderValues load(StaticPropertyConfigurationInternal appConfigDef, ValidatedValuesWithContext existingValues) {
-
- if (values != null && values.size() > 0) {
- List vvs = new ArrayList(values.size());
-
- for (int i = 0; i < values.size(); i++) {
- ValidatedValue vv = new ValidatedValue(values.get(i).getProperty(), values.get(i).getValue());
- vvs.add(vv);
- }
-
- return new LoaderValues(this, vvs, ProblemList.EMPTY_PROBLEM_LIST);
-
- } else {
- return new LoaderValues(this, Collections.emptyList(), ProblemList.EMPTY_PROBLEM_LIST);
- }
+
+ List vvs = new ArrayList(values.size());
+ ProblemList problems = new ProblemList();
+
+ //Add all the PropertyValue's. The Property and value references are 'live',
+ //so lots of potential errors are not possible, however, the value type may
+ //not match the Property, so use 'attemptToAdd' to verify.
+ values.stream().forEach(
+ v -> this.attemptToAdd(appConfigDef, vvs, problems, v.getProperty(), v.getValue())
+ );
+
+ //Add all the KeyObjectPairs
+ keyObjectPairValues.stream().forEach(
+ kop -> this.attemptToAdd(appConfigDef, vvs, problems, kop.getName(), kop.getValue())
+ );
+
+ return new LoaderValues(this, vvs, problems);
}
@@ -63,7 +101,7 @@ public boolean isTrimmingRequiredForStringValues() {
@Override
public String getSpecificLoadDescription() {
- return "a list of fixed values passed in by the construction code (not dynamically loaded)";
+ return "a list of fixed values passed in during startup (not dynamically loaded)";
}
@Override
@@ -88,7 +126,8 @@ public boolean isUnknownPropertyAProblem() {
@Override
public void releaseResources() {
- values = null;
+ values.clear();
+ keyObjectPairValues.clear();
}
}
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/load/KeyObjectPair.java b/andhow-core/src/main/java/org/yarnandtail/andhow/load/KeyObjectPair.java
new file mode 100644
index 00000000..815c9a76
--- /dev/null
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/load/KeyObjectPair.java
@@ -0,0 +1,75 @@
+package org.yarnandtail.andhow.load;
+
+import org.yarnandtail.andhow.api.ParsingException;
+import org.yarnandtail.andhow.util.TextUtil;
+
+/**
+ * Key-Object Pair
+ * Contains a String name and an Object value.
+ * Names are always trimmed (leading and trailing spaces removed) because they are
+ * expected to match up to AndHow Property canonical names or alias, which don't includes spaces.
+ * After trimming, names must be non-empty and non-null.
+ */
+public class KeyObjectPair {
+
+ private String name;
+ private Object value;
+
+ /**
+ * Construct an instance with a null value.
+ * @param name A name that will be trimmed to remove all leading and trailing spaces.
+ * @throws ParsingException If the name is empty after trimming.
+ */
+ public KeyObjectPair(final String name) throws ParsingException {
+ this(name, null);
+ }
+
+ /**
+ * New instance with name and value.
+ * @param name A name that will be trimmed to remove all leading and trailing spaces.
+ * @param value The value, which will not be trimmed or modified.
+ * @throws ParsingException If the name is empty after trimming.
+ */
+ public KeyObjectPair(final String name, final Object value) throws ParsingException {
+ String cleanName = TextUtil.trimToNull(name);
+
+ if (cleanName == null) {
+ throw new ParsingException("The key (parameter name) cannot be empty or null", name);
+ }
+
+ this.name = cleanName;
+ this.value = value;
+ }
+
+ /**
+ * The KeyObjectPair name, which has been trimmed to remove leading and trailing spaces.
+ * @return
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * The value exactly as passed in the constructor, which may be null.
+ * @return The value as set.
+ */
+ public Object getValue() {
+ return value;
+ }
+
+ //Hashcode and Equals
+ //Normally a class like this would override these methods, but AndHow never needs to compare
+ //equality of this class, or if it does, is only concerned with the name and equality based
+ //only on name seems 'broken'.
+
+ /**
+ * String representation: name : "value"
+ *
+ * If the value is null, the string [null] is used.
+ * @return A formatted string version of the instance.
+ */
+ @Override
+ public String toString() {
+ return name + " : " + "\"" + (value!=null?value.toString():"[null]") + "\"";
+ }
+}
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/load/PropFileOnClasspathLoader.java b/andhow-core/src/main/java/org/yarnandtail/andhow/load/PropFileOnClasspathLoader.java
index 83d632e0..e6a8c1c3 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/load/PropFileOnClasspathLoader.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/load/PropFileOnClasspathLoader.java
@@ -7,8 +7,8 @@
import org.yarnandtail.andhow.internal.LoaderProblem;
/**
- * Reads from a Java .property file on the classpath, following standard java
- * conventions for the structure of those file.
+ * Reads from a Java .properties file on the classpath, following standard java
+ * conventions for the structure of those key:value pair files.
*
* This loader finds the properties file via either a String or StrProperty
* specified in the constructor. If the StrProperty is used, an earlier an
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/load/std/StdFixedValueLoader.java b/andhow-core/src/main/java/org/yarnandtail/andhow/load/std/StdFixedValueLoader.java
index 6c94ceef..85111360 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/load/std/StdFixedValueLoader.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/load/std/StdFixedValueLoader.java
@@ -1,6 +1,6 @@
package org.yarnandtail.andhow.load.std;
-import org.yarnandtail.andhow.api.StandardLoader;
+import org.yarnandtail.andhow.api.*;
import org.yarnandtail.andhow.load.FixedValueLoader;
/**
@@ -27,7 +27,7 @@
*
Basic Behaviors
*
*
Pre-trims String values: No (Individual Properties may still trim values)
- *
Complains about unrecognized properties: NA - This is not possible
+ *
Complains about unrecognized properties: Yes - Only applies to Properties specified by name
*
Default behavior: None - This loader is only active if values are directly set as shown below
*
*
Loader Details and Configuration
@@ -43,7 +43,10 @@
*
* {@literal @}Override public AndHowConfiguration getConfiguration() {
* return StdConfig.instance()
- * .addFixedValue(MY_PROP, "some value"); //MY_PROP is some visible property
+ * .addFixedValue(MY_PROP, 23L); //MY_PROP is some visible property of type Long.
+ * .addFixedValue("A_PROPERTY_NAME", "abc") //A name or alias of a Property works as well
+ * //In both cases, the value (23L or "abc") must be of the same type as the Property or
+ * //an error will be thrown.
* }
* }
*
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/load/std/StdPropFileOnClasspathLoader.java b/andhow-core/src/main/java/org/yarnandtail/andhow/load/std/StdPropFileOnClasspathLoader.java
index ff3b7166..c59cfa6c 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/load/std/StdPropFileOnClasspathLoader.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/load/std/StdPropFileOnClasspathLoader.java
@@ -42,13 +42,39 @@
* Full details on how Java parses properties files can be found in the
* properties file specification.
*
- * Configuring the name or classpath of the properties file can be used to
- * enable different configuration profiles based on the environment.
- * For instance, a system property could specify that {@code /test.properties}
- * be used on a test server and {@code /production.properties} on a production
- * server. An example of configuring the property file path:
- *
- *
+ * Since resources on the test classpath override those on the main classpath,
+ * its easy to have separate configurations for production and testing simply
+ * by having two {@code andhow.properties} files, one on the main classpath
+ * and one on test. There are many other options with classpath properties files.
+ *
+ * To change the name of the properties file used for testing, a class like the
+ * example below can be added to your test classpath:
+ *
+ * If AndHow finds a {@Code AndHowTestInit} implementation on the classpath,
+ * it uses it in preference to other initiation points.
+ * With this only on the test classpath, AndHow will still use the default
+ * {@code andhow.properties} for production.
+ *
+ * With slightly fancier configuration, something like {@code /staging.properties}
+ * could be used in a staging environment and {@code /production.properties}
+ * used in production. In a case like this we need the classpath to be
+ * configurable, not hard-coded like the example above. To do that we need
+ * a Property to configure it with, then we tell AndHow which property was
+ * used.
+ * Here is an example of a configurable classpath:
+ *
* The code above adds the property {@code MY_CLASSPATH}
* (the name is arbitrary) which is used to configure the
* {@code StdPropFileOnClasspathLoader} with a custom property file location.
@@ -71,6 +97,9 @@
* see if a value has been loaded for {@code MY_CLASSPATH} by any prior loader.
* If a value is present, the loader tries to load from the configured classpath.
* If no value is configured, the default classpath is assumed.
+ * Properties file loaders are the last loaders, so configuration the classpath
+ * can be done by virtually anything: environment vars, system properties,
+ * command line args, JNDI...
*
*
This is a Standard Loader
* Like all {@code StandardLoader}'s, this loader is intended to be auto-created
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/property/BigDecProp.java b/andhow-core/src/main/java/org/yarnandtail/andhow/property/BigDecProp.java
new file mode 100644
index 00000000..e80a6f22
--- /dev/null
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/property/BigDecProp.java
@@ -0,0 +1,107 @@
+package org.yarnandtail.andhow.property;
+
+import org.yarnandtail.andhow.api.*;
+import org.yarnandtail.andhow.valid.BigDecValidator;
+import org.yarnandtail.andhow.valuetype.BigDecType;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * A Property that refers to a BigDecimal value.
+ *
+ * By default this uses the TrimToNullTrimmer, which removes all whitespace from
+ * the value and ultimately null if the value is all whitespace. The String
+ * constructor version is used when creating instances of BigDecimal.
+ *
+ * @author chace86
+ */
+public class BigDecProp extends PropertyBase {
+
+ /**
+ * Construct an instance of BigDecProp
+ * @param defaultValue default value
+ * @param required make the property required or not
+ * @param shortDesc short description of the property
+ * @param validators list of validators for the property
+ * @param aliases aliases of the property
+ * @param paramType property type
+ * @param valueType property value type
+ * @param trimmer trimmer associated with the property
+ * @param helpText help text of the property
+ */
+ public BigDecProp(BigDecimal defaultValue, boolean required, String shortDesc, List> validators,
+ List aliases, PropertyType paramType, ValueType valueType, Trimmer trimmer,
+ String helpText) {
+
+ super(defaultValue, required, shortDesc, validators, aliases, paramType, valueType, trimmer, helpText);
+ }
+
+ /**
+ * Return an instance of BigDecBuilder
+ */
+ public static BigDecBuilder builder() {
+ return new BigDecBuilder();
+ }
+
+ /**
+ * Build a BigDecProp
+ */
+ public static class BigDecBuilder extends PropertyBuilderBase {
+
+ /**
+ * Construct an instance of BigDecBuilder
+ */
+ public BigDecBuilder() {
+ instance = this;
+ valueType(BigDecType.instance());
+ trimmer(TrimToNullTrimmer.instance());
+ }
+
+ @Override
+ public BigDecProp build() {
+ return new BigDecProp(_defaultValue, _nonNull, _desc, _validators,
+ _aliases, PropertyType.SINGLE_NAME_VALUE, _valueType, _trimmer, _helpText);
+ }
+
+ /**
+ * The property must be greater than the reference
+ * @param reference value the property must be greater than
+ * @return the builder instance
+ */
+ public BigDecBuilder mustBeGreaterThan(BigDecimal reference) {
+ validation(new BigDecValidator.GreaterThan(reference));
+ return instance;
+ }
+
+ /**
+ * The property must be greater than or equal to the reference
+ * @param reference value the property must be greater than or equal to
+ * @return the builder instance
+ */
+ public BigDecBuilder mustBeGreaterThanOrEqualTo(BigDecimal reference) {
+ validation(new BigDecValidator.GreaterThanOrEqualTo(reference));
+ return instance;
+ }
+
+ /**
+ * The property must be less than the reference
+ * @param reference value the property must be less than
+ * @return the builder instance
+ */
+ public BigDecBuilder mustBeLessThan(BigDecimal reference) {
+ validation(new BigDecValidator.LessThan(reference));
+ return instance;
+ }
+
+ /**
+ * The property must be less than or equal to the reference
+ * @param reference value the property must be less than or equal to
+ * @return the builder instance
+ */
+ public BigDecBuilder mustBeLessThanOrEqualTo(BigDecimal reference) {
+ validation(new BigDecValidator.LessThanOrEqualTo(reference));
+ return instance;
+ }
+ }
+}
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/property/DblProp.java b/andhow-core/src/main/java/org/yarnandtail/andhow/property/DblProp.java
index f42a210d..f786cbad 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/property/DblProp.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/property/DblProp.java
@@ -13,6 +13,21 @@
*
* By default this uses the TrimToNullTrimmer, which removes all whitespace from
* the value and ultimately null if the value is all whitespace.
+ *
+ * If a DblProp is configured as a string, such as from a properties file, on
+ * command line, environment variable, etc., the value MAY include a trailing
+ * 'D' of 'F' (lower case is ok too), as is done with Java literals. This is
+ * different than the behavior of LngProp, but is a result of how Java parses
+ * these values.
+ *
+ * E.g., here are several correct ways to spec double value in a properties file:
+ *
+ * name.of.my.double.property.MY_PROPERTY_1 = 90.00
+ * name.of.my.double.property.MY_PROPERTY_2 = 80.00D
+ * name.of.my.double.property.MY_PROPERTY_3 = 70.00F
+ * name.of.my.double.property.MY_PROPERTY_4 = 60.00d
+ * name.of.my.double.property.MY_PROPERTY_5 = 4
+ *
*
* @author eeverman
*/
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/property/LngProp.java b/andhow-core/src/main/java/org/yarnandtail/andhow/property/LngProp.java
index 089d4386..150e87d9 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/property/LngProp.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/property/LngProp.java
@@ -13,6 +13,14 @@
*
* By default this uses the TrimToNullTrimmer, which removes all whitespace from
* the value and ultimately null if the value is all whitespace.
+ *
+ * If a LngProp is configured as a string, such as from a properties file, on
+ * command line, environment variable, etc., the value does NOT include a trailing
+ * 'L', as is done with Java literals. E.g., this is the correct way to spec
+ * a long value in a properties file:
+ *
+ * name.of.my.long.property.MY_PROPERTY = 90
+ *
*
* @author eeverman
*/
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/property/StrProp.java b/andhow-core/src/main/java/org/yarnandtail/andhow/property/StrProp.java
index 4b9307e9..977de4c9 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/property/StrProp.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/property/StrProp.java
@@ -9,7 +9,7 @@
* A Property that refers to a String value.
*
* All the basic Java types use a three letter abv. to keep declaration lines
- * short, in the form of: [Type]Prop
+ * short, in the form of: [Type]Prop
*
* By default, this uses the QuotedSpacePreservingTrimmer, which will keep
* whitespace inside double quotes.
@@ -17,23 +17,19 @@
* @author eeverman
*/
public class StrProp extends PropertyBase {
-
- public StrProp(
- String defaultValue, boolean nonNull, String shortDesc, List> validators,
- List aliases, PropertyType paramType, ValueType valueType, Trimmer trimmer,
- String helpText) {
-
+
+ public StrProp(String defaultValue, boolean nonNull, String shortDesc, List> validators,
+ List aliases, PropertyType paramType, ValueType valueType, Trimmer trimmer, String helpText) {
+
super(defaultValue, nonNull, shortDesc, validators, aliases, paramType, valueType, trimmer, helpText);
}
-
+
public static StrBuilder builder() {
return new StrBuilder();
}
-
-
+
public static class StrBuilder extends PropertyBuilderBase {
-
public StrBuilder() {
instance = this;
valueType(StrType.instance());
@@ -43,35 +39,40 @@ public StrBuilder() {
@Override
public StrProp build() {
- return new StrProp(_defaultValue, _nonNull, _desc, _validators,
- _aliases, PropertyType.SINGLE_NAME_VALUE, _valueType, _trimmer, _helpText);
+ return new StrProp(_defaultValue, _nonNull, _desc, _validators, _aliases, PropertyType.SINGLE_NAME_VALUE,
+ _valueType, _trimmer, _helpText);
}
-
+
public StrBuilder mustMatchRegex(String regex) {
this.validation(new StringValidator.Regex(regex));
return this;
}
-
+
public StrBuilder mustStartWith(String prefix) {
this.validation(new StringValidator.StartsWith(prefix, false));
return this;
}
-
+
public StrBuilder mustStartWithIgnoreCase(String prefix) {
this.validation(new StringValidator.StartsWith(prefix, true));
return this;
}
-
+
public StrBuilder mustEndWith(String sufix) {
this.validation(new StringValidator.EndsWith(sufix, false));
return this;
}
-
+
public StrBuilder mustEndWithIgnoreCase(String sufix) {
this.validation(new StringValidator.EndsWith(sufix, true));
return this;
}
+ public StrBuilder mustEqual(String... values) {
+ this.validation(new StringValidator.Equals(values));
+ return this;
+ }
+
}
-
+
}
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/util/IOUtil.java b/andhow-core/src/main/java/org/yarnandtail/andhow/util/IOUtil.java
index 04f31bfb..d4fe3b9f 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/util/IOUtil.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/util/IOUtil.java
@@ -4,37 +4,59 @@
import java.nio.charset.Charset;
/**
- *
+ * Utility class to handle some IO purposes.
* @author ericeverman
*/
public class IOUtil {
+
private static final String JAVA_TMP_KEY = "java.io.tmpdir";
- private static final String USER_DIR_KEY = "user.home";
-
+
+ /**
+ * All utility methods are static.
+ */
private IOUtil() {
//no instances
}
-
+
+ /**
+ * Retrieve a String from an UTF-8 encoded classpath resource.
+ * @param path location on the classpath.
+ * @return a string reprensenting the entire content of the resource.
+ * @throws IOException when the resource cannot be loaded.
+ */
public static String getUTF8ResourceAsString(String path) throws IOException {
InputStream in = IOUtil.class.getResourceAsStream(path);
-
+
if (in == null) {
throw new IOException("Unable to find the resource '" + path + "'");
}
-
+
return toString(in, Charset.forName("UTF-8"));
}
-
+
+ /**
+ * Retrieve a String from a classpath resource.
+ * @param path location on the classpath.
+ * @return a string reprensenting the entire content of the resource.
+ * @throws IOException when the resource cannot be loaded.
+ */
public static String getResourceAsString(String path, Charset encoding) throws IOException {
InputStream in = IOUtil.class.getResourceAsStream(path);
-
+
if (in == null) {
throw new IOException("Unable to find the resource '" + path + "'");
}
-
+
return toString(in, encoding);
}
-
+
+ /**
+ * Build a String from the content on an InputStream using the encoding provided.
+ * @param input Stream containing the data to extract.
+ * @param encoding Charset used to read the input.
+ * @return String representing the content of the input.
+ * @throws IOException when the resource cannot be read.
+ */
public static String toString(InputStream input, Charset encoding) throws IOException {
StringBuilder builder = new StringBuilder();
@@ -48,43 +70,54 @@ public static String toString(InputStream input, Charset encoding) throws IOExce
if (builder.length() > 0)
builder.setLength(builder.length() - System.lineSeparator().length());
-
+
return builder.toString();
}
-
+
+ /**
+ * Expands directory path, replacing known values like java.io.tmpdir
+ * w/ their values. Paths are assumed to use forward slashes, which are replaced
+ * on Windows systems to be backslashes.
+ *
+ * As the provided path has to be a directory, the returned value will end with
+ * a file separator character.
+ *
+ * @param path directory path to expand if needed.
+ * @return the expanded directory path.
+ */
public static String expandDirectoryPath(String path) {
path = expandFilePath(path);
-
+
if (! path.endsWith(String.valueOf(File.separatorChar))) {
return path + File.separatorChar;
} else {
return path;
}
}
-
+
/**
* Expands a file or directory path, replacing known values like java.io.tmpdir
* w/ their values. Paths are assumed to use forward slashes, which are replaced
* on Windows systems to be backslashes.
- *
+ *
* The returned path may be either a file path (not ending with a separator)
* or directory (ending with a separator).
- *
- * @param path
- * @return
+ *
+ * @param path path to expand if needed.
+ * @return the expanded path.
*/
public static String expandFilePath(String path) {
-
+
String tmpDir = System.getProperty(JAVA_TMP_KEY);
path = path.trim();
-
+
//rm the trailing separatorChar from the temp directory if there is
//possibly more to the path than just the temp directory. Otherwise the
//sep char gets doubled up.
if (path.length() > JAVA_TMP_KEY.length() && tmpDir.endsWith(System.getProperty("file.separator"))) {
tmpDir = tmpDir.substring(0, tmpDir.length() - 1);
}
-
+
path = path.replace((CharSequence)JAVA_TMP_KEY, (CharSequence)tmpDir);
path = path.replace('/', System.getProperty("file.separator").charAt(0));
return path;
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/util/TextUtil.java b/andhow-core/src/main/java/org/yarnandtail/andhow/util/TextUtil.java
index 93ab1d24..03e46850 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/util/TextUtil.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/util/TextUtil.java
@@ -324,7 +324,9 @@ public static List wrap(String in, int length) {
/**
* Find the first occurrence of one of an array of possible strings in another string,
- * starting at the specified position.
+ * starting at the specified position, looking to the right. If there are more than one
+ * string arguments to be searched from, it returns the index of the argument which is
+ * of the lower index
*
* @param toBeSearched
* @param searchFrom Same symantics as String.indexOf(String, int)
@@ -334,7 +336,7 @@ public static List wrap(String in, int length) {
public static int findFirstInstanceOf(String toBeSearched, int searchFrom, String... toBeFound) {
int result = -1;
- if (toBeSearched != null || toBeFound.length > 0) {
+ if (toBeSearched != null && toBeFound.length > 0) {
for (String s : toBeFound) {
int i = toBeSearched.indexOf(s, searchFrom);
if (i > -1 && (i < result || result == -1)) {
@@ -347,7 +349,11 @@ public static int findFirstInstanceOf(String toBeSearched, int searchFrom, Strin
}
/**
- *
+ * Find the first occurrence of one of an array of possible strings in another string,
+ * starting at the specified position and seeking to the left. If there are more than
+ * one string arguments to be searched from, it returns the index of the argument which is
+ * of the higher index
+ *
* @param toBeSearched
* @param searchFrom Start looking from this position and to the left
* @param toBeFound
@@ -356,7 +362,7 @@ public static int findFirstInstanceOf(String toBeSearched, int searchFrom, Strin
public static int findLastInstanceOf(String toBeSearched, int searchFrom, String... toBeFound) {
int result = -1;
- if (toBeSearched != null || toBeFound.length > 0) {
+ if (toBeSearched != null && toBeFound.length > 0) {
for (String s : toBeFound) {
int i = toBeSearched.lastIndexOf(s, searchFrom);
if (i > result) {
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/BigDecValidator.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/BigDecValidator.java
new file mode 100644
index 00000000..3ca8a6d8
--- /dev/null
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/BigDecValidator.java
@@ -0,0 +1,125 @@
+package org.yarnandtail.andhow.valid;
+
+import org.yarnandtail.andhow.api.Validator;
+
+import java.math.BigDecimal;
+
+/**
+ * Abstract class implementing Validator interface for BigDec.
+ * Extended by nested static classes. The nested classes implement
+ * constraints that may be used when building the property.
+ */
+public abstract class BigDecValidator implements Validator {
+
+ final BigDecimal ref;
+
+ /**
+ * Base constructor of BigDecValidator constraints
+ * @param ref to be compared to property value
+ */
+ BigDecValidator(BigDecimal ref) {
+ this.ref = ref;
+ }
+
+ @Override
+ public boolean isSpecificationValid() {
+ return ref != null;
+ }
+
+ @Override
+ public String getInvalidSpecificationMessage() {
+ return "The constraint may not be null";
+ }
+
+ /**
+ * Validate that a BigDecimal is greater than a specified reference.
+ */
+ public static class GreaterThan extends BigDecValidator {
+ /**
+ * Construct a GreaterThan property constraint
+ * @param ref to be compared to property value
+ */
+ public GreaterThan(BigDecimal ref) {
+ super(ref);
+ }
+
+ @Override
+ public boolean isValid(BigDecimal value) {
+ return (value != null) && (value.compareTo(ref) > 0);
+ }
+
+ @Override
+ public String getTheValueMustDescription() {
+ return "be greater than " + ref;
+ }
+ }
+
+ /**
+ * Validate that a BigDecimal is greater than or equal to a specified reference.
+ */
+ public static class GreaterThanOrEqualTo extends BigDecValidator {
+ /**
+ * Construct a GreaterThanOrEqualTo property constraint
+ * @param ref to be compared to property value
+ */
+ public GreaterThanOrEqualTo(BigDecimal ref) {
+ super(ref);
+ }
+
+ @Override
+ public boolean isValid(BigDecimal value) {
+ return (value != null) && (value.compareTo(ref) >= 0);
+ }
+
+ @Override
+ public String getTheValueMustDescription() {
+ return "be greater than or equal to " + ref;
+ }
+ }
+
+ /**
+ * Validate that a BigDecimal is less than a specified reference.
+ */
+ public static class LessThan extends BigDecValidator {
+ /**
+ * Construct a LessThan property constraint
+ * @param ref to be compared to property value
+ */
+ public LessThan(BigDecimal ref) {
+ super(ref);
+ }
+
+ @Override
+ public boolean isValid(BigDecimal value) {
+ return (value != null) && (value.compareTo(ref) < 0);
+ }
+
+ @Override
+ public String getTheValueMustDescription() {
+ return "be less than " + ref;
+ }
+ }
+
+ /**
+ * Validate that a BigDecimal is less than or equal to a specified reference.
+ */
+ public static class LessThanOrEqualTo extends BigDecValidator {
+ /**
+ * Construct a LessThanOrEqualTo property constraint
+ * @param ref to be compared to property value
+ */
+ public LessThanOrEqualTo(BigDecimal ref) {
+ super(ref);
+ }
+
+ @Override
+ public boolean isValid(BigDecimal value) {
+ return (value != null) && (value.compareTo(ref) <= 0);
+ }
+
+ @Override
+ public String getTheValueMustDescription() {
+ return "be less than or equal to " + ref;
+ }
+ }
+}
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/DblValidator.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/DblValidator.java
index 4b037c2d..ad3365df 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/DblValidator.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/DblValidator.java
@@ -24,7 +24,7 @@ public String getInvalidSpecificationMessage() {
*/
public static class GreaterThan extends DblValidator {
- double ref;
+ private double ref;
public GreaterThan(double ref) {
this.ref = ref;
@@ -50,7 +50,7 @@ public String getTheValueMustDescription() {
*/
public static class GreaterThanOrEqualTo extends DblValidator {
- double ref;
+ private double ref;
public GreaterThanOrEqualTo(double ref) {
this.ref = ref;
@@ -76,7 +76,7 @@ public String getTheValueMustDescription() {
*/
public static class LessThan extends DblValidator {
- double ref;
+ private double ref;
public LessThan(double ref) {
this.ref = ref;
@@ -102,7 +102,7 @@ public String getTheValueMustDescription() {
*/
public static class LessThanOrEqualTo extends DblValidator {
- double ref;
+ private double ref;
public LessThanOrEqualTo(double ref) {
this.ref = ref;
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/IntValidator.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/IntValidator.java
index 564a5aec..936de764 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/IntValidator.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/IntValidator.java
@@ -24,7 +24,7 @@ public String getInvalidSpecificationMessage() {
*/
public static class GreaterThan extends IntValidator {
- int ref;
+ private int ref;
public GreaterThan(int ref) {
this.ref = ref;
@@ -50,7 +50,7 @@ public String getTheValueMustDescription() {
*/
public static class GreaterThanOrEqualTo extends IntValidator {
- int ref;
+ private int ref;
public GreaterThanOrEqualTo(int ref) {
this.ref = ref;
@@ -76,7 +76,7 @@ public String getTheValueMustDescription() {
*/
public static class LessThan extends IntValidator {
- int ref;
+ private int ref;
public LessThan(int ref) {
this.ref = ref;
@@ -102,7 +102,7 @@ public String getTheValueMustDescription() {
*/
public static class LessThanOrEqualTo extends IntValidator {
- int ref;
+ private int ref;
public LessThanOrEqualTo(int ref) {
this.ref = ref;
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/LngValidator.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/LngValidator.java
index 0f9cda60..3632372d 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/LngValidator.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/LngValidator.java
@@ -50,7 +50,7 @@ public String getTheValueMustDescription() {
*/
public static class GreaterThanOrEqualTo extends LngValidator {
- long ref;
+ private long ref;
public GreaterThanOrEqualTo(long ref) {
this.ref = ref;
@@ -76,7 +76,7 @@ public String getTheValueMustDescription() {
*/
public static class LessThan extends LngValidator {
- long ref;
+ private long ref;
public LessThan(long ref) {
this.ref = ref;
@@ -102,7 +102,7 @@ public String getTheValueMustDescription() {
*/
public static class LessThanOrEqualTo extends LngValidator {
- long ref;
+ private long ref;
public LessThanOrEqualTo(long ref) {
this.ref = ref;
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/StringValidator.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/StringValidator.java
index 79fa04fb..16504142 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/StringValidator.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/StringValidator.java
@@ -1,5 +1,7 @@
package org.yarnandtail.andhow.valid;
+import java.util.Arrays;
+
import org.yarnandtail.andhow.api.Validator;
/**
@@ -8,14 +10,49 @@
* @author ericeverman
*/
public class StringValidator {
-
+
+ /**
+ * Validate that a string is one from the specified set.
+ */
+ public static class Equals implements Validator {
+
+ String[] values;
+
+ public Equals(String... values) {
+ this.values = values;
+ }
+
+ @Override
+ public boolean isSpecificationValid() {
+ return values != null && values.length != 0;
+ }
+
+ @Override
+ public String getInvalidSpecificationMessage() {
+ return "The Equals list must contain at least one value";
+ }
+
+ @Override
+ public boolean isValid(String value) {
+ if (value != null) {
+ return Arrays.stream(values).anyMatch(value::equals);
+ }
+ return false;
+ }
+
+ @Override
+ public String getTheValueMustDescription() {
+ return "be equal to one of '" + Arrays.deepToString(values) + "'";
+ }
+ }
+
/**
* Validate that a string starts with a specific string.
*/
public static class StartsWith implements Validator {
- String prefix;
- boolean ignoreCase;
+ private String prefix;
+ private boolean ignoreCase;
public StartsWith(String prefix, boolean ignoreCase) {
this.prefix = prefix;
@@ -29,7 +66,7 @@ public boolean isSpecificationValid() {
@Override
public String getInvalidSpecificationMessage() {
- return "The StartWith expression cannot be null";
+ return "The StartsWith expression cannot be null";
}
@Override
@@ -43,21 +80,20 @@ public boolean isValid(String value) {
}
return false;
}
-
+
@Override
public String getTheValueMustDescription() {
return "start with '" + prefix + "'";
}
}
-
/**
* Validate that a string ends with a specific string.
*/
public static class EndsWith implements Validator {
- String sufix;
- boolean ignoreCase;
+ private String sufix;
+ private boolean ignoreCase;
public EndsWith(String sufix, boolean ignoreCase) {
this.sufix = sufix;
@@ -85,19 +121,19 @@ public boolean isValid(String value) {
}
return false;
}
-
+
@Override
public String getTheValueMustDescription() {
return "end with '" + sufix + "'";
}
}
-
+
/**
* Validate based on a regex string.
*/
public static class Regex implements Validator {
- String regex;
+ private String regex;
public Regex(String regex) {
this.regex = regex;
@@ -128,11 +164,11 @@ public boolean isValid(String value) {
return false;
}
}
-
+
@Override
public String getTheValueMustDescription() {
return "match the regex expression '" + regex + "'";
}
- }
+ }
}
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BigDecType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BigDecType.java
new file mode 100644
index 00000000..0bec4fe9
--- /dev/null
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BigDecType.java
@@ -0,0 +1,52 @@
+package org.yarnandtail.andhow.valuetype;
+
+import org.yarnandtail.andhow.api.ParsingException;
+
+import java.math.BigDecimal;
+
+/**
+ * Type representation of Java BigDecimal objects.
+ *
+ * This class is threadsafe and uses a singleton pattern to prevent multiple
+ * instances, since all users can safely use the same instance.
+ *
+ * @author chace86
+ */
+public class BigDecType extends BaseValueType {
+
+ private static final BigDecType INSTANCE = new BigDecType();
+
+ private BigDecType() {
+ super(BigDecimal.class);
+ }
+
+ /**
+ * Construct an instance of BigDecType
+ */
+ public static BigDecType instance() {
+ return INSTANCE;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ * For more information on how the sourceValue is parsed, see
+ * BigDecimal String constructor.
+ */
+ @Override
+ public BigDecimal parse(String sourceValue) throws ParsingException {
+ if (sourceValue == null) {
+ return null;
+ }
+ try {
+ return new BigDecimal(sourceValue);
+ } catch (Exception e) {
+ throw new ParsingException("Unable to convert to a BigDecimal numeric value", sourceValue, e);
+ }
+ }
+
+ @Override
+ public BigDecimal cast(Object o) throws RuntimeException {
+ return (BigDecimal)o;
+ }
+}
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BolType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BolType.java
index 0915e767..2206b785 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BolType.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BolType.java
@@ -17,11 +17,20 @@ public class BolType extends BaseValueType {
private BolType() {
super(Boolean.class);
}
-
+
+ /**
+ * @deprecated since 0.4.1. Use {@link #instance()} instead
+ *
+ * @return An instance of the {@link #BolType()}
+ */
+ @Deprecated()
public static BolType get() {
- return instance;
+ return instance();
}
-
+
+ /**
+ * @return An instance of the {@link #BolType()}
+ */
public static BolType instance() {
return instance;
}
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/DblType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/DblType.java
index 94847605..fa1aac6b 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/DblType.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/DblType.java
@@ -17,11 +17,20 @@ public class DblType extends BaseValueType {
private DblType() {
super(Double.class);
}
-
+
+ /**
+ * @deprecated since 0.4.1. Use {@link #instance()} instead
+ *
+ * @return An instance of the {@link #DblType()}
+ */
+ @Deprecated()
public static DblType get() {
- return instance;
+ return instance();
}
-
+
+ /**
+ * @return An instance of the {@link #DblType()}
+ */
public static DblType instance() {
return instance;
}
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/FlagType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/FlagType.java
index 0285b986..042fecd6 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/FlagType.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/FlagType.java
@@ -17,11 +17,20 @@ public class FlagType extends BaseValueType {
private FlagType() {
super(Boolean.class);
}
-
+
+ /**
+ * @deprecated since 0.4.1. Use {@link #instance()} instead
+ *
+ * @return An instance of the {@link #FlagType()}
+ */
+ @Deprecated()
public static FlagType get() {
- return instance;
+ return instance();
}
-
+
+ /**
+ * @return An instance of the {@link #FlagType()}
+ */
public static FlagType instance() {
return instance;
}
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/IntType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/IntType.java
index d371b3c6..7ca664b5 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/IntType.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/IntType.java
@@ -17,11 +17,20 @@ public class IntType extends BaseValueType {
private IntType() {
super(Integer.class);
}
-
+
+ /**
+ * @deprecated since 0.4.1. Use {@link #instance()} instead
+ *
+ * @return An instance of the {@link #IntType()}
+ */
+ @Deprecated()
public static IntType get() {
- return instance;
+ return instance();
}
-
+
+ /**
+ * @return An instance of the {@link #IntType()}
+ */
public static IntType instance() {
return instance;
}
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/LngType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/LngType.java
index 3bbfd154..b57cf81f 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/LngType.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/LngType.java
@@ -17,11 +17,20 @@ public class LngType extends BaseValueType {
private LngType() {
super(Long.class);
}
-
+
+ /**
+ * @deprecated since 0.4.1. Use {@link #instance()} instead
+ *
+ * @return An instance of the {@link #LngType()}
+ */
+ @Deprecated()
public static LngType get() {
- return instance;
+ return instance();
}
-
+
+ /**
+ * @return An instance of the {@link #LngType()}
+ */
public static LngType instance() {
return instance;
}
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/LocalDateTimeType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/LocalDateTimeType.java
index 120fe847..b6778e7b 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/LocalDateTimeType.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/LocalDateTimeType.java
@@ -23,11 +23,20 @@ public class LocalDateTimeType extends BaseValueType {
private LocalDateTimeType() {
super(LocalDateTime.class);
}
-
+
+ /**
+ * @deprecated since 0.4.1. Use {@link #instance()} instead
+ *
+ * @return An instance of the {@link #LocalDateTimeType()}
+ */
+ @Deprecated
public static LocalDateTimeType get() {
- return instance;
+ return instance();
}
-
+
+ /**
+ * @return An instance of the {@link #LocalDateTimeType()}
+ */
public static LocalDateTimeType instance() {
return instance;
}
@@ -45,9 +54,9 @@ public static LocalDateTimeType instance() {
*
2011-12-03T00:15:30 - The first hour is hour zero
*
2011-12-03T23:00:00 - The last hour is hour 23
*
- * @param sourceValue
- * @return
- * @throws ParsingException
+ * @param sourceValue The @{@link String} value to be parsed into a @{@link LocalDateTime}
+ * @return A valid @{@link LocalDateTime}
+ * @throws ParsingException If the @{@link String} can't be parsed into a @{@link LocalDateTime}
*/
@Override
public LocalDateTime parse(String sourceValue) throws ParsingException {
diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/StrType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/StrType.java
index 7ed37302..f32a4cb0 100644
--- a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/StrType.java
+++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/StrType.java
@@ -17,7 +17,10 @@ public class StrType extends BaseValueType {
private StrType() {
super(String.class);
}
-
+
+ /**
+ * @return An instance of the {@link #StrType()}
+ */
public static StrType instance() {
return instance;
}
diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowCoreTestBase.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowCoreTestBase.java
index 79a40c2e..f99bab10 100644
--- a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowCoreTestBase.java
+++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowCoreTestBase.java
@@ -2,7 +2,11 @@
import java.util.Properties;
import javax.naming.NamingException;
-import org.junit.*;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
import org.springframework.mock.jndi.SimpleNamingContextBuilder;
/**
@@ -45,57 +49,50 @@ public class AndHowCoreTestBase {
*/
private static SimpleNamingContextBuilder builder;
- @BeforeClass
+ @BeforeAll
public static void killAndHowStateBeforeClass() {
AndHowCoreTestUtil.destroyAndHow();
}
- @BeforeClass
+ @BeforeAll
public static void storeSysPropsBeforeClass() {
beforeClassSystemProps = AndHowCoreTestUtil.clone(System.getProperties());
}
- @Before
+ @BeforeEach
public void killAndHowStateBeforeTest() {
AndHowCoreTestUtil.destroyAndHow();
}
- @Before
+ @BeforeEach
public void storeSysPropsBeforeTest() {
beforeTestSystemProps = AndHowCoreTestUtil.clone(System.getProperties());
}
- @After
- public void clearJNDIBeforeTest() {
- if (builder != null) {
- builder.clear();
- }
- }
-
- @After
+ @AfterEach
public void killAndHowStateAfterTest() {
AndHowCoreTestUtil.destroyAndHow();
}
- @After
+ @AfterEach
public void clearJNDIAfterTest() {
if (builder != null) {
builder.clear();
}
}
- @After
+ @AfterEach
public void restoreSysPropsAfterTest() {
System.setProperties(beforeTestSystemProps);
}
- @AfterClass
+ @AfterAll
public static void killAndHowStateAfterClass() {
AndHowCoreTestUtil.destroyAndHow();
}
- @AfterClass
+ @AfterAll
public static void restoreSysPropsAfterClass() {
System.setProperties(beforeClassSystemProps);
}
diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowCoreTestUtil.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowCoreTestUtil.java
index 19332d95..9ff913d6 100644
--- a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowCoreTestUtil.java
+++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowCoreTestUtil.java
@@ -105,7 +105,6 @@ public static AndHow setAndHowInstance(AndHow newInstance) {
}
-
public static void forceRebuild(AndHowConfiguration config) {
AndHow ahInstance = getAndHowInstance();
diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest.java
new file mode 100644
index 00000000..6a3163e8
--- /dev/null
+++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest.java
@@ -0,0 +1,71 @@
+package org.yarnandtail.andhow;
+
+
+import static org.junit.jupiter.api.Assertions.*;
+import org.junit.jupiter.api.TestMethodOrder;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.MethodOrderer;
+import org.yarnandtail.andhow.api.AppFatalException;
+import org.yarnandtail.andhow.internal.ConstructionProblem;
+
+/**
+ *
+ * Note: The order of the tests is important and is sorted by method name.
+ * The key reason for this is to test that an InitiationLoopException does not
+ * bleed over from one test to another.
+ * @author eeverman
+ */
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class AndHowReentrantTest extends AndHowCoreTestBase {
+
+
+ @Test
+ public void test_1_AndHowReentrantTest_BadSample_1() {
+ AndHowConfiguration config = AndHowCoreTestConfig.instance()
+ .group(AndHowReentrantTest_BadSample_1.class);
+
+ try {
+ AndHow.instance(config);
+ fail("This should have blown up");
+ } catch (Throwable t) {
+ assertTrue(t.getCause() instanceof AppFatalException);
+ AppFatalException afe = (AppFatalException)t.getCause();
+ assertEquals(1, afe.getProblems().size());
+ assertTrue(afe.getProblems().get(0) instanceof ConstructionProblem.InitiationLoopException);
+ }
+ }
+
+ @Test
+ public void test_2_AndHowReentrantTest_OkSample_1() {
+ AndHowConfiguration config = AndHowCoreTestConfig.instance()
+ .group(AndHowReentrantTest_OkSample_1.class);
+
+
+ AndHow.instance(config);
+
+ assertEquals("onetwo", AndHowReentrantTest_OkSample_1.getSomeString());
+ assertEquals("one", AndHowReentrantTest_OkSample_1.STR_1.getValue());
+ assertEquals("two", AndHowReentrantTest_OkSample_1.STR_2.getValue());
+
+ }
+
+ @Test
+ public void test_3_AndHowReentrantTest_BadSample_2() {
+ AndHowConfiguration config = AndHowCoreTestConfig.instance()
+ .group(AndHowReentrantTest_BadSample_2.class);
+
+ try {
+ AndHow.instance(config);
+ fail("This should have blown up");
+ } catch (Throwable t) {
+ assertTrue(t.getCause() instanceof AppFatalException);
+ AppFatalException afe = (AppFatalException)t.getCause();
+ assertEquals(1, afe.getProblems().size());
+ assertTrue(afe.getProblems().get(0) instanceof ConstructionProblem.InitiationLoopException);
+ }
+ }
+
+
+
+}
diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_BadSample_1.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_BadSample_1.java
new file mode 100644
index 00000000..8f80a5cd
--- /dev/null
+++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_BadSample_1.java
@@ -0,0 +1,13 @@
+package org.yarnandtail.andhow;
+
+import org.yarnandtail.andhow.property.*;
+
+/**
+ * Demonstrates an invalid reference to a Property value at construction time.
+ * @author ericeverman
+ */
+public class AndHowReentrantTest_BadSample_1 {
+ static final StrProp STR_1 = StrProp.builder().defaultValue("one").build();
+ static final StrProp STR_2 = StrProp.builder().defaultValue(STR_1.getValue()).build();
+}
+
diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_BadSample_2.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_BadSample_2.java
new file mode 100644
index 00000000..6b3f174c
--- /dev/null
+++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_BadSample_2.java
@@ -0,0 +1,18 @@
+package org.yarnandtail.andhow;
+
+import org.yarnandtail.andhow.property.*;
+
+/**
+ * Demonstrates an invalid reference to a Property value at construction time.
+ * @author ericeverman
+ */
+public class AndHowReentrantTest_BadSample_2 {
+ static final StrProp STR_1 = StrProp.builder().defaultValue("one").build();
+ static final StrProp STR_2 = StrProp.builder().defaultValue("two").build();
+ static final String SOME_STRING;
+
+ static {
+ SOME_STRING = STR_1.getValue() + STR_2.getValue(); //This should be ok
+ }
+}
+
diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_OkSample_1.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_OkSample_1.java
new file mode 100644
index 00000000..2f41854b
--- /dev/null
+++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_OkSample_1.java
@@ -0,0 +1,17 @@
+package org.yarnandtail.andhow;
+
+import org.yarnandtail.andhow.property.*;
+
+/**
+ * This should be just fine
+ * @author ericeverman
+ */
+public class AndHowReentrantTest_OkSample_1 {
+ static final StrProp STR_1 = StrProp.builder().defaultValue("one").build();
+ static final StrProp STR_2 = StrProp.builder().defaultValue("two").build();
+
+ static String getSomeString() {
+ return STR_1.getValue() + STR_2.getValue(); //This should be ok
+ }
+}
+
diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowTest.java
index b8136006..a5779235 100644
--- a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowTest.java
+++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowTest.java
@@ -3,7 +3,9 @@
import java.io.File;
import java.time.LocalDateTime;
import java.util.*;
-import org.junit.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.yarnandtail.andhow.api.AppFatalException;
import org.yarnandtail.andhow.api.Property;
import org.yarnandtail.andhow.internal.*;
@@ -12,7 +14,7 @@
import org.yarnandtail.andhow.property.FlagProp;
import org.yarnandtail.andhow.property.StrProp;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
/**
* This test is a minimal unit teat because this class is not really testable
@@ -39,7 +41,7 @@ public static interface RequiredParams {
FlagProp FLAG_NULL = FlagProp.builder().mustBeNonNull().build();
}
- @Before
+ @BeforeEach
public void setup() throws Exception {
configPtGroups.clear();
@@ -62,6 +64,20 @@ public void setup() throws Exception {
}
+ @Test
+ public void testInitialization() {
+ Long startTime = System.currentTimeMillis();
+
+ AndHow.Initialization init = new AndHow.Initialization();
+
+ Long endTime = System.currentTimeMillis();
+
+ assertEquals(this.getClass().getName(), init.getStackTrace()[0].getClassName());
+ assertEquals("testInitialization", init.getStackTrace()[0].getMethodName());
+ assertTrue(startTime <= init.getTimeStamp());
+ assertTrue(endTime >= init.getTimeStamp());
+ }
+
@Test
public void testIsInitialize() {
@@ -84,6 +100,7 @@ public void testCmdLineLoaderUsingClassBaseName() {
AndHow.instance(config);
+ assertTrue(AndHow.getInitializationTrace().length > 0);
assertEquals("test", SimpleParams.STR_BOB.getValue());
assertEquals("not_null", SimpleParams.STR_NULL.getValue());
assertEquals(false, SimpleParams.FLAG_TRUE.getValue());
diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowUsageExampleTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowUsageExampleTest.java
index d8e123d7..c63d35cd 100644
--- a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowUsageExampleTest.java
+++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowUsageExampleTest.java
@@ -1,10 +1,10 @@
package org.yarnandtail.andhow;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.yarnandtail.andhow.api.AppFatalException;
import org.yarnandtail.andhow.internal.RequirementProblem;
import org.yarnandtail.andhow.load.KeyValuePairLoader;
@@ -21,7 +21,7 @@ public class AndHowUsageExampleTest extends AndHowCoreTestBase {
String[] cmdLineArgsWFullClassName = new String[0];
- @Before
+ @BeforeEach
public void setup() {
cmdLineArgsWFullClassName = new String[] {
diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHow_AliasInTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHow_AliasInTest.java
index 19e24a7b..314d8665 100644
--- a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHow_AliasInTest.java
+++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHow_AliasInTest.java
@@ -1,7 +1,7 @@
package org.yarnandtail.andhow;
import java.util.List;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.springframework.mock.jndi.SimpleNamingContextBuilder;
import org.yarnandtail.andhow.api.AppFatalException;
import org.yarnandtail.andhow.internal.ConstructionProblem;
@@ -10,7 +10,7 @@
import org.yarnandtail.andhow.property.IntProp;
import org.yarnandtail.andhow.property.StrProp;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
/**
*
diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHow_AliasOutTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHow_AliasOutTest.java
index f40eb774..a860581c 100644
--- a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHow_AliasOutTest.java
+++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHow_AliasOutTest.java
@@ -2,18 +2,14 @@
import java.util.List;
-import static org.junit.Assert.*;
-
-import org.junit.*;
+import static org.junit.jupiter.api.Assertions.*;
+import org.junit.jupiter.api.Test;
import org.yarnandtail.andhow.api.*;
import org.yarnandtail.andhow.export.SysPropExporter;
-import org.yarnandtail.andhow.util.AndHowUtil;
import org.yarnandtail.andhow.internal.ConstructionProblem;
-import org.yarnandtail.andhow.internal.NameAndProperty;
import org.yarnandtail.andhow.property.IntProp;
import org.yarnandtail.andhow.property.StrProp;
-import org.yarnandtail.andhow.util.NameUtil;
/**
*
diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/PropertyValueTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/PropertyValueTest.java
index 6b99d4bb..771214b4 100644
--- a/andhow-core/src/test/java/org/yarnandtail/andhow/PropertyValueTest.java
+++ b/andhow-core/src/test/java/org/yarnandtail/andhow/PropertyValueTest.java
@@ -1,9 +1,10 @@
package org.yarnandtail.andhow;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
+import org.yarnandtail.andhow.api.ParsingException;
import org.yarnandtail.andhow.property.StrProp;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
import static org.yarnandtail.andhow.SimpleParams.*;
/**
@@ -140,9 +141,11 @@ public void testGetValue() {
assertEquals(SPV5.getValue(), "abc");
}
- @Test(expected = RuntimeException.class)
+ @Test
public void testConstructor() {
- PropertyValue NULL_PROP = new PropertyValue((StrProp)null, null);
+ assertThrows(RuntimeException.class, () ->
+ new PropertyValue((StrProp)null, null)
+ );
}
}
diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigGetterAndSetterTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigGetterAndSetterTest.java
new file mode 100644
index 00000000..63b1da97
--- /dev/null
+++ b/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigGetterAndSetterTest.java
@@ -0,0 +1,431 @@
+/*
+ */
+package org.yarnandtail.andhow;
+
+import java.util.*;
+
+import org.junit.jupiter.api.Test;
+import org.yarnandtail.andhow.StdConfig.StdConfigImpl;
+import org.yarnandtail.andhow.api.*;
+import org.yarnandtail.andhow.internal.ValidatedValuesWithContextMutable;
+import org.yarnandtail.andhow.load.*;
+import org.yarnandtail.andhow.load.std.*;
+import org.yarnandtail.andhow.name.CaseInsensitiveNaming;
+import org.yarnandtail.andhow.property.DblProp;
+import org.yarnandtail.andhow.property.LngProp;
+import org.yarnandtail.andhow.property.StrProp;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.hamcrest.Matchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ *
+ * @author ericeverman
+ */
+public class StdConfigGetterAndSetterTest {
+
+ public StdConfigGetterAndSetterTest() {
+ }
+
+ /**
+ * Test of getNamingStrategy method, of class BaseConfig.
+ */
+ @Test
+ public void testGetNamingStrategy() {
+ StdConfigImpl std = StdConfig.instance();
+ assertTrue(std.getNamingStrategy() instanceof CaseInsensitiveNaming);
+ }
+
+ /**
+ * Test of buildLoaders method, of class BaseConfig.
+ */
+ @Test
+ public void testBuildLoadersWithoutAnyChanges() {
+ StdConfigImpl std = StdConfig.instance();
+ List loaders = std.buildLoaders();
+ assertEquals(7, loaders.size());
+ assertEquals(StdFixedValueLoader.class, loaders.get(0).getClass());
+ assertEquals(StdMainStringArgsLoader.class, loaders.get(1).getClass());
+ assertEquals(StdSysPropLoader.class, loaders.get(2).getClass());
+ assertEquals(StdEnvVarLoader.class, loaders.get(3).getClass());
+ assertEquals(StdJndiLoader.class, loaders.get(4).getClass());
+ assertEquals(StdPropFileOnFilesystemLoader.class, loaders.get(5).getClass());
+ assertEquals(StdPropFileOnClasspathLoader.class, loaders.get(6).getClass());
+ }
+
+ @Test
+ public void testBuildLoadersWithCustomListOfStandardLoaders() {
+ StdConfigImpl std = StdConfig.instance();
+
+ List> newLoaders = new ArrayList();
+ newLoaders.add(StdFixedValueLoader.class);
+ newLoaders.add(StdJndiLoader.class);
+
+ std.setStandardLoaders(newLoaders);
+ List loaders = std.buildLoaders();
+ assertEquals(2, loaders.size());
+ assertEquals(StdFixedValueLoader.class, loaders.get(0).getClass());
+ assertEquals(StdJndiLoader.class, loaders.get(1).getClass());
+
+ std.setStandardLoaders(std.getDefaultLoaderList());
+ loaders = std.buildLoaders();
+ assertEquals(7, loaders.size());
+ assertEquals(StdFixedValueLoader.class, loaders.get(0).getClass());
+ assertEquals(StdPropFileOnClasspathLoader.class, loaders.get(6).getClass());
+
+ std.setStandardLoaders(StdSysPropLoader.class, StdPropFileOnFilesystemLoader.class);
+ loaders = std.buildLoaders();
+ assertEquals(2, loaders.size());
+ assertEquals(StdSysPropLoader.class, loaders.get(0).getClass());
+ assertEquals(StdPropFileOnFilesystemLoader.class, loaders.get(1).getClass());
+ }
+
+ @Test
+ public void testBuildLoadersWithInsertingLoadersBeforeAndAfter() {
+ StdConfigImpl std = StdConfig.instance();
+
+ Loader loader1 = new MapLoader();
+ Loader loader2 = new KeyValuePairLoader();
+ Loader loader3 = new PropFileOnClasspathLoader();
+ Loader loader4 = new PropFileOnFilesystemLoader();
+ Loader loader5 = new FixedValueLoader();
+ Loader loader6 = new MapLoader();
+ Loader loader7 = new KeyValuePairLoader();
+ Loader loader8 = new PropFileOnClasspathLoader();
+ Loader loader9 = new PropFileOnFilesystemLoader();
+
+ //At the beginning of the list
+ std.insertLoaderBefore(StdFixedValueLoader.class, loader1);
+ std.insertLoaderBefore(StdFixedValueLoader.class, loader2);
+ std.insertLoaderAfter(StdFixedValueLoader.class, loader3);
+ std.insertLoaderAfter(StdFixedValueLoader.class, loader4);
+ std.insertLoaderBefore(StdMainStringArgsLoader.class, loader5);
+
+ //At the end of the list
+ std.insertLoaderBefore(StdPropFileOnFilesystemLoader.class, loader6);
+ std.insertLoaderAfter(StdPropFileOnFilesystemLoader.class, loader7);
+ std.insertLoaderBefore(StdPropFileOnClasspathLoader.class, loader8);
+ std.insertLoaderAfter(StdPropFileOnClasspathLoader.class, loader9);
+
+ List loaders = std.buildLoaders();
+ assertEquals(16, loaders.size());
+ assertEquals(loader1, loaders.get(0));
+ assertEquals(loader2, loaders.get(1));
+ assertEquals(StdFixedValueLoader.class, loaders.get(2).getClass());
+ assertEquals(loader3, loaders.get(3));
+ assertEquals(loader4, loaders.get(4));
+ assertEquals(loader5, loaders.get(5));
+ assertEquals(StdMainStringArgsLoader.class, loaders.get(6).getClass());
+ assertEquals(StdSysPropLoader.class, loaders.get(7).getClass());
+ assertEquals(StdEnvVarLoader.class, loaders.get(8).getClass());
+ assertEquals(StdJndiLoader.class, loaders.get(9).getClass());
+ assertEquals(loader6, loaders.get(10));
+ assertEquals(StdPropFileOnFilesystemLoader.class, loaders.get(11).getClass());
+ assertEquals(loader7, loaders.get(12));
+ assertEquals(loader8, loaders.get(13));
+ assertEquals(StdPropFileOnClasspathLoader.class, loaders.get(14).getClass());
+ assertEquals(loader9, loaders.get(15));
+ }
+
+ @Test
+ public void FixedValuesBasedOnPropertiesTest() {
+ MyStdConfig config = new MyStdConfig();
+
+ StrProp MY_STR_1 = StrProp.builder().build();
+ LngProp MY_LNG_2 = LngProp.builder().build();
+ DblProp MY_DBL_3 = DblProp.builder().build();
+
+ config.addFixedValue(MY_STR_1, "ABC");
+ config.addFixedValue(MY_LNG_2, 23L);
+
+ assertEquals(2, config.getFixedValues().size());
+ assertTrue(containsPropertyAndValue(config.getFixedValues(), MY_STR_1, "ABC"));
+ assertTrue(containsPropertyAndValue(config.getFixedValues(), MY_LNG_2, 23L));
+
+ //Try to add a duplicate property
+ assertThrows(IllegalArgumentException.class, () -> {
+ config.addFixedValue(MY_STR_1, "ZZZ");
+ });
+
+ assertEquals("ABC",
+ config.getFixedValues().stream().filter(p -> p.getProperty().equals(MY_STR_1)).findFirst().get()
+ .getValue().toString(),
+ "The value set for this Property should be unchanged");
+
+ //Try to add a null property
+ assertThrows(IllegalArgumentException.class, () -> {
+ config.addFixedValue((Property)null, "ZZZ");
+ });
+
+ assertEquals(2, config.getFixedValues().size(), "Still should have two values");
+
+ //Try removing some
+ config.removeFixedValue(MY_STR_1);
+ assertEquals(1, config.getFixedValues().size());
+ assertTrue(containsPropertyAndValue(config.getFixedValues(), MY_LNG_2, 23L));
+
+ //it should be OK and a no-op to attempt to remove a non-existing property
+ //...but how would this ever happen??
+ config.removeFixedValue(MY_DBL_3);
+ assertEquals(1, config.getFixedValues().size());
+
+ config.removeFixedValue(MY_LNG_2);
+ assertEquals(0, config.getFixedValues().size());
+ }
+
+
+ @Test
+ public void FixedValuesBasedOnNamesTest() {
+ MyStdConfig config = new MyStdConfig();
+
+ //These properties don't really exist - no checking is done until loading, when
+ //the Loader attempts to match up the name w/ a property. For now this just tests
+ //the logic in StdConfig.
+ config.addFixedValue("MY_STR_1", "ABC");
+ config.addFixedValue("MY_LNG_2", 23L);
+
+ assertEquals(2, config.getFixedKeyObjectPairValues().size());
+ assertTrue(containsNameAndValue(config.getFixedKeyObjectPairValues(), "MY_STR_1", "ABC"));
+ assertTrue(containsNameAndValue(config.getFixedKeyObjectPairValues(), "MY_LNG_2", 23L));
+
+ //Try to add a duplicate property
+ assertThrows(IllegalArgumentException.class, () -> {
+ config.addFixedValue("MY_STR_1", "ZZZ");
+ });
+
+ assertEquals("ABC",
+ config.getFixedKeyObjectPairValues().stream().filter(k -> k.getName().equals("MY_STR_1")).findFirst().get()
+ .getValue().toString(),
+ "The value set for this Property should be unchanged");
+
+ //Try to add a null property name
+ assertThrows(IllegalArgumentException.class, () -> {
+ config.addFixedValue((String)null, "ZZZ");
+ });
+
+ //Try to add an empty (all space) property name
+ assertThrows(IllegalArgumentException.class, () -> {
+ config.addFixedValue(" ", "ZZZ");
+ });
+
+ assertEquals(2, config.getFixedKeyObjectPairValues().size(), "Still should have two values");
+
+ //Try removing some
+ config.removeFixedValue("MY_STR_1");
+ assertEquals(1, config.getFixedKeyObjectPairValues().size());
+ assertTrue(containsNameAndValue(config.getFixedKeyObjectPairValues(), "MY_LNG_2", 23L));
+
+ //it should be OK and a no-op to attempt to remove a non-existing property, or a property not in the list.
+ config.removeFixedValue("MY_DBL_3");
+ assertEquals(1, config.getFixedKeyObjectPairValues().size());
+
+ config.removeFixedValue("MY_LNG_2");
+ assertEquals(0, config.getFixedKeyObjectPairValues().size());
+ }
+
+ @Test
+ public void setCmdLineArgsTest() {
+ MyStdConfig config = new MyStdConfig();
+
+ String[] args = new String[] {"abc=123", "xyz='456"};
+ config.setCmdLineArgs(args);
+ assertThat(config.getCmdLineArgs().toArray(), arrayContainingInAnyOrder(args));
+
+ Object o = TestUtil.getField(config.buildStdMainStringArgsLoader(), "keyValuePairs");
+ List kvps = (List)o;
+
+ assertEquals(2, kvps.size());
+ assertThat(kvps.toArray(), arrayContainingInAnyOrder(args));
+ }
+
+ @Test
+ public void setEnvironmentPropertiesTest() {
+ MyStdConfig config = new MyStdConfig();
+
+ Map envVars = new HashMap<>();
+ envVars.put("abc", "123");
+ envVars.put("xyz", "456");
+
+ config.setEnvironmentProperties(envVars);
+
+ assertEquals(2, config.getEnvironmentProperties().size());
+ assertTrue(envVars.equals(config.getEnvironmentProperties()));
+ assertFalse(envVars == config.getEnvironmentProperties(), "Should be disconnected object");
+
+ //Now try setting new values - they should replace the old
+ Map envVars2 = new HashMap<>();
+ envVars2.put("bob", "bob_val");
+ config.setEnvironmentProperties(envVars2);
+
+ assertEquals(1, config.getEnvironmentProperties().size());
+ assertTrue(envVars2.equals(config.getEnvironmentProperties()));
+
+ //Now set to null
+ config.setEnvironmentProperties(null);
+ assertNull(config.getEnvironmentProperties());
+
+ }
+
+ @Test
+ public void setClasspathPropFilePathViaStringTest() throws Exception {
+ MyStdConfig config = new MyStdConfig();
+ ValidatedValuesWithContextMutable vvs = new ValidatedValuesWithContextMutable();
+ Class> vvsClass = ValidatedValuesWithContext.class; //class of getEffectivePath argument
+
+ assertNull(config.getClasspathPropFileProp());
+ assertNull(config.getClasspathPropFilePath());
+ assertEquals("/andhow.properties",
+ TestUtil.stringMethod(config.buildStdPropFileOnClasspathLoader(), "getEffectivePath", vvs, vvsClass),
+ "Loader should see the default value");
+
+ config.setClasspathPropFilePath("/andhow.test.properties");
+ assertEquals("/andhow.test.properties", config.getClasspathPropFilePath());
+ assertNull(config.getClasspathPropFileProp());
+ assertEquals("/andhow.test.properties",
+ TestUtil.stringMethod(config.buildStdPropFileOnClasspathLoader(), "getEffectivePath", vvs, vvsClass),
+ "Loader should see this configured value");
+
+ config.setClasspathPropFilePath("/andhow.test.props");
+ assertEquals("/andhow.test.props", config.getClasspathPropFilePath());
+ assertEquals("/andhow.test.props",
+ TestUtil.stringMethod(config.buildStdPropFileOnClasspathLoader(), "getEffectivePath", vvs, vvsClass),
+ "Loader should see this configured value");
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ config.setClasspathPropFilePath("andhow.test.props");
+ }, "paths containing dots must start w/ a slash");
+
+ assertEquals("/andhow.test.props", config.getClasspathPropFilePath(), "Should be unchanged");
+
+ config.setClasspathPropFilePath("/org/comcorp/project/andhow.test.properties");
+ assertEquals("/org/comcorp/project/andhow.test.properties", config.getClasspathPropFilePath());
+ assertEquals("/org/comcorp/project/andhow.test.properties",
+ TestUtil.stringMethod(config.buildStdPropFileOnClasspathLoader(), "getEffectivePath", vvs, vvsClass),
+ "Loader should see this configured value");
+
+ config.setClasspathPropFilePath((String)null);
+ assertNull(config.getClasspathPropFilePath());
+ assertEquals("/andhow.properties",
+ TestUtil.stringMethod(config.buildStdPropFileOnClasspathLoader(), "getEffectivePath", vvs, vvsClass),
+ "Loader should see the default value");
+
+ }
+
+ @Test
+ public void setClasspathPropFilePathViaStrPropTest() throws Exception {
+ StrProp MY_PATH_PROPERTY = StrProp.builder().build();
+
+ MyStdConfig config = new MyStdConfig();
+
+ ValidatedValue validatedValue = new ValidatedValue(MY_PATH_PROPERTY, "/my.prop.file");
+ List vvList = new ArrayList<>();
+ vvList.add(validatedValue);
+
+ LoaderValues loaderValues = new LoaderValues(new FixedValueLoader(), vvList, ProblemList.EMPTY_PROBLEM_LIST);
+ ValidatedValuesWithContextMutable validatedValues = new ValidatedValuesWithContextMutable();
+ validatedValues.addValues(loaderValues);
+ Class> vvsClass = ValidatedValuesWithContext.class; //class of getEffectivePath argument
+
+ config.setClasspathPropFilePath(MY_PATH_PROPERTY);
+ assertNull(config.getClasspathPropFilePath());
+ assertEquals(MY_PATH_PROPERTY, config.getClasspathPropFileProp());
+ assertEquals("/my.prop.file",
+ TestUtil.stringMethod(config.buildStdPropFileOnClasspathLoader(), "getEffectivePath", validatedValues, vvsClass),
+ "Loader should see this configured value");
+
+ config.setClasspathPropFilePath((StrProp)null);
+ assertNull(config.getClasspathPropFilePath());
+ assertNull(config.getClasspathPropFileProp());
+ assertEquals("/andhow.properties",
+ TestUtil.stringMethod(config.buildStdPropFileOnClasspathLoader(), "getEffectivePath", validatedValues, vvsClass),
+ "Loader should revert to default");
+ }
+
+ @Test
+ public void setClasspathPropFilePathInteractionOfStringAndStrPropTest() {
+ StrProp MY_PATH_PROPERTY = StrProp.builder().build();
+ MyStdConfig config = new MyStdConfig();
+
+ config.setClasspathPropFilePath(MY_PATH_PROPERTY);
+ assertThrows(IllegalArgumentException.class, () -> {
+ config.setClasspathPropFilePath("/some.other.path");
+ }, "Can't set via String and StrProp at the same time");
+ assertNull(config.getClasspathPropFilePath(), "Should not have set the value due to error");
+
+ config.setClasspathPropFilePath((StrProp)null);
+ config.setClasspathPropFilePath("/some.other.path"); //now its OK
+ assertEquals("/some.other.path", config.getClasspathPropFilePath());
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ config.setClasspathPropFilePath(MY_PATH_PROPERTY);
+ }, "String version is set, so now this one causes the error");
+ assertNull(config.getClasspathPropFileProp(), "Should not have set the value due to error");
+
+ }
+
+ @Test void classpathPropertiesRequiredOrNotTest() {
+ MyStdConfig config = new MyStdConfig();
+
+ assertFalse(config.getClasspathPropFileRequired(), "False should be the default");
+ assertFalse(config.buildStdPropFileOnClasspathLoader().isMissingFileAProblem(), "False should be the default");
+
+ config.classpathPropertiesRequired();
+
+ assertTrue(config.getClasspathPropFileRequired());
+ assertTrue(config.buildStdPropFileOnClasspathLoader().isMissingFileAProblem());
+ }
+
+ @Test void filesystemPropertiesRequiredOrNotTest() {
+ MyStdConfig config = new MyStdConfig();
+
+ assertFalse(config.getFilesystemPropFileRequired(), "False should be the default");
+ assertFalse(config.buildStdPropFileOnFilesystemLoader().isMissingFileAProblem(), "False should be the default");
+
+ config.filesystemPropFileRequired();
+
+ assertTrue(config.getFilesystemPropFileRequired());
+ assertTrue(config.buildStdPropFileOnFilesystemLoader().isMissingFileAProblem());
+ }
+
+ boolean containsPropertyAndValue(List propertyValues, Property property, T value) {
+ PropertyValue pv = propertyValues.stream().filter(p -> p.getProperty().equals(property)).findFirst().get();
+ return pv != null && pv.getValue().equals(value);
+ }
+
+ boolean containsNameAndValue(List keyObjectPairs, String name, Object value) {
+ KeyObjectPair kop = keyObjectPairs.stream().filter(p -> p.getName().equals(name)).findFirst().get();
+ return kop != null && kop.getValue().equals(value);
+ }
+
+ /**
+ * Custom StdConfig class that has access methods for fields not otherwise accessable.
+ */
+ public static final class MyStdConfig extends StdConfig.StdConfigAbstract {
+ public List getFixedValues() {
+ return _fixedVals;
+ }
+
+ public List getFixedKeyObjectPairValues() {
+ return _fixedKeyObjectPairVals;
+ }
+
+ public List getCmdLineArgs() {
+ return _cmdLineArgs;
+ }
+
+ public Map getEnvironmentProperties() {
+ return envProperties;
+ }
+
+ public String getClasspathPropFilePath() { return classpathPropFilePathStr; }
+
+ public StrProp getClasspathPropFileProp() { return this.classpathPropFilePathProp; }
+
+ public boolean getClasspathPropFileRequired() { return _missingClasspathPropFileAProblem; }
+
+ public boolean getFilesystemPropFileRequired() { return _missingFilesystemPropFileAProblem; }
+ }
+
+}
diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigSimulatedAppTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigSimulatedAppTest.java
new file mode 100644
index 00000000..e38c12d6
--- /dev/null
+++ b/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigSimulatedAppTest.java
@@ -0,0 +1,267 @@
+package org.yarnandtail.andhow;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.mock.jndi.SimpleNamingContextBuilder;
+import org.yarnandtail.andhow.api.AppFatalException;
+import org.yarnandtail.andhow.api.Property;
+import org.yarnandtail.andhow.internal.LoaderProblem;
+import org.yarnandtail.andhow.internal.ValueProblem;
+import org.yarnandtail.andhow.load.KeyValuePairLoader;
+import org.yarnandtail.andhow.property.FlagProp;
+import org.yarnandtail.andhow.property.IntProp;
+import org.yarnandtail.andhow.property.StrProp;
+import static org.yarnandtail.andhow.load.KeyValuePairLoader.KVP_DELIMITER;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ *
+ * @author eeverman
+ */
+public class StdConfigSimulatedAppTest extends AndHowCoreTestBase {
+
+ private static final String GROUP_PATH = "org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup";
+ private static final String CLASSPATH_BEGINNING = "/org/yarnandtail/andhow/StdConfigSimulatedAppTest.";
+
+
+ @Test
+ public void testAllValuesAreSetViaCmdLineArgAndPropFile() {
+
+ //Prop name case ignored
+ String[] cmdLineArgs = new String[] {
+ GROUP_PATH + ".classpath_prop_file" + KVP_DELIMITER + CLASSPATH_BEGINNING + "all.props.speced.properties"
+ };
+
+ AndHowConfiguration config = AndHowCoreTestConfig.instance()
+ .group(SampleRestClientGroup.class)
+ .setCmdLineArgs(cmdLineArgs)
+ .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE)
+ .classpathPropertiesRequired();
+
+ AndHow.instance(config);
+
+ assertEquals("/org/yarnandtail/andhow/StdConfigSimulatedAppTest.all.props.speced.properties",
+ SampleRestClientGroup.CLASSPATH_PROP_FILE.getValue());
+ assertAllPointsSpecedValues("testAllValuesAreSetViaCmdLineArgAndPropFile");
+ }
+
+ @Test
+ public void testAllValuesAreSetViaFixedPropertyValueAndPropFile() {
+
+ AndHowConfiguration config = AndHowCoreTestConfig.instance()
+ .group(SampleRestClientGroup.class)
+ .addFixedValue(SampleRestClientGroup.CLASSPATH_PROP_FILE,
+ CLASSPATH_BEGINNING + "all.props.speced.properties") //set via FixedValue (PropertyValue)
+ .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE)
+ .classpathPropertiesRequired();
+
+ AndHow.instance(config);
+
+ assertEquals("/org/yarnandtail/andhow/StdConfigSimulatedAppTest.all.props.speced.properties",
+ SampleRestClientGroup.CLASSPATH_PROP_FILE.getValue());
+ assertAllPointsSpecedValues("testAllValuesAreSetViaFixedPropertyValueAndPropFile");
+ }
+
+ @Test
+ public void testAllValuesAreSetViaFixedPropertyValueAndPropFileOverridingWithFixedPropertyValue() {
+
+ AndHowConfiguration config = AndHowCoreTestConfig.instance()
+ .group(SampleRestClientGroup.class)
+ .addFixedValue(SampleRestClientGroup.CLASSPATH_PROP_FILE,
+ CLASSPATH_BEGINNING + "all.props.speced.properties") //set via FixedValue (PropertyValue)
+ .addFixedValue(SampleRestClientGroup.REST_PORT, 99) //Override value in prop file
+ .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE)
+ .classpathPropertiesRequired();
+
+ AndHow.instance(config);
+
+ assertEquals(" Big App ", SampleRestClientGroup.APP_NAME.getValue()); //Just check one prop file val
+ assertEquals(99, SampleRestClientGroup.REST_PORT.getValue(), "Fixed value should override prop file");
+ }
+
+ @Test
+ public void testAllValuesAreSetViaFixedKeyObjectPairAndPropFile() {
+
+ AndHowConfiguration config = AndHowCoreTestConfig.instance()
+ .group(SampleRestClientGroup.class)
+ .addFixedValue(GROUP_PATH + ".classpath_prop_file", /* case ignored */
+ CLASSPATH_BEGINNING + "all.props.speced.properties") //set via FixedValue (KeyObjectPair)
+ .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE)
+ .classpathPropertiesRequired();
+
+ AndHow.instance(config);
+
+ assertEquals("/org/yarnandtail/andhow/StdConfigSimulatedAppTest.all.props.speced.properties",
+ SampleRestClientGroup.CLASSPATH_PROP_FILE.getValue());
+ assertAllPointsSpecedValues("testAllValuesAreSetViaFixedKeyObjectPairAndPropFile");
+ }
+
+ @Test
+ public void testAllValuesAreSetViaFixedKeyObjectPairAndPropFileOverridingWithFixedKeyObjectPair() {
+
+ AndHowConfiguration config = AndHowCoreTestConfig.instance()
+ .group(SampleRestClientGroup.class)
+ .addFixedValue(GROUP_PATH + ".classpath_prop_file", /* case ignored */
+ CLASSPATH_BEGINNING + "all.props.speced.properties") //set via FixedValue (KeyObjectPair)
+ .addFixedValue(GROUP_PATH + ".REST_PORT", 98) //Override value in prop file
+ .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE)
+ .classpathPropertiesRequired();
+
+ AndHow.instance(config);
+
+ assertEquals(" Big App ", SampleRestClientGroup.APP_NAME.getValue()); //Just check one prop file val
+ assertEquals(98, SampleRestClientGroup.REST_PORT.getValue(), "Fixed value should override prop file");
+ }
+
+ @Test
+ public void testAllValuesAreSetViaEnvVarAndPropFile() {
+
+ Map envvars = new HashMap();
+ envvars.put(
+ GROUP_PATH + ".classpath_prop_file", /* case ignored */
+ CLASSPATH_BEGINNING + "all.props.speced.properties"
+ );
+
+ AndHowConfiguration config = AndHowCoreTestConfig.instance()
+ .group(SampleRestClientGroup.class)
+ .setEnvironmentProperties(envvars)
+ .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE)
+ .classpathPropertiesRequired();
+
+ AndHow.instance(config);
+
+ assertEquals("/org/yarnandtail/andhow/StdConfigSimulatedAppTest.all.props.speced.properties",
+ SampleRestClientGroup.CLASSPATH_PROP_FILE.getValue());
+ assertAllPointsSpecedValues("testAllValuesAreSetViaEnvVarAndPropFile");
+ }
+
+ @Test
+ public void testAllValuesAreSetViaSystemPropertiesAndPropFile() {
+
+ Properties props = new Properties();
+ props.put(
+ GROUP_PATH + ".classpath_prop_file", /* case ignored */
+ CLASSPATH_BEGINNING + "all.props.speced.properties"
+ );
+
+ AndHowConfiguration config = AndHowCoreTestConfig.instance()
+ .group(SampleRestClientGroup.class)
+ .setSystemProperties(props)
+ .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE)
+ .classpathPropertiesRequired();
+
+ AndHow.instance(config);
+
+ assertEquals("/org/yarnandtail/andhow/StdConfigSimulatedAppTest.all.props.speced.properties",
+ SampleRestClientGroup.CLASSPATH_PROP_FILE.getValue());
+ assertAllPointsSpecedValues("testAllValuesAreSetViaSystemPropertiesAndPropFile");
+ }
+
+ /**
+ * Asserts each value to match the values spec'ed in the StdConfigSimulatedAppTest.all.props.speced.properties file.
+ */
+ void assertAllPointsSpecedValues(String calledFromTestName) {
+ String failDesc = "assertion failure in the test named '" + calledFromTestName + "'";
+
+ assertEquals(" Big App ", SampleRestClientGroup.APP_NAME.getValue(), failDesc);
+ assertEquals("aquarius.usgs.gov", SampleRestClientGroup.REST_HOST.getValue(), failDesc);
+ assertEquals(new Integer(8080), SampleRestClientGroup.REST_PORT.getValue(), failDesc);
+ assertEquals("doquery/", SampleRestClientGroup.REST_SERVICE_NAME.getValue(), failDesc);
+ assertEquals("abc123", SampleRestClientGroup.AUTH_KEY.getValue(), failDesc);
+ assertEquals(new Integer(4), SampleRestClientGroup.RETRY_COUNT.getValue(), failDesc);
+ assertFalse(SampleRestClientGroup.REQUEST_META_DATA.getValue(), failDesc);
+ assertTrue(SampleRestClientGroup.REQUEST_SUMMARY_DATA.getValue(), failDesc);
+ }
+
+ @Test
+ public void testMinimumPropsAreSetViaCmdLineArgAndPropFile() {
+
+ String[] cmdLineArgs = new String[] {
+ GROUP_PATH + ".CLASSPATH_PROP_FILE" + KVP_DELIMITER + CLASSPATH_BEGINNING + "minimum.props.speced.properties"
+ };
+
+ AndHowConfiguration config = AndHowCoreTestConfig.instance()
+ .group(SampleRestClientGroup.class)
+ .setCmdLineArgs(cmdLineArgs)
+ .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE)
+ .classpathPropertiesRequired();
+
+ AndHow.instance(config);
+
+ assertEquals("/org/yarnandtail/andhow/StdConfigSimulatedAppTest.minimum.props.speced.properties",
+ SampleRestClientGroup.CLASSPATH_PROP_FILE.getValue());
+ assertEquals("aquarius.usgs.gov", SampleRestClientGroup.REST_HOST.getValue());
+ assertEquals(new Integer(8080), SampleRestClientGroup.REST_PORT.getValue());
+ assertEquals("query/", SampleRestClientGroup.REST_SERVICE_NAME.getValue()); //a default value
+ assertEquals("abc123", SampleRestClientGroup.AUTH_KEY.getValue());
+ assertEquals(new Integer(2), SampleRestClientGroup.RETRY_COUNT.getValue()); //a default
+ assertTrue(SampleRestClientGroup.REQUEST_META_DATA.getValue()); //a default
+ assertFalse(SampleRestClientGroup.REQUEST_SUMMARY_DATA.getValue()); //a default
+
+ }
+
+ @Test
+ public void testInvalidValuesViaCmdLineArgAndPropFile() throws Exception {
+
+ SimpleNamingContextBuilder jndi = getJndi();
+ jndi.activate();
+
+ String[] cmdLineArgs = new String[] {
+ GROUP_PATH + ".CLASSPATH_PROP_FILE" + KVP_DELIMITER + CLASSPATH_BEGINNING + "invalid.properties"
+ };
+
+ try {
+
+ //Error expected b/c some values are invalid
+ AndHowConfiguration config = AndHowCoreTestConfig.instance()
+ .group(SampleRestClientGroup.class)
+ .setCmdLineArgs(cmdLineArgs)
+ .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE)
+ .classpathPropertiesRequired();
+
+ AndHow.instance(config);
+
+ } catch (AppFatalException e) {
+
+ //Value Problems (validation)
+ //Due to loading from a prop file, the order of the file is not preserved,
+ //so we cannot know the order that problems were encountered.
+ ArrayList> expectedProblemPoints = new ArrayList();
+ expectedProblemPoints.add(SampleRestClientGroup.REST_HOST);
+ expectedProblemPoints.add(SampleRestClientGroup.REST_PORT);
+ expectedProblemPoints.add(SampleRestClientGroup.REST_SERVICE_NAME);
+
+ assertEquals(3, e.getProblems().filter(ValueProblem.class).size());
+ assertTrue(expectedProblemPoints.contains(e.getProblems().filter(ValueProblem.class).get(0).getBadValueCoord().getProperty()));
+ assertTrue(expectedProblemPoints.contains(e.getProblems().filter(ValueProblem.class).get(1).getBadValueCoord().getProperty()));
+ assertTrue(expectedProblemPoints.contains(e.getProblems().filter(ValueProblem.class).get(2).getBadValueCoord().getProperty()));
+
+ //
+ // Loader problems
+ assertEquals(1, e.getProblems().filter(LoaderProblem.class).size());
+ assertEquals(SampleRestClientGroup.RETRY_COUNT, e.getProblems().filter(LoaderProblem.class).get(0).getBadValueCoord().getProperty());
+ }
+
+
+ }
+
+ interface SampleRestClientGroup {
+
+ StrProp CLASSPATH_PROP_FILE = StrProp.builder().desc("Classpath location of a properties file w/ props").build();
+ StrProp APP_NAME = StrProp.builder().aliasIn("app.name").aliasIn("app_name").build();
+ StrProp REST_HOST = StrProp.builder().mustMatchRegex(".*\\.usgs\\.gov") .mustBeNonNull().build();
+ IntProp REST_PORT = IntProp.builder().mustBeNonNull().mustBeGreaterThanOrEqualTo(80).mustBeLessThan(10000).build();
+ StrProp REST_SERVICE_NAME = StrProp.builder().defaultValue("query/").mustEndWith("/").build();
+ StrProp AUTH_KEY = StrProp.builder().mustBeNonNull().build();
+ IntProp RETRY_COUNT = IntProp.builder().defaultValue(2).build();
+ FlagProp REQUEST_META_DATA = FlagProp.builder().defaultValue(true).build();
+ FlagProp REQUEST_SUMMARY_DATA = FlagProp.builder().build();
+ }
+
+}
diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigTest.java
deleted file mode 100644
index 4a0e615d..00000000
--- a/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- */
-package org.yarnandtail.andhow;
-
-import java.util.ArrayList;
-import java.util.List;
-import org.junit.Test;
-import org.yarnandtail.andhow.StdConfig.StdConfigImpl;
-import org.yarnandtail.andhow.api.Loader;
-import org.yarnandtail.andhow.api.StandardLoader;
-import org.yarnandtail.andhow.load.*;
-import org.yarnandtail.andhow.load.std.*;
-import org.yarnandtail.andhow.name.CaseInsensitiveNaming;
-
-import static org.junit.Assert.*;
-
-/**
- *
- * @author ericeverman
- */
-public class StdConfigTest {
-
- public StdConfigTest() {
- }
-
- /**
- * Test of getNamingStrategy method, of class BaseConfig.
- */
- @Test
- public void testGetNamingStrategy() {
- StdConfigImpl std = StdConfig.instance();
- assertTrue(std.getNamingStrategy() instanceof CaseInsensitiveNaming);
- }
-
- /**
- * Test of buildLoaders method, of class BaseConfig.
- */
- @Test
- public void testBuildLoadersWithoutAnyChanges() {
- StdConfigImpl std = StdConfig.instance();
- List loaders = std.buildLoaders();
- assertEquals(7, loaders.size());
- assertEquals(StdFixedValueLoader.class, loaders.get(0).getClass());
- assertEquals(StdMainStringArgsLoader.class, loaders.get(1).getClass());
- assertEquals(StdSysPropLoader.class, loaders.get(2).getClass());
- assertEquals(StdEnvVarLoader.class, loaders.get(3).getClass());
- assertEquals(StdJndiLoader.class, loaders.get(4).getClass());
- assertEquals(StdPropFileOnFilesystemLoader.class, loaders.get(5).getClass());
- assertEquals(StdPropFileOnClasspathLoader.class, loaders.get(6).getClass());
- }
-
- @Test
- public void testBuildLoadersWithCustomListOfStandardLoaders() {
- StdConfigImpl std = StdConfig.instance();
-
- List> newLoaders = new ArrayList();
- newLoaders.add(StdFixedValueLoader.class);
- newLoaders.add(StdJndiLoader.class);
-
- std.setStandardLoaders(newLoaders);
- List loaders = std.buildLoaders();
- assertEquals(2, loaders.size());
- assertEquals(StdFixedValueLoader.class, loaders.get(0).getClass());
- assertEquals(StdJndiLoader.class, loaders.get(1).getClass());
-
- std.setStandardLoaders(BaseConfig.getDefaultLoaderList());
- loaders = std.buildLoaders();
- assertEquals(7, loaders.size());
- assertEquals(StdFixedValueLoader.class, loaders.get(0).getClass());
- assertEquals(StdPropFileOnClasspathLoader.class, loaders.get(6).getClass());
-
- std.setStandardLoaders(StdSysPropLoader.class, StdPropFileOnFilesystemLoader.class);
- loaders = std.buildLoaders();
- assertEquals(2, loaders.size());
- assertEquals(StdSysPropLoader.class, loaders.get(0).getClass());
- assertEquals(StdPropFileOnFilesystemLoader.class, loaders.get(1).getClass());
- }
-
- @Test
- public void testBuildLoadersWithInsertingLoadersBeforeAndAfter() {
- StdConfigImpl std = StdConfig.instance();
-
- Loader loader1 = new MapLoader();
- Loader loader2 = new KeyValuePairLoader();
- Loader loader3 = new PropFileOnClasspathLoader();
- Loader loader4 = new PropFileOnFilesystemLoader();
- Loader loader5 = new FixedValueLoader();
- Loader loader6 = new MapLoader();
- Loader loader7 = new KeyValuePairLoader();
- Loader loader8 = new PropFileOnClasspathLoader();
- Loader loader9 = new PropFileOnFilesystemLoader();
-
- //At the beginning of the list
- std.insertLoaderBefore(StdFixedValueLoader.class, loader1);
- std.insertLoaderBefore(StdFixedValueLoader.class, loader2);
- std.insertLoaderAfter(StdFixedValueLoader.class, loader3);
- std.insertLoaderAfter(StdFixedValueLoader.class, loader4);
- std.insertLoaderBefore(StdMainStringArgsLoader.class, loader5);
-
- //At the end of the list
- std.insertLoaderBefore(StdPropFileOnFilesystemLoader.class, loader6);
- std.insertLoaderAfter(StdPropFileOnFilesystemLoader.class, loader7);
- std.insertLoaderBefore(StdPropFileOnClasspathLoader.class, loader8);
- std.insertLoaderAfter(StdPropFileOnClasspathLoader.class, loader9);
-
- List loaders = std.buildLoaders();
- assertEquals(16, loaders.size());
- assertEquals(loader1, loaders.get(0));
- assertEquals(loader2, loaders.get(1));
- assertEquals(StdFixedValueLoader.class, loaders.get(2).getClass());
- assertEquals(loader3, loaders.get(3));
- assertEquals(loader4, loaders.get(4));
- assertEquals(loader5, loaders.get(5));
- assertEquals(StdMainStringArgsLoader.class, loaders.get(6).getClass());
- assertEquals(StdSysPropLoader.class, loaders.get(7).getClass());
- assertEquals(StdEnvVarLoader.class, loaders.get(8).getClass());
- assertEquals(StdJndiLoader.class, loaders.get(9).getClass());
- assertEquals(loader6, loaders.get(10));
- assertEquals(StdPropFileOnFilesystemLoader.class, loaders.get(11).getClass());
- assertEquals(loader7, loaders.get(12));
- assertEquals(loader8, loaders.get(13));
- assertEquals(StdPropFileOnClasspathLoader.class, loaders.get(14).getClass());
- assertEquals(loader9, loaders.get(15));
- }
-
-}
diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/TestUtil.java b/andhow-core/src/test/java/org/yarnandtail/andhow/TestUtil.java
new file mode 100644
index 00000000..9b1ce7db
--- /dev/null
+++ b/andhow-core/src/test/java/org/yarnandtail/andhow/TestUtil.java
@@ -0,0 +1,92 @@
+package org.yarnandtail.andhow;
+
+import org.junit.platform.commons.function.Try;
+import org.junit.platform.commons.support.HierarchyTraversalMode;
+import org.junit.platform.commons.support.ReflectionSupport;
+import org.junit.platform.commons.util.Preconditions;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP;
+
+/**
+ * Testing Utils
+ */
+public class TestUtil {
+
+ /**
+ * Invokes the named String returning method on the instance via reflection,
+ * bypassing visibility.
+ *
+ * For some tests, this is an easy way test the internal effect of some actions.
+ * JUnit ReflectionSupport *should* find methods without having to pass types, but
+ * this bug prevents that:
+ * https://github.com/junit-team/junit5/issues/2663
+ *
+ * @param instance The object instance to call the method on.
+ * @param name The name of a String returning method, which must be an instance method.
+ * @param args Arguments to the method
+ * @param types The argument types of the method which must match the method, not the args.
+ * @return The String returned by the method
+ * @throws Exception
+ */
+ public static String stringMethod(Object instance, String name, Object[] args, Class>[] types) {
+ return (String)invokeMethod(instance, name, args, types);
+ }
+
+ public static String stringMethod(Object instance, String name, Object arg, Class> type) {
+ return (String)invokeMethod(instance, name, new Object[]{arg}, new Class>[]{type});
+ }
+
+ public static String stringMethod(Object instance, String name, Object... args) {
+ return (String)invokeMethod(instance, name, args, getTypes(args));
+ }
+
+ public static Object getField(Object instance, String name) {
+ List fields = ReflectionSupport.findFields(instance.getClass(), f -> f.getName().equals(name), HierarchyTraversalMode.BOTTOM_UP);
+
+ if (fields.size() != 1) {
+ throw new IllegalArgumentException("Expected to find 1 field, instead found: " + fields.size());
+ }
+
+ Optional
-
-
-
- junit
- junit
-
-
diff --git a/andhow-testing/andhow-annotation-processor-tests/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrarLoaderTest.java b/andhow-testing/andhow-annotation-processor-tests/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrarLoaderTest.java
index a97661dd..1a3a92c4 100644
--- a/andhow-testing/andhow-annotation-processor-tests/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrarLoaderTest.java
+++ b/andhow-testing/andhow-annotation-processor-tests/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrarLoaderTest.java
@@ -1,12 +1,13 @@
package org.yarnandtail.andhow.service;
import java.util.List;
-import org.junit.Test;
+
+import org.junit.jupiter.api.Test;
import org.yarnandtail.andhow.internal.NameAndProperty;
import org.yarnandtail.classvistests.sample.NonStaticInnerClassSample;
import org.yarnandtail.classvistests.sample.PropertySample;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
import org.yarnandtail.andhow.api.GroupProxy;
diff --git a/andhow-testing/andhow-annotation-processor-tests/src/test/java/org/yarnandtail/classvistests/nonapp/sample/VisibilitySampleTest.java b/andhow-testing/andhow-annotation-processor-tests/src/test/java/org/yarnandtail/classvistests/nonapp/sample/VisibilitySampleTest.java
index 3297ee54..533b3f50 100644
--- a/andhow-testing/andhow-annotation-processor-tests/src/test/java/org/yarnandtail/classvistests/nonapp/sample/VisibilitySampleTest.java
+++ b/andhow-testing/andhow-annotation-processor-tests/src/test/java/org/yarnandtail/classvistests/nonapp/sample/VisibilitySampleTest.java
@@ -2,9 +2,9 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
/**
* These are some characterization tests of how visibility of non-public
diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/pom.xml b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/pom.xml
index 7f0b8de4..90eedebb 100755
--- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/pom.xml
+++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/pom.xml
@@ -3,7 +3,7 @@
org.yarnandtailandhow-multimodule-dataprocess
- 0.4.1-SNAPSHOT
+ 0.4.2-SNAPSHOTandhow-default-behavior-dep1
@@ -14,25 +14,6 @@
Sample to be used as a compiled jar by the
andhow-default-behavior-test to test property behavior
in external compiled libraries.
-
-
-
-
- org.yarnandtail
- andhow
- ${project.version}
-
-
-
- org.yarnandtail
- andhow-test-harness
- ${project.version}
- test
-
-
- junit
- junit
-
-
+
diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/src/test/java/com/dep1/EarthMapMakerNotUsingAHBaseTestClassTest.java b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/src/test/java/com/dep1/EarthMapMakerNotUsingAHBaseTestClassTest.java
index f9c3ad71..e8ea8766 100644
--- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/src/test/java/com/dep1/EarthMapMakerNotUsingAHBaseTestClassTest.java
+++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/src/test/java/com/dep1/EarthMapMakerNotUsingAHBaseTestClassTest.java
@@ -1,7 +1,7 @@
package com.dep1;
-import org.junit.Test;
-import static org.junit.Assert.*;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
/**
*
diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/src/test/java/com/dep1/EarthMapMakerUsingAHBaseTestClassTest.java b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/src/test/java/com/dep1/EarthMapMakerUsingAHBaseTestClassTest.java
index 7eb866b1..38770de4 100644
--- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/src/test/java/com/dep1/EarthMapMakerUsingAHBaseTestClassTest.java
+++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/src/test/java/com/dep1/EarthMapMakerUsingAHBaseTestClassTest.java
@@ -1,12 +1,12 @@
package com.dep1;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-import org.yarnandtail.andhow.AndHowTestBase;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.MethodOrderer;
+import static org.junit.jupiter.api.Assertions.*;
+import org.yarnandtail.andhow.AndHowJunit5TestBase;
import org.yarnandtail.andhow.NonProductionConfig;
-import static org.junit.Assert.*;
/**
* Note that these test methods are specified to be executed in Alph sort order
@@ -14,8 +14,8 @@
*
* @author ericeverman
*/
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class EarthMapMakerUsingAHBaseTestClassTest extends AndHowTestBase {
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class EarthMapMakerUsingAHBaseTestClassTest extends AndHowJunit5TestBase {
public EarthMapMakerUsingAHBaseTestClassTest() {
}
diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/pom.xml b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/pom.xml
index 4d6b8d65..ed4b2157 100644
--- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/pom.xml
+++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/pom.xml
@@ -4,7 +4,7 @@
org.yarnandtailandhow-multimodule-dataprocess
- 0.4.1-SNAPSHOT
+ 0.4.2-SNAPSHOTandhow-default-behavior-dep2jar
@@ -12,24 +12,5 @@
andhow-default-behavior-test to test property behavior
in external compiled libraries.
-
-
-
- org.yarnandtail
- andhow
- ${project.version}
-
-
-
- org.yarnandtail
- andhow-test-harness
- ${project.version}
- test
-
-
- junit
- junit
-
-
\ No newline at end of file
diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/src/test/java/com/dep2/MarsMapMakerNotUsingAHBaseTestClassTest.java b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/src/test/java/com/dep2/MarsMapMakerNotUsingAHBaseTestClassTest.java
index 051d3f9c..026ec253 100644
--- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/src/test/java/com/dep2/MarsMapMakerNotUsingAHBaseTestClassTest.java
+++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/src/test/java/com/dep2/MarsMapMakerNotUsingAHBaseTestClassTest.java
@@ -1,7 +1,7 @@
package com.dep2;
-import org.junit.Test;
-import static org.junit.Assert.*;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
/**
*
diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/src/test/java/com/dep2/MarsMapMakerUsingAHBaseTestClassTest.java b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/src/test/java/com/dep2/MarsMapMakerUsingAHBaseTestClassTest.java
index fcca940f..8cc8a8ec 100644
--- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/src/test/java/com/dep2/MarsMapMakerUsingAHBaseTestClassTest.java
+++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/src/test/java/com/dep2/MarsMapMakerUsingAHBaseTestClassTest.java
@@ -1,12 +1,12 @@
package com.dep2;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-import org.yarnandtail.andhow.AndHowTestBase;
-import org.yarnandtail.andhow.NonProductionConfig;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.MethodOrderer;
+import static org.junit.jupiter.api.Assertions.*;
-import static org.junit.Assert.*;
+import org.yarnandtail.andhow.AndHowJunit5TestBase;
+import org.yarnandtail.andhow.NonProductionConfig;
/**
* Note that these test methods are specified to be executed in Alph sort order
@@ -14,8 +14,8 @@
*
* @author ericeverman
*/
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class MarsMapMakerUsingAHBaseTestClassTest extends AndHowTestBase {
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class MarsMapMakerUsingAHBaseTestClassTest extends AndHowJunit5TestBase {
public MarsMapMakerUsingAHBaseTestClassTest() {
}
diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/pom.xml b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/pom.xml
index 097c0780..14712a46 100755
--- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/pom.xml
+++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/pom.xml
@@ -3,7 +3,7 @@
org.yarnandtailandhow-multimodule-dataprocess
- 0.4.1-SNAPSHOT
+ 0.4.2-SNAPSHOTandhow-default-behavior-test
@@ -24,22 +24,6 @@
andhow-default-behavior-dep2${project.version}
-
-
- org.yarnandtail
- andhow
- ${project.version}
-
-
- ${project.groupId}
- andhow-test-harness
- ${project.version}
- test
-
-
- junit
- junit
- org.springframeworkspring-test
diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/com/dep1/EarthMapMakerTest.java b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/com/dep1/EarthMapMakerTest.java
index d2bd0282..68d8729d 100644
--- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/com/dep1/EarthMapMakerTest.java
+++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/com/dep1/EarthMapMakerTest.java
@@ -1,29 +1,33 @@
package com.dep1;
import org.dataprocess.ExternalServiceConnector;
-import org.junit.Test;
import org.springframework.mock.jndi.SimpleNamingContextBuilder;
import org.yarnandtail.andhow.*;
import org.yarnandtail.andhow.api.AppFatalException;
import org.yarnandtail.andhow.internal.LoaderProblem;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
/**
*
* @author ericeverman
*/
-public class EarthMapMakerTest extends AndHowTestBase {
+public class EarthMapMakerTest extends AndHowJunit5TestBase {
+ /**
+ * This test will see the properties set in the andhow.properties file on the classpath.
+ * Currently that file is at: /src/main/resources/andhow.properties
+ * Optionally, an set of properties used only during testing could be placed at:
+ * /src/test/resources/andhow.properties
+ * That file would then be used exclusively during testing.
+ */
@Test
public void testConfigFromPropertiesFileOnly() {
EarthMapMaker emm = new EarthMapMaker();
- //Actual values
+ //These values match what is loaded from the andhow.properties file
assertEquals("My Map", emm.getMapName());
assertEquals(-125, emm.getWestBound());
assertEquals(51, emm.getNorthBound());
@@ -34,62 +38,44 @@ public void testConfigFromPropertiesFileOnly() {
assertEquals("http://prod.mybiz.com.logger/EarthMapMaker/", emm.getLogServerUrl());
}
-
- @Test
- public void testConfigFromSysProps() {
-
- System.setProperty("com.dep1.EarthMapMaker.MAP_NAME", "SysPropMapName");
- System.setProperty("com.dep1.EarthMapMaker.EAST_BOUND", "-99");
-
- NonProductionConfig.instance().forceBuild();
-
- EarthMapMaker emm = new EarthMapMaker();
-
- //Actual values
- assertEquals("SysPropMapName", emm.getMapName());
- assertEquals(-99, emm.getEastBound());
- }
-
+
+ /**
+ * Sometimes a test will need custom configuration, such as wanting to
+ * test your app when it is configured a particular way.
+ * This example does that for just this one method.
+ *
+ * By using the base class AndHowJunit5TestBase, AndHow
+ * configuration will automatically revert to its original state
+ * (configured via the property file andhow.properties)
+ * when this method completes.
+ */
@Test
- public void testConfigFromCmdLineThenSysPropsViaNonProdConfigForceBuild() {
-
- System.setProperty("com.dep1.EarthMapMaker.MAP_NAME", "SysPropMapName");
- System.setProperty("com.dep1.EarthMapMaker.EAST_BOUND", "-99");
-
+ public void testUsingCustomConfiguration() {
- NonProductionConfig.instance().setCmdLineArgs(new String[] {
- "com.dep1.EarthMapMaker.MAP_NAME=CmdLineMapName",
- "com.dep1.EarthMapMaker.WEST_BOUND=-179"})
+ NonProductionConfig.instance()
+ .addFixedValue(EarthMapMaker.MAP_NAME, "SomeNameILike") /* via Property reference */
+ .addFixedValue("com.dep1.EarthMapMaker.EAST_BOUND", 42) /* via Property name */
.forceBuild();
-
- EarthMapMaker emm = new EarthMapMaker();
-
- //Actual values
- assertEquals("CmdLineMapName", emm.getMapName());
- assertEquals(-99, emm.getEastBound());
- assertEquals(-179, emm.getWestBound());
- }
-
- @Test
- public void testConfigFromCmdLineThenSysPropsViaProdConfigUtilForceBuild() {
-
- System.setProperty("com.dep1.EarthMapMaker.MAP_NAME", "SysPropMapName");
- System.setProperty("com.dep1.EarthMapMaker.EAST_BOUND", "-99");
-
- AndHowNonProductionUtil.forceRebuild(
- AndHow.findConfig().setCmdLineArgs(new String[] {
- "com.dep1.EarthMapMaker.MAP_NAME=CmdLineMapName",
- "com.dep1.EarthMapMaker.WEST_BOUND=-179"})
- );
-
+
EarthMapMaker emm = new EarthMapMaker();
-
- //Actual values
- assertEquals("CmdLineMapName", emm.getMapName());
- assertEquals(-99, emm.getEastBound());
- assertEquals(-179, emm.getWestBound());
+
+ //These values were customized above
+ assertEquals("SomeNameILike", emm.getMapName());
+ assertEquals(42, emm.getEastBound());
+
+ //All other values still come from the andhow.properties file
+ assertEquals(51, emm.getNorthBound());
}
-
+
+
+ /**
+ * This just demonstrates the order of loading.
+ * The System Properties Loader is earlier in the loader list than the
+ * JNDI loader, thus, values set as System Properties 'win' and override
+ * values found in JNDI. Properties files are read last of all.
+ *
+ * @throws Exception
+ */
@Test
public void testOrderOfLoading() throws Exception {
@@ -102,6 +88,8 @@ public void testOrderOfLoading() throws Exception {
jndi.bind("java:" + "com.dep1.EarthMapMaker.SOUTH_BOUND", "7");
jndi.bind("java:comp/env/" + "org.dataprocess.ExternalServiceConnector.ConnectionConfig.SERVICE_URL", "test/");
jndi.activate();
+
+ NonProductionConfig.instance().forceBuild();
//VALUES IN THE PROPS FILE
//org.dataprocess.ExternalServiceConnector.ConnectionConfig.SERVICE_URL = http://forwardcorp.com/service/
@@ -124,25 +112,19 @@ public void testOrderOfLoading() throws Exception {
assertEquals(-99, emm.getEastBound());
assertEquals(7, emm.getSouthBound());
}
-
+
+ /**
+ * Validation is always enforced
+ */
@Test
- public void testBadInt() {
-
- System.setProperty("com.dep1.EarthMapMaker.MAP_NAME", "SysPropMapName");
- System.setProperty("com.dep1.EarthMapMaker.EAST_BOUND", "East");
-
- try {
+ public void testAttemptToAssignAnInvalidValue() {
+
+ //LOG_SERVER must start w/ "http://"
+ assertThrows(AppFatalException.class, () ->{
NonProductionConfig.instance()
- .setCmdLineArgs(new String[] {
- "com.dep1.EarthMapMaker.MAP_NAME=CmdLineMapName",
- "com.dep1.EarthMapMaker.WEST_BOUND=-179"})
+ .addFixedValue("com.dep1.EarthMapMaker.LOG_SERVER", "ftp://blah/")
.forceBuild();
-
- fail("Expected an exception");
- } catch (AppFatalException afe) {
- assertEquals(1, afe.getProblems().size());
- assertTrue(afe.getProblems().get(0) instanceof LoaderProblem.StringConversionLoaderProblem);
- }
+ });
}
}
diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/com/dep2/MarsMapMakerTest.java b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/com/dep2/MarsMapMakerTest.java
index 00a46c60..e7d55601 100644
--- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/com/dep2/MarsMapMakerTest.java
+++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/com/dep2/MarsMapMakerTest.java
@@ -1,31 +1,39 @@
package com.dep2;
-import com.dep2.*;
import org.dataprocess.ExternalServiceConnector;
-import org.junit.Test;
import org.springframework.mock.jndi.SimpleNamingContextBuilder;
import org.yarnandtail.andhow.*;
import org.yarnandtail.andhow.api.AppFatalException;
import org.yarnandtail.andhow.internal.LoaderProblem;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
/**
*
* @author ericeverman
*/
-public class MarsMapMakerTest extends AndHowTestBase {
+public class MarsMapMakerTest extends AndHowJunit5TestBase {
+ /**
+ * Its OK to do nothing. AndHow will initialize itself at the point
+ * of the first Property value access.
+ *
+ * AndHowJunit5TestBase allows you to modify AndHow's state
+ * for each test class and/or for each test method - something
+ * that is NOT possible in production.
+ * If you use the AndHowJunit5TestBase for your tests, any changes
+ * you make to AndHow's state in a test class or during a test
+ * method are reverted after each method / test class completes.
+ */
@Test
public void testConfigFromPropertiesFileOnly() {
MarsMapMaker mmm = new MarsMapMaker();
-
- //Actual values
- assertEquals("My Map", mmm.getMapName());
+
+ //These values read from andhow.properties - see the EarthMapMakerTest for
+ //more details.
+ assertEquals("My Map", mmm.getMapName()); //AndHow initializes here if it hasn't already.
assertEquals(-125, mmm.getWestBound());
assertEquals(51, mmm.getNorthBound());
assertEquals(-65, mmm.getEastBound());
@@ -35,95 +43,5 @@ public void testConfigFromPropertiesFileOnly() {
assertEquals("http://prod.mybiz.com.logger/MarsMapMaker/", mmm.getLogServerUrl());
}
-
- @Test
- public void testConfigFromSysProps() {
-
- System.setProperty("com.dep2.MarsMapMaker.MAP_NAME", "SysPropMapName");
- System.setProperty("com.dep2.MarsMapMaker.EAST_BOUND", "-99");
-
- NonProductionConfig.instance().forceBuild();
-
- MarsMapMaker mmm = new MarsMapMaker();
-
- //Actual values
- assertEquals("SysPropMapName", mmm.getMapName());
- assertEquals(-99, mmm.getEastBound());
- }
-
- @Test
- public void testConfigFromCmdLineThenSysProps() {
-
- System.setProperty("com.dep2.MarsMapMaker.MAP_NAME", "SysPropMapName");
- System.setProperty("com.dep2.MarsMapMaker.EAST_BOUND", "-99");
-
-
- NonProductionConfig.instance().setCmdLineArgs(new String[] {
- "com.dep2.MarsMapMaker.MAP_NAME=CmdLineMapName",
- "com.dep2.MarsMapMaker.WEST_BOUND=-179"})
- .forceBuild();
-
- MarsMapMaker mmm = new MarsMapMaker();
-
- //Actual values
- assertEquals("CmdLineMapName", mmm.getMapName());
- assertEquals(-99, mmm.getEastBound());
- assertEquals(-179, mmm.getWestBound());
- }
-
- @Test
- public void testOrderOfLoading() throws Exception {
-
- System.setProperty("com.dep2.MarsMapMaker.MAP_NAME", "SysPropMapName");
- System.setProperty("com.dep2.MarsMapMaker.EAST_BOUND", "-99");
-
-
- SimpleNamingContextBuilder jndi = getJndi();
- jndi.bind("java:" + "com.dep2.MarsMapMaker.MAP_NAME", "JndiPropMapName");
- jndi.bind("java:" + "com.dep2.MarsMapMaker.SOUTH_BOUND", "7");
- jndi.bind("java:comp/env/" + "org.dataprocess.ExternalServiceConnector.ConnectionConfig.SERVICE_URL", "test/");
- jndi.activate();
-
- //VALUES IN THE PROPS FILE
- //org.dataprocess.ExternalServiceConnector.ConnectionConfig.SERVICE_URL = http://forwardcorp.com/service/
- //org.dataprocess.ExternalServiceConnector.ConnectionConfig.TIMEOUT = 60
- //com.dep2.MarsMapMaker.EAST_BOUND = -65
- //com.dep2.MarsMapMaker.MAP_NAME = My Map
- //com.dep2.MarsMapMaker.NORTH_BOUND = 51
- //com.dep2.MarsMapMaker.SOUTH_BOUND = 23
- //com.dep2.MarsMapMaker.WEST_BOUND = -125
-
-
- ExternalServiceConnector esc = new ExternalServiceConnector();
- assertEquals("test/", esc.getConnectionUrl());
- assertEquals(60, esc.getConnectionTimeout());
-
- MarsMapMaker mmm = new MarsMapMaker();
- assertEquals("SysPropMapName", mmm.getMapName());
- assertEquals(-125, mmm.getWestBound());
- assertEquals(51, mmm.getNorthBound());
- assertEquals(-99, mmm.getEastBound());
- assertEquals(7, mmm.getSouthBound());
- }
-
- @Test
- public void testBadInt() {
-
- System.setProperty("com.dep2.MarsMapMaker.MAP_NAME", "SysPropMapName");
- System.setProperty("com.dep2.MarsMapMaker.EAST_BOUND", "East");
-
- try {
- NonProductionConfig.instance()
- .setCmdLineArgs(new String[] {
- "com.dep2.MarsMapMaker.MAP_NAME=CmdLineMapName",
- "com.dep2.MarsMapMaker.WEST_BOUND=-179"})
- .forceBuild();
-
- fail("Expected an exception");
- } catch (AppFatalException afe) {
- assertEquals(1, afe.getProblems().size());
- assertTrue(afe.getProblems().get(0) instanceof LoaderProblem.StringConversionLoaderProblem);
- }
- }
}
diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/org/dataprocess/ExternalServiceConnectorTest.java b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/org/dataprocess/ExternalServiceConnectorTest.java
index 9715aff7..b17c0d58 100644
--- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/org/dataprocess/ExternalServiceConnectorTest.java
+++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/org/dataprocess/ExternalServiceConnectorTest.java
@@ -1,23 +1,20 @@
package org.dataprocess;
-import org.junit.Test;
-import org.yarnandtail.andhow.AndHowTestBase;
+import org.yarnandtail.andhow.AndHowJunit5TestBase;
-import static org.junit.Assert.*;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
/**
+ * The EarthMapMakerTest has more complete examples of testing.
*
* @author ericeverman
*/
-public class ExternalServiceConnectorTest extends AndHowTestBase {
+public class ExternalServiceConnectorTest extends AndHowJunit5TestBase {
public ExternalServiceConnectorTest() {
}
-
- /**
- * Test of getConnectionUrl method, of class EarthMapMaker.
- */
@Test
public void testAllConfigValues() {
ExternalServiceConnector esc = new ExternalServiceConnector();
diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/pom.xml b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/pom.xml
index 31ca1b7d..a903aeb0 100644
--- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/pom.xml
+++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/pom.xml
@@ -4,7 +4,7 @@
org.yarnandtailandhow-simulated-app-tests
- 0.4.1-SNAPSHOT
+ 0.4.2-SNAPSHOTandhow-multimodule-dataprocesspom
@@ -14,4 +14,19 @@
andhow-default-behavior-dep1andhow-default-behavior-dep2
+
+
+
+
+ org.yarnandtail
+ andhow
+ ${project.version}
+
+
+ org.yarnandtail
+ andhow-test-harness
+ ${project.version}
+ test
+
+
\ No newline at end of file
diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-1/pom.xml b/andhow-testing/andhow-simulated-app-tests/example-app-1/pom.xml
new file mode 100644
index 00000000..ffa8b852
--- /dev/null
+++ b/andhow-testing/andhow-simulated-app-tests/example-app-1/pom.xml
@@ -0,0 +1,45 @@
+
+
+ 4.0.0
+
+ andhow-simulated-app-tests
+ org.yarnandtail
+ 0.4.2-SNAPSHOT
+
+ example-app-1
+
+
+
+
+ org.yarnandtail
+ andhow
+ ${project.version}
+
+
+ org.yarnandtail
+ andhow-test-harness
+ ${project.version}
+
+
+
+ app
+
+
+ maven-assembly-plugin
+
+
+
+ com.bigcorp.Checker
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+
+
\ No newline at end of file
diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-1/src/main/java/com/bigcorp/Calculator.java b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/main/java/com/bigcorp/Calculator.java
new file mode 100644
index 00000000..2f61273c
--- /dev/null
+++ b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/main/java/com/bigcorp/Calculator.java
@@ -0,0 +1,91 @@
+package com.bigcorp;
+
+import org.yarnandtail.andhow.property.StrProp;
+
+/**
+ * A simple calculator that can use two different modes, which an AndHow {@Code StrProp} allows
+ * selection of based on configuration from (in this case) a properties file.
+ *
+ * This isn't a complete application, but its easy to imagine {@Code doCalc} being called by an
+ * AWS Lambda function, part of a command line utility, or just some library part of a larger app.
+ */
+public class Calculator {
+
+ //The AndHow configuration Property to select between two modes (doesn't have to be public)
+ public static final StrProp MODE = StrProp.builder()
+ .mustBeNonNull().mustEqual("DOUBLE", "FLOAT").build();
+
+ /**
+ * Do the calculation, but choose which implementation to use based on CALC_MODE
+ * @param a
+ * @param b
+ * @return
+ */
+ public Number doCalc(Number a, Number b) {
+
+ Number result = null;
+
+ if (MODE.getValue().equals("DOUBLE")) {
+ result = doDoubleCalc(a, b);
+ } else if (MODE.getValue().equals("FLOAT")) {
+ result = doFloatCalc(a, b);
+ } else {
+ //throw new IllegalStateException("Validation on CALC_MODE ensures this never happens");
+ }
+
+ return result;
+ }
+
+ protected Number doDoubleCalc(Number a, Number b) {
+ return a.doubleValue() / b.doubleValue();
+ }
+
+ protected Number doFloatCalc(Number a, Number b) {
+ return a.floatValue() / b.floatValue();
+ }
+
+ /**
+ * A main method to run this app as it would be in a 'production' environment.
+ * Run it from an IDE (configure the IDE to pass two numbers as args), or via command line.
+ *
+ * To run from command line, first use Maven to create a runnable jar.
+ * Here are the commands, executed from the root of the AndHow project, to build and run the jar:
+ *
+ * The output of running this command will be:
+ *
{@Code
+ * Result is 0.26973684210526316 (Double)
+ * }
+ * How did it know to use the Double implementation? AndHow finds the {@Code checker.production.properties}
+ * file on the classpath and reads the configured value for CALC_MODE. {@CODE CALC_MODE.getValue()}
+ * returns 'DOUBLE'. Compare this to the unit test for this class...
+ *
+ * Properties files are just one way AndHow reads configuration. Values in the properties file
+ * could be overwritten via env. vars., JNDI, system properties, etc..
+ * Rerunning the main method with a system property like this:
+ *
+ * will result in {@Code Result is 0.26973686 (Float)}
+ *
+ * @param args Two arguments that are parsable to numbers.
+ */
+ public static void main(String[] args) {
+ Double a = Double.parseDouble(args[0]);
+ Double b = Double.parseDouble(args[1]);
+
+ Calculator mult = new Calculator();
+
+ Number result = mult.doCalc(a, b);
+
+ System.out.println(mult.toString(result));
+ }
+
+ public String toString(Number result) {
+ return "Result is " + result + " (" + result.getClass().getSimpleName() + ")";
+ }
+
+
+}
diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-1/src/main/resources/andhow.properties b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/main/resources/andhow.properties
new file mode 100644
index 00000000..88c08e6d
--- /dev/null
+++ b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/main/resources/andhow.properties
@@ -0,0 +1,8 @@
+# By default, AndHow will find and load a properties file named
+# 'checker.production.properties' if it exists on the classpath.
+#
+# An additional 'checker.production.properties' file can be placed on the
+# test classpath to provide a different configuration during
+# testing, as this example project does.
+
+com.bigcorp.Calculator.MODE: DOUBLE
\ No newline at end of file
diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/java/com/bigcorp/CalculatorDefaultTest.java b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/java/com/bigcorp/CalculatorDefaultTest.java
new file mode 100644
index 00000000..82942a70
--- /dev/null
+++ b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/java/com/bigcorp/CalculatorDefaultTest.java
@@ -0,0 +1,48 @@
+package com.bigcorp;
+
+import org.junit.jupiter.api.Test;
+
+import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemOut;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * In the test environment, the application is configured to use the 'FLOAT'
+ * implementation, as configured in the {@Code andhow.properties} on the TEST CLASSPATH.
+ *
+ * Resources on the test classpath override the main classpath, so this is a natural and useful
+ * outcome. Just like in production, AndHow will auto-discover its configuration during testing.
+ *
+ * Using the test {@Code andhow.properties} file, however, means that all the tests run with the
+ * same 'FLOAT' configuration value. How do we test the application with the DOUBLE configuration?
+ * See {@Code CalculatorDoubleConfigTest} for the answer...
+ *
+ * ...and much more flexible and complex configuration is possible for production and testing -
+ * be sure to look at other of the 'simulated-app-tests'.
+ */
+class CalculatorDefaultTest {
+
+ /**
+ * Verify that the app runs in FLOAT mode in the test environment.
+ */
+ @Test
+ public void verifyTestEnvironmentConfiguration() {
+ Calculator mult = new Calculator();
+
+ Number result = mult.doCalc(1.23D, 4.56D);
+
+ assertEquals(Float.class, result.getClass(), "The result should be 'Float', as configured");
+ assertEquals("FLOAT", Calculator.MODE.getValue());
+
+ //System.out.println(mult.toString(result));
+ }
+
+ @Test
+ public void mainMethodShouldPrintResultToSystemOut() throws Exception {
+
+ String text = tapSystemOut(() -> {
+ Calculator.main(new String[] {"4", "2"});
+ });
+
+ assertTrue(text.contains("2.0") && text.contains("Float"));
+ }
+}
\ No newline at end of file
diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/java/com/bigcorp/CalculatorDoubleConfigTest.java b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/java/com/bigcorp/CalculatorDoubleConfigTest.java
new file mode 100644
index 00000000..36249a6d
--- /dev/null
+++ b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/java/com/bigcorp/CalculatorDoubleConfigTest.java
@@ -0,0 +1,50 @@
+package com.bigcorp;
+
+import org.junit.jupiter.api.Test;
+import org.yarnandtail.andhow.AndHow;
+import org.yarnandtail.andhow.junit5.KillAndHowBeforeEachTest;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * {@Code CalculatorDefaultTest} didn't fully test the app because only some code is executed in the
+ * FLOAT configuration. This test demos testing other configurations.
+ *
+ * The {@Code @KillAndHowBeforeEachTest} annotation erases the configured state of AndHow before
+ * each test so each test can specify its own AndHow configuration. If a test doesn't explicitly
+ * initialize AndHow, AndHow will initialize normally as soon as the first Property value is referenced.
+ *
+ * When all tests are complete, {@Code KillAndHowBeforeEachTest} resets the AndHow state
+ * back to what it was at the start of the test.
+ */
+@KillAndHowBeforeEachTest // <-- Uses the JUnit extension mechanism
+class CalculatorDoubleConfigTest {
+
+ /**
+ * Test the app with the DOUBLE configuration.
+ */
+ @Test
+ public void testAppInDoubleConfiguration() {
+
+ //Force AndHow to see DOUBLE for the duration of this test
+ AndHow.findConfig().addFixedValue(Calculator.MODE, "DOUBLE").build();
+
+ Calculator mult = new Calculator();
+ Number result = mult.doCalc(1.23D, 4.56D);
+ assertEquals(Double.class, result.getClass(), "The result should now be a Double");
+ }
+
+ /**
+ * If the test method doesn't initialize AndHow, AndHow will initialize automatically as soon
+ * as a Property is accessed and load the default configuration from the test
+ * {@Code checker.production.properties} file.
+ */
+ @Test
+ public void revertToDefaultConfigIfTheTestDoesNotInitializeAndHow() {
+
+ Calculator mult = new Calculator();
+ Number result = mult.doCalc(1.23D, 4.56D);
+ assertEquals(Float.class, result.getClass(), "The result should revert to Float");
+ }
+
+}
\ No newline at end of file
diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/resources/andhow.properties b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/resources/andhow.properties
new file mode 100644
index 00000000..770dde19
--- /dev/null
+++ b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/resources/andhow.properties
@@ -0,0 +1,4 @@
+# This properties file on the test classpath will override the
+# one on the main classpath for testing.
+
+com.bigcorp.Calculator.MODE: FLOAT
\ No newline at end of file
diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/pom.xml b/andhow-testing/andhow-simulated-app-tests/example-app-2/pom.xml
new file mode 100644
index 00000000..340f4411
--- /dev/null
+++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/pom.xml
@@ -0,0 +1,41 @@
+
+
+ 4.0.0
+
+ andhow-simulated-app-tests
+ org.yarnandtail
+ 0.4.2-SNAPSHOT
+
+ example-app-2
+
+
+
+
+ org.yarnandtail
+ andhow
+ ${project.version}
+
+
+ org.yarnandtail
+ andhow-test-harness
+ ${project.version}
+
+
+
+ app
+
+
+ maven-assembly-plugin
+
+
+
+ com.bigcorp.Checker
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/java/com/bigcorp/AppInitiation.java b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/java/com/bigcorp/AppInitiation.java
new file mode 100644
index 00000000..da340d69
--- /dev/null
+++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/java/com/bigcorp/AppInitiation.java
@@ -0,0 +1,20 @@
+package com.bigcorp;
+
+import org.yarnandtail.andhow.*;
+import org.yarnandtail.andhow.property.StrProp;
+
+public class AppInitiation implements AndHowInit {
+
+ public static final StrProp ANDHOW_CLASSPATH_FILE = StrProp.builder().mustStartWith("/")
+ .aliasIn("AH_CLASSPATH") /* Make this easier to config by adding an alias */
+ .defaultValue("/checker.default.properties")
+ .description("Path to a file on the classpath. Classpaths must start w/ a slash.").build();
+
+ @Override
+ public AndHowConfiguration getConfiguration() {
+
+ return
+ StdConfig.instance().setClasspathPropFilePath(ANDHOW_CLASSPATH_FILE);
+
+ }
+}
diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/java/com/bigcorp/Checker.java b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/java/com/bigcorp/Checker.java
new file mode 100644
index 00000000..d0459844
--- /dev/null
+++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/java/com/bigcorp/Checker.java
@@ -0,0 +1,82 @@
+package com.bigcorp;
+
+import org.yarnandtail.andhow.AndHow;
+import org.yarnandtail.andhow.property.IntProp;
+import org.yarnandtail.andhow.property.StrProp;
+
+/**
+ * A hypothetical class that checks if a configured service url is 'live'.
+ * The url of the external service is configured by AndHow properties.
+ *
+ * Of course, this isn't a complete application, but its easy to imagine {@Code doCheck} being
+ * called by an AWS Lambda function, as a command line utility, or as just a library in a larger app.
+ */
+public class Checker {
+
+ //All the config Properties for this class
+ // - Its a best practice to group AndHow Properties into an interface.
+ interface Config {
+ StrProp PROTOCOL = StrProp.builder().mustBeNonNull().
+ mustEqual("http", "https").build();
+ StrProp SERVER = StrProp.builder().mustBeNonNull().build();
+ IntProp PORT = IntProp.builder().mustBeNonNull().
+ mustBeGreaterThanOrEqualTo(80).mustBeLessThanOrEqualTo(8888).build();
+ StrProp PATH = StrProp.builder().mustStartWith("/").build();
+ }
+
+ /**
+ * Builds a url using the AndHow Properties.
+ * @return
+ */
+ public String getServiceUrl() {
+ return Config.PROTOCOL.getValue() + "://" + Config.SERVER.getValue() + ":" +
+ Config.PORT.getValue() + Config.PATH.getValue();
+ }
+
+ /*
+ * In the real world, this method would verify the configured url works...
+ */
+// public boolean checkTheUrl() {
+// ... verify the url return a http 200 code or something ...
+// }
+
+ /**
+ * A main method to run this app as it might be in a real environment.
+ *
+ * In this main method the {@Code AndHow.findConfig()} method is used to append the cmd line
+ * arguments to the AndHow configuration. This allows Property values to be configured from
+ * these arguments, in addition to all the other way they could be configured.
+ *
>
+ * Run it from an IDE , or via command line.
+ *
+ * To run from command line, first use Maven to create a runnable jar.
+ * Here are the commands, executed from the root of the AndHow project, to build and run the jar:
+ *
+ * will result in {@Code Result is 0.26973686 (Float)}
+ *
+ * @param args Two arguments that are parsable to numbers.
+ */
+ public static void main(String[] args) {
+
+ //Find the AndHow Configuration and add to it the commandline args so they can be used for
+ //configuration as well.
+ AndHow.findConfig().setCmdLineArgs(args).build(); //Have to call build here or it doesn't work!!
+
+ Checker v = new Checker();
+ System.out.println("Service url: " + v.getServiceUrl()); // <--display the configured url
+ }
+
+
+
+}
diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.default.properties b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.default.properties
new file mode 100644
index 00000000..84f787b5
--- /dev/null
+++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.default.properties
@@ -0,0 +1,30 @@
+# AndHow knows all the Property metadata, so it can create this file w/ place-holders for values.
+
+# ##########################################################################################
+# Sample properties file generated by AndHow!
+# strong.simple.valid.AppConfiguration - https://github.com/eeverman/andhow
+# ##########################################################################################
+
+# ##########################################################################################
+# Property Group com.bigcorp.Checker.Config
+
+#
+# PATH (String)
+# The property value must start with '/'
+com.bigcorp.Checker.Config.PATH = /validate
+
+#
+# PORT (Integer) NON-NULL
+# The property value must:
+# - be greater than or equal to 80
+# - be less than or equal to 8888
+com.bigcorp.Checker.Config.PORT = 80
+
+#
+# PROTOCOL (String) NON-NULL
+# The property value must be equal to one of '[http, https]'
+com.bigcorp.Checker.Config.PROTOCOL = https
+
+#
+# SERVER (String) NON-NULL
+com.bigcorp.Checker.Config.SERVER = default.bigcorp.com
diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.production.properties b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.production.properties
new file mode 100644
index 00000000..4cb1a073
--- /dev/null
+++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.production.properties
@@ -0,0 +1,30 @@
+# AndHow knows all the Property metadata, so it can create this file w/ place-holders for values.
+
+# ##########################################################################################
+# Sample properties file generated by AndHow!
+# strong.simple.valid.AppConfiguration - https://github.com/eeverman/andhow
+# ##########################################################################################
+
+# ##########################################################################################
+# Property Group com.bigcorp.Checker.Config
+
+#
+# PATH (String)
+# The property value must start with '/'
+com.bigcorp.Checker.Config.PATH = /validate
+
+#
+# PORT (Integer) NON-NULL
+# The property value must:
+# - be greater than or equal to 80
+# - be less than or equal to 8888
+com.bigcorp.Checker.Config.PORT = 80
+
+#
+# PROTOCOL (String) NON-NULL
+# The property value must be equal to one of '[http, https]'
+com.bigcorp.Checker.Config.PROTOCOL = https
+
+#
+# SERVER (String) NON-NULL
+com.bigcorp.Checker.Config.SERVER = prod.bigcorp.com
diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.stage.properties b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.stage.properties
new file mode 100644
index 00000000..41abd034
--- /dev/null
+++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.stage.properties
@@ -0,0 +1,30 @@
+# AndHow knows all the Property metadata, so it can create this file w/ place-holders for values.
+
+# ##########################################################################################
+# Sample properties file generated by AndHow!
+# strong.simple.valid.AppConfiguration - https://github.com/eeverman/andhow
+# ##########################################################################################
+
+# ##########################################################################################
+# Property Group com.bigcorp.Checker.Config
+
+#
+# PATH (String)
+# The property value must start with '/'
+com.bigcorp.Checker.Config.PATH = /validate
+
+#
+# PORT (Integer) NON-NULL
+# The property value must:
+# - be greater than or equal to 80
+# - be less than or equal to 8888
+com.bigcorp.Checker.Config.PORT = 80
+
+#
+# PROTOCOL (String) NON-NULL
+# The property value must be equal to one of '[http, https]'
+com.bigcorp.Checker.Config.PROTOCOL = https
+
+#
+# SERVER (String) NON-NULL
+com.bigcorp.Checker.Config.SERVER = stage.bigcorp.com
diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/java/com/bigcorp/CheckerDefaultAndMainArgsTest.java b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/java/com/bigcorp/CheckerDefaultAndMainArgsTest.java
new file mode 100644
index 00000000..a8d245d3
--- /dev/null
+++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/java/com/bigcorp/CheckerDefaultAndMainArgsTest.java
@@ -0,0 +1,74 @@
+package com.bigcorp;
+
+import org.junit.jupiter.api.Test;
+import org.yarnandtail.andhow.junit5.KillAndHowBeforeThisTest;
+
+import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemOut;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class CheckerDefaultAndMainArgsTest {
+
+ /**
+ * Verify the the app sees its default configuration.
+ *
+ * In the {@Code AppInitialtion.ANDHOW_CLASSPATH_FILE} Property, the default value is
+ * 'checker.default.properties'. In that same class we also tell AndHow to use the value of that
+ * Property to decide which property file to use, thus property values are read from
+ * 'checker.default.properties'.
+ */
+ @Test
+ public void verifyTestEnvironmentConfiguration() {
+ Checker v = new Checker();
+
+ String url = v.getServiceUrl();
+
+ assertEquals("https://default.bigcorp.com:80/validate", url,
+ "The url should match the configuration in checker.default.properties");
+ }
+
+ /**
+ * Since the main method calls {@Code AndHow.findConfig()...build()}, forcing AndHow to build and
+ * initialize itself, we must 'kill' the AndHow configured state before calling main.
+ * Otherwise AndHow would throw a RuntimeException.
+ *
+ * In production, its AndHow's job to enforce a single, stable configuration state and it complains
+ * loudly if application code tries to re-initialize it. During testing, however, we need to
+ * 'break the rules' with things like {@Code @KillAndHowBeforeThisTest} and other AndHow test
+ * helpers.
+ * @throws Exception
+ */
+ @KillAndHowBeforeThisTest
+ @Test
+ public void mainMethodAlsoSeesDefaultProperties() throws Exception {
+
+ String text = tapSystemOut(() -> {
+ Checker.main(new String[]{});
+ });
+
+ assertTrue(text.contains("https://default.bigcorp.com:80/validate"),
+ "The url should match the configuration in checker.default.properties");
+ }
+
+ /**
+ * The main method includes {@Code AndHow.findConfig().setCmdLineArgs(args)...} to allow AndHow
+ * to process the main args.
+ *
+ * This test shows how a key=value pair can be passed as an argument to main. Looking at the
+ * code in {@Code AppInitiation}, the key matches the {@Code ANDHOW_CLASSPATH_FILE} Property we
+ * told AndHow to use to decide which file to read. To make it easier to specify that Property,
+ * we added the alias 'AH_CLASSPATH' so we don't need to use the full class name of that Property.
+ * @throws Exception
+ */
+ @KillAndHowBeforeThisTest
+ @Test
+ public void mainMethodWillAcceptConfigForFilePath() throws Exception {
+
+ String text = tapSystemOut(() -> {
+ //pass the alias name of ANDHOW_CLASSPATH_FILE and a value as a main arg
+ Checker.main(new String[]{ "AH_CLASSPATH=/checker.test.properties"});
+ });
+
+ assertTrue(text.contains("http://localhost:8888/localValidate"),
+ "The url should match the configuration in checker.test.properties");
+ }
+}
\ No newline at end of file
diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/java/com/bigcorp/CheckerFixedValuesTest.java b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/java/com/bigcorp/CheckerFixedValuesTest.java
new file mode 100644
index 00000000..d4dc8a6e
--- /dev/null
+++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/java/com/bigcorp/CheckerFixedValuesTest.java
@@ -0,0 +1,85 @@
+package com.bigcorp;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.yarnandtail.andhow.AndHow;
+import org.yarnandtail.andhow.api.AppFatalException;
+import org.yarnandtail.andhow.junit5.KillAndHowBeforeAllTests;
+import org.yarnandtail.andhow.junit5.KillAndHowBeforeThisTest;
+
+import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemOut;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Lets say there is a very specific configuration state we need to test our app in.
+ * We could do that with a separate properties file, but another way is to configure AndHow with
+ * 'fixed values' right in a test.
+ *
+ * The {@Code @KillAndHowBeforeAllTests} annotation resets AndHow before the start of testing and
+ * restores it after all tests are complete. The setup method uses
+ * {@Code AndHow.findConfig()...build()} to initialize AndHow with several 'fixed values' for the
+ * test scenario. The AndHow state is not modified between tests, so all tests share the same
+ * AndHow configuration state.
+ */
+@KillAndHowBeforeAllTests
+public class CheckerFixedValuesTest {
+
+ /**
+ * AndHow was 'killed' at the start of the test class, so this setup method can create a new
+ * AndHow configuration state for all the tests in this class. When all the tests are complete,
+ * the original state is restored.
+ */
+ @BeforeAll
+ public static void setup() {
+ AndHow.findConfig()
+ .addFixedValue(Checker.Config.PROTOCOL, "https")
+ .addFixedValue(Checker.Config.SERVER, "imgs.xkcd.com")
+ .addFixedValue(Checker.Config.PORT, 80)
+ .addFixedValue(Checker.Config.PATH, "/comics/the_mother_of_all_suspicious_files.png")
+ .build(); //build() initializes AndHow, otherwise it would wait for the 1st Property access.
+ }
+
+ /**
+ * Verify the the app only sees the 'fixed' values from above.
+ */
+ @Test
+ public void verifyTestEnvironmentConfiguration() {
+ Checker v = new Checker();
+
+ String url = v.getServiceUrl();
+
+ assertEquals("https://imgs.xkcd.com:80/comics/the_mother_of_all_suspicious_files.png", url,
+ "The url is build based on the fixed values in the 'BeforeAll' method");
+ }
+
+ /**
+ * All the tests in this class will see the same AndHow configuration...
+ */
+ @Test
+ public void verifyTestEnvironmentConfiguration2() {
+ assertEquals("imgs.xkcd.com", Checker.Config.SERVER.getValue());
+ }
+
+ /**
+ * One of AndHow's main jobs is to enforce a single, stable configuration state and complain
+ * loudly (with an AppFatalException) if application code tries to change that state by
+ * re-initializing AndHow. This test verifies that.
+ *
+ * The main() method explicitly initializes AndHow and so does this test class. Thus, the main
+ * method will throw a {@Code AppFatalException} because AndHow detects the attempt to
+ * reinitialize it.
+ *
+ * If we did want to call the main method here, @{Code @KillAndHowBeforeThisTest} could be added
+ * to this test method to start over with an unconfigured state for just this test.
+ * @throws Exception
+ */
+ @Test
+ public void mainMethodShouldThrowAnErrorBecauseAndHowIsAlreadyInitialized() throws Exception {
+
+ assertThrows(AppFatalException.class, () ->
+ Checker.main(new String[]{})
+ );
+
+ }
+
+}
\ No newline at end of file
diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/resources/checker.test.properties b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/resources/checker.test.properties
new file mode 100644
index 00000000..1fcd74a2
--- /dev/null
+++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/resources/checker.test.properties
@@ -0,0 +1,30 @@
+# AndHow knows all the Property metadata, so it can create this file w/ place-holders for values.
+
+# ##########################################################################################
+# Sample properties file generated by AndHow!
+# strong.simple.valid.AppConfiguration - https://github.com/eeverman/andhow
+# ##########################################################################################
+
+# ##########################################################################################
+# Property Group com.bigcorp.Checker.Config
+
+#
+# PATH (String)
+# The property value must start with '/'
+com.bigcorp.Checker.Config.PATH = /localValidate
+
+#
+# PORT (Integer) NON-NULL
+# The property value must:
+# - be greater than or equal to 80
+# - be less than or equal to 8888
+com.bigcorp.Checker.Config.PORT = 8888
+
+#
+# PROTOCOL (String) NON-NULL
+# The property value must be equal to one of '[http, https]'
+com.bigcorp.Checker.Config.PROTOCOL = http
+
+#
+# SERVER (String) NON-NULL
+com.bigcorp.Checker.Config.SERVER = localhost
diff --git a/andhow-testing/andhow-simulated-app-tests/pom.xml b/andhow-testing/andhow-simulated-app-tests/pom.xml
index 8f11b49d..55dd0793 100644
--- a/andhow-testing/andhow-simulated-app-tests/pom.xml
+++ b/andhow-testing/andhow-simulated-app-tests/pom.xml
@@ -4,19 +4,41 @@
org.yarnandtailandhow-parent
- 0.4.1-SNAPSHOT
+ 0.4.2-SNAPSHOT../../pom.xmlandhow-simulated-app-testspomandhow-multimodule-dataprocess
+ example-app-1
+ example-app-2
+
+
+
+ maven-assembly-plugin
+
+
+ jar-with-dependencies
+
+ false
+
+
+
+ package
+
+ single
+
+
+
+
+
+
- truemaven-deploy-plugintrue
diff --git a/andhow-testing/andhow-system-tests/pom.xml b/andhow-testing/andhow-system-tests/pom.xml
index 21718d43..f4740f2a 100644
--- a/andhow-testing/andhow-system-tests/pom.xml
+++ b/andhow-testing/andhow-system-tests/pom.xml
@@ -3,7 +3,7 @@
org.yarnandtailandhow-parent
- 0.4.1-SNAPSHOT
+ 0.4.2-SNAPSHOT../../pom.xml
@@ -20,7 +20,7 @@
${project.groupId}andhow${project.version}
- provided
+ test
${project.groupId}
@@ -28,12 +28,6 @@
${project.version}test
-
-
- junit
- junit
- test
- commons-iocommons-io
diff --git a/andhow-testing/andhow-system-tests/src/test/java/org/yarnandtail/andhow/AndHowTest.java b/andhow-testing/andhow-system-tests/src/test/java/org/yarnandtail/andhow/AndHowTest.java
index d50e097f..bc57e865 100644
--- a/andhow-testing/andhow-system-tests/src/test/java/org/yarnandtail/andhow/AndHowTest.java
+++ b/andhow-testing/andhow-system-tests/src/test/java/org/yarnandtail/andhow/AndHowTest.java
@@ -1,11 +1,13 @@
package org.yarnandtail.andhow;
import java.lang.reflect.Field;
-import org.junit.*;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.AfterEach;
+import static org.junit.jupiter.api.Assertions.*;
import org.yarnandtail.andhow.name.CaseInsensitiveNaming;
import org.yarnandtail.andhow.property.StrProp;
-import static org.junit.Assert.*;
/**
*
@@ -24,12 +26,12 @@ public class AndHowTest {
private AndHow originalAndHowInstance;
- @Before
+ @BeforeEach
public void clearAndHow() {
originalAndHowInstance = setAndHowInstance(null);
}
- @After
+ @AfterEach
public void restoreAndHow() {
setAndHowInstance(originalAndHowInstance);
}
@@ -45,8 +47,8 @@ public void testFindConfig() {
AndHowConfiguration extends AndHowConfiguration> config1 = AndHow.findConfig();
AndHowConfiguration extends AndHowConfiguration> config2 = AndHow.findConfig();
- assertNotEquals("Should return a new instance each time", config1, config2);
- assertFalse("findConfig should not force initialization", AndHow.isInitialize());
+ assertNotEquals(config1, config2, "Should return a new instance each time");
+ assertFalse(AndHow.isInitialize(), "findConfig should not force initialization");
}
/**
diff --git a/andhow-testing/andhow-test-harness/pom.xml b/andhow-testing/andhow-test-harness/pom.xml
index 2944339f..b921d495 100644
--- a/andhow-testing/andhow-test-harness/pom.xml
+++ b/andhow-testing/andhow-test-harness/pom.xml
@@ -3,7 +3,7 @@
org.yarnandtailandhow-parent
- 0.4.1-SNAPSHOT
+ 0.4.2-SNAPSHOT../../pom.xml
@@ -19,18 +19,49 @@
${project.groupId}andhow-core${project.version}
- provided
+ compile
+ true
-
+
+
+
+ org.springframework
+ spring-test
+ compile
+
+
+
junitjunit
- provided
+ compile
+ true
- org.springframework
- spring-test
+ org.junit.jupiter
+ junit-jupiter-api
+ compile
+ true
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ compile
+ true
+
+
+ org.junit.vintage
+ junit-vintage-enginecompile
+ true
diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowJunit4TestBase.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowJunit4TestBase.java
new file mode 100644
index 00000000..9b8d711c
--- /dev/null
+++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowJunit4TestBase.java
@@ -0,0 +1,69 @@
+package org.yarnandtail.andhow;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+
+/**
+ * Optional class that can be used by app developers as a base class for JUnit 4 tests.
+ *
+ * This class resets AndHow and System.properties to their prior
+ * state after each test run, as well as after all tests have run. This, used with
+ * NonProductionConfig.instance().forceBuild()
+ * and setting SystemProperties at the start of a test, can be used to set a
+ * specific AndHow configuration for a test or suite of tests.
+ *
+ *
+ * Here is a exampleHere is a example that shows how this can be used.
+ *
+ * @author eeverman
+ */
+public class AndHowJunit4TestBase extends AndHowTestBaseImpl {
+
+ /**
+ * Stores the AndHow Core (its state) and System Properties prior to a test class.
+ * It also sets the logging level for SimpleNamingContextBuilder (a JNDI
+ * related class) to SEVERE. If JNDI is used for a test, it's startup
+ * is verbose to System.out, so this turns it off.
+ */
+ @BeforeClass
+ public static void andHowSnapshotBeforeTestClass() {
+ AndHowTestBaseImpl.andHowSnapshotBeforeTestClass();
+ }
+
+ /**
+ * Restores the AndHow Core (its state) and System Properties
+ * that were previously stored prior to this class' test run.
+ * It also resets the logging level for SimpleNamingContextBuilder (a JNDI
+ * related class) to what ever it was prior to the run.
+ */
+ @AfterClass
+ public static void resetAndHowSnapshotAfterTestClass() {
+ AndHowTestBaseImpl.resetAndHowSnapshotAfterTestClass();
+ }
+
+ /**
+ * Stores the AndHow Core (its state) and System Properties prior to a test.
+ * It also sets the logging level for SimpleNamingContextBuilder (a JNDI
+ * related class) to SEVERE. If JNDI is used for a test, it's startup
+ * is verbose to System.out, so this turns it off.
+ */
+ @Before
+ public void andHowSnapshotBeforeSingleTest() {
+ super.andHowSnapshotBeforeSingleTest();
+ }
+
+ /**
+ * Restores the AndHow Core (its state) and System Properties
+ * that were previously stored prior to a test run.
+ * It also resets the logging level for SimpleNamingContextBuilder (a JNDI
+ * related class) to what ever it was prior to the run.
+ */
+ @After
+ public void resetAndHowSnapshotAfterSingleTest() {
+ super.resetAndHowSnapshotAfterSingleTest();
+ }
+
+}
diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowJunit5TestBase.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowJunit5TestBase.java
new file mode 100644
index 00000000..59d316f7
--- /dev/null
+++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowJunit5TestBase.java
@@ -0,0 +1,69 @@
+package org.yarnandtail.andhow;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.BeforeAll;
+
+
+/**
+ * Optional class that can be used by app developers as a base class for JUnit 4 tests.
+ *
+ * This class resets AndHow and System.properties to their prior
+ * state after each test run, as well as after all tests have run. This, used with
+ * NonProductionConfig.instance().forceBuild()
+ * and setting SystemProperties at the start of a test, can be used to set a
+ * specific AndHow configuration for a test or suite of tests.
+ *
+ *
+ * Here is a exampleHere is a example that shows how this can be used.
+ *
+ * @author eeverman
+ */
+public class AndHowJunit5TestBase extends AndHowTestBaseImpl {
+
+ /**
+ * Stores the AndHow Core (its state) and System Properties prior to a test class.
+ * It also sets the logging level for SimpleNamingContextBuilder (a JNDI
+ * related class) to SEVERE. If JNDI is used for a test, it's startup
+ * is verbose to System.out, so this turns it off.
+ */
+ @BeforeAll
+ public static void andHowSnapshotBeforeTestClass() {
+ AndHowTestBaseImpl.andHowSnapshotBeforeTestClass();
+ }
+
+ /**
+ * Restores the AndHow Core (its state) and System Properties
+ * that were previously stored prior to this class' test run.
+ * It also resets the logging level for SimpleNamingContextBuilder (a JNDI
+ * related class) to what ever it was prior to the run.
+ */
+ @AfterAll
+ public static void resetAndHowSnapshotAfterTestClass() {
+ AndHowTestBaseImpl.resetAndHowSnapshotAfterTestClass();
+ }
+
+ /**
+ * Stores the AndHow Core (its state) and System Properties prior to a test.
+ * It also sets the logging level for SimpleNamingContextBuilder (a JNDI
+ * related class) to SEVERE. If JNDI is used for a test, it's startup
+ * is verbose to System.out, so this turns it off.
+ */
+ @BeforeEach
+ public void andHowSnapshotBeforeSingleTest() {
+ super.andHowSnapshotBeforeSingleTest();
+ }
+
+ /**
+ * Restores the AndHow Core (its state) and System Properties
+ * that were previously stored prior to a test run.
+ * It also resets the logging level for SimpleNamingContextBuilder (a JNDI
+ * related class) to what ever it was prior to the run.
+ */
+ @AfterEach
+ public void resetAndHowSnapshotAfterSingleTest() {
+ super.resetAndHowSnapshotAfterSingleTest();
+ }
+
+}
diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowNonProductionUtil.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowNonProductionUtil.java
index 87ed5875..00fe900d 100644
--- a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowNonProductionUtil.java
+++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowNonProductionUtil.java
@@ -59,7 +59,7 @@ public static AndHowCore getAndHowCore() {
/**
* Sets a new {@code AndHowCore}
*
- * This inserts an entire new state into AndHow. Inserting a {@null} core
+ * This inserts an entire new state into AndHow. Inserting a null core
* puts AndHow into a reset state that is invalid during production, but
* can be useful during testing. In this state, AndHow will allow itself
* to be reinitialized, which is not the intended operation during
diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowTestBase.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowTestBase.java
index 4e9640eb..b4cc0e76 100644
--- a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowTestBase.java
+++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowTestBase.java
@@ -1,106 +1,67 @@
package org.yarnandtail.andhow;
-import java.util.Properties;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.naming.NamingException;
import org.junit.*;
-import org.springframework.mock.jndi.SimpleNamingContextBuilder;
-import org.yarnandtail.andhow.internal.AndHowCore;
+
/**
- * All tests using AppConfig must extend this class so they have access to the
- * one and only AppConfig.Reloader, which is a single backdoor to cause the
- * AppConfig to reload.
+ * Optional class that can be used by app developers as a base class for JUnit 4 tests.
+ *
+ * This class resets AndHow and System.properties to their prior
+ * state after each test run, as well as after all tests have run. This, used with
+ * NonProductionConfig.instance().forceBuild()
+ * and setting SystemProperties at the start of a test, can be used to set a
+ * specific AndHow configuration for a test or suite of tests.
+ *
+ *
+ * Here is a exampleHere is a example that shows how this can be used.
*
* @author eeverman
+ * @depricated Use AndHowJunit4TestBase instead, or upgrade to AndHowJunit5TestBase.
*/
-public class AndHowTestBase {
-
- private static AndHowCore beforeClassCore;
-
- private AndHowCore beforeTestCore;
-
- private static Level beforeClassLogLevel;
-
- /**
- * System properties before the tests and subclass @BeforeClass initializer
- * are run. Properties prior to the initialization of this test class are
- * stored and then reinstated after the test class is complete.
- */
- private static Properties beforeClassSystemProps;
-
- /**
- * System properties before an individual test is run and the subclass @Before
- * initializers are run. Properties prior to a test are
- * stored and then reinstated after the test is complete. If a Test classes
- * uses a @BeforeClass initializer that sets system properties, this will
- * reinstate to that state.
- */
- private Properties beforeTestSystemProps;
-
- /**
- * Builder for a temporary JNDI context
- */
- private static SimpleNamingContextBuilder builder;
-
-
-
+public class AndHowTestBase extends AndHowTestBaseImpl {
+
/**
- * Simple consistent way to get an empty JNDI context.
- *
- * bind() each variable, then call build().
- *
- * @return
- * @throws NamingException
+ * Stores the AndHow Core (its state) and System Properties prior to a test class.
+ * It also sets the logging level for SimpleNamingContextBuilder (a JNDI
+ * related class) to SEVERE. If JNDI is used for a test, it's startup
+ * is verbose to System.out, so this turns it off.
*/
- public SimpleNamingContextBuilder getJndi() throws NamingException {
- if (builder == null) {
- builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
- }
- return builder;
- }
-
@BeforeClass
- public static void andHowSnapshotBeforeTestClass() throws Exception {
- //The SimpleNamingContextBuilder uses Commons Logging, which defaults to
- //using Java logging. It spews a bunch of stuff the console during tests,
- //so this turns that off.
-
- beforeClassLogLevel = Logger.getGlobal().getLevel(); //store log level before class
- Logger.getGlobal().setLevel(Level.SEVERE);
- Logger.getLogger(SimpleNamingContextBuilder.class.getCanonicalName()).setLevel(Level.SEVERE);
-
-
- beforeClassCore = AndHowNonProductionUtil.getAndHowCore();
- beforeClassSystemProps = AndHowNonProductionUtil.clone(System.getProperties());
+ public static void andHowSnapshotBeforeTestClass() {
+ AndHowTestBaseImpl.andHowSnapshotBeforeTestClass();
}
-
+
+ /**
+ * Restores the AndHow Core (its state) and System Properties
+ * that were previously stored prior to this class' test run.
+ * It also resets the logging level for SimpleNamingContextBuilder (a JNDI
+ * related class) to what ever it was prior to the run.
+ */
@AfterClass
public static void resetAndHowSnapshotAfterTestClass() {
- System.setProperties(beforeClassSystemProps);
- AndHowNonProductionUtil.setAndHowCore(beforeClassCore);
-
- //Reset to the log level prior to the test class
- Logger.getGlobal().setLevel(beforeClassLogLevel);
- Logger.getLogger(SimpleNamingContextBuilder.class.getCanonicalName()).setLevel(beforeClassLogLevel);
+ AndHowTestBaseImpl.resetAndHowSnapshotAfterTestClass();
}
-
+
+ /**
+ * Stores the AndHow Core (its state) and System Properties prior to a test.
+ * It also sets the logging level for SimpleNamingContextBuilder (a JNDI
+ * related class) to SEVERE. If JNDI is used for a test, it's startup
+ * is verbose to System.out, so this turns it off.
+ */
@Before
public void andHowSnapshotBeforeSingleTest() {
- beforeTestSystemProps = AndHowNonProductionUtil.clone(System.getProperties());
- beforeTestCore = AndHowNonProductionUtil.getAndHowCore();
+ super.andHowSnapshotBeforeSingleTest();
}
-
+
+ /**
+ * Restores the AndHow Core (its state) and System Properties
+ * that were previously stored prior to a test run.
+ * It also resets the logging level for SimpleNamingContextBuilder (a JNDI
+ * related class) to what ever it was prior to the run.
+ */
@After
public void resetAndHowSnapshotAfterSingleTest() {
- System.setProperties(beforeTestSystemProps);
- AndHowNonProductionUtil.setAndHowCore(beforeTestCore);
-
- if (builder != null) {
- builder.clear();
- }
+ super.resetAndHowSnapshotAfterSingleTest();
}
-
-
+
}
diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowTestBaseImpl.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowTestBaseImpl.java
new file mode 100644
index 00000000..42ba03e1
--- /dev/null
+++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowTestBaseImpl.java
@@ -0,0 +1,133 @@
+package org.yarnandtail.andhow;
+
+import org.springframework.mock.jndi.SimpleNamingContextBuilder;
+import org.yarnandtail.andhow.internal.AndHowCore;
+
+import javax.naming.NamingException;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Base class for AndHowJunit4/5TestBase classes.
+ * See those classes for documentation.
+ */
+public class AndHowTestBaseImpl {
+ private static AndHowCore beforeClassCore;
+ private static Level beforeClassLogLevel;
+ /**
+ * System properties before the tests and subclass @BeforeClass initializer
+ * are run. Properties prior to the initialization of this test class are
+ * stored and then reinstated after the test class is complete.
+ */
+ private static Properties beforeClassSystemProps;
+ /**
+ * Builder for a temporary JNDI context
+ */
+ private static SimpleNamingContextBuilder builder;
+ private AndHowCore beforeTestCore;
+ /**
+ * System properties before an individual test is run and the subclass @Before
+ * initializers are run. Properties prior to a test are
+ * stored and then reinstated after the test is complete. If a Test classes
+ * uses a @BeforeClass initializer that sets system properties, this will
+ * reinstate to that state.
+ */
+ private Properties beforeTestSystemProps;
+
+ /**
+ * Stores the AndHow Core (its state) and System Properties prior to a test class.
+ * It also sets the logging level for SimpleNamingContextBuilder (a JNDI
+ * related class) to SEVERE. If JNDI is used for a test, it's startup
+ * is verbose to System.out, so this turns it off.
+ */
+ public static void andHowSnapshotBeforeTestClass() {
+ //The SimpleNamingContextBuilder uses Commons Logging, which defaults to
+ //using Java logging. It spews a bunch of stuff the console during tests,
+ //so this turns that off.
+
+ beforeClassLogLevel = Logger.getGlobal().getLevel(); //store log level before class
+ Logger.getGlobal().setLevel(Level.SEVERE);
+ Logger.getLogger(SimpleNamingContextBuilder.class.getCanonicalName()).setLevel(Level.SEVERE);
+
+
+ beforeClassCore = AndHowNonProductionUtil.getAndHowCore();
+ beforeClassSystemProps = AndHowNonProductionUtil.clone(System.getProperties());
+ }
+
+ /**
+ * Restores the AndHow Core (its state) and System Properties
+ * that were previously stored prior to this class' test run.
+ * It also resets the logging level for SimpleNamingContextBuilder (a JNDI
+ * related class) to what ever it was prior to the run.
+ * Any JNDI bindings set via getJndi() are cleared.
+ */
+ public static void resetAndHowSnapshotAfterTestClass() {
+ System.setProperties(beforeClassSystemProps);
+ AndHowNonProductionUtil.setAndHowCore(beforeClassCore);
+
+ //Reset to the log level prior to the test class
+ Logger.getGlobal().setLevel(beforeClassLogLevel);
+ Logger.getLogger(SimpleNamingContextBuilder.class.getCanonicalName()).setLevel(beforeClassLogLevel);
+
+ if (builder != null) {
+ builder.clear();
+ builder.deactivate();
+ }
+ }
+
+ /**
+ * Simple consistent way to get an empty JNDI context.
+ *
+ * Call {@code SimpleNamingContextBuilder.bind()} for each variable to add
+ * to the context, then {@code SimpleNamingContextBuilder.activate()} to
+ * make the context active. To fetch values from the context,
+ * use:
+ *
+ * The context is deactivated and cleared after each test and after the
+ * test class completes.
+ *
+ * @deprecated This will be removed in the next major release to avoid
+ * having JNDI dependencies in a user visible class. Most user will not
+ * need to test their apps with JNDI.
+ * @return A JNDI context for setting properties via JNDI.
+ * @throws NamingException If JNDI cannot be initiated.
+ */
+ public SimpleNamingContextBuilder getJndi() throws NamingException {
+ if (builder == null) {
+ builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
+ }
+ return builder;
+ }
+
+ /**
+ * Stores the AndHow Core (its state) and System Properties prior to a test.
+ * It also sets the logging level for SimpleNamingContextBuilder (a JNDI
+ * related class) to SEVERE. If JNDI is used for a test, it's startup
+ * is verbose to System.out, so this turns it off.
+ */
+ public void andHowSnapshotBeforeSingleTest() {
+ beforeTestSystemProps = AndHowNonProductionUtil.clone(System.getProperties());
+ beforeTestCore = AndHowNonProductionUtil.getAndHowCore();
+ }
+
+ /**
+ * Restores the AndHow Core (its state) and System Properties
+ * that were previously stored prior to a test run.
+ * It also resets the logging level for SimpleNamingContextBuilder (a JNDI
+ * related class) to what ever it was prior to the run.
+ * Any JNDI bindings set via getJndi() are cleared.
+ */
+ public void resetAndHowSnapshotAfterSingleTest() {
+ System.setProperties(beforeTestSystemProps);
+ AndHowNonProductionUtil.setAndHowCore(beforeTestCore);
+
+ if (builder != null) {
+ builder.clear();
+ builder.deactivate();
+ }
+ }
+}
diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTests.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTests.java
new file mode 100644
index 00000000..b8b6c00a
--- /dev/null
+++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTests.java
@@ -0,0 +1,45 @@
+package org.yarnandtail.andhow.junit5;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation that can be placed on a test class to reset AndHow to its unconfigured state
+ * (only) before the first test runs. When all tests in the class are done, the
+ * original AndHow configured state is restored, which may be unconfigured.
+ *
+ * Example usage:
+ *
{@Code
+ * @KillAndHowBeforeAllTests
+ * public class MyJunit5TestClass {
+ *
+ * @BeforeAll
+ * public static void configAndHowForAllTests(){
+ * AndHow.findConfig()
+ * .addFixedValue([AndHowProperty reference or name], [Value for that Property])
+ * .addFixedValue(...)
+ * .build();
+ * }
+ *
+ * ...tests that will all share the same configuration...
+ *
+ * }
+ * }
+ *
+ * Note: Using this annotation on a JUnit test class is the same as using
+ * {@Code @ExtendWith(KillAndHowBeforeAllTestsExtension.class)} on a class, but this annotation is
+ * safer because it cannot be put on a method. '@ExtendWith' allows placement on a method,
+ * but the extension will only work properly on a class.
+ */
+@Target({ TYPE, ANNOTATION_TYPE })
+@Retention(RUNTIME)
+@ExtendWith(KillAndHowBeforeAllTestsExtension.class)
+public @interface KillAndHowBeforeAllTests {
+
+}
diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTestsExtension.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTestsExtension.java
new file mode 100644
index 00000000..b22ca81a
--- /dev/null
+++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTestsExtension.java
@@ -0,0 +1,70 @@
+package org.yarnandtail.andhow.junit5;
+
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.yarnandtail.andhow.AndHowNonProductionUtil;
+import org.yarnandtail.andhow.internal.AndHowCore;
+
+/**
+ * JUnit Extension that can be placed on a test class to reset AndHow to its unconfigured
+ * state (only) before the first test runs. When all tests in the class are done, the
+ * original AndHow configured state is restored, which may be unconfigured.
+ *
+ * With this extension, all tests in the class share the same AndHow configuration, which
+ * can be set in a {@Code @BeforeAll} setup method.
+ *
+ * It is easier and safer to use the @{Code @KillAndHowBeforeAllTests} annotation.
+ * That annotation uses this class and prevents this extension from being used on a test method
+ * (this extension only works correctly on a test class).
+ *
+ * Usage example:
+ *
{@Code
+ * @ExtendWith(KillAndHowBeforeAllTestsExtension.class)
+ * public class MyJunit5TestClass {
+ *
+ * @BeforeAll
+ * public static void configAndHowForAllTests(){
+ * AndHow.findConfig()
+ * .addFixedValue([AndHowProperty reference or name], [Value for that Property])
+ * .addFixedValue(...)
+ * .build();
+ * }
+ *
+ * ...tests that will all share the same configuration...
+ *
+ * }
+ * }
+ */
+public class KillAndHowBeforeAllTestsExtension implements BeforeAllCallback, AfterAllCallback {
+
+ protected static final String CORE_KEY = "core_key";
+
+ /**
+ * Store the state of AndHow before any test is run, then destroy the state
+ * so AndHow is unconfigured.
+ * @param extensionContext
+ * @throws Exception
+ */
+ @Override
+ public void beforeAll(ExtensionContext extensionContext) throws Exception {
+ AndHowCore core = AndHowNonProductionUtil.getAndHowCore();
+ getStore(extensionContext).put(CORE_KEY, core);
+ AndHowNonProductionUtil.destroyAndHowCore();
+ }
+
+ @Override
+ public void afterAll(ExtensionContext extensionContext) throws Exception {
+ AndHowCore core = getStore(extensionContext).remove(CORE_KEY, AndHowCore.class);
+ AndHowNonProductionUtil.setAndHowCore(core);
+ }
+
+ /**
+ * Create or return the unique storage space for this test class for this extension.
+ * @param context
+ * @return
+ */
+ protected ExtensionContext.Store getStore(ExtensionContext context) {
+ return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestClass()));
+ }
+}
diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTest.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTest.java
new file mode 100644
index 00000000..3d11463d
--- /dev/null
+++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTest.java
@@ -0,0 +1,48 @@
+package org.yarnandtail.andhow.junit5;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.yarnandtail.andhow.junit5.KillAndHowBeforeEachTestExtension;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation that can be used on a JUnit test class to reset AndHow to its unconfigured state
+ * prior to each individual test. When all tests in the class are done, the original
+ * configured state of AndHow is restored, which may be unconfigured.
+ *
+ * Example usage:
+ *
{@Code
+ * @KillAndHowBeforeEachTest
+ * public class MyJunit5TestClass {
+ *
+ * @Test
+ * public void doATest(){
+ * AndHow.findConfig()
+ * .addFixedValue([AndHowProperty reference or name], [Value for that Property])
+ * .addFixedValue(...)
+ * .build();
+ *
+ * ... code for this test...
+ * }
+ *
+ * ...other tests that can each configure AndHow...
+ *
+ * }
+ * }
+ *
+ * Note: Using this annotation on a JUnit test class is the same as using
+ * {@Code @ExtendWith(KillAndHowBeforeEachTestExtension.class)} on a class, but this annotation is
+ * safer because it cannot be put on a method. '@ExtendWith' allows placement on a method,
+ * but the extension will only work properly on a class.
+ */
+@Target({ TYPE, ANNOTATION_TYPE })
+@Retention(RUNTIME)
+@ExtendWith(KillAndHowBeforeEachTestExtension.class)
+public @interface KillAndHowBeforeEachTest {
+
+}
diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTestExtension.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTestExtension.java
new file mode 100644
index 00000000..4c034b22
--- /dev/null
+++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTestExtension.java
@@ -0,0 +1,50 @@
+package org.yarnandtail.andhow.junit5;
+
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.yarnandtail.andhow.AndHowNonProductionUtil;
+
+
+/**
+ * JUnit Extension that can be placed on a test class to reset AndHow to its
+ * unconfigured state before each test runs. When all tests in the class are done, the
+ * original AndHow configured state is restored, which may be unconfigured.
+ *
+ * It is easier and safer to use the @{Code @KillAndHowBeforeEachTest} annotation.
+ * That annotation uses this class and prevents this extension from being used on a test method
+ * (this extension only works correctly on a test class).
+ *
+ * Usage example:
+ *
{@Code
+ * @ExtendWith(KillAndHowBeforeEachTestExtension.class)
+ * public class MyJunit5TestClass {
+ *
+ * @Test
+ * public void doATest(){
+ * AndHow.findConfig()
+ * .addFixedValue([AndHowProperty reference or name], [Value for that Property])
+ * .addFixedValue(...)
+ * .build();
+ *
+ * ... code for this test...
+ * }
+ *
+ * ...other tests that can each configure AndHow...
+ *
+ * }
+ * }
+ */
+public class KillAndHowBeforeEachTestExtension extends KillAndHowBeforeAllTestsExtension
+ implements BeforeEachCallback {
+
+ /**
+ * Destroy the AndHow state before each test so that each starts with AndHow unconfigured.
+ * @param extensionContext
+ * @throws Exception
+ */
+ @Override
+ public void beforeEach(ExtensionContext extensionContext) throws Exception {
+ AndHowNonProductionUtil.destroyAndHowCore();
+ }
+
+}
diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTest.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTest.java
new file mode 100644
index 00000000..c3ba7260
--- /dev/null
+++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTest.java
@@ -0,0 +1,38 @@
+package org.yarnandtail.andhow.junit5;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation that can be placed on an individual test method to reset AndHow to its
+ * unconfigured state before the test runs. When the test is done, the original AndHow configured
+ * state is restored, which may be unconfigured.
+ *
+ * Note: Using this annotation on a JUnit test method is the same as using
+ * {@Code @ExtendWith(KillAndHowBeforeThisTestExtension.class)} on a method, but this annotation is
+ * safer because it cannot be put on a class. '@ExtendWith' allows placement on a class,
+ * but the extension will only work properly on a method.
+ */
+@Target({ METHOD, ANNOTATION_TYPE })
+@Retention(RUNTIME)
+@ExtendWith(KillAndHowBeforeThisTestExtension.class)
+public @interface KillAndHowBeforeThisTest {
+
+}
diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTestExtension.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTestExtension.java
new file mode 100644
index 00000000..0dbabc5e
--- /dev/null
+++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTestExtension.java
@@ -0,0 +1,72 @@
+package org.yarnandtail.andhow.junit5;
+
+import org.junit.jupiter.api.extension.*;
+import org.yarnandtail.andhow.AndHowNonProductionUtil;
+import org.yarnandtail.andhow.internal.AndHowCore;
+
+import java.lang.reflect.Method;
+import java.util.Optional;
+
+/**
+ * JUnit Extension that can be placed on an individual test method to reset AndHow to its
+ * unconfigured state before the test runs. When the test is done, the original AndHow configured
+ * state is restored, which may be unconfigured.
+ *
+ * It is easier and safer to use the @{Code @KillAndHowBeforeThisTest} annotation.
+ * That annotation uses this class and prevents this extension from being used on a test class
+ * (this extension only works correctly on a test method).
+ *