diff --git a/core/src/main/java/foundation/stack/datamill/configuration/BooleanValue.java b/core/src/main/java/foundation/stack/datamill/configuration/BooleanValue.java
new file mode 100644
index 0000000..4dcf129
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/BooleanValue.java
@@ -0,0 +1,20 @@
+package foundation.stack.datamill.configuration;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to be used together with a {@link foundation.stack.datamill.configuration.impl.ConstantsClassSource} to
+ * specify String values. For example, a constant declared in a constants class as:
+ *
+ * \@BooleanValue(true) public static final String PROPERTY_NAME = "configuration/propertyName";
+ *
+ * This defines a property called "configuration/propertyName" that has the boolean value true.
+ *
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface BooleanValue {
+ boolean value();
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/IntegerValue.java b/core/src/main/java/foundation/stack/datamill/configuration/IntegerValue.java
new file mode 100644
index 0000000..0080320
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/IntegerValue.java
@@ -0,0 +1,20 @@
+package foundation.stack.datamill.configuration;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to be used together with a {@link foundation.stack.datamill.configuration.impl.ConstantsClassSource} to
+ * specify String values. For example, a constant declared in a constants class as:
+ *
+ * \@IntegerValue(16) public static final String PROPERTY_NAME = "configuration/propertyName";
+ *
+ * This defines a property called "configuration/propertyName" that has the integer value 16.
+ *
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface IntegerValue {
+ int value();
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/Named.java b/core/src/main/java/foundation/stack/datamill/configuration/Named.java
index 213a780..4a3b081 100644
--- a/core/src/main/java/foundation/stack/datamill/configuration/Named.java
+++ b/core/src/main/java/foundation/stack/datamill/configuration/Named.java
@@ -10,7 +10,7 @@
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.PARAMETER)
+@Target({ElementType.PARAMETER})
public @interface Named {
String value();
}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/Properties.java b/core/src/main/java/foundation/stack/datamill/configuration/Properties.java
index e7f07e6..323901a 100644
--- a/core/src/main/java/foundation/stack/datamill/configuration/Properties.java
+++ b/core/src/main/java/foundation/stack/datamill/configuration/Properties.java
@@ -12,6 +12,11 @@
* @author Ravi Chodavarapu (rchodava@gmail.com)
*/
public class Properties {
+ /** @see PropertySourceChain#orConstantsClass(Class) */
+ public static PropertySourceChain fromConstantsClass(Class constantsClass) {
+ return fromSource(new ConstantsClassSource<>(constantsClass));
+ }
+
/** @see PropertySourceChain#orFile(String) */
public static PropertySourceChain fromFile(String path) {
try {
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/PropertySourceChain.java b/core/src/main/java/foundation/stack/datamill/configuration/PropertySourceChain.java
index 3ffc770..82cf525 100644
--- a/core/src/main/java/foundation/stack/datamill/configuration/PropertySourceChain.java
+++ b/core/src/main/java/foundation/stack/datamill/configuration/PropertySourceChain.java
@@ -30,6 +30,14 @@ public interface PropertySourceChain extends PropertySource {
*/
PropertySourceChain alias(String alias, String original);
+ /**
+ * Add a property source to the chain which retrieves properties from a constants interface or class. The constants
+ * are expected to
+ *
+ * @param constantsClass Constants class to add as a source.
+ */
+ PropertySourceChain orConstantsClass(Class constantsClass);
+
/**
* Add a property source to the chain which retrieves properties specified in the file at the specified path.
*
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/StringValue.java b/core/src/main/java/foundation/stack/datamill/configuration/StringValue.java
new file mode 100644
index 0000000..26c4b7d
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/StringValue.java
@@ -0,0 +1,20 @@
+package foundation.stack.datamill.configuration;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to be used together with a {@link foundation.stack.datamill.configuration.impl.ConstantsClassSource} to
+ * specify String values. For example, a constant declared in a constants class as:
+ *
+ * \@StringValue("value") public static final String PROPERTY_NAME = "configuration/propertyName";
+ *
+ * This defines a property called "configuration/propertyName" that has the value "value".
+ *
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface StringValue {
+ String value();
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/Wiring.java b/core/src/main/java/foundation/stack/datamill/configuration/Wiring.java
index 947fc73..ca88057 100644
--- a/core/src/main/java/foundation/stack/datamill/configuration/Wiring.java
+++ b/core/src/main/java/foundation/stack/datamill/configuration/Wiring.java
@@ -426,7 +426,27 @@ private T constructWithConstructor(
return null;
}
} else {
- logger.error("Could not build class {} as the following type was not found {}", clazz, parameterType);
+ StringBuilder parameterNames = new StringBuilder();
+
+ Named[] annotations = parameters[i].getAnnotationsByType(Named.class);
+ if (annotations != null && annotations.length > 0) {
+ for (Named annotation : annotations) {
+ if (parameterNames.length() > 0) {
+ parameterNames.append(", ");
+ }
+
+ parameterNames.append(annotation.value());
+ }
+ }
+
+ if (parameterNames.length() > 0) {
+ logger.error("Could not build class {} as the following named parameter was not found: {}",
+ clazz, parameterNames.toString());
+ } else {
+ logger.error("Could not build class {} as the following type was not found {}",
+ clazz, parameterType);
+ }
+
return null;
}
}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/impl/ConstantsClassSource.java b/core/src/main/java/foundation/stack/datamill/configuration/impl/ConstantsClassSource.java
new file mode 100644
index 0000000..018ed69
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/impl/ConstantsClassSource.java
@@ -0,0 +1,85 @@
+package foundation.stack.datamill.configuration.impl;
+
+import foundation.stack.datamill.configuration.BooleanValue;
+import foundation.stack.datamill.configuration.IntegerValue;
+import foundation.stack.datamill.configuration.StringValue;
+import javassist.Modifier;
+
+import java.lang.reflect.Field;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public class ConstantsClassSource extends AbstractSource {
+ private static String getConstantValue(Field field) {
+ if (field != null && Modifier.isStatic(field.getModifiers())) {
+ try {
+ performSecure(() -> field.setAccessible(true));
+ return String.valueOf(field.get(null));
+ } catch (IllegalAccessException e) {
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+ private static void performSecure(Runnable runnable) {
+ if (System.getSecurityManager() != null) {
+ AccessController.doPrivileged((PrivilegedAction>) () -> {
+ runnable.run();
+ return null;
+ });
+ } else {
+ runnable.run();
+ }
+ }
+
+ private final Class constantsClass;
+ private Map annotatedConstants;
+
+ public ConstantsClassSource(Class constantsClass) {
+ this.constantsClass = constantsClass;
+ }
+
+ private Map buildAnnotatedConstants() {
+ HashMap constants = new HashMap<>();
+
+ Field[] fields = constantsClass.getDeclaredFields();
+ for (Field field : fields) {
+ String propertyName = getConstantValue(field);
+ if (propertyName != null) {
+ StringValue stringValue = field.getAnnotation(StringValue.class);
+ if (stringValue != null) {
+ constants.put(propertyName, stringValue.value());
+ } else {
+ BooleanValue booleanValue = field.getAnnotation(BooleanValue.class);
+ if (booleanValue != null) {
+ constants.put(propertyName, String.valueOf(booleanValue.value()));
+ } else {
+ IntegerValue integerValue = field.getAnnotation(IntegerValue.class);
+ if (integerValue != null) {
+ constants.put(propertyName, String.valueOf(integerValue.value()));
+ }
+ }
+ }
+ }
+ }
+
+ return constants;
+ }
+
+ @Override
+ protected Optional getOptional(String name) {
+ if (annotatedConstants == null) {
+ annotatedConstants = buildAnnotatedConstants();
+ }
+
+ return Optional.ofNullable(annotatedConstants.get(name));
+ }
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/impl/PropertySourceChainImpl.java b/core/src/main/java/foundation/stack/datamill/configuration/impl/PropertySourceChainImpl.java
index 1d8242d..fdf98ee 100644
--- a/core/src/main/java/foundation/stack/datamill/configuration/impl/PropertySourceChainImpl.java
+++ b/core/src/main/java/foundation/stack/datamill/configuration/impl/PropertySourceChainImpl.java
@@ -43,6 +43,11 @@ public Optional getOptional(String name) {
return Optional.empty();
}
+ @Override
+ public PropertySourceChain orConstantsClass(Class constantsClass) {
+ return orSource(new ConstantsClassSource<>(constantsClass));
+ }
+
@Override
public PropertySource orDefaults(Action1 defaultsInitializer) {
DefaultsSource defaults = new DefaultsSource();
diff --git a/core/src/test/java/foundation/stack/datamill/configuration/PropertiesTest.java b/core/src/test/java/foundation/stack/datamill/configuration/PropertiesTest.java
index d054ca5..c7b7819 100644
--- a/core/src/test/java/foundation/stack/datamill/configuration/PropertiesTest.java
+++ b/core/src/test/java/foundation/stack/datamill/configuration/PropertiesTest.java
@@ -42,6 +42,26 @@ public void systemProperties() {
assertEquals("value", Properties.fromSystem().getRequired("test").asString());
}
+ @Test
+ public void constantClasses() {
+ assertEquals("value", Properties.fromConstantsClass(ConstantsClass.class).get(ConstantsClass.property).get());
+ assertFalse(Properties.fromConstantsClass(ConstantsClass.class).get("property2").isPresent());
+ assertFalse(Properties.fromConstantsClass(ConstantsClass.class).get("config/instance").isPresent());
+
+ assertEquals("publicValue", Properties.fromConstantsClass(ConstantsClass.class).get(ConstantsClass.publicProperty).get());
+ assertEquals("privateValue", Properties.fromConstantsClass(ConstantsClass.class).get(ConstantsClass.privateProperty).get());
+ assertEquals("nonFinalValue", Properties.fromConstantsClass(ConstantsClass.class).get(ConstantsClass.nonFinalProperty).get());
+ assertEquals("1", Properties.fromConstantsClass(ConstantsClass.class).get(ConstantsClass.integerProperty).get());
+ assertEquals("true", Properties.fromConstantsClass(ConstantsClass.class).get(ConstantsClass.booleanProperty).get());
+
+ assertEquals("value", Properties.fromConstantsClass(ConstantsClass.class).getRequired("config/property").asString());
+
+ assertEquals("ifacePublic", Properties.fromConstantsClass(ConstantsClass.class).orConstantsClass(ConstantsInterface.class).get(ConstantsInterface.IFACE_PUBLIC_TEST).get());
+ assertEquals("2", Properties.fromConstantsClass(ConstantsClass.class).orConstantsClass(ConstantsInterface.class).get(ConstantsInterface.IFACE_INTEGER).get());
+ assertEquals("true", Properties.fromConstantsClass(ConstantsClass.class).orConstantsClass(ConstantsInterface.class).get(ConstantsInterface.IFACE_BOOLEAN).get());
+ assertEquals("true", Properties.fromConstantsClass(ConstantsClass.class).orConstantsClass(ConstantsInterface.class).get(ConstantsClass.booleanProperty).get());
+ }
+
@Test
public void files() {
assertEquals("value", Properties.fromFile("test.properties").get("test").get());
@@ -131,4 +151,20 @@ public void missingRequiredProperties() throws Exception {
} catch (IllegalArgumentException e) {
}
}
+
+ private static class ConstantsClass {
+ @StringValue("value") static final String property = "config/property";
+ @StringValue("publicValue") public static final String publicProperty = "config/public";
+ @StringValue("privateValue") private static final String privateProperty = "config/private";
+ @StringValue("nonFinalValue") static String nonFinalProperty = "config/nonFinal";
+ @IntegerValue(1) static String integerProperty = "config/integer";
+ @BooleanValue(true) static String booleanProperty = "config/boolean";
+ @StringValue("instanceValue") String instanceProperty = "config/instance";
+ }
+
+ private interface ConstantsInterface {
+ @StringValue("ifacePublic") String IFACE_PUBLIC_TEST = "iface/public";
+ @IntegerValue(2) String IFACE_INTEGER = "iface/integer";
+ @BooleanValue(true) String IFACE_BOOLEAN = "iface/boolean";
+ }
}