diff --git a/core/src/main/java/foundation/stack/datamill/configuration/Defaults.java b/core/src/main/java/foundation/stack/datamill/configuration/Defaults.java
deleted file mode 100644
index e860ec6..0000000
--- a/core/src/main/java/foundation/stack/datamill/configuration/Defaults.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package foundation.stack.datamill.configuration;
-
-/**
- * Used in defining defaults for properties.
- *
- * @see PropertySourceChain
- * @author Ravi Chodavarapu (rchodava@gmail.com)
- */
-public interface Defaults {
- Defaults put(String name, String value);
-}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/DelegatingPropertySource.java b/core/src/main/java/foundation/stack/datamill/configuration/DelegatingPropertySource.java
new file mode 100644
index 0000000..f609c4c
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/DelegatingPropertySource.java
@@ -0,0 +1,29 @@
+package foundation.stack.datamill.configuration;
+
+import foundation.stack.datamill.configuration.impl.AbstractSource;
+
+import java.util.Optional;
+
+/**
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public class DelegatingPropertySource extends AbstractSource {
+ private volatile PropertySource delegate;
+
+ public DelegatingPropertySource(PropertySource delegate) {
+ setDelegate(delegate);
+ }
+
+ public DelegatingPropertySource() {
+ this(null);
+ }
+
+ public void setDelegate(PropertySource delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ protected Optional getOptional(String name) {
+ return delegate != null ? delegate.get(name) : Optional.empty();
+ }
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/Factory.java b/core/src/main/java/foundation/stack/datamill/configuration/Factory.java
new file mode 100644
index 0000000..46b4597
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/Factory.java
@@ -0,0 +1,18 @@
+package foundation.stack.datamill.configuration;
+
+import rx.functions.Func2;
+
+/**
+ * A factory for constructing objects of particular types. Use {@link FactoryChains} to create factories and compose
+ * them together into chains. Note that the contract of a factory is to return a fully constructed instance of the
+ * requested type (it can use the provided {@link Wiring} to construct the instance, or return null if the factory
+ * cannot construct an instance.
+ *
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+@FunctionalInterface
+public interface Factory extends Func2, R> {
+ static Factory, ?> wrap(TypeLessFactory> factory) {
+ return (w, c) -> factory.call(w);
+ }
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/FactoryChain.java b/core/src/main/java/foundation/stack/datamill/configuration/FactoryChain.java
new file mode 100644
index 0000000..3c68c0b
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/FactoryChain.java
@@ -0,0 +1,74 @@
+package foundation.stack.datamill.configuration;
+
+/**
+ * A chain of object factories used by {@link Wiring}s to create objects. Use {@link FactoryChains} to create
+ * {@link FactoryChain}s.
+ *
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public interface FactoryChain extends QualifyingFactory {
+ /**
+ * Remove a factory from the chain, returning a chain without the specified factory.
+ */
+ FactoryChain exclude(QualifyingFactory, ?> factory);
+
+ /**
+ * Add a factory which will be used to attempt construction of instances for all types.
+ */
+ FactoryChain thenForAny(Factory factory);
+
+ /**
+ * Add a factory which will be used to attempt construction of instances of concrete classes.
+ */
+ FactoryChain thenForAnyConcreteClass();
+
+ /**
+ * Add a factory for a type, and it's super-classes and interfaces. Note that this factory will be invoked if the
+ * type requested is exactly the specified type, or one of it's super-classes or interfaces.
+ */
+ FactoryChain thenForSuperOf(Class type, Factory factory);
+
+ /**
+ * Add a factory for a specific type. Note that this factory is only invoked if the type being constructed matches
+ * the exact type specified.
+ *
+ * @param type Exact type for which the specified factory will be used.
+ */
+ FactoryChain thenForType(Class type, Factory factory);
+
+ /**
+ * @see #thenForAny(Factory)
+ */
+ FactoryChain thenForAny(TypeLessFactory factory);
+
+ /**
+ * @see #thenForSuperOf(Class, Factory)
+ */
+ FactoryChain thenForSuperOf(Class type, TypeLessFactory> factory);
+
+ /**
+ * @see #thenForType(Class, Factory)
+ */
+ FactoryChain thenForType(Class type, TypeLessFactory factory);
+
+ /**
+ * Add a qualifying factory which will be used to attempt construction of instances for all types.
+ *
+ * @see #thenForAny(Factory)
+ */
+ FactoryChain thenForAny(QualifyingFactory factory);
+
+ /**
+ * Add a qualifying factory for a type, and it's super-classes and interfaces.
+ *
+ * @see #thenForSuperOf(Class, Factory)
+ */
+ FactoryChain thenForSuperOf(Class type, QualifyingFactory factory);
+
+ /**
+ * Add a qualifying factory for a specific type.
+ *
+ * @see #thenForType(Class, Factory)
+ */
+ FactoryChain thenForType(Class type, QualifyingFactory factory);
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/FactoryChains.java b/core/src/main/java/foundation/stack/datamill/configuration/FactoryChains.java
new file mode 100644
index 0000000..e280b9b
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/FactoryChains.java
@@ -0,0 +1,124 @@
+package foundation.stack.datamill.configuration;
+
+import foundation.stack.datamill.configuration.impl.ConcreteClassFactory;
+import foundation.stack.datamill.configuration.impl.FactoryChainImpl;
+
+/**
+ *
+ * Use this class to create{@link FactoryChain}s. For example, consider a chain composed and used as follows:
+ *
+ *
+ * chain = FactoryChains.forType(DatabaseClient.class, w -> createDatabaseClient())
+ * .thenForSuperOf(UserRepository.class, w -> new UserRepository(...))
+ * .thenForAny(Core.FACTORY)
+ * .thenForAnyConcreteClass();
+ *
+ *
+ * This chain will first check if the class requested is a DatabaseClient, and if so, will construct it. Then, it will
+ * check if the class requested is a super-class or interface of the UserRepository class, and if so will construct it.
+ * Then, it will delegate to the Core.FACTORY factory chain. Finally, if none of those factories are able to construct
+ * the requested instance, the chain will ask the concrete class factory to construct an instance.
+ *
+ *
+ * Note that if you do not add the concrete class factory at the end of a chain, your chain will only construct the
+ * exact types for which your factories can construct instances. Thus, it is often useful to add the concrete class
+ * factory to the end of a factory chain.
+ *
+ *
+ * Note that it is easy to construct factory chains that result in infinite recursion. For example, consider the
+ * following:
+ *
+ *
+ * chain = FactoryChains.forSuperOf(EmailService.class, w -> w.singleton(EmailService.class))
+ * .thenForAnyConcreteClass();
+ *
+ * new Wiring(chain).singleton(EmailService.class);
+ *
+ *
+ * If the chain is used in this way to construct an instance of the EmailService class (a concrete class), the first
+ * factory in the chain is used. This results in turn to a call to the wiring to construct a singleton instance of
+ * EmailService. The Wiring will use the same chain, starting at the beginning of the chain. This will again result
+ * in the first factory in the chain to be used, and would lead to an infinite recursion. To solve this, when a particular
+ * factory in the chain in turn uses the Wiring to construct an instance, the factory will be excluded from the chain in
+ * the next call. This will essentially break the infinite recursion - the call to construct the EmailService from the
+ * first factory in the chain will go directly to the concrete class factory in the second pass through the chain.
+ *
+ *
+ * Note that factory chains are immutable, and each of the chaining methods returns a new chain with the additional
+ * factory added. This allows you to have common chains which you can combine in different ways, because each
+ * of those combinations is a new chain.
+ *
+ *
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public class FactoryChains {
+ /**
+ * @see FactoryChain#thenForAnyConcreteClass()
+ */
+ public static FactoryChain forAnyConcreteClass() {
+ return forAny(ConcreteClassFactory.instance());
+ }
+
+ /**
+ * @see FactoryChain#thenForAny(Factory)
+ */
+ public static FactoryChain forAny(Factory factory) {
+ return new FactoryChainImpl(FactoryChainImpl.tautology(), factory);
+ }
+
+ /**
+ * @see FactoryChain#thenForSuperOf(Class, Factory)
+ */
+ public static FactoryChain forSuperOf(Class> type, Factory, ?> factory) {
+ return new FactoryChainImpl(FactoryChainImpl.isSuperOf(type), factory);
+ }
+
+ /**
+ * @see FactoryChain#thenForType(Class, Factory)
+ */
+ public static FactoryChain forType(Class type, Factory factory) {
+ return new FactoryChainImpl(FactoryChainImpl.isExactType(type), factory);
+ }
+
+ /**
+ * @see FactoryChain#thenForAny(QualifyingFactory)
+ */
+ public static FactoryChain forAny(QualifyingFactory factory) {
+ return new FactoryChainImpl(FactoryChainImpl.tautology(), factory);
+ }
+
+ /**
+ * @see FactoryChain#thenForSuperOf(Class, QualifyingFactory)
+ */
+ public static FactoryChain forSuperOf(Class> type, QualifyingFactory, ?> factory) {
+ return new FactoryChainImpl(FactoryChainImpl.isSuperOf(type), factory);
+ }
+
+ /**
+ * @see FactoryChain#thenForType(Class, QualifyingFactory)
+ */
+ public static FactoryChain forType(Class type, QualifyingFactory factory) {
+ return new FactoryChainImpl(FactoryChainImpl.isExactType(type), factory);
+ }
+
+ /**
+ * @see FactoryChain#thenForAny(TypeLessFactory)
+ */
+ public static FactoryChain forAny(TypeLessFactory factory) {
+ return new FactoryChainImpl(FactoryChainImpl.tautology(), factory);
+ }
+
+ /**
+ * @see FactoryChain#thenForSuperOf(Class, TypeLessFactory)
+ */
+ public static FactoryChain forSuperOf(Class> type, TypeLessFactory> factory) {
+ return new FactoryChainImpl(FactoryChainImpl.isSuperOf(type), factory);
+ }
+
+ /**
+ * @see FactoryChain#thenForType(Class, TypeLessFactory)
+ */
+ public static FactoryChain forType(Class type, TypeLessFactory factory) {
+ return new FactoryChainImpl(FactoryChainImpl.isExactType(type), factory);
+ }
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/ImmediatePropertySource.java b/core/src/main/java/foundation/stack/datamill/configuration/ImmediatePropertySource.java
new file mode 100644
index 0000000..ecb86c7
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/ImmediatePropertySource.java
@@ -0,0 +1,17 @@
+package foundation.stack.datamill.configuration;
+
+/**
+ * Used in defining an immediate immutable set of properties.
+ *
+ * @see PropertySources
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public interface ImmediatePropertySource {
+ ImmediatePropertySource put(String name, String value);
+
+ /**
+ * Add a formatted value to the immediate set of properties. Uses the
+ * {@link java.text.MessageFormat#format(String, Object...)} method to format the value.
+ */
+ ImmediatePropertySource put(String name, String format, Object... arguments);
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/Module.java b/core/src/main/java/foundation/stack/datamill/configuration/Module.java
deleted file mode 100644
index 75316cb..0000000
--- a/core/src/main/java/foundation/stack/datamill/configuration/Module.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package foundation.stack.datamill.configuration;
-
-import rx.functions.Action1;
-
-/**
- * @author Ravi Chodavarapu (rchodava@gmail.com)
- */
-@FunctionalInterface
-public interface Module extends Action1 {
-}
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..7b938ce 100644
--- a/core/src/main/java/foundation/stack/datamill/configuration/Named.java
+++ b/core/src/main/java/foundation/stack/datamill/configuration/Named.java
@@ -3,14 +3,14 @@
import java.lang.annotation.*;
/**
- * Annotation to be used to add a name qualifier to constructor parameters.
+ * Annotation to be used to request a named property to be injected into a constructor parameter.
*
* @see Wiring
* @author Ravi Chodavarapu (rchodava@gmail.com)
*/
@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
deleted file mode 100644
index e7f07e6..0000000
--- a/core/src/main/java/foundation/stack/datamill/configuration/Properties.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package foundation.stack.datamill.configuration;
-
-import foundation.stack.datamill.configuration.impl.*;
-import rx.functions.Func1;
-
-import java.io.IOException;
-
-/**
- * Starting points for creating {@link PropertySourceChain}s.
- *
- * @see PropertySourceChain
- * @author Ravi Chodavarapu (rchodava@gmail.com)
- */
-public class Properties {
- /** @see PropertySourceChain#orFile(String) */
- public static PropertySourceChain fromFile(String path) {
- try {
- return fromSource(new FileSource(path));
- } catch (IOException e) {
- return fromSource(EmptySource.INSTANCE);
- }
- }
-
- /** @see PropertySourceChain#orSource(PropertySource) */
- public static PropertySourceChain fromSource(PropertySource source) {
- return new PropertySourceChainImpl(source);
- }
-
- /** @see PropertySourceChain#orEnvironment() */
- public static PropertySourceChain fromEnvironment() {
- return fromSource(EnvironmentPropertiesSource.IDENTITY);
- }
-
- /** @see PropertySourceChain#orEnvironment(Func1) */
- public static PropertySourceChain fromEnvironment(Func1 transformer) {
- return fromSource(new EnvironmentPropertiesSource(transformer));
- }
-
- /** @see PropertySourceChain#orSystem() */
- public static PropertySourceChain fromSystem() {
- return fromSource(SystemPropertiesSource.IDENTITY);
- }
-
- /** @see PropertySourceChain#orSystem(Func1) */
- public static PropertySourceChain fromSystem(Func1 transformer) {
- return fromSource(new SystemPropertiesSource(transformer));
- }
-}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/PropertyNameTransformers.java b/core/src/main/java/foundation/stack/datamill/configuration/PropertyNameTransformers.java
new file mode 100644
index 0000000..84ed9bc
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/PropertyNameTransformers.java
@@ -0,0 +1,55 @@
+package foundation.stack.datamill.configuration;
+
+import com.google.common.base.CaseFormat;
+import rx.functions.Func1;
+
+/**
+ * Some common property name transformers that are useful when creating {@link PropertySources}.
+ */
+public class PropertyNameTransformers { /**
+ * Transforms from lower-camel case to upper-underscore case, for example: propertyName -> PROPERTY_NAME.
+ */
+ public static final Func1 LOWER_CAMEL_TO_UPPER_UNDERSCORE =
+ name -> CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, name);
+
+ /**
+ * Same as calling {@link #leaf(char)} with '/'.
+ */
+ public static final Func1 LEAF = leaf('/');
+
+ /**
+ *
+ * Returns a transformation function that applies the first function, and then the second function on the result
+ * of the first. So compose(LEAF, LOWER_CAMEL_TO_UPPER_UNDERSCORE) will return a function that transforms:
+ *
+ *
+ * category/propertyName -> PROPERTY_NAME
+ *
+ */
+ public static final Func1 compose(Func1 a, Func1 b) {
+ return name -> b.call(a.call(name));
+ }
+
+ /**
+ *
+ * Returns a transformation function that returns the leaf name of a name separated by the specified separator
+ * character. For example:
+ *
+ *
+ * leaf('/') returns a function which transforms: category/propertyName -> propertyName
+ * leaf(':') returns a function which transforms: category:propertyName -> propertyName
+ *
+ */
+ public static final Func1 leaf(char separator) {
+ return name -> {
+ if (name != null) {
+ int leafSeparator = name.lastIndexOf('/');
+ if (leafSeparator > 0) {
+ return name.substring(leafSeparator + 1);
+ }
+ }
+
+ return name;
+ };
+ }
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/PropertySource.java b/core/src/main/java/foundation/stack/datamill/configuration/PropertySource.java
index ea7fe0e..0669131 100644
--- a/core/src/main/java/foundation/stack/datamill/configuration/PropertySource.java
+++ b/core/src/main/java/foundation/stack/datamill/configuration/PropertySource.java
@@ -1,25 +1,16 @@
package foundation.stack.datamill.configuration;
import foundation.stack.datamill.values.Value;
-import rx.functions.Action1;
+import rx.functions.Func1;
import java.util.Optional;
/**
- * A source of properties.
+ * A source of properties. Use {@link PropertySources} to create {@link PropertySource}s.
*
* @author Ravi Chodavarapu (rchodava@gmail.com)
*/
public interface PropertySource {
- /**
- * Alias a property so that the alias can be used in a {@link #get(String)} call in order to retrieve the original
- * property's value.
- *
- * @param alias New alias for the original property.
- * @param original Original property to create an alias for.
- */
- PropertySource alias(String alias, String original);
-
/**
* Get the specified property from the source, if it exists.
*
@@ -43,5 +34,5 @@ public interface PropertySource {
*
* @param propertiesConsumer Lambda that receives this property source.
*/
- PropertySource with(Action1 propertiesConsumer);
+ R with(Func1 propertiesConsumer);
}
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..bd6df5c 100644
--- a/core/src/main/java/foundation/stack/datamill/configuration/PropertySourceChain.java
+++ b/core/src/main/java/foundation/stack/datamill/configuration/PropertySourceChain.java
@@ -4,38 +4,50 @@
import rx.functions.Func1;
/**
- * Allows {@link PropertySource}s to be linked together into a chain so that alternative sources of desired properties
- * can be specified when looking up configuration properties. For example, consider a chain composed and used as follows:
- *
- *
- * chain = Properties.fromFile("service.properties").orFile("defaults.properties").orEnvironment()
- * .orDefaults(defaults -> defaults.put("property1", "value1").put("property2", "value2"));
- *
- * chain.getRequired("property1");
- * chain.getRequired("property3");
- *
- *
- * This chain will first attempt to retrieve requested properties from the service.properties file. It will consult the
- * defaults.properties file next if it can't find properties in service.properties. Subsequently, it will attempt to
- * look for those properties amongst environment variables if it still fails to find a requested property. Finally,
- * it will return the defaults specified by the orDefaults(...) call.
- *
- * Note that each of the chaining methods returns a new chain with the additional property source added.
- *
* @author Ravi Chodavarapu (rchodava@gmail.com)
*/
public interface PropertySourceChain extends PropertySource {
/**
- * @see PropertySource#alias(String, String)
+ * Add a custom property source to the chain at this point.
+ *
+ * @param source Source to add to the chain.
*/
- PropertySourceChain alias(String alias, String original);
+ PropertySourceChain or(PropertySource source);
/**
- * Add a property source to the chain which retrieves properties specified in the file at the specified path.
+ * @See PropertySources
+ * @see #or(PropertySource)
+ */
+ PropertySourceChain or(PropertySource source, Func1 transformer);
+
+ /**
+ * Add a property source to the chain which computes the value of property using the specified function. The
+ * function can return either a value, or a null to indicate it cannot compute the value of the property - in this
+ * case, the chain will look to the next source.
*
- * @param path Path to properties file to add as a source.
+ * @param computation Computation function for the source.
*/
- PropertySourceChain orFile(String path);
+ PropertySourceChain orComputed(Func1 computation);
+
+ /**
+ * @See PropertySources
+ * @see #orComputed(Func1)
+ */
+ PropertySourceChain orComputed(Func1 computation, Func1 transformer);
+
+ /**
+ * Add a property source to the chain which retrieves properties from a constants interface or class. The interface
+ * or class is expected to define String constants that are annotated with {@link Value} annotations.
+ *
+ * @param constantsClass Constants class to add as a source.
+ */
+ PropertySourceChain orConstantsClass(Class constantsClass);
+
+ /**
+ * @See PropertySources
+ * @see #orConstantsClass(Class)
+ */
+ PropertySourceChain orConstantsClass(Class constantsClass, Func1 transformer);
/**
* Add a source which looks up properties defined as environment variables to the chain.
@@ -43,39 +55,45 @@ public interface PropertySourceChain extends PropertySource {
PropertySourceChain orEnvironment();
/**
- * Add a source which looks up properties defined as environment variables but first transforms the property names
- * using the specified function before looking up the environment variable.
- *
- * @param transformer Transformation function used to transform property names before looking them up as
- * environment variables. For example, you may want to prefix property names before looking them
- * up as environment variables.
+ * @See PropertySources
+ * @see #orEnvironment()
*/
PropertySourceChain orEnvironment(Func1 transformer);
/**
- * Add a custom property source to the chain at this point..
+ * Add a property source to the chain which retrieves properties specified in the file at the specified path.
*
- * @param source Source to add to the chain.
+ * @param path Path to properties file to add as a source.
*/
- PropertySourceChain orSource(PropertySource source);
+ PropertySourceChain orFile(String path);
/**
- * Add a source which looks up properties defined as system properties to the chain.
+ * @See PropertySources
+ * @see #orFile(String)
*/
- PropertySourceChain orSystem();
+ PropertySourceChain orFile(String path, Func1 transformer);
/**
- * Add a source which looks up properties defined as system properties but first transforms the property names
- * using the specified function before looking up the system property.
+ * Add immediate set of properties that can be looked up at this point in the chain.
*
- * @see #orEnvironment(Func1)
+ * @param initializer Function which sets up immediate properties at this point in the chain.
*/
- PropertySourceChain orSystem(Func1 transformer);
+ PropertySourceChain orImmediate(Action1 initializer);
/**
- * Add defaults for properties that cannot be found by any other source in the chain.
- *
- * @param defaultsInitializer Function which sets up defaults for properties not found in the chain.
+ * @See PropertySources
+ * @see #orImmediate(Action1)
*/
- PropertySource orDefaults(Action1 defaultsInitializer);
+ PropertySourceChain orImmediate(Action1 initializer, Func1 transformer);
+
+ /**
+ * Add a source which looks up properties defined as system properties to the chain.
+ */
+ PropertySourceChain orSystem();
+
+ /**
+ * @See PropertySources
+ * @see #orSystem()
+ */
+ PropertySourceChain orSystem(Func1 transformer);
}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/PropertySources.java b/core/src/main/java/foundation/stack/datamill/configuration/PropertySources.java
new file mode 100644
index 0000000..728bc42
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/PropertySources.java
@@ -0,0 +1,153 @@
+package foundation.stack.datamill.configuration;
+
+import foundation.stack.datamill.configuration.impl.*;
+import rx.functions.Action1;
+import rx.functions.Func1;
+
+import java.io.IOException;
+
+/**
+ *
+ * Use this class to create some common {@link PropertySource}s. This class can also be used to compose together
+ * {@link PropertySource}s into {@link PropertySourceChain}s so that alternative sources of desired properties can be
+ * specified when looking up configuration properties. For example, consider a chain composed and used as follows:
+ *
+ *
+ * chain = PropertySources.fromFile("service.properties").orFile("defaults.properties").orEnvironment()
+ * .orImmediate(defaults -> defaults.put("property1", "value1").put("property2", "value2"));
+ *
+ * chain.getRequired("property1");
+ * chain.getRequired("property3");
+ *
+ *
+ * This chain will first attempt to retrieve the requested properties from the service.properties file. It will consult
+ * the defaults.properties file next if it can't find properties in service.properties. Subsequently, it will attempt to
+ * look for those properties amongst environment variables if it still fails to find a requested property. Finally,
+ * it will return the defaults specified by the orImmediate(...) call.
+ *
+ *
+ * Note that each of the methods accepts a transformer to transform the names of the properties before looking up
+ * the property in that particular property source. See {@link PropertyNameTransformers} for some useful common
+ * transformers.
+ *
+ *
+ * Note that property chains are immutable, and each of the chaining methods returns a new chain with the additional
+ * property source added. This allows you to have common chains which you can combine in different ways, because each
+ * of those combinations is a new chain. For example:
+ *
+ *
+ * chain1 = common.orEnvironment(PropertyNameTransformers.LOWER_CAMEL_TO_UPPER_UNDERSCORE);
+ * chain2 = common.orFile("defaults.properties");
+ *
+ *
+ * Each of these chains is a separate chain that has some common section.
+ *
+ *
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public final class PropertySources {
+ /**
+ * @see PropertySourceChain#or(PropertySource)
+ */
+ public static PropertySourceChain from(PropertySource source) {
+ return from(source, null);
+ }
+
+ /**
+ * @see PropertySourceChain#or(PropertySource, Func1)
+ */
+ public static PropertySourceChain from(PropertySource source, Func1 transformer) {
+ return new PropertySourceChainImpl(source, transformer);
+ }
+
+ /**
+ * @see PropertySourceChain#orConstantsClass(Class)
+ */
+ public static PropertySourceChain fromConstantsClass(Class constantsClass) {
+ return fromConstantsClass(constantsClass, null);
+ }
+
+ /**
+ * @see PropertySourceChain#orConstantsClass(Class, Func1)
+ */
+ public static PropertySourceChain fromConstantsClass(Class constantsClass, Func1 transformer) {
+ return from(new ConstantsClassSource<>(constantsClass), transformer);
+ }
+
+ /**
+ * @see PropertySourceChain#orComputed(Func1)
+ */
+ public static PropertySourceChain fromComputed(Func1 computation) {
+ return fromComputed(computation, null);
+ }
+
+ /**
+ * @see PropertySourceChain#orComputed(Func1, Func1)
+ */
+ public static PropertySourceChain fromComputed(Func1 computation, Func1 transformer) {
+ return from(new ComputedSource(computation), transformer);
+ }
+
+ /**
+ * @see PropertySourceChain#orEnvironment()
+ */
+ public static PropertySourceChain fromEnvironment() {
+ return fromEnvironment(null);
+ }
+
+ /**
+ * @see PropertySourceChain#orEnvironment(Func1)
+ */
+ public static PropertySourceChain fromEnvironment(Func1 transformer) {
+ return from(EnvironmentPropertiesSource.DEFAULT, transformer);
+ }
+
+ public static PropertySourceChain fromFile(String path) {
+ return fromFile(path, null);
+ }
+
+ /**
+ * @see PropertySourceChain#orFile(String)
+ */
+ public static PropertySourceChain fromFile(String path, Func1 transformer) {
+ try {
+ return from(new FileSource(path), transformer);
+ } catch (IOException e) {
+ return from(EmptySource.INSTANCE);
+ }
+ }
+
+ /**
+ * @see PropertySourceChain#orImmediate(Action1)
+ */
+ public static PropertySourceChain fromImmediate(Action1 initializer) {
+ return fromImmediate(initializer, null);
+ }
+
+ /**
+ * @see PropertySourceChain#orImmediate(Action1, Func1)
+ */
+ public static PropertySourceChain fromImmediate(
+ Action1 initializer,
+ Func1 transformer) {
+ ImmediatePropertySourceImpl immediateSource = new ImmediatePropertySourceImpl();
+ initializer.call(immediateSource);
+
+ return from(immediateSource, transformer);
+ }
+
+ /**
+ * @see PropertySourceChain#orSystem()
+ */
+ public static PropertySourceChain fromSystem() {
+ return fromSystem(null);
+ }
+
+ /**
+ * @see PropertySourceChain#orSystem(Func1)
+ */
+ public static PropertySourceChain fromSystem(Func1 transformer) {
+ return from(SystemPropertiesSource.DEFAULT, transformer);
+ }
+
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/Qualifier.java b/core/src/main/java/foundation/stack/datamill/configuration/Qualifier.java
new file mode 100644
index 0000000..b8f6e37
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/Qualifier.java
@@ -0,0 +1,18 @@
+package foundation.stack.datamill.configuration;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to be used to add a qualifier to constructor parameters, and to classes. Adding this annotation to a
+ * constructor means that a {@link QualifyingFactory} must have been
+ *
+ * @see Wiring
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.TYPE})
+@Repeatable(Qualifiers.class)
+public @interface Qualifier {
+ String value();
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/Qualifiers.java b/core/src/main/java/foundation/stack/datamill/configuration/Qualifiers.java
new file mode 100644
index 0000000..667b83e
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/Qualifiers.java
@@ -0,0 +1,16 @@
+package foundation.stack.datamill.configuration;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to be used to add qualifiers to constructor parameters, and to classes.
+ *
+ * @see Wiring
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.TYPE})
+public @interface Qualifiers {
+ Qualifier[] value();
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/QualifyingFactory.java b/core/src/main/java/foundation/stack/datamill/configuration/QualifyingFactory.java
new file mode 100644
index 0000000..bb75621
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/QualifyingFactory.java
@@ -0,0 +1,19 @@
+package foundation.stack.datamill.configuration;
+
+import rx.functions.Func3;
+
+import java.util.Collection;
+
+/**
+ * An object factory like {@link Factory} but one that requires qualifiers (see {@link Qualifier}) to be present before
+ * it constructs objects. The qualifying factory is passed a set of qualifiers (as a {@link Collection} of Strings to
+ * allow the factory to test which qualifiers are present.
+ *
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+@FunctionalInterface
+public interface QualifyingFactory extends Func3, Collection, R> {
+ static QualifyingFactory, ?> wrap(Factory, ?> factory) {
+ return (w, c, q) -> factory.call(w, (Class) c);
+ }
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/Scope.java b/core/src/main/java/foundation/stack/datamill/configuration/Scope.java
new file mode 100644
index 0000000..3b1454d
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/Scope.java
@@ -0,0 +1,14 @@
+package foundation.stack.datamill.configuration;
+
+import java.util.Collection;
+
+/**
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public interface Scope {
+ R resolve(
+ Wiring wiring,
+ QualifyingFactory factory,
+ Class extends T> type,
+ Collection qualifiers);
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/SingletonScope.java b/core/src/main/java/foundation/stack/datamill/configuration/SingletonScope.java
new file mode 100644
index 0000000..5da1566
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/SingletonScope.java
@@ -0,0 +1,57 @@
+package foundation.stack.datamill.configuration;
+
+import java.util.*;
+
+/**
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public class SingletonScope implements Scope {
+ private Map constructed = new HashMap<>();
+
+ @Override
+ public R resolve(
+ Wiring wiring,
+ QualifyingFactory factory,
+ Class extends T> type,
+ Collection qualifiers) {
+ Object instance = constructed.get(new QualifiedType(type, qualifiers));
+ if (instance == null) {
+ instance = factory.call(wiring, type, qualifiers);
+ if (instance != null) {
+ constructed.put(new QualifiedType(type, qualifiers), instance);
+ }
+ }
+
+ return (R) instance;
+ }
+
+ private static class QualifiedType {
+ private final Class> type;
+ private final String[] qualifiers;
+
+ public QualifiedType(Class> type, Collection qualifiers) {
+ this.type = type;
+ this.qualifiers = qualifiers != null && qualifiers.size() > 0 ?
+ qualifiers.toArray(new String[qualifiers.size()]) : null;
+ }
+
+ @Override
+ public int hashCode() {
+ return qualifiers != null ?
+ Objects.hash(type, Arrays.hashCode(qualifiers)) :
+ type.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof QualifiedType) {
+ QualifiedType other = (QualifiedType) obj;
+ return qualifiers != null ?
+ Arrays.equals(qualifiers, other.qualifiers) && type == other.type :
+ type == other.type;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/TypeLessFactory.java b/core/src/main/java/foundation/stack/datamill/configuration/TypeLessFactory.java
new file mode 100644
index 0000000..6fdb2cd
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/TypeLessFactory.java
@@ -0,0 +1,13 @@
+package foundation.stack.datamill.configuration;
+
+import rx.functions.Func1;
+
+/**
+ * An object factory like {@link Factory} but one that doesn't need the {@link Class} parameter to be passed into it in
+ * order to construct objects.
+ *
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+@FunctionalInterface
+public interface TypeLessFactory extends Func1 {
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/Value.java b/core/src/main/java/foundation/stack/datamill/configuration/Value.java
new file mode 100644
index 0000000..b550c52
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/Value.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:
+ *
+ * \@Value("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 Value {
+ 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..c332f6f 100644
--- a/core/src/main/java/foundation/stack/datamill/configuration/Wiring.java
+++ b/core/src/main/java/foundation/stack/datamill/configuration/Wiring.java
@@ -1,23 +1,13 @@
package foundation.stack.datamill.configuration;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
-import foundation.stack.datamill.Pair;
-import foundation.stack.datamill.configuration.impl.Classes;
-import foundation.stack.datamill.reflection.impl.TypeSwitch;
import foundation.stack.datamill.values.StringValue;
import foundation.stack.datamill.values.Value;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import rx.functions.Action1;
import rx.functions.Func1;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Modifier;
-import java.lang.reflect.Parameter;
-import java.text.MessageFormat;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Optional;
/**
*
@@ -28,712 +18,239 @@
* For example, consider:
*
* public class UserRepository {
- * public UserRepository(DatabaseClient databaseClient, OutlineBuilder outlineBuilder) {
+ * public UserRepository(DataStore dataStore) {
* }
* }
*
*
*
- * You can use a Wiring to construct a UserRepository:
+ * You can use a Wiring to create a UserRepository instance:
*
- * UserRepository repository = new Wiring()
- * .add(new OutlineBuilder(), new DatabaseClient(...))
- * .construct(UserRepository.class);
+ * UserRepository repository = new Wiring().newInstance(UserRepository.class);
*
*
*
- * This constructs a new UserRepository using the public constructor, injecting the provided instances of DatabaseClient
- * and OutlineBuilder. Note that the wiring does not care about the ordering of the constructor parameters.
+ * This constructs a new UserRepository using the public constructor, creating an instance of DataStore, and any
+ * dependencies DataStore may have in the process. How did the wiring know how to construct DataStore and it's
+ * dependencies? By default, a Wiring will automatically create instances of concrete classes for which it can
+ * transitively create dependencies. But what if we have an interface, or a class that has dependencies that we cannot
+ * be satisfied? You setup the Wiring with a {@link FactoryChain} that tells it how to construct specific classes.
+ * For example, consider:
*
+ *
+ * public class ApplicationRepository {
+ * public ApplicationRepository(DatabaseClient databaseClient, OutlineBuilder outlineBuilder) {
+ * }
+ * }
+ *
+ *
+ * FactoryChain chain =
+ * FactoryChains.forType(DatabaseClient.class, w -> new DatabaseClient(...))
+ * .thenForType(OutlineBuilder.class, w -> OutlineBuilder.DEFAULT)
+ * .thenForAnyConcreteClass();
+ * ApplicationRepository repository = new Wiring(chain).newInstance(ApplicationRepository.class);
+ *
*
- * Factory methods can be registered with the Wiring using {@link #addFactory(Class, Func1)} and the Wiring will use the
- * factory methods whenever it cannot satisfy dependencies with explicitly added and constructed instances. Note that
- * once a factory method is invoked to construct an instance for a type, the built instance will be added to the Wiring.
+ * Note that the {@link FactoryChain} we built and passed in to the Wiring will tell it exactly how to build objects of
+ * the types it encounters. The chain will be consulted in the order it is composed - in this case, when the Wiring
+ * needs to construct a DatabaseClient, it will use the first lambda provided. If it needs an OutlineBuilder, it
+ * will use the second lambda and for all other concrete classes, it will use a concrete class factory to construct them.
+ * It will use the chain every time a dependency needs to be constructed, starting at the beginning. Use
+ * {@link FactoryChains} as a starting point for creating factory chains.
*
*
- * When the constructor's parameter types are concrete classes, Wirings will attempt to recursively construct instances
- * of those concrete types. Most of the time, this is the intended behavior, and it reduces the amount of explicit calls
- * that are required. Note that the Wiring will first attempt to use explicitly added and constructed instances when
- * trying to satisfy dependencies before constructing them automatically.
+ * Notice that in the previous examples we called the newInstance method on the Wiring. This will create a new instance
+ * of that class every single time. While this is sometimes the desired behavior, most of the time, you will want to
+ * use {@link Wiring#singleton(Class)} instead to create a singleton instance of a type. If you call singleton to
+ * construct instances of some type multiple times, you will get back the same singleton instance every time. The
+ * singletons are scoped to the {@link Wiring} itself, and it's default singleton {@link Scope}. A different
+ * {@link Scope} can be passed in to a Wiring when it is constructed to change how instances are scoped. If you pass in
+ * a different scope, you can use the {@link Wiring#defaultScoped(Class)} method to construct instances using that scope.
*
*
- * When dealing with a constructor which has multiple parameters of the same type, Wirings support using a name as a
- * qualifier for constructor injection. For example, consider:
+ * Occasionally, you may have multiple parameters of the same type in a constructor and you want to have the Wiring
+ * create different instances for each parameter. For example, consider:
+ *
*
- * public class DatabaseClient {
- * public DatabaseClient(@Named("url") String url, @Named("username") String username, @Named("password") String password) {
+ * public class UserController {
+ * public UserController(Func1<ServerRequest, Boolean> authenticationFilter, Func1<ServerRequest, Boolean> authorizationFilter) {
* }
* }
*
- *
*
- * You can use a Wiring to construct a DatabaseClient using the named parameters:
+ * You can add {@link Qualifier}s to these parameters to tell the Wiring to inject different instances:
+ *
*
- * DatabaseClient client = new Wiring()
- * .addFormatted("url", "jdbc:mysql://{0}:{1}/{2}", "localhost", 3306, "database")
- * .addNamed("username", "dbuser")
- * .addNamed("password", "dbpass")
- * .construct(DatabaseClient.class);
+ * public class UserController {
+ * public UserController(
+ * @Qualifier("authentication") Func1<ServerRequest, Boolean> authenticationFilter,
+ * @Qualifier("authorization") Func1<ServerRequest, Boolean> authorizationFilter) {
+ * }
+ * }
*
+ *
+ * You can now add a {@link QualifyingFactory} to your {@link FactoryChain} in order to construct different instances
+ * based on the qualifier:
*
+ *
+ * FactoryChain chain =
+ * FactoryChains.forType(Func1.class, (w, c, q) -> q.contains("authentication") ?
+ * authenticationFilter() : authorizationFilter())
+ * .thenForAnyConcreteClass();
+ * UserController controller = new Wiring(chain).singleton(UserController.class);
+ *
*
- * This constructs a new DatabaseClient using the constructor shown, injecting the provided named Strings as parameters.
- * Note that for Strings and primitives, Wirings only support injection using a name.
+ * In addition to using the factory chain to create dependencies, a Wiring will also resolve any {@link Named}
+ * constructor parameters using a {@link PropertySource} it is setup with. This is useful for picking up externalized
+ * properties from files, environment variables, system properties, constants classes and other sources. For example,
+ * consider:
+ *
+ * public class DatabaseClient {
+ * public DatabaseClient(@Named("url") String url, @Named("username") String username, @Named("password") String password) {
+ * }
+ * }
+ *
*
*
- * Wirings are very light-weight containers for objects and properties that are meant to be wired together. Each
- * separate Wiring instance is self-contained, and when the {@link #construct(Class)} method is called, only
- * the objects (including named objects) added to the Wiring are considered as candidates when injecting.
+ * You can use a Wiring to construct a DatabaseClient using the named parameters:
+ *
+ * DatabaseClient client = new Wiring(
+ * PropertySources.fromFile("database.properties")
+ * .orImmediate(s -> s
+ * .put("url", "jdbc:mysql://localhost:3306")
+ * .put("username", "dbuser")
+ * .put("password", "dbpass")))
+ * .singleton(DatabaseClient.class);
+ *
*
*
- * You can associate a {@link PropertySource} or {@link PropertySourceChain} with a Wiring using
- * {@link #setNamedPropertySource} so that it can resolve any named parameters by looking them up in the
- * {@link PropertySource}. Note that any named values explicitly added to the Wiring using
- * {@link #addNamed(String, Object)} will take precedence over the property source.
+ * This constructs a new DatabaseClient using the constructor shown, injecting the provided named values as parameters.
+ * As with factories, property sources are setup as a chain - you create {@link PropertySourceChain}s using
+ * {@link PropertySources} as a starting point. You can see how in this example, the properties will first be looked up
+ * in a "database.properties" file, and will use some defaults as a fallback. Note that only Strings, primitives and
+ * primitive wrappers are supported for {@link Named} parameters.
*
*
* @author Ravi Chodavarapu (rchodava@gmail.com)
*/
public class Wiring {
- private static final Logger logger = LoggerFactory.getLogger(Wiring.class);
-
- private static final TypeSwitch valueCast = new TypeSwitch() {
- @Override
- protected Object caseBoolean(Value value, Void __) {
- return value.asBoolean();
- }
-
- @Override
- protected Object caseByte(Value value, Void __) {
- return value.asByte();
- }
-
- @Override
- protected Object caseCharacter(Value value, Void __) {
- return value.asCharacter();
- }
-
- @Override
- protected Object caseShort(Value value, Void __) {
- return value.asShort();
- }
-
- @Override
- protected Object caseInteger(Value value, Void __) {
- return value.asInteger();
- }
-
- @Override
- protected Object caseLong(Value value, Void __) {
- return value.asLong();
- }
-
- @Override
- protected Object caseFloat(Value value, Void __) {
- return value.asFloat();
- }
-
- @Override
- protected Object caseDouble(Value value, Void __) {
- return value.asDouble();
- }
-
- @Override
- protected Object caseLocalDateTime(Value value, Void __) {
- return value.asLocalDateTime();
- }
-
- @Override
- protected Object caseByteArray(Value value, Void __) {
- return value.asByteArray();
- }
-
- @Override
- protected Object caseString(Value value1, Void value2) {
- return value1.asString();
- }
-
- @Override
- protected Object defaultCase(Value value, Void __) {
- return value;
- }
- };
-
- private static boolean canAutoConstruct(Class> type) {
- return !type.isInterface() &&
- !Modifier.isAbstract(type.getModifiers()) &&
- type != String.class &&
- !type.isPrimitive() &&
- !Classes.isPrimitiveWrapper(type);
- }
-
- private final Map, List>>> factories = new HashMap<>();
- private final Multimap, Object> members = HashMultimap.create();
- private final Map named = new HashMap<>();
- private PropertySource propertySource;
-
- /**
- * Add the specified modules to this wiring.
- *
- * @param modules Modules to add.
- */
- public Wiring include(Module... modules) {
- for (Module module : modules) {
- module.call(this);
- }
- return this;
- }
-
- private void add(Class> clazz, Object addition) {
- members.put(clazz, addition);
-
- registerUnderParentClass(clazz, addition);
- registerUnderInterfaces(clazz, addition);
- }
-
- private void registerUnderInterfaces(Class> clazz, Object addition) {
- Class>[] interfaces = clazz.getInterfaces();
- if (interfaces != null) {
- for (Class> interfaceClass : interfaces) {
- add(interfaceClass, addition);
- }
- }
- }
-
- private void registerUnderParentClass(Class> clazz, Object addition) {
- Class> superClass = clazz.getSuperclass();
- if (superClass != null && superClass != Object.class) {
- add(superClass, addition);
- }
- }
-
- /**
- * Add one or more objects to the Wiring. These objects are then available for constructor injection when any
- * matching constructor parameters are found. Adding primitives to a Wiring without a name (i.e., without using
- * {@link #addNamed(String, Object)} is not supported).
- *
- * @param additions Objects to add.
- */
- public Wiring add(Object... additions) {
- if (additions == null) {
- throw new IllegalArgumentException("Cannot add null to a Wiring");
- }
-
- for (Object addition : additions) {
- if (addition == null) {
- throw new IllegalArgumentException("Cannot add null to a Wiring");
- }
+ private final PropertySource propertySource;
+ private final FactoryChain factoryChain;
+ private final Scope defaultScope;
+ private final Scope singletonScope;
- if (addition instanceof String || addition.getClass().isPrimitive()) {
- throw new IllegalArgumentException("Cannot add Strings and primitives to a Wiring without a name");
- }
-
- if (addition instanceof Optional) {
- add(((Optional) addition).orElse(null));
- } else {
- add(addition.getClass(), addition);
- }
- }
-
- return this;
- }
-
- /**
- * Add a factory method to the Wiring which is invoked to create an instance of the specified class. The factory
- * method is added with a default priority of 0.
- *
- * @param clazz Class for which we want to add a factory.
- * @param factory Factory method to invoke for the class specified.
- */
- public Wiring addFactory(Class clazz, Func1 factory) {
- return addFactory(0, clazz, factory);
- }
-
- /**
- * Add a factory method to the Wiring which is invoked to create an instance of the specified class.
- *
- * @param priority Priority to give this factory - if multiple factory methods exist for a type, the instance
- * returned by the method with higher priority will be used.
- * @param clazz Class for which we want to add a factory.
- * @param factory Factory method to invoke for the class specified.
- */
- public Wiring addFactory(int priority, Class clazz, Func1 factory) {
- factories.compute(clazz, (__, existing) -> {
- if (existing == null) {
- existing = new ArrayList<>();
- }
-
- int insertionPoint = 0;
- for (int i = 0; i < existing.size(); i++) {
- if (priority >= existing.get(i).getFirst()) {
- insertionPoint = i;
- break;
- }
- }
- existing.add(insertionPoint, new Pair<>(priority, factory));
- return existing;
- });
- return this;
- }
-
- /**
- * Add an object to the Wiring under the specified name. These objects are only injected when a constructor
- * parameter has a {@link Named} annotation with the specified name.
- *
- * @param name Name for the object.
- * @param addition Object to add.
- */
- public Wiring addNamed(String name, Object addition) {
- if (addition == null) {
- throw new IllegalArgumentException("Cannot add null to graph");
- }
-
- if (addition instanceof Optional) {
- addition = ((Optional) addition).orElse(null);
- return addNamed(name, addition);
- }
-
- if (named.containsKey(name)) {
- throw new IllegalArgumentException("Wiring already contains an object with name " + name);
- }
-
- named.put(name, addition);
- return this;
+ public Wiring(PropertySource propertySource, FactoryChain factoryChain, Scope defaultScope) {
+ this.propertySource = propertySource;
+ this.singletonScope = new SingletonScope();
+ this.defaultScope = defaultScope != null ? defaultScope : singletonScope;
+ this.factoryChain = factoryChain;
}
- /**
- * Add a new formatted string to the Wiring under the specified name. These strings are only injected when a constuctor
- * parameter has a {@link Named} annotation with the specified name.
- *
- * @param name Name for the string.
- * @param format Format template to use for the string.
- * @param arguments Arguments to be used with the template to construct a formatted string.
- */
- public Wiring addFormatted(String name, String format, Object... arguments) {
- Object[] casted = new Object[arguments.length];
- for (int i = 0; i < arguments.length; i++) {
- if (arguments[i] instanceof Value) {
- casted[i] = ((Value) arguments[i]).asString();
- } else {
- casted[i] = arguments[i];
- }
- }
-
- addNamed(name, MessageFormat.format(format, casted));
- return this;
+ public Wiring(PropertySource propertySource, FactoryChain factoryChain) {
+ this(propertySource, factoryChain, null);
}
- /**
- * Construct an instance of the specified class using one of it's public constructors. This method will use the first
- * constructor for which it can provide all parameters. The Wiring will use all the objects that it currently knows
- * about (i.e., all objects that have been added or constructed by this Wiring at the time the construct method is
- * called) to perform the injection. After the instance is constructed, the instance is added to the Wiring as one
- * of the objects it knows about for injection into other constructors. If a particular dependency is unsatisfied,
- * if the parameter's type is a concrete class, the Wiring will try to recursively {@link #construct(Class)} an
- * instance of that parameter type.
- *
- * @param clazz Class we want to create an instance of.
- * @param Type of instance.
- * @return Instance that was constructed.
- * @throws IllegalArgumentException If the class is an interface, abstract class or has no public constructors.
- * @throws IllegalStateException If all dependencies for constructing an instance cannot be satisfied.
- */
- public T construct(Class clazz) {
- return construct(clazz, new HashSet<>());
+ public Wiring(FactoryChain factoryChain, Scope defaultScope) {
+ this(null, factoryChain, defaultScope);
}
- private T construct(Class clazz, Set> autoConstructionCandidates) {
- autoConstructionCandidates.add(clazz);
- Constructor>[] constructors = getPublicConstructors(clazz);
-
- for (Constructor> constructor : constructors) {
- T instance = constructWithConstructor(clazz, constructor, autoConstructionCandidates);
- if (instance != null) {
- return instance;
- }
- }
-
- throw new IllegalStateException("Unable to satisfy all dependencies needed to construct instance of " + clazz.getName());
+ public Wiring(FactoryChain factoryChain) {
+ this(factoryChain, null);
}
- /**
- * Check if the wiring already contains an instance of the provided class. If it exists, return it, otherwise, delegate
- * to {@link #construct(Class)} in order to construct it.
- *
- * @param clazz Class we want to create an instance of.
- * @param Type of instance.
- * @return Instance that was constructed.
- * @throws IllegalArgumentException If the class is an interface, abstract class or has no public constructors.
- * @throws IllegalStateException If all dependencies for constructing an instance cannot be satisfied.
- */
- public T constructIfMissing(Class clazz) {
- T target = get(clazz);
- if (target != null) {
- return target;
- }
- return construct(clazz);
+ public Wiring(PropertySource propertySource, Scope defaultScope) {
+ this(propertySource, FactoryChains.forAnyConcreteClass(), defaultScope);
}
- /**
- * Construct an instance of the specified class using a public constructors that has parameters of the specified
- * types.
- *
- * @see #construct(Class)
- */
- public T constructWith(Class clazz, Class>... parameterTypes) {
- try {
- Constructor constructor = clazz.getConstructor(parameterTypes);
- return constructWithConstructor(clazz, constructor, new HashSet<>());
- } catch (NoSuchMethodException | SecurityException e) {
- throw new IllegalStateException("Unable to find a constructor with specified parameters on " + clazz.getName());
- }
+ public Wiring(PropertySource propertySource) {
+ this(propertySource, (Scope) null);
}
- /**
- * Check if the wiring already contains an instance of the provided class. If it exists, return it, otherwise, delegate
- * to {@link #constructWith(Class, Class[])} in order to construct it.
- *
- * @param clazz Class we want to create an instance of.
- * @param parameterTypes Parameter types to be matched in constructor to be used.
- * @return Instance that was constructed.
- * @throws IllegalArgumentException If the class is an interface, abstract class or has no public constructors.
- * @throws IllegalStateException If all dependencies for constructing an instance cannot be satisfied.
- */
- public T constructWithIfMissing(Class clazz, Class>... parameterTypes) {
- T target = get(clazz);
- if (target != null) {
- return target;
- }
- return constructWith(clazz, parameterTypes);
+ public Wiring() {
+ this((PropertySource) null);
}
- private T constructWithConstructor(
- Class clazz,
- Constructor> constructor,
- Set> autoConstructionCandidates) {
- Parameter[] parameters = constructor.getParameters();
- Object[] values = new Object[parameters.length];
-
- for (int i = 0; i < parameters.length; i++) {
- values[i] = getValueForParameter(parameters[i]);
- if (values[i] == null) {
- Class> parameterType = parameters[i].getType();
- if (canAutoConstruct(parameterType) && !autoConstructionCandidates.contains(parameterType)) {
- try {
- values[i] = construct(parameterType, autoConstructionCandidates);
- } catch (IllegalArgumentException | IllegalStateException e) {
- logger.error("Could not build class {} as the following type could not be constructed {}", clazz, parameterType, e);
- return null;
- }
- } else {
- logger.error("Could not build class {} as the following type was not found {}", clazz, parameterType);
- return null;
- }
- }
- }
-
- return instantiate(clazz, constructor, values);
+ public Wiring(Wiring wiring, FactoryChain factoryChain) {
+ this(wiring.propertySource, factoryChain, wiring.defaultScope, wiring.singletonScope);
}
- public T instantiate(Class clazz, Constructor> constructor, Object[] arguments) {
- try {
- T constructed = (T) constructor.newInstance(arguments);
- add(constructed);
- return constructed;
- } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
- throw new IllegalArgumentException("Unable to construct instance of " + clazz.getName(), e);
- }
+ private Wiring(PropertySource propertySource, FactoryChain factoryChain, Scope defaultScope, Scope singletonScope) {
+ this.propertySource = propertySource;
+ this.singletonScope = singletonScope;
+ this.defaultScope = defaultScope;
+ this.factoryChain = factoryChain;
}
- private Constructor>[] getPublicConstructors(Class clazz) {
- Constructor>[] constructors = clazz.getConstructors();
- if (constructors == null || constructors.length == 0) {
- throw new IllegalArgumentException("Class " + clazz.getName() + " has no public constructors!");
- }
-
- return constructors;
+ public FactoryChain getFactoryChain() {
+ return factoryChain;
}
- private Object getValueForParameter(Parameter parameter) {
- Named[] names = parameter.getAnnotationsByType(Named.class);
- if (names != null && names.length > 0) {
- Object namedValue = getValueForNamedParameter(parameter, names);
- if (namedValue != null) {
- return namedValue;
- }
- } else {
- Object value = getValueForParameterByType(parameter);
- if (value != null) {
- return value;
- }
- }
-
-
- return null;
+ public Value getRequiredNamed(String property) {
+ return getNamed(property)
+ .orElseThrow(() -> new IllegalStateException("Required named property [" + property + "] not present!"));
}
- private Object getValueForParameterByType(Parameter parameter) {
- Class> type = parameter.getType();
- return get(type);
+ public Optional getNamed(String property) {
+ return propertySource != null ?
+ propertySource.get(property).map(value -> new StringValue(value)) :
+ Optional.empty();
}
- /**
- * Get an object of the specified type that was previously added to or constructed by this wiring, or that can be
- * built by a registered factory, or null if no value of the specified type was added or constructed.
- */
- public T get(Class type) {
- if (type.isPrimitive() || type == String.class || Classes.isPrimitiveWrapper(type)) {
- logger.debug("Injection of dependencies that primitives and not named is not supported!");
- return null;
- }
-
- Object value = getObjectOfType(type);
- if (value != null) {
- return (T) value;
- }
-
- value = getValueOfType(type);
- if (value != null) {
- return (T) value;
- }
-
- List>> typeFactories = factories.get(type);
- if (typeFactories != null) {
- for (Pair> factory : typeFactories) {
- value = factory.getSecond().call(this);
- if (value != null) {
- T casted = (T) value;
- add(casted);
- return casted;
- }
- }
- }
-
- return null;
+ private R construct(Scope scope, Class extends T> type, Collection qualifiers) {
+ return scope != null ?
+ (R) scope.resolve(this, factoryChain, (Class) type, qualifiers) :
+ (R) factoryChain.call(this, (Class) type, qualifiers);
}
- /**
- * @see #getNamedAs(String, Class)
- */
- public Value getNamed(String name) {
- return getNamedAs(name, Value.class);
+ public R defaultScoped(Class extends T> type) {
+ return (R) defaultScoped(type, Collections.emptyList());
}
- /**
- * Get the object identified by the give name that was added by using {@link Wiring#addNamed(String, Object)},
- * or available from the property source chain set on the wiring.
- */
- public T getNamedAs(String name, Class type) {
- Object value = named.get(name);
-
- if (value == null && propertySource != null) {
- value = propertySource.get(name).map(string -> new StringValue(string)).orElse(null);
- }
-
- if (value != null) {
- if (type.isInstance(value)) {
- return (T) value;
- }
-
- if (type.isPrimitive()) {
- Class> wrapper = Classes.primitiveToWrapper(type);
- if (wrapper.isInstance(value)) {
- return (T) value;
- }
- }
-
- if (Value.class.isAssignableFrom(value.getClass())) {
- if (type == Value.class) {
- return (T) value;
- }
-
- return (T) castValueToTypeIfPossible((Value) value, type);
- }
-
- if (type == Value.class) {
- return (T) new StringValue(value.toString());
- }
- }
-
- return null;
+ public R defaultScoped(Class extends T> type, String... qualifiers) {
+ return (R) defaultScoped(type, Arrays.asList(qualifiers));
}
- private Object getObjectOfType(Class> type) {
- Collection> values = members.get(type);
- if (values != null) {
- if (values.size() == 0) {
- return null;
- }
-
- if (values.size() == 1) {
- return values.iterator().next();
- }
-
- throw new IllegalStateException("Multiple objects in graph match type " + type.getName());
- }
-
- return null;
+ public R defaultScoped(Class extends T> type, Collection qualifiers) {
+ return (R) construct(defaultScope, type, qualifiers);
}
- private Object getValueOfType(Class> type) {
- Collection> values = members.get(Value.class);
- if (values != null) {
- if (values.size() == 0) {
- return null;
- }
-
- ArrayList casted = new ArrayList<>();
- for (Object value : values) {
- Object castedValue = castValueToTypeIfPossible((Value) value, type);
- if (castedValue != null) {
- casted.add(castedValue);
- }
- }
-
- if (casted.size() == 1) {
- return casted.iterator().next();
- }
-
- if (casted.size() == 0) {
- return null;
- }
-
- throw new IllegalStateException("Multiple objects in graph match type " + type.getName());
- }
-
- return null;
+ public R newInstance(Class type) {
+ return newInstance(type, Collections.emptyList());
}
- private Object getValueForNamedParameter(Parameter parameter, Named[] names) {
- for (Named name : names) {
- Object value = getValueForNamedParameter(parameter, name);
- if (value != null) {
- return value;
- }
- }
-
- return null;
+ public R newInstance(Class type, String... qualifiers) {
+ return newInstance(type, Arrays.asList(qualifiers));
}
- private Object getValueForNamedParameter(Parameter parameter, Named name) {
- return getNamedAs(name.value(), parameter.getType());
+ public R newInstance(Class type, Collection qualifiers) {
+ return construct(null, type, qualifiers);
}
-
- private Object castValueToTypeIfPossible(Value value, Class> type) {
- Object castedValue = valueCast.doSwitch(type, value, null);
- if (castedValue != null && Classes.isAssignable(type, castedValue.getClass())) {
- return castedValue;
- }
-
- return null;
+ public R singleton(Class type) {
+ return singleton(type, Collections.emptyList());
}
- /**
- * Similar convenience mechanism to {@link #with(Action1)} but the action is invoked if condition is true. If it is
- * not true, an else clause can be chained, as in:
- *
- *
- * new Wiring().add(...)
- * .performIf(..., w -> {
- * w.construct(...);
- * })
- * .orElse(...);
- *
- */
- public ElseBuilder performIf(boolean condition, Action1 consumer) {
- if (condition) {
- consumer.call(this);
- }
-
- return new ElseBuilder() {
- @Override
- public Wiring orElseDoNothing() {
- return Wiring.this;
- }
-
- @Override
- public Wiring orElse(Action1 consumer) {
- if (!condition) {
- consumer.call(Wiring.this);
- }
-
- return Wiring.this;
- }
- };
+ public R singleton(Class type, String... qualifiers) {
+ return singleton(type, Arrays.asList(qualifiers));
}
- /**
- * Similar convenience mechanism to {@link #with(Action1)} and {@link #performIf(boolean, Action1)} but the function
- * is invoked if condition is true. If it is not true, an else clause can be chained, as in:
- *
- *
- * new Wiring().add(...)
- * .returnIf(..., w -> {
- * w.construct(...);
- * })
- * .orElse(...);
- *
- */
- public ReturningElseBuilder returnIf(boolean condition, Func1 function) {
- if (condition) {
- return new ReturningElseBuilder() {
- @Override
- public T orElseReturnNull() {
- return function.call(Wiring.this);
- }
-
- @Override
- public T orElse(Func1 __) {
- return function.call(Wiring.this);
- }
- };
- } else {
- return new ReturningElseBuilder() {
- @Override
- public T orElseReturnNull() {
- return null;
- }
-
- @Override
- public T orElse(Func1 elseFunction) {
- return elseFunction.call(Wiring.this);
- }
- };
- }
+ public R singleton(Class type, Collection qualifiers) {
+ return construct(singletonScope, type, qualifiers);
}
- public Wiring setNamedPropertySource(PropertySource propertySource) {
- this.propertySource = propertySource;
- return this;
+ public R with(PropertySource propertySource, FactoryChain factoryChain, Func1 wiringConsumer) {
+ return wiringConsumer.call(new Wiring(propertySource, factoryChain, defaultScope, singletonScope));
}
- /**
- * A convenience mechanism to quickly construct multiple objects - for example:
- *
- * new Wiring().add(databaseClient, outlineBuilder).with(w -> {
- * w.construct(UserRepository.class);
- * w.construct(WidgetRepository.class);
- *
- * w.construct(UserController.class);
- * w.construct(WidgetController.class);
- * });
- *
- */
- public Wiring with(Action1 consumer) {
- consumer.call(this);
- return this;
+ public R with(PropertySource propertySource, Func1 wiringConsumer) {
+ return with(propertySource, factoryChain, wiringConsumer);
}
- public interface ElseBuilder {
- Wiring orElseDoNothing();
-
- Wiring orElse(Action1 consumer);
+ public R with(FactoryChain factoryChain, Func1 wiringConsumer) {
+ return with(propertySource, factoryChain, wiringConsumer);
}
- public interface ReturningElseBuilder {
- T orElseReturnNull();
-
- T orElse(Func1 function);
+ public R with(Func1 wiringConsumer) {
+ return wiringConsumer.call(this);
}
}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/WiringException.java b/core/src/main/java/foundation/stack/datamill/configuration/WiringException.java
new file mode 100644
index 0000000..2fe68c5
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/WiringException.java
@@ -0,0 +1,55 @@
+package foundation.stack.datamill.configuration;
+
+import java.util.List;
+
+/**
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public class WiringException extends RuntimeException {
+ private final List components;
+
+ public WiringException(String message) {
+ this(message, null);
+ }
+
+ public WiringException(String message, List components) {
+ super(message);
+
+ this.components = components;
+ }
+
+ public List getComponents() {
+ return components;
+ }
+
+ private void indent(StringBuilder formatted, int indent) {
+ for (int i = 0; i < indent; i++) {
+ formatted.append(" ");
+ }
+
+ formatted.append(" |- ");
+ }
+
+ public String toString(int indent) {
+ StringBuilder formatted = new StringBuilder();
+
+ if (getMessage() != null) {
+ indent(formatted, indent);
+ formatted.append(getMessage());
+ }
+
+ if (components != null && components.size() > 0) {
+ for (WiringException component : components) {
+ formatted.append('\n');
+ formatted.append(component.toString(indent + 1));
+ }
+ }
+
+ return formatted.toString();
+ }
+
+ @Override
+ public String toString() {
+ return toString(0);
+ }
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/impl/AbstractSource.java b/core/src/main/java/foundation/stack/datamill/configuration/impl/AbstractSource.java
index 49b2ced..d57efc3 100644
--- a/core/src/main/java/foundation/stack/datamill/configuration/impl/AbstractSource.java
+++ b/core/src/main/java/foundation/stack/datamill/configuration/impl/AbstractSource.java
@@ -3,37 +3,19 @@
import foundation.stack.datamill.configuration.PropertySource;
import foundation.stack.datamill.values.StringValue;
import foundation.stack.datamill.values.Value;
-import rx.functions.Action1;
+import rx.functions.Func1;
-import java.util.HashMap;
-import java.util.Map;
import java.util.Optional;
/**
* @author Ravi Chodavarapu (rchodava@gmail.com)
*/
public abstract class AbstractSource implements PropertySource {
- private final Map aliases = new HashMap<>();
-
- @Override
- public PropertySource alias(String alias, String original) {
- aliases.put(alias, original);
- return this;
- }
-
protected abstract Optional getOptional(String name);
@Override
public final Optional get(String name) {
- Optional value = getOptional(name);
- if (!value.isPresent()) {
- String original = aliases.get(name);
- if (original != null) {
- value = getOptional(original);
- }
- }
-
- return value;
+ return getOptional(name);
}
@Override
@@ -47,8 +29,7 @@ public Value getRequired(String name) {
}
@Override
- public PropertySource with(Action1 propertiesConsumer) {
- propertiesConsumer.call(this);
- return this;
+ public R with(Func1 propertiesConsumer) {
+ return propertiesConsumer.call(this);
}
}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/impl/Classes.java b/core/src/main/java/foundation/stack/datamill/configuration/impl/Classes.java
index 9b0895a..e34809a 100644
--- a/core/src/main/java/foundation/stack/datamill/configuration/impl/Classes.java
+++ b/core/src/main/java/foundation/stack/datamill/configuration/impl/Classes.java
@@ -62,7 +62,7 @@ public static Class> primitiveToWrapper(final Class> clazz) {
return convertedClass;
}
- private static Class> wrapperToPrimitive(final Class> clazz) {
+ public static Class> wrapperToPrimitive(final Class> clazz) {
return wrapperPrimitiveMap.get(clazz);
}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/impl/ComputedSource.java b/core/src/main/java/foundation/stack/datamill/configuration/impl/ComputedSource.java
new file mode 100644
index 0000000..599f9e8
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/impl/ComputedSource.java
@@ -0,0 +1,21 @@
+package foundation.stack.datamill.configuration.impl;
+
+import rx.functions.Func1;
+
+import java.util.Optional;
+
+/**
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public class ComputedSource extends AbstractSource {
+ private final Func1 computation;
+
+ public ComputedSource(Func1 computation) {
+ this.computation = computation;
+ }
+
+ @Override
+ public Optional getOptional(String name) {
+ return Optional.ofNullable(computation.call(name));
+ }
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/impl/ConcreteClassFactory.java b/core/src/main/java/foundation/stack/datamill/configuration/impl/ConcreteClassFactory.java
new file mode 100644
index 0000000..6a7bc9c
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/impl/ConcreteClassFactory.java
@@ -0,0 +1,196 @@
+package foundation.stack.datamill.configuration.impl;
+
+import foundation.stack.datamill.configuration.Qualifier;
+import foundation.stack.datamill.configuration.QualifyingFactory;
+import foundation.stack.datamill.configuration.Wiring;
+import foundation.stack.datamill.configuration.WiringException;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Parameter;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public class ConcreteClassFactory implements QualifyingFactory {
+ private static final ConcreteClassFactory, ?> INSTANCE = new ConcreteClassFactory<>();
+
+ private static void appendException(StringBuilder message, Throwable e) {
+ message.append(e.getClass().getName());
+
+ if (e.getCause() != null && e.getCause() != e) {
+ message.append(": ");
+ appendException(message, e.getCause());
+ }
+
+ if (e.getMessage() != null) {
+ message.append(": ");
+ message.append(e.getMessage());
+ }
+ }
+
+ private static Constructor>[] getPublicConstructors(Class type) {
+ Constructor>[] constructors = type.getConstructors();
+ if (constructors == null || constructors.length == 0) {
+ throw new WiringException("Class [" + type.getName() + "] has no public constructors!");
+ }
+
+ return constructors;
+ }
+
+ public static final ConcreteClassFactory, ?> instance() {
+ return INSTANCE;
+ }
+
+ private static boolean canAutoConstruct(Class> type) {
+ return !type.isInterface() &&
+ !Modifier.isAbstract(type.getModifiers()) &&
+ type != String.class &&
+ !type.isPrimitive() &&
+ !Classes.isPrimitiveWrapper(type);
+ }
+
+ private static void performSecure(Runnable runnable) {
+ if (System.getSecurityManager() != null) {
+ AccessController.doPrivileged((PrivilegedAction>) () -> {
+ runnable.run();
+ return null;
+ });
+ } else {
+ runnable.run();
+ }
+ }
+
+ private static void throwFailedConstructionException(Class> type, List componentExceptions) {
+ StringBuilder message = new StringBuilder("Could not construct [");
+ message.append(type.getName());
+ message.append("] using any constructor!");
+ throw new WiringException(message.toString(), Collections.unmodifiableList(componentExceptions));
+ }
+
+ private static void throwFailedInstantiationException(Constructor> constructor, ReflectiveOperationException e) {
+ StringBuilder message = new StringBuilder("Failed to instantiate using constructor [");
+ message.append(constructor.getName());
+ message.append("] - ");
+ appendException(message, e);
+ throw new WiringException(message.toString());
+ }
+ private static void throwInvalidParameterValue(Parameter parameter, IllegalArgumentException e) {
+ StringBuilder message = new StringBuilder("Value retrieved for parameter [");
+ message.append(parameter.getName());
+ message.append("] was invalid: ");
+ appendException(message, e);
+
+ throw new WiringException(message.toString());
+ }
+
+ private static void throwUnsatisfiedParameter(
+ Constructor> constructor,
+ Parameter parameter,
+ WiringException component) {
+ StringBuilder message = new StringBuilder("Failed to satisfy parameter [");
+ message.append(parameter.getName());
+ message.append(" (");
+ message.append(parameter.getType().getName());
+ message.append(")] in [");
+ message.append(constructor.getName());
+ message.append("]");
+
+ throw new WiringException(message.toString(), component != null ? Collections.singletonList(component) : null);
+ }
+
+ private ConcreteClassFactory() {
+ }
+
+ private R construct(Wiring wiring, Class extends T> type) {
+ Constructor>[] constructors = getPublicConstructors(type);
+ ArrayList componentExceptions = new ArrayList<>();
+
+ for (Constructor> constructor : constructors) {
+ try {
+ R instance = constructWithConstructor(wiring, constructor);
+ if (instance != null) {
+ return instance;
+ }
+ } catch (WiringException e) {
+ componentExceptions.add(e);
+ }
+ }
+
+ throwFailedConstructionException(type, componentExceptions);
+ return null;
+ }
+
+ private Collection retrieveQualifierIfPresent(Parameter parameter) {
+ Qualifier[] qualifiers = parameter.getAnnotationsByType(Qualifier.class);
+ if (qualifiers != null) {
+ ArrayList qualifierNames = new ArrayList<>();
+ for (Qualifier qualifier : qualifiers) {
+ if (qualifier.value() != null) {
+ qualifierNames.add(qualifier.value());
+ }
+ }
+
+ if (!qualifierNames.isEmpty()) {
+ return qualifierNames;
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+ private R constructWithConstructor(
+ Wiring wiring,
+ Constructor> constructor) {
+ Parameter[] parameters = constructor.getParameters();
+ Object[] values = new Object[parameters.length];
+
+ for (int i = 0; i < parameters.length; i++) {
+ Class> parameterType = parameters[i].getType();
+ try {
+ values[i] = wiring.defaultScoped(parameterType, retrieveQualifierIfPresent(parameters[i]));
+ if (values[i] == null) {
+ try {
+ values[i] = NamedParameterValueRetriever.retrieveValueIfNamedParameter(wiring, parameters[i]);
+ } catch (IllegalArgumentException e) {
+ throwInvalidParameterValue(parameters[i], e);
+ }
+ }
+ } catch (WiringException e) {
+ throwUnsatisfiedParameter(constructor, parameters[i], e);
+ }
+
+ if (values[i] == null) {
+ throwUnsatisfiedParameter(constructor, parameters[i], null);
+ }
+ }
+
+ return instantiate(constructor, values);
+ }
+
+ private R instantiate(Constructor> constructor, Object[] arguments) {
+ try {
+ performSecure(() -> constructor.setAccessible(true));
+ return (R) constructor.newInstance(arguments);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ throwFailedInstantiationException(constructor, e);
+ return null;
+ }
+ }
+
+ @Override
+ public R call(Wiring wiring, Class extends T> type, Collection qualifiers) {
+ if (canAutoConstruct(type)) {
+ return construct(wiring, type);
+ }
+
+ 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..c012cc1
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/impl/ConstantsClassSource.java
@@ -0,0 +1,73 @@
+package foundation.stack.datamill.configuration.impl;
+
+import foundation.stack.datamill.configuration.Value;
+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) {
+ Value value = field.getAnnotation(Value.class);
+ if (value != null) {
+ constants.put(propertyName, value.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/DefaultsSource.java b/core/src/main/java/foundation/stack/datamill/configuration/impl/DefaultsSource.java
deleted file mode 100644
index 8dd11ab..0000000
--- a/core/src/main/java/foundation/stack/datamill/configuration/impl/DefaultsSource.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package foundation.stack.datamill.configuration.impl;
-
-import foundation.stack.datamill.configuration.Defaults;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-
-/**
- * @author Ravi Chodavarapu (rchodava@gmail.com)
- */
-public class DefaultsSource extends AbstractSource implements Defaults {
- private final Map defaults = new HashMap<>();
-
- @Override
- public Optional getOptional(String name) {
- return Optional.ofNullable(defaults.get(name));
- }
-
- @Override
- public Defaults put(String name, String value) {
- defaults.put(name, value);
- return this;
- }
-}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/impl/EnvironmentPropertiesSource.java b/core/src/main/java/foundation/stack/datamill/configuration/impl/EnvironmentPropertiesSource.java
index ce5d102..318a33d 100644
--- a/core/src/main/java/foundation/stack/datamill/configuration/impl/EnvironmentPropertiesSource.java
+++ b/core/src/main/java/foundation/stack/datamill/configuration/impl/EnvironmentPropertiesSource.java
@@ -1,28 +1,18 @@
package foundation.stack.datamill.configuration.impl;
-import com.github.davidmoten.rx.Functions;
-import rx.functions.Func1;
-
import java.util.Optional;
/**
* @author Ravi Chodavarapu (rchodava@gmail.com)
*/
public class EnvironmentPropertiesSource extends AbstractSource {
- public static final EnvironmentPropertiesSource IDENTITY = new EnvironmentPropertiesSource();
-
- private final Func1 transformer;
-
- public EnvironmentPropertiesSource(Func1 transformer) {
- this.transformer = transformer != null ? transformer : Functions.identity();
- }
+ public static final EnvironmentPropertiesSource DEFAULT = new EnvironmentPropertiesSource();
private EnvironmentPropertiesSource() {
- this(null);
}
@Override
public Optional getOptional(String name) {
- return Optional.ofNullable(System.getenv(transformer.call(name)));
+ return Optional.ofNullable(System.getenv(name));
}
}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/impl/FactoryChainImpl.java b/core/src/main/java/foundation/stack/datamill/configuration/impl/FactoryChainImpl.java
new file mode 100644
index 0000000..c1790d1
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/impl/FactoryChainImpl.java
@@ -0,0 +1,154 @@
+package foundation.stack.datamill.configuration.impl;
+
+import foundation.stack.datamill.configuration.*;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public class FactoryChainImpl implements FactoryChain {
+ private static final Predicate> TAUTOLOGY = __ -> true;
+
+ public static Predicate> isExactType(Class> factoryType) {
+ return type -> type == factoryType;
+ }
+
+ public static Predicate> isSuperOf(Class> factoryType) {
+ return type -> type.isAssignableFrom(factoryType);
+ }
+
+ public static Predicate> tautology() {
+ return TAUTOLOGY;
+ }
+
+ private final List delegates;
+ private final List> exclusions;
+
+ public FactoryChainImpl(Predicate> condition, TypeLessFactory> factory) {
+ this(condition, Factory.wrap(factory));
+ }
+
+ public FactoryChainImpl(Predicate> condition, Factory, ?> factory) {
+ this(condition, QualifyingFactory.wrap(factory));
+ }
+
+ public FactoryChainImpl(Predicate> condition, QualifyingFactory, ?> factory) {
+ this.delegates = Collections.singletonList(new FactoryDelegate(condition, factory));
+ this.exclusions = null;
+ }
+
+ private FactoryChainImpl(List delegates) {
+ this(delegates, null);
+ }
+
+ private FactoryChainImpl(List delegates, List> exclusions) {
+ this.delegates = delegates;
+ this.exclusions = exclusions;
+ }
+
+ @Override
+ public Object call(Wiring wiring, Class extends Object> type, Collection qualifiers) {
+ for (FactoryDelegate delegate : delegates) {
+ if (delegate.condition.test(type) &&
+ (exclusions == null || !exclusions.contains(delegate.factory))) {
+ Wiring childWiring = shouldExcludeDelegateFactory(delegate) ?
+ new Wiring(wiring, wiring.getFactoryChain().exclude(delegate.factory)) : wiring;
+
+ Object constructed = delegate.factory.call(childWiring, (Class) type, qualifiers);
+ if (constructed != null) {
+ return constructed;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public FactoryChain exclude(QualifyingFactory, ?> factory) {
+ ArrayList> exclusions =
+ new ArrayList<>(this.exclusions != null ? this.exclusions.size() + 1 : 1);
+ if (this.exclusions != null) {
+ exclusions.addAll(this.exclusions);
+ }
+
+ exclusions.add(factory);
+
+ return new FactoryChainImpl(delegates, exclusions);
+ }
+
+ private boolean shouldExcludeDelegateFactory(FactoryDelegate delegate) {
+ return delegate.factory != ConcreteClassFactory.instance() && delegate.condition != TAUTOLOGY;
+ }
+
+ @Override
+ public FactoryChain thenForAny(Factory factory) {
+ return thenForAny(QualifyingFactory.wrap(factory));
+ }
+
+ @Override
+ public FactoryChain thenForAnyConcreteClass() {
+ return thenForAny(ConcreteClassFactory.instance());
+ }
+
+ @Override
+ public FactoryChain thenForSuperOf(Class type, Factory factory) {
+ return thenForSuperOf((Class) type, QualifyingFactory.wrap(factory));
+ }
+
+ @Override
+ public FactoryChain thenForType(Class type, Factory factory) {
+ return thenForType((Class) type, QualifyingFactory.wrap(factory));
+ }
+
+ @Override
+ public FactoryChain thenForAny(TypeLessFactory factory) {
+ return thenForAny(Factory.wrap(factory));
+ }
+
+ @Override
+ public FactoryChain thenForSuperOf(Class type, TypeLessFactory> factory) {
+ return thenForSuperOf((Class) type, Factory.wrap(factory));
+ }
+
+ @Override
+ public FactoryChain thenForType(Class type, TypeLessFactory factory) {
+ return thenForType((Class) type, Factory.wrap(factory));
+ }
+
+ @Override
+ public FactoryChain thenForAny(QualifyingFactory factory) {
+ return withAppendedDelegate(new FactoryDelegate(tautology(), factory));
+ }
+
+ @Override
+ public FactoryChain thenForSuperOf(Class type, QualifyingFactory factory) {
+ return withAppendedDelegate(new FactoryDelegate(isSuperOf(type), factory));
+ }
+
+ @Override
+ public FactoryChain thenForType(Class type, QualifyingFactory factory) {
+ return withAppendedDelegate(new FactoryDelegate(isExactType(type), factory));
+ }
+
+ private FactoryChain withAppendedDelegate(FactoryDelegate delegate) {
+ ArrayList delegates = new ArrayList<>(this.delegates);
+ delegates.add(delegate);
+ return new FactoryChainImpl(delegates);
+ }
+
+ private static class FactoryDelegate {
+ private final Predicate> condition;
+ private final QualifyingFactory, ?> factory;
+
+ public FactoryDelegate(Predicate> condition, QualifyingFactory, ?> factory) {
+ this.condition = condition;
+ this.factory = factory;
+ }
+ }
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/impl/ImmediatePropertySourceImpl.java b/core/src/main/java/foundation/stack/datamill/configuration/impl/ImmediatePropertySourceImpl.java
new file mode 100644
index 0000000..6fac8f9
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/impl/ImmediatePropertySourceImpl.java
@@ -0,0 +1,42 @@
+package foundation.stack.datamill.configuration.impl;
+
+import foundation.stack.datamill.configuration.ImmediatePropertySource;
+import foundation.stack.datamill.values.Value;
+
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public class ImmediatePropertySourceImpl extends AbstractSource implements ImmediatePropertySource {
+ private final Map immediates = new HashMap<>();
+
+ @Override
+ public Optional getOptional(String name) {
+ return Optional.ofNullable(immediates.get(name));
+ }
+
+ @Override
+ public ImmediatePropertySource put(String name, String value) {
+ immediates.put(name, value);
+ return this;
+ }
+
+ @Override
+ public ImmediatePropertySource put(String name, String format, Object... arguments) {
+ Object[] casted = new Object[arguments.length];
+ for (int i = 0; i < arguments.length; i++) {
+ if (arguments[i] instanceof Value) {
+ casted[i] = ((Value) arguments[i]).asString();
+ } else {
+ casted[i] = arguments[i];
+ }
+ }
+
+ return put(name, MessageFormat.format(format, casted));
+ }
+
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/impl/NamedParameterValueRetriever.java b/core/src/main/java/foundation/stack/datamill/configuration/impl/NamedParameterValueRetriever.java
new file mode 100644
index 0000000..c41c67d
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/impl/NamedParameterValueRetriever.java
@@ -0,0 +1,57 @@
+package foundation.stack.datamill.configuration.impl;
+
+import foundation.stack.datamill.configuration.Named;
+import foundation.stack.datamill.configuration.Wiring;
+import foundation.stack.datamill.configuration.WiringException;
+
+import java.lang.reflect.Parameter;
+
+/**
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public class NamedParameterValueRetriever {
+ private static Object getValueForNamedParameter(Wiring wiring, Parameter parameter, Named[] names) {
+ for (Named name : names) {
+ Object value = getValueForNamedParameter(wiring, parameter, name);
+ if (value != null) {
+ return value;
+ }
+ }
+
+ throwUnsatisfiedNamedParameterException(names);
+ return null;
+ }
+
+ private static Object getValueForNamedParameter(Wiring wiring, Parameter parameter, Named name) {
+ return SimpleValueConverter.convert(wiring.getNamed(name.value()).orElse(null), parameter.getType());
+ }
+
+ public static Object retrieveValueIfNamedParameter(Wiring wiring, Parameter parameter) {
+ Named[] names = parameter.getAnnotationsByType(Named.class);
+ if (names != null && names.length > 0) {
+ Object namedValue = getValueForNamedParameter(wiring, parameter, names);
+ if (namedValue != null) {
+ return namedValue;
+ }
+ }
+
+ return null;
+ }
+
+ private static void throwUnsatisfiedNamedParameterException(Named[] names) {
+ StringBuilder message = new StringBuilder("Failed to satisfy named parameter [");
+
+ for (int i = 0; i < names.length; i++) {
+ if (names[i].value() != null) {
+ message.append(names[i].value());
+ if (i < names.length - 1) {
+ message.append(", ");
+ }
+ }
+ }
+
+ message.append(']');
+
+ throw new WiringException(message.toString());
+ }
+}
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..3eb7723 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
@@ -1,8 +1,6 @@
package foundation.stack.datamill.configuration.impl;
-import foundation.stack.datamill.configuration.Defaults;
-import foundation.stack.datamill.configuration.PropertySource;
-import foundation.stack.datamill.configuration.PropertySourceChain;
+import foundation.stack.datamill.configuration.*;
import rx.functions.Action1;
import rx.functions.Func1;
@@ -16,25 +14,22 @@
* @author Ravi Chodavarapu (rchodava@gmail.com)
*/
public class PropertySourceChainImpl extends AbstractSource implements PropertySourceChain {
- private final List chain;
+ private static final Func1 IDENTITY = name -> name;
- public PropertySourceChainImpl(PropertySource initialSource) {
- chain = Collections.singletonList(initialSource);
- }
+ private final List chain;
- private PropertySourceChainImpl(List chain) {
- this.chain = chain;
+ public PropertySourceChainImpl(PropertySource initialSource, Func1 transformer) {
+ chain = Collections.singletonList(new TransformedSource(initialSource, transformer));
}
- @Override
- public PropertySourceChain alias(String alias, String original) {
- return (PropertySourceChain) super.alias(alias, original);
+ private PropertySourceChainImpl(List chain) {
+ this.chain = chain;
}
@Override
public Optional getOptional(String name) {
- for (PropertySource source : chain) {
- Optional value = source.get(name);
+ for (TransformedSource source : chain) {
+ Optional value = source.propertySource.get(source.transformer.call(name));
if (value.isPresent()) {
return value;
}
@@ -44,47 +39,92 @@ public Optional getOptional(String name) {
}
@Override
- public PropertySource orDefaults(Action1 defaultsInitializer) {
- DefaultsSource defaults = new DefaultsSource();
- defaultsInitializer.call(defaults);
+ public PropertySourceChain or(PropertySource source) {
+ return or(source, null);
+ }
+
+ @Override
+ public PropertySourceChain or(PropertySource source, Func1 transformer) {
+ ArrayList newChain = new ArrayList<>(chain);
+ newChain.add(new TransformedSource(source, transformer));
+
+ return new PropertySourceChainImpl(newChain);
+ }
+
+ @Override
+ public PropertySourceChain orComputed(Func1 computation) {
+ return or(new ComputedSource(computation), null);
+ }
- return orSource(defaults);
+ @Override
+ public PropertySourceChain orComputed(Func1 computation, Func1 transformer) {
+ return or(new ComputedSource(computation), transformer);
+ }
+
+ @Override
+ public PropertySourceChain orConstantsClass(Class constantsClass) {
+ return orConstantsClass(constantsClass, null);
+ }
+
+ @Override
+ public PropertySourceChain orConstantsClass(Class constantsClass, Func1 transformer) {
+ return or(new ConstantsClassSource<>(constantsClass), transformer);
}
@Override
public PropertySourceChain orEnvironment() {
- return orSource(EnvironmentPropertiesSource.IDENTITY);
+ return orEnvironment(null);
}
@Override
public PropertySourceChain orEnvironment(Func1 transformer) {
- return orSource(new EnvironmentPropertiesSource(transformer));
+ return or(EnvironmentPropertiesSource.DEFAULT, transformer);
}
@Override
public PropertySourceChain orFile(String path) {
+ return orFile(path, null);
+ }
+
+ @Override
+ public PropertySourceChain orFile(String path, Func1 transformer) {
try {
- return orSource(new FileSource(path));
+ return or(new FileSource(path), transformer);
} catch (IOException e) {
- return orSource(EmptySource.INSTANCE);
+ return or(EmptySource.INSTANCE);
}
}
@Override
- public PropertySourceChain orSource(PropertySource source) {
- ArrayList newChain = new ArrayList<>(chain);
- newChain.add(source);
+ public PropertySourceChain orImmediate(Action1 initializer) {
+ return orImmediate(initializer, null);
+ }
- return new PropertySourceChainImpl(newChain);
+ @Override
+ public PropertySourceChain orImmediate(Action1 initializer, Func1 transformer) {
+ ImmediatePropertySourceImpl defaults = new ImmediatePropertySourceImpl();
+ initializer.call(defaults);
+
+ return or(defaults, transformer);
}
@Override
public PropertySourceChain orSystem() {
- return orSource(SystemPropertiesSource.IDENTITY);
+ return orSystem(null);
}
@Override
public PropertySourceChain orSystem(Func1 transformer) {
- return orSource(new SystemPropertiesSource(transformer));
+ return or(SystemPropertiesSource.DEFAULT, transformer);
+ }
+
+ private static class TransformedSource {
+ private final PropertySource propertySource;
+ private final Func1 transformer;
+
+ public TransformedSource(PropertySource propertySource, Func1 transformer) {
+ this.propertySource = propertySource;
+ this.transformer = transformer != null ? transformer : IDENTITY;
+ }
}
}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/impl/SimpleValueConverter.java b/core/src/main/java/foundation/stack/datamill/configuration/impl/SimpleValueConverter.java
new file mode 100644
index 0000000..2c5de18
--- /dev/null
+++ b/core/src/main/java/foundation/stack/datamill/configuration/impl/SimpleValueConverter.java
@@ -0,0 +1,246 @@
+package foundation.stack.datamill.configuration.impl;
+
+import foundation.stack.datamill.reflection.impl.TypeSwitch;
+import foundation.stack.datamill.values.StringValue;
+import foundation.stack.datamill.values.Value;
+
+/**
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public class SimpleValueConverter {
+ private static final TypeSwitch downCast = new TypeSwitch() {
+ @Override
+ protected Object caseBoolean(Object value, Void __) {
+ return (boolean) value;
+ }
+
+ @Override
+ protected Object caseByte(Object value, Void __) {
+ return (byte) (long) value;
+ }
+
+ @Override
+ protected Object caseCharacter(Object value, Void __) {
+ return (char) (long) value;
+ }
+
+ @Override
+ protected Object caseShort(Object value, Void __) {
+ return (short) (long) value;
+ }
+
+ @Override
+ protected Object caseInteger(Object value, Void __) {
+ return (int) (long) value;
+ }
+
+ @Override
+ protected Object caseLong(Object value, Void __) {
+ return (long) value;
+ }
+
+ @Override
+ protected Object caseFloat(Object value, Void __) {
+ return (float) (double) value;
+ }
+
+ @Override
+ protected Object caseDouble(Object value, Void __) {
+ return (double) value;
+ }
+
+ @Override
+ protected Object caseLocalDateTime(Object value, Void __) {
+ return null;
+ }
+
+ @Override
+ protected Object caseByteArray(Object value, Void __) {
+ return null;
+ }
+
+ @Override
+ protected Object caseString(Object value1, Void value2) {
+ return null;
+ }
+
+ @Override
+ protected Object defaultCase(Object value, Void __) {
+ return null;
+ }
+ };
+
+ private static final TypeSwitch upCast = new TypeSwitch() {
+ @Override
+ protected Object caseBoolean(Object value, Void __) {
+ return (boolean) value;
+ }
+
+ @Override
+ protected Object caseByte(Object value, Void __) {
+ return (long) (byte) value;
+ }
+
+ @Override
+ protected Object caseCharacter(Object value, Void __) {
+ return (long) (char) value;
+ }
+
+ @Override
+ protected Object caseShort(Object value, Void __) {
+ return (long) (short) value;
+ }
+
+ @Override
+ protected Object caseInteger(Object value, Void __) {
+ return (long) (int) value;
+ }
+
+ @Override
+ protected Object caseLong(Object value, Void __) {
+ return (long) value;
+ }
+
+ @Override
+ protected Object caseFloat(Object value, Void __) {
+ return (double) (float) value;
+ }
+
+ @Override
+ protected Object caseDouble(Object value, Void __) {
+ return (double) value;
+ }
+
+ @Override
+ protected Object caseLocalDateTime(Object value, Void __) {
+ return null;
+ }
+
+ @Override
+ protected Object caseByteArray(Object value, Void __) {
+ return null;
+ }
+
+ @Override
+ protected Object caseString(Object value1, Void value2) {
+ return null;
+ }
+
+ @Override
+ protected Object defaultCase(Object value, Void __) {
+ return null;
+ }
+ };
+
+ private static final TypeSwitch valueCast = new TypeSwitch() {
+ @Override
+ protected Object caseBoolean(Value value, Void __) {
+ return value.asBoolean();
+ }
+
+ @Override
+ protected Object caseByte(Value value, Void __) {
+ return value.asByte();
+ }
+
+ @Override
+ protected Object caseCharacter(Value value, Void __) {
+ return value.asCharacter();
+ }
+
+ @Override
+ protected Object caseShort(Value value, Void __) {
+ return value.asShort();
+ }
+
+ @Override
+ protected Object caseInteger(Value value, Void __) {
+ return value.asInteger();
+ }
+
+ @Override
+ protected Object caseLong(Value value, Void __) {
+ return value.asLong();
+ }
+
+ @Override
+ protected Object caseFloat(Value value, Void __) {
+ return value.asFloat();
+ }
+
+ @Override
+ protected Object caseDouble(Value value, Void __) {
+ return value.asDouble();
+ }
+
+ @Override
+ protected Object caseLocalDateTime(Value value, Void __) {
+ return value.asLocalDateTime();
+ }
+
+ @Override
+ protected Object caseByteArray(Value value, Void __) {
+ return value.asByteArray();
+ }
+
+ @Override
+ protected Object caseString(Value value1, Void value2) {
+ return value1.asString();
+ }
+
+ @Override
+ protected Object defaultCase(Value value, Void __) {
+ return value;
+ }
+ };
+
+ private static Object castValueToTypeIfPossible(Value value, Class> type) {
+ Object castedValue = valueCast.doSwitch(type, value, null);
+ if (castedValue != null && Classes.isAssignable(type, castedValue.getClass())) {
+ return castedValue;
+ }
+
+ return null;
+ }
+
+ public static T convert(Object value, Class type) {
+ if (value != null) {
+ if (type.isInstance(value)) {
+ return (T) value;
+ }
+
+ if (type.isPrimitive()) {
+ Class> wrapper = Classes.primitiveToWrapper(type);
+ if (wrapper.isInstance(value.getClass())) {
+ return (T) value;
+ }
+
+ Class> valuePrimitive = Classes.wrapperToPrimitive(value.getClass());
+ if (Classes.isAssignable(valuePrimitive, type) || Classes.isAssignable(type, valuePrimitive)) {
+ return (T) downCast.doSwitch(type, upCast.doSwitch(value.getClass(), value, null), null);
+ }
+ } else if (Classes.isPrimitiveWrapper(type)) {
+ Class> typePrimitive = Classes.wrapperToPrimitive(type);
+ Class> valuePrimitive = Classes.wrapperToPrimitive(value.getClass());
+
+ if (Classes.isAssignable(valuePrimitive, typePrimitive) || Classes.isAssignable(typePrimitive, valuePrimitive)) {
+ return (T) downCast.doSwitch(typePrimitive, upCast.doSwitch(value.getClass(), value, null), null);
+ }
+ }
+
+ if (Value.class.isAssignableFrom(value.getClass())) {
+ if (type == Value.class) {
+ return (T) value;
+ }
+
+ return (T) castValueToTypeIfPossible((Value) value, type);
+ }
+
+ if (type == Value.class) {
+ return (T) new StringValue(value.toString());
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/core/src/main/java/foundation/stack/datamill/configuration/impl/SystemPropertiesSource.java b/core/src/main/java/foundation/stack/datamill/configuration/impl/SystemPropertiesSource.java
index 1330759..7f77706 100644
--- a/core/src/main/java/foundation/stack/datamill/configuration/impl/SystemPropertiesSource.java
+++ b/core/src/main/java/foundation/stack/datamill/configuration/impl/SystemPropertiesSource.java
@@ -1,28 +1,18 @@
package foundation.stack.datamill.configuration.impl;
-import com.github.davidmoten.rx.Functions;
-import rx.functions.Func1;
-
import java.util.Optional;
/**
* @author Ravi Chodavarapu (rchodava@gmail.com)
*/
public class SystemPropertiesSource extends AbstractSource {
- public static final SystemPropertiesSource IDENTITY = new SystemPropertiesSource();
-
- private final Func1 transformer;
-
- public SystemPropertiesSource(Func1 transformer) {
- this.transformer = transformer != null ? transformer : Functions.identity();
- }
+ public static final SystemPropertiesSource DEFAULT = new SystemPropertiesSource();
private SystemPropertiesSource() {
- this(null);
}
@Override
public Optional getOptional(String name) {
- return Optional.ofNullable(System.getProperty(transformer.call(name)));
+ return Optional.ofNullable(System.getProperty(name));
}
}
diff --git a/core/src/main/java/foundation/stack/datamill/db/DatabaseClient.java b/core/src/main/java/foundation/stack/datamill/db/DatabaseClient.java
index 9ca8687..1a9de62 100644
--- a/core/src/main/java/foundation/stack/datamill/db/DatabaseClient.java
+++ b/core/src/main/java/foundation/stack/datamill/db/DatabaseClient.java
@@ -6,9 +6,12 @@
import foundation.stack.datamill.db.impl.RowImpl;
import foundation.stack.datamill.db.impl.UnsubscribeOnNextOperator;
import org.flywaydb.core.Flyway;
+import org.flywaydb.core.api.MigrationInfo;
+import org.flywaydb.core.api.callback.FlywayCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
+import rx.functions.Action1;
import rx.functions.Func1;
import javax.sql.DataSource;
@@ -111,8 +114,17 @@ public void clean() {
getFlyway().clean();
}
+ public void migrate(Action1 migrationPreparation) {
+ Flyway flyway = getFlyway();
+ if (migrationPreparation != null) {
+ flyway.setCallbacks(new MigrationCallback(migrationPreparation));
+ }
+
+ flyway.migrate();
+ }
+
public void migrate() {
- getFlyway().migrate();
+ migrate(null);
}
@Override
@@ -212,4 +224,92 @@ public Observable stream() {
return results;
}
}
+
+ private static class MigrationCallback implements FlywayCallback {
+ private final Action1 migrationAction;
+
+ public MigrationCallback(Action1 migrationAction) {
+ this.migrationAction = migrationAction;
+ }
+
+ @Override
+ public void beforeClean(Connection connection) {
+
+ }
+
+ @Override
+ public void afterClean(Connection connection) {
+
+ }
+
+ @Override
+ public void beforeMigrate(Connection connection) {
+ migrationAction.call(connection);
+ }
+
+ @Override
+ public void afterMigrate(Connection connection) {
+
+ }
+
+ @Override
+ public void beforeEachMigrate(Connection connection, MigrationInfo info) {
+
+ }
+
+ @Override
+ public void afterEachMigrate(Connection connection, MigrationInfo info) {
+
+ }
+
+ @Override
+ public void beforeValidate(Connection connection) {
+
+ }
+
+ @Override
+ public void afterValidate(Connection connection) {
+
+ }
+
+ @Override
+ public void beforeBaseline(Connection connection) {
+
+ }
+
+ @Override
+ public void afterBaseline(Connection connection) {
+
+ }
+
+ @Override
+ public void beforeInit(Connection connection) {
+
+ }
+
+ @Override
+ public void afterInit(Connection connection) {
+
+ }
+
+ @Override
+ public void beforeRepair(Connection connection) {
+
+ }
+
+ @Override
+ public void afterRepair(Connection connection) {
+
+ }
+
+ @Override
+ public void beforeInfo(Connection connection) {
+
+ }
+
+ @Override
+ public void afterInfo(Connection connection) {
+
+ }
+ }
}
diff --git a/core/src/main/java/foundation/stack/datamill/http/builder/ElseBuilder.java b/core/src/main/java/foundation/stack/datamill/http/builder/ElseBuilder.java
index f966629..4e38b96 100644
--- a/core/src/main/java/foundation/stack/datamill/http/builder/ElseBuilder.java
+++ b/core/src/main/java/foundation/stack/datamill/http/builder/ElseBuilder.java
@@ -18,7 +18,7 @@ public interface ElseBuilder {
ElseBuilder elseIfMethodMatches(foundation.stack.datamill.http.Method method, Route route);
ElseBuilder elseIfUriMatches(String pattern, Route route);
ElseBuilder elseIfMethodAndUriMatch(foundation.stack.datamill.http.Method method, String pattern, Route route);
- ElseBuilder elseIfMatchesBeanMethod(T bean);
+ ElseBuilder elseIfMatchesInstanceMethod(T bean);
ElseBuilder elseIfMatchesBeanMethod(Bean> bean);
ElseBuilder elseIfMatchesBeanMethod(
Bean> bean,
diff --git a/core/src/main/java/foundation/stack/datamill/http/builder/RouteBuilder.java b/core/src/main/java/foundation/stack/datamill/http/builder/RouteBuilder.java
index e526af7..858918f 100644
--- a/core/src/main/java/foundation/stack/datamill/http/builder/RouteBuilder.java
+++ b/core/src/main/java/foundation/stack/datamill/http/builder/RouteBuilder.java
@@ -17,7 +17,7 @@ public interface RouteBuilder {
ElseBuilder ifUriMatches(String pattern, Route route);
ElseBuilder ifMethodMatches(Method method, Route route);
ElseBuilder ifMethodAndUriMatch(Method method, String pattern, Route route);
- ElseBuilder ifMatchesBeanMethod(T bean);
+ ElseBuilder ifMatchesInstanceMethod(T bean);
ElseBuilder ifMatchesBeanMethod(Bean> bean);
ElseBuilder ifMatchesBeanMethod(
Bean> bean,
diff --git a/core/src/main/java/foundation/stack/datamill/http/impl/RouteBuilderImpl.java b/core/src/main/java/foundation/stack/datamill/http/impl/RouteBuilderImpl.java
index 3dc98fb..a1abc2c 100644
--- a/core/src/main/java/foundation/stack/datamill/http/impl/RouteBuilderImpl.java
+++ b/core/src/main/java/foundation/stack/datamill/http/impl/RouteBuilderImpl.java
@@ -22,7 +22,7 @@ public class RouteBuilderImpl implements RouteBuilder, ElseBuilder {
private final List matchers = new ArrayList<>();
@Override
- public ElseBuilder elseIfMatchesBeanMethod(T bean) {
+ public ElseBuilder elseIfMatchesInstanceMethod(T bean) {
return elseIfMatchesBeanMethod(OutlineBuilder.DEFAULT.wrap(bean));
}
@@ -52,7 +52,7 @@ public ElseBuilder elseIfUriMatches(String pattern, Route route) {
}
@Override
- public ElseBuilder ifMatchesBeanMethod(T bean) {
+ public ElseBuilder ifMatchesInstanceMethod(T bean) {
return ifMatchesBeanMethod(OutlineBuilder.DEFAULT.wrap(bean));
}
diff --git a/core/src/test/java/foundation/stack/datamill/configuration/FactoryChainsTest.java b/core/src/test/java/foundation/stack/datamill/configuration/FactoryChainsTest.java
new file mode 100644
index 0000000..b99818d
--- /dev/null
+++ b/core/src/test/java/foundation/stack/datamill/configuration/FactoryChainsTest.java
@@ -0,0 +1,127 @@
+package foundation.stack.datamill.configuration;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public class FactoryChainsTest {
+ @Test
+ public void chains() {
+ ConcreteClass instance = new ConcreteClass();
+ DerivedClass derived = new DerivedClass();
+
+ assertEquals(instance,
+ new Wiring(FactoryChains.forType(ConcreteClass.class, w -> instance).thenForAnyConcreteClass())
+ .singleton(ConcreteClass.class));
+
+ assertEquals(derived,
+ new Wiring(FactoryChains.forSuperOf(DerivedClass.class, w -> derived).thenForAnyConcreteClass())
+ .singleton(ConcreteClass.class));
+
+ assertEquals(derived,
+ new Wiring(FactoryChains.forSuperOf(DerivedClass.class, w -> derived).thenForAnyConcreteClass())
+ .singleton(Interface.class));
+
+ assertEquals(derived,
+ new Wiring(FactoryChains.forSuperOf(DerivedClass.class, w -> derived).thenForAnyConcreteClass())
+ .singleton(DerivedClass.class));
+
+ assertEquals(instance,
+ new Wiring(FactoryChains.forAny(w -> instance).thenForAnyConcreteClass())
+ .singleton(ConcreteClass.class));
+
+ assertTrue(new Wiring(FactoryChains.forAnyConcreteClass().thenForSuperOf(DerivedClass.class, w -> derived))
+ .singleton(ConcreteClass.class) instanceof ConcreteClass);
+
+ assertTrue(new Wiring(FactoryChains.forAnyConcreteClass().thenForSuperOf(DerivedClass.class, w -> derived))
+ .singleton(ConcreteClass.class) != derived);
+
+ assertTrue(new Wiring(FactoryChains.forAnyConcreteClass().thenForType(DerivedClass.class, w -> derived))
+ .singleton(DerivedClass.class) != derived);
+ }
+
+ @Test
+ public void noInfiniteRecursion() {
+ assertNotNull(
+ new Wiring(FactoryChains.forType(ConcreteClass.class, w -> w.singleton(ConcreteClass.class))
+ .thenForAnyConcreteClass()).singleton(ConcreteClass.class));
+
+ assertNotNull(
+ new Wiring(FactoryChains.forSuperOf(DerivedClass.class, w -> w.singleton(ConcreteClass.class))
+ .thenForAnyConcreteClass()).singleton(ConcreteClass.class));
+ }
+
+ @Test
+ public void typedFactories() {
+ ConcreteClass instance = new ConcreteClass();
+ DerivedClass derived = new DerivedClass();
+
+ assertEquals(instance,
+ new Wiring(FactoryChains.forType(ConcreteClass.class, (w, c) -> c == ConcreteClass.class ? instance : null)
+ .thenForAnyConcreteClass())
+ .singleton(ConcreteClass.class));
+
+ assertEquals(derived,
+ new Wiring(FactoryChains.forSuperOf(DerivedClass.class, (w, c) -> c == (Class) ConcreteClass.class ? derived : null)
+ .thenForAnyConcreteClass())
+ .singleton(ConcreteClass.class));
+
+ assertEquals(instance,
+ new Wiring(FactoryChains.forAny((w, c) -> c == ConcreteClass.class ? instance : null)
+ .thenForAnyConcreteClass())
+ .singleton(ConcreteClass.class));
+ }
+
+ @Test
+ public void qualifiedFactories() {
+ ConcreteClass instance = new ConcreteClass();
+ DerivedClass derived = new DerivedClass();
+
+ assertTrue(instance !=
+ new Wiring(FactoryChains.forType(ConcreteClass.class,
+ (w, c, q) -> q.contains("qualifier") ? instance : null)
+ .thenForAnyConcreteClass())
+ .singleton(ConcreteClass.class));
+
+ assertEquals(instance,
+ new Wiring(FactoryChains.forType(ConcreteClass.class,
+ (w, c, q) -> q.contains("qualifier") ? instance : null)
+ .thenForAnyConcreteClass())
+ .singleton(ConcreteClass.class, "qualifier"));
+
+ assertTrue(derived !=
+ new Wiring(FactoryChains.forSuperOf(DerivedClass.class,
+ (w, c, q) -> q.contains("qualifier") ? derived : null)
+ .thenForAnyConcreteClass())
+ .singleton(ConcreteClass.class));
+
+ assertEquals(instance,
+ new Wiring(FactoryChains.forType(ConcreteClass.class,
+ (w, c, q) -> q.contains("qualifier") ? instance : null)
+ .thenForAnyConcreteClass())
+ .singleton(ConcreteClass.class, "qualifier"));
+
+ assertEquals(instance,
+ new Wiring(FactoryChains.forAny((w, c, q) -> q.contains("qualifier") ? instance : null)
+ .thenForAnyConcreteClass())
+ .singleton(ConcreteClass.class, "qualifier"));
+ }
+
+ private interface Interface {
+ }
+
+ private static class ConcreteClass {
+ public ConcreteClass() {
+ }
+ }
+
+ private static class DerivedClass extends ConcreteClass implements Interface {
+ public DerivedClass() {
+ }
+ }
+}
diff --git a/core/src/test/java/foundation/stack/datamill/configuration/PropertiesTest.java b/core/src/test/java/foundation/stack/datamill/configuration/PropertiesTest.java
deleted file mode 100644
index d054ca5..0000000
--- a/core/src/test/java/foundation/stack/datamill/configuration/PropertiesTest.java
+++ /dev/null
@@ -1,134 +0,0 @@
-package foundation.stack.datamill.configuration;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.contrib.java.lang.system.EnvironmentVariables;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.fail;
-
-/**
- * @author Ravi Chodavarapu (rchodava@gmail.com)
- */
-public class PropertiesTest {
- @Rule public final EnvironmentVariables environmentVariables = new EnvironmentVariables();
-
- @Test
- public void environmentVariables() {
- environmentVariables.set("test", "value");
- environmentVariables.set("prefix_transformed", "value2");
-
- assertEquals("value", Properties.fromEnvironment().get("test").get());
- assertFalse(Properties.fromEnvironment().get("test2").isPresent());
-
- assertFalse(Properties.fromEnvironment().get("transformed").isPresent());
- assertEquals("value2", Properties.fromEnvironment(v -> "prefix_" + v).get("transformed").get());
-
- assertEquals("value", Properties.fromEnvironment().getRequired("test").asString());
- }
-
- @Test
- public void systemProperties() {
- System.setProperty("test", "value");
- System.setProperty("prefix_transformed", "value2");
-
- assertEquals("value", Properties.fromSystem().get("test").get());
- assertFalse(Properties.fromSystem().get("test2").isPresent());
-
- assertFalse(Properties.fromSystem().get("transformed").isPresent());
- assertEquals("value2", Properties.fromSystem(v -> "prefix_" + v).get("transformed").get());
-
- assertEquals("value", Properties.fromSystem().getRequired("test").asString());
- }
-
- @Test
- public void files() {
- assertEquals("value", Properties.fromFile("test.properties").get("test").get());
- assertFalse(Properties.fromFile("test.properties").get("test2").isPresent());
-
- assertEquals("value3", Properties.fromFile("test.properties").orFile("test2.properties").get("test3").get());
- assertFalse(Properties.fromFile("test.properties").orFile("test2.properties").get("test4").isPresent());
-
- assertEquals("value", Properties.fromFile("test.properties").getRequired("test").asString());
- assertEquals("value3", Properties.fromFile("test.properties").orFile("test2.properties").getRequired("test3").asString());
- }
-
- @Test
- public void defaults() {
- System.setProperty("test", "value");
-
- assertEquals("value", Properties.fromSystem().orDefaults(d -> d.put("test", "value2")).get("test").get());
- assertEquals("value2", Properties.fromSystem().orDefaults(d -> d.put("test2", "value2")).get("test2").get());
-
- assertEquals("value", Properties.fromSystem().orDefaults(d -> d.put("test", "value2")).getRequired("test").asString());
- assertEquals("value2", Properties.fromSystem().orDefaults(d -> d.put("test2", "value2")).getRequired("test2").asString());
- }
-
-
- @Test
- public void aliases() {
- System.setProperty("test", "value");
-
- assertEquals("value", Properties.fromSystem().get("test").get());
- assertEquals("value", Properties.fromSystem().alias("test2", "test").get("test2").get());
- }
-
- @Test
- public void chains() {
- environmentVariables.set("test4", "value4");
- System.setProperty("test5", "value5");
-
- PropertySource chain = Properties.fromFile("test.properties").orFile("test2.properties").orSystem()
- .orEnvironment().orDefaults(d -> d.put("test6", "value6"));
-
- assertEquals("value", chain.get("test").get());
- assertEquals("value3", chain.get("test3").get());
- assertEquals("value4", chain.get("test4").get());
- assertEquals("value5", chain.get("test5").get());
- assertEquals("value6", chain.get("test6").get());
-
- assertEquals("value", chain.getRequired("test").asString());
- assertEquals("value3", chain.getRequired("test3").asString());
- assertEquals("value4", chain.getRequired("test4").asString());
- assertEquals("value5", chain.getRequired("test5").asString());
- assertEquals("value6", chain.getRequired("test6").asString());
- }
-
- @Test
- public void missingRequiredProperties() throws Exception {
- environmentVariables.set("test4", "value4");
- System.setProperty("test5", "value5");
-
- try {
- Properties.fromEnvironment().getRequired("test3");
- fail();
- } catch (IllegalArgumentException e) {
- }
-
- try {
- Properties.fromSystem().getRequired("test3");
- fail();
- } catch (IllegalArgumentException e) {
- }
-
- try {
- Properties.fromFile("test.properties").getRequired("test3");
- fail();
- } catch (IllegalArgumentException e) {
- }
-
- try {
- Properties.fromFile("test.properties").orFile("test2.properties").getRequired("test4");
- fail();
- } catch (IllegalArgumentException e) {
- }
-
- try {
- Properties.fromFile("test.properties").orFile("test2.properties").orSystem().orEnvironment()
- .orDefaults(d -> d.put("test6", "value6")).getRequired("test2");
- fail();
- } catch (IllegalArgumentException e) {
- }
- }
-}
diff --git a/core/src/test/java/foundation/stack/datamill/configuration/PropertySourcesTest.java b/core/src/test/java/foundation/stack/datamill/configuration/PropertySourcesTest.java
new file mode 100644
index 0000000..45c5db5
--- /dev/null
+++ b/core/src/test/java/foundation/stack/datamill/configuration/PropertySourcesTest.java
@@ -0,0 +1,247 @@
+package foundation.stack.datamill.configuration;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.contrib.java.lang.system.EnvironmentVariables;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+/**
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public class PropertySourcesTest {
+ @Rule
+ public final EnvironmentVariables environmentVariables = new EnvironmentVariables();
+
+ @Test
+ public void environmentVariables() {
+ environmentVariables.set("test", "value");
+ environmentVariables.set("prefix_transformed", "value2");
+
+ assertEquals("value", PropertySources.fromEnvironment().get("test").get());
+ assertFalse(PropertySources.fromEnvironment().get("test2").isPresent());
+
+ assertFalse(PropertySources.fromEnvironment().get("transformed").isPresent());
+ assertEquals("value2", PropertySources.fromEnvironment(v -> "prefix_" + v).get("transformed").get());
+
+ assertEquals("value", PropertySources.fromEnvironment().getRequired("test").asString());
+ }
+
+ @Test
+ public void systemProperties() {
+ System.setProperty("test", "value");
+ System.setProperty("prefix_transformed", "value2");
+
+ assertEquals("value", PropertySources.fromSystem().get("test").get());
+ assertFalse(PropertySources.fromSystem().get("test2").isPresent());
+
+ assertFalse(PropertySources.fromSystem().get("transformed").isPresent());
+ assertEquals("value2", PropertySources.fromSystem(v -> "prefix_" + v).get("transformed").get());
+
+ assertEquals("value", PropertySources.fromSystem().getRequired("test").asString());
+ }
+
+ @Test
+ public void constantClasses() {
+ assertEquals("value", PropertySources.fromConstantsClass(ConstantsClass.class).get(ConstantsClass.property).get());
+ assertFalse(PropertySources.fromConstantsClass(ConstantsClass.class).get("property2").isPresent());
+ assertFalse(PropertySources.fromConstantsClass(ConstantsClass.class).get("config/instance").isPresent());
+
+ assertEquals("publicValue", PropertySources.fromConstantsClass(ConstantsClass.class).get(ConstantsClass.publicProperty).get());
+ assertEquals("privateValue", PropertySources.fromConstantsClass(ConstantsClass.class).get(ConstantsClass.privateProperty).get());
+ assertEquals("nonFinalValue", PropertySources.fromConstantsClass(ConstantsClass.class).get(ConstantsClass.nonFinalProperty).get());
+ assertEquals("1", PropertySources.fromConstantsClass(ConstantsClass.class).get(ConstantsClass.integerProperty).get());
+ assertEquals("true", PropertySources.fromConstantsClass(ConstantsClass.class).get(ConstantsClass.booleanProperty).get());
+
+ assertEquals("value", PropertySources.fromConstantsClass(ConstantsClass.class).getRequired("config/property").asString());
+
+ assertEquals("ifacePublic", PropertySources.fromConstantsClass(ConstantsClass.class).orConstantsClass(ConstantsInterface.class).get(ConstantsInterface.IFACE_PUBLIC_TEST).get());
+ assertEquals("2", PropertySources.fromConstantsClass(ConstantsClass.class).orConstantsClass(ConstantsInterface.class).get(ConstantsInterface.IFACE_INTEGER).get());
+ assertEquals("true", PropertySources.fromConstantsClass(ConstantsClass.class).orConstantsClass(ConstantsInterface.class).get(ConstantsInterface.IFACE_BOOLEAN).get());
+ assertEquals("true", PropertySources.fromConstantsClass(ConstantsClass.class).orConstantsClass(ConstantsInterface.class).get(ConstantsClass.booleanProperty).get());
+ }
+
+ @Test
+ public void computation() {
+ assertEquals("computed", PropertySources.fromComputed(name -> "computed").orFile("test.properties").get("test").get());
+ assertEquals("value", PropertySources.fromComputed(name -> "test2".equals(name) ? "computed" : null)
+ .orFile("test.properties").get("test").get());
+ }
+
+ @Test
+ public void files() throws IOException {
+ assertFalse(PropertySources.fromFile("nonexistent.properties").get("test2").isPresent());
+
+ File externalProperties = File.createTempFile("test", ".properties");
+ try {
+ Files.write("test4=value4\n", externalProperties, Charsets.UTF_8);
+
+ assertEquals("value", PropertySources.fromFile("test.properties").get("test").get());
+ assertFalse(PropertySources.fromFile("test.properties").get("test2").isPresent());
+
+ assertEquals("value3", PropertySources.fromFile("test.properties").orFile("test2.properties").get("test3").get());
+ assertFalse(PropertySources.fromFile("test.properties").orFile("test2.properties").get("test4").isPresent());
+
+ assertEquals("value", PropertySources.fromFile("test.properties").getRequired("test").asString());
+ assertEquals("value3", PropertySources.fromFile("test.properties").orFile("test2.properties").getRequired("test3").asString());
+
+ assertEquals("value4", PropertySources.fromFile(externalProperties.getPath()).orFile("test2.properties").getRequired("test4").asString());
+ } finally {
+ externalProperties.delete();
+ }
+ }
+
+ @Test
+ public void immediates() {
+ System.setProperty("test", "value");
+
+ assertEquals("value", PropertySources.fromSystem().orImmediate(d -> d.put("test", "value2")).get("test").get());
+ assertEquals("value2", PropertySources.fromSystem().orImmediate(d -> d.put("test2", "value2")).get("test2").get());
+
+ assertEquals("value", PropertySources.fromSystem().orImmediate(d -> d.put("test", "value2")).getRequired("test").asString());
+ assertEquals("value2", PropertySources.fromSystem().orImmediate(d -> d.put("test2", "value2")).getRequired("test2").asString());
+
+ assertEquals("value2", PropertySources.fromImmediate(d -> d.put("test2", "value2")).getRequired("test2").asString());
+
+ assertEquals("value-value2", PropertySources.fromImmediate(d -> d.put("test", "{0}-{1}","value", "value2")).getRequired("test").asString());
+ }
+
+ @Test
+ public void chains() {
+ environmentVariables.set("test4", "value4");
+ System.setProperty("test5", "value5");
+
+ PropertySource chain = PropertySources
+ .fromFile("test.properties")
+ .orFile("nonexistent.proeprties")
+ .orFile("test2.properties").orSystem().orEnvironment()
+ .orImmediate(p -> p.put("test6", "value6"));
+
+ assertEquals("value", chain.get("test").get());
+ assertEquals("value3", chain.get("test3").get());
+ assertEquals("value4", chain.get("test4").get());
+ assertEquals("value5", chain.get("test5").get());
+ assertEquals("value6", chain.get("test6").get());
+
+ assertEquals("value", chain.getRequired("test").asString());
+ assertEquals("value3", chain.getRequired("test3").asString());
+ assertEquals("value4", chain.getRequired("test4").asString());
+ assertEquals("value5", chain.getRequired("test5").asString());
+ assertEquals("value6", chain.getRequired("test6").asString());
+ }
+
+ @Test
+ public void conveniences() {
+ assertEquals("value", PropertySources.fromImmediate(s -> s.put("name", "value"))
+ .with(s -> s.getRequired("name").asString()));
+ }
+
+ @Test
+ public void delegating() {
+ DelegatingPropertySource delegatingSource = new DelegatingPropertySource();
+ assertEquals("value2", PropertySources.from(delegatingSource)
+ .orImmediate(d -> d.put("test2", "value2")).getRequired("test2").asString());
+
+ delegatingSource.setDelegate(PropertySources.fromComputed(name -> "value"));
+ assertEquals("value", PropertySources.from(delegatingSource)
+ .orImmediate(d -> d.put("test2", "value2")).getRequired("test2").asString());
+ }
+
+ @Test
+ public void transformers() {
+ environmentVariables.set("PROPERTY_NAME", "value4");
+ System.setProperty("PROPERTY2_NAME", "value5");
+
+ PropertySource base = PropertySources
+ .fromFile("test.properties")
+ .orFile("test2.properties")
+ .orSystem()
+ .orEnvironment()
+ .orImmediate(d -> d.put("test6", "value6"));
+
+ PropertySource leaf = PropertySources.from(base).or(base, PropertyNameTransformers.LEAF);
+ PropertySource upper = PropertySources.from(base).or(base,
+ PropertyNameTransformers.compose(
+ PropertyNameTransformers.LEAF,
+ PropertyNameTransformers.LOWER_CAMEL_TO_UPPER_UNDERSCORE));
+
+ assertEquals("value", leaf.get("category/test").get());
+ assertEquals("value3", leaf.get("category/test3").get());
+ assertEquals("value4", leaf.get("category/PROPERTY_NAME").get());
+ assertEquals("value5", leaf.get("category/PROPERTY2_NAME").get());
+ assertEquals("value6", leaf.get("category/test6").get());
+
+ assertEquals("value4", upper.getRequired("category/subcategory/propertyName").asString());
+ assertEquals("value5", upper.getRequired("category/subcategory/property2Name").asString());
+ }
+
+ @Test
+ public void missingRequiredProperties() throws Exception {
+ environmentVariables.set("test4", "value4");
+ System.setProperty("test5", "value5");
+
+ try {
+ PropertySources.fromEnvironment().getRequired("test3");
+ fail();
+ } catch (IllegalArgumentException e) {
+ }
+
+ try {
+ PropertySources.fromSystem().getRequired("test3");
+ fail();
+ } catch (IllegalArgumentException e) {
+ }
+
+ try {
+ PropertySources.fromFile("test.properties").getRequired("test3");
+ fail();
+ } catch (IllegalArgumentException e) {
+ }
+
+ try {
+ PropertySources.fromFile("test.properties").orFile("test2.properties").getRequired("test4");
+ fail();
+ } catch (IllegalArgumentException e) {
+ }
+
+ try {
+ PropertySources.fromFile("test.properties").orFile("test2.properties").orSystem().orEnvironment()
+ .orImmediate(d -> d.put("test6", "value6")).getRequired("test2");
+ fail();
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ private static class ConstantsClass {
+ @Value("value")
+ static final String property = "config/property";
+ @Value("publicValue")
+ public static final String publicProperty = "config/public";
+ @Value("privateValue")
+ private static final String privateProperty = "config/private";
+ @Value("nonFinalValue")
+ static String nonFinalProperty = "config/nonFinal";
+ @Value("1")
+ static String integerProperty = "config/integer";
+ @Value("true")
+ static String booleanProperty = "config/boolean";
+ @Value("instanceValue")
+ String instanceProperty = "config/instance";
+ }
+
+ private interface ConstantsInterface {
+ @Value("ifacePublic")
+ String IFACE_PUBLIC_TEST = "iface/public";
+ @Value("2")
+ String IFACE_INTEGER = "iface/integer";
+ @Value("true")
+ String IFACE_BOOLEAN = "iface/boolean";
+ }
+}
diff --git a/core/src/test/java/foundation/stack/datamill/configuration/WiringTest.java b/core/src/test/java/foundation/stack/datamill/configuration/WiringTest.java
index 824480f..ba31dfa 100644
--- a/core/src/test/java/foundation/stack/datamill/configuration/WiringTest.java
+++ b/core/src/test/java/foundation/stack/datamill/configuration/WiringTest.java
@@ -1,454 +1,103 @@
package foundation.stack.datamill.configuration;
-import foundation.stack.datamill.values.StringValue;
import org.junit.Test;
-import rx.functions.Func0;
-import rx.functions.Func1;
-import java.time.LocalDateTime;
-import java.util.Optional;
-
-import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
/**
* @author Ravi Chodavarapu (rchodava@gmail.com)
*/
public class WiringTest {
- private static class Test1 {
- private final String arg1;
- private final String arg2;
-
- public Test1(@Named("arg1") String arg1, @Named("arg2") String arg2) {
- this.arg1 = arg1;
- this.arg2 = arg2;
+ private static class ConcreteClass {
+ public ConcreteClass() {
}
}
- private static class Test2 {
- private final Func1 func1String;
- private final Func0 func0String;
+ private static class ConcreteClass2 {
+ private final ConcreteClass concreteClass;
- public Test2(Func0 func0String, Func1 func1String) {
- this.func0String = func0String;
- this.func1String = func1String;
+ public ConcreteClass2(ConcreteClass concreteClass) {
+ this.concreteClass = concreteClass;
}
}
- private static class Base {
- protected String get() {
- return "base";
- }
- }
+ private static class ConcreteClass3 {
+ private final ConcreteClass concreteClass;
+ private final ConcreteClass2 concreteClass2;
- private static class Derived extends Base {
- @Override
- protected String get() {
- return "derived";
- }
- }
-
- public interface Iface1 {
- String iface1();
- }
-
- public interface Iface2 {
- String iface2();
- }
-
- private static class Interfaces extends Base implements Iface1 {
- @Override
- public String iface1() {
- return "iface1";
- }
- }
-
- private static class DerivedInterfaces extends Interfaces implements Iface2 {
- @Override
- public String iface2() {
- return "iface2";
- }
- }
-
- private static class Test3 {
- private final Base base;
- private final Func0 stringSupplier;
-
- public Test3(Base base, Func0 stringSupplier) {
- this.base = base;
- this.stringSupplier = stringSupplier;
- }
- }
-
- private static class Test4 {
- private final Iface1 iface1;
- private final Iface2 iface2;
-
- public Test4(Iface1 iface1, Iface2 iface2) {
- this.iface1 = iface1;
- this.iface2 = iface2;
- }
- }
-
- private static class Test5 {
- private final boolean booleanProperty;
- private final Boolean booleanWrapperProperty;
- private final byte byteProperty;
- private final Byte byteWrapperProperty;
- private final char characterProperty;
- private final Character characterWrapperProperty;
- private final short shortProperty;
- private final Short shortWrapperProperty;
- private final int integerProperty;
- private final Integer integerWrapperProperty;
- private final long longProperty;
- private final Long longWrapperProperty;
- private final float floatProperty;
- private final Float floatWrapperProperty;
- private final double doubleProperty;
- private final Double doubleWrapperProperty;
- private final LocalDateTime localDateTimeProperty;
- private final byte[] byteArrayProperty;
- private final String stringProperty;
-
- public Test5(@Named("boolean") boolean booleanProperty, @Named("booleanWrapper") Boolean booleanWrapperProperty,
- @Named("byte") byte byteProperty, @Named("byteWrapper") Byte byteWrapperProperty,
- @Named("char") char characterProperty, @Named("charWrapper") Character characterWrapperProperty,
- @Named("short") short shortProperty, @Named("shortWrapper") Short shortWrapperProperty,
- @Named("int") int integerProperty, @Named("intWrapper") Integer integerWrapperProperty,
- @Named("long") long longProperty, @Named("longWrapper") Long longWrapperProperty,
- @Named("float") float floatProperty, @Named("floatWrapper") Float floatWrapperProperty,
- @Named("double") double doubleProperty, @Named("doubleWrapper") Double doubleWrapperProperty,
- @Named("LocalDateTime") LocalDateTime localDateTimeProperty,
- @Named("byteArray") byte[] byteArrayProperty,
- @Named("String") String stringProperty) {
- this.booleanProperty = booleanProperty;
- this.booleanWrapperProperty = booleanWrapperProperty;
- this.byteProperty = byteProperty;
- this.byteWrapperProperty = byteWrapperProperty;
- this.characterProperty = characterProperty;
- this.characterWrapperProperty = characterWrapperProperty;
- this.shortProperty = shortProperty;
- this.shortWrapperProperty = shortWrapperProperty;
- this.integerProperty = integerProperty;
- this.integerWrapperProperty = integerWrapperProperty;
- this.longProperty = longProperty;
- this.longWrapperProperty = longWrapperProperty;
- this.floatProperty = floatProperty;
- this.floatWrapperProperty = floatWrapperProperty;
- this.doubleProperty = doubleProperty;
- this.doubleWrapperProperty = doubleWrapperProperty;
- this.localDateTimeProperty = localDateTimeProperty;
- this.byteArrayProperty = byteArrayProperty;
- this.stringProperty = stringProperty;
- }
- }
-
- private static class Test6 {
- private final LocalDateTime localDateTimeProperty;
- private final byte[] byteArrayProperty;
-
- public Test6(LocalDateTime localDateTimeProperty,
- byte[] byteArrayProperty) {
- this.localDateTimeProperty = localDateTimeProperty;
- this.byteArrayProperty = byteArrayProperty;
- }
- }
-
- private static class Test7 {
- private final Func0 stringSupplier;
-
- public Test7() {
- stringSupplier = () -> "default";
- }
-
- public Test7(Func0 stringSupplier) {
- this.stringSupplier = stringSupplier;
- }
- }
-
- private static class Test8 {
- private final Test7 test7;
-
- public Test8(Test7 test7) {
- this.test7 = test7;
- }
- }
-
- private static class Test9 {
- private final Test7 test7;
- private final Test8 test8;
-
- public Test9(Test7 test7, Test8 test8) {
- this.test7 = test7;
- this.test8 = test8;
+ public ConcreteClass3(ConcreteClass concreteClass, ConcreteClass2 concreteClass2) {
+ this.concreteClass = concreteClass;
+ this.concreteClass2 = concreteClass2;
}
}
@Test
public void autoConstruction() {
- Test7 test7 = new Test7();
- Test9 test9 = new Wiring().add(test7).construct(Test9.class);
-
- assertEquals(test7, test9.test7);
- assertEquals(test7, test9.test8.test7);
- }
-
- @Test
- public void builders() {
- Test9 test9 = new Wiring().addFactory(Test7.class, w -> new Test7(() -> "custom")).construct(Test9.class);
-
- assertEquals("custom", test9.test7.stringSupplier.call());
- assertEquals("custom", test9.test8.test7.stringSupplier.call());
- }
-
- @Test
- public void named() {
- Test1 instance = new Wiring()
- .addNamed("arg1", "value1")
- .addNamed("arg2", Optional.of("value2"))
- .construct(Test1.class);
+ ConcreteClass concreteClass = new ConcreteClass();
+ ConcreteClass3 concreteClass3 = new Wiring(FactoryChains.forType(ConcreteClass.class, (w, c) -> concreteClass)
+ .thenForAnyConcreteClass())
+ .newInstance(ConcreteClass3.class);
- assertEquals("value1", instance.arg1);
- assertEquals("value2", instance.arg2);
+ assertEquals(concreteClass, concreteClass3.concreteClass);
+ assertEquals(concreteClass, concreteClass3.concreteClass2.concreteClass);
}
@Test
- public void typed() {
- Wiring wiring = new Wiring()
- .add((Func0) () -> "func0String",
- (Func1) s -> "func1String" + s);
- Test2 instance = wiring.construct(Test2.class);
-
- assertEquals("func0String", instance.func0String.call());
- assertEquals("func1StringS", instance.func1String.call("S"));
-
- assertEquals("func0String", wiring.get(Func0.class).call());
- assertEquals("func1StringS", wiring.get(Func1.class).call("S"));
- }
-
- @Test
- public void parents() {
- Test3 instance = new Wiring()
- .add(new Derived(), (Func0) () -> "testString")
- .construct(Test3.class);
-
- assertEquals("derived", instance.base.get());
- assertEquals("testString", instance.stringSupplier.call());
- }
-
- @Test
- public void interfaces() {
- Wiring wiring = new Wiring()
- .add(new DerivedInterfaces());
-
- Test4 instance = wiring.construct(Test4.class);
-
- assertEquals("iface1", instance.iface1.iface1());
- assertEquals("iface2", instance.iface2.iface2());
-
- assertEquals("iface1", wiring.get(Iface1.class).iface1());
- assertEquals("iface2", wiring.get(Iface2.class).iface2());
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void noNullAdditions() {
- new Wiring().add(null);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void noNullOptionals() {
- new Wiring().add(Optional.empty());
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void noNullNamedAdditions() {
- new Wiring().addNamed("name", null);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void noNullNamedOptionals() {
- new Wiring().addNamed("name", Optional.empty());
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void noNamedDuplicates() {
- new Wiring().addNamed("test", "value").addNamed("test", "value2");
- }
-
- @Test
- public void formattedValues() {
- Test1 instance = new Wiring().addNamed("arg1", "value")
- .addFormatted("arg2", "{0}:{1}", "value1", "value2")
- .construct(Test1.class);
-
- assertEquals("value1:value2", instance.arg2);
+ public void namedParameters() {
+ assertEquals("value", new Wiring(PropertySources.fromImmediate(s -> s.put("name", "value")))
+ .getNamed("name").map(v -> v.asString()).get());
+ assertEquals("value", new Wiring(PropertySources.fromImmediate(s -> s.put("name", "value")))
+ .getRequiredNamed("name").asString());
+ assertEquals(12, (int) new Wiring(PropertySources.fromImmediate(s -> s.put("name", "12")))
+ .getNamed("name").map(v -> v.asInteger()).get());
+
+ try {
+ assertEquals("value", new Wiring(PropertySources.fromImmediate(s -> s.put("name", "value")))
+ .getRequiredNamed("name2").asString());
+ fail("Expected retrieving a non-existent required named property to fail!");
+ } catch (IllegalStateException e) {
+ }
}
@Test
public void conveniences() {
- Test1 instance = new Wiring().with(w -> w.addNamed("arg1", "value1").addNamed("arg2", "value2"))
- .construct(Test1.class);
-
- assertEquals("value1", instance.arg1);
- assertEquals("value2", instance.arg2);
-
- instance = new Wiring().performIf(true, w -> w.addNamed("arg1", "true1").addNamed("arg2", "true2"))
- .orElse(w -> w.addNamed("arg1", "false1").addNamed("arg2", "false2"))
- .construct(Test1.class);
-
- assertEquals("true1", instance.arg1);
- assertEquals("true2", instance.arg2);
-
- instance = new Wiring().performIf(false, w -> w.addNamed("arg1", "true1").addNamed("arg2", "true2"))
- .orElse(w -> w.addNamed("arg1", "false1").addNamed("arg2", "false2"))
- .construct(Test1.class);
-
- assertEquals("false1", instance.arg1);
- assertEquals("false2", instance.arg2);
-
- }
+ ConcreteClass instance = new ConcreteClass();
+ ConcreteClass instance2 = new ConcreteClass();
- @Test
- public void namedValuesFromPropertySource() {
- Wiring wiring = new Wiring()
- .setNamedPropertySource(Properties.fromSystem().orDefaults(d -> d
- .put("boolean", "true")
- .put("booleanWrapper", "true")
- .put("byte", "1")
- .put("byteWrapper", "1")
- .put("char", "a")
- .put("charWrapper", "a")
- .put("short", "2")
- .put("shortWrapper", "2")
- .put("int", "3")
- .put("intWrapper", "3")
- .put("long", "4")
- .put("longWrapper", "4")
- .put("float", "1.1")
- .put("floatWrapper", "1.1")
- .put("double", "2.2")
- .put("doubleWrapper", "2.2")
- .put("LocalDateTime", "2007-12-03T10:15:30")
- .put("String", "value")
- .put("byteArray", "array")));
- Test5 instance = wiring.construct(Test5.class);
-
- assertEquals(true, instance.booleanProperty);
- assertEquals(true, instance.booleanWrapperProperty);
- assertEquals(1, instance.byteProperty);
- assertEquals(1, (byte) instance.byteWrapperProperty);
- assertEquals('a', instance.characterProperty);
- assertEquals('a', (char) instance.characterWrapperProperty);
- assertEquals(2, instance.shortProperty);
- assertEquals(2, (short) instance.shortWrapperProperty);
- assertEquals(3, instance.integerProperty);
- assertEquals(3, (int) instance.integerWrapperProperty);
- assertEquals(4, instance.longProperty);
- assertEquals(4, (long) instance.longWrapperProperty);
- assertEquals(1.1f, instance.floatProperty, 0.1f);
- assertEquals(1.1f, instance.floatWrapperProperty, 0.1f);
- assertEquals(2.2d, instance.doubleProperty, 0.1d);
- assertEquals(2.2d, instance.doubleWrapperProperty, 0.1d);
- assertEquals(LocalDateTime.parse("2007-12-03T10:15:30"), instance.localDateTimeProperty);
- assertEquals("value", instance.stringProperty);
- assertArrayEquals("array".getBytes(), instance.byteArrayProperty);
-
- assertEquals(true, wiring.getNamed("boolean").asBoolean());
- assertEquals(true, wiring.getNamed("booleanWrapper").asBoolean());
- assertEquals(1, wiring.getNamed("byte").asByte());
- assertEquals(1, wiring.getNamed("byteWrapper").asByte());
- assertEquals(1.1f, wiring.getNamedAs("floatWrapper", Float.class), 0.001f);
- }
-
- @Test
- public void typedNamedValues() {
- Test5 instance = new Wiring()
- .addNamed("boolean", new StringValue("true"))
- .addNamed("booleanWrapper", new StringValue("true"))
- .addNamed("byte", new StringValue("1"))
- .addNamed("byteWrapper", new StringValue("1"))
- .addNamed("char", new StringValue("a"))
- .addNamed("charWrapper", new StringValue("a"))
- .addNamed("short", new StringValue("2"))
- .addNamed("shortWrapper", new StringValue("2"))
- .addNamed("int", new StringValue("3"))
- .addNamed("intWrapper", new StringValue("3"))
- .addNamed("long", new StringValue("4"))
- .addNamed("longWrapper", new StringValue("4"))
- .addNamed("float", new StringValue("1.1"))
- .addNamed("floatWrapper", new StringValue("1.1"))
- .addNamed("double", new StringValue("2.2"))
- .addNamed("doubleWrapper", new StringValue("2.2"))
- .addNamed("LocalDateTime", new StringValue("2007-12-03T10:15:30"))
- .addNamed("String", new StringValue("value"))
- .addNamed("byteArray", new StringValue("array"))
- .construct(Test5.class);
+ assertEquals("value", new Wiring(PropertySources.fromImmediate(s -> s.put("name", "value")))
+ .with(w -> w.getNamed("name").map(v -> v.asString()).get()));
- assertEquals(true, instance.booleanProperty);
- assertEquals(true, instance.booleanWrapperProperty);
- assertEquals(1, instance.byteProperty);
- assertEquals(1, (byte) instance.byteWrapperProperty);
- assertEquals('a', instance.characterProperty);
- assertEquals('a', (char) instance.characterWrapperProperty);
- assertEquals(2, instance.shortProperty);
- assertEquals(2, (short) instance.shortWrapperProperty);
- assertEquals(3, instance.integerProperty);
- assertEquals(3, (int) instance.integerWrapperProperty);
- assertEquals(4, instance.longProperty);
- assertEquals(4, (long) instance.longWrapperProperty);
- assertEquals(1.1f, instance.floatProperty, 0.1f);
- assertEquals(1.1f, instance.floatWrapperProperty, 0.1f);
- assertEquals(2.2d, instance.doubleProperty, 0.1d);
- assertEquals(2.2d, instance.doubleWrapperProperty, 0.1d);
- assertEquals(LocalDateTime.parse("2007-12-03T10:15:30"), instance.localDateTimeProperty);
- assertEquals("value", instance.stringProperty);
- assertArrayEquals("array".getBytes(), instance.byteArrayProperty);
- }
-
- @Test
- public void values() {
- Wiring wiring = new Wiring()
- .add(new StringValue("2007-12-03T10:15:30"))
- .add("array".getBytes());
+ assertEquals("value2", new Wiring(PropertySources.fromImmediate(s -> s.put("name", "value")))
+ .with(PropertySources.fromImmediate(s -> s.put("name", "value2")),
+ w -> w.getNamed("name").map(v -> v.asString()).get()));
- Test6 instance = wiring.construct(Test6.class);
-
- assertEquals(LocalDateTime.parse("2007-12-03T10:15:30"), instance.localDateTimeProperty);
- assertArrayEquals("array".getBytes(), instance.byteArrayProperty);
-
- assertNull(wiring.get(boolean.class));
- assertNull(wiring.get(Boolean.class));
- assertNull(wiring.get(byte.class));
- assertNull(wiring.get(Byte.class));
- assertNull(wiring.get(char.class));
- assertNull(wiring.get(Character.class));
- assertNull(wiring.get(short.class));
- assertNull(wiring.get(Short.class));
- assertNull(wiring.get(int.class));
- assertNull(wiring.get(Integer.class));
- assertNull(wiring.get(long.class));
- assertNull(wiring.get(Long.class));
- assertNull(wiring.get(float.class));
- assertNull(wiring.get(Float.class));
- assertNull(wiring.get(double.class));
- assertNull(wiring.get(Double.class));
- assertNull(wiring.get(String.class));
- assertEquals(LocalDateTime.parse("2007-12-03T10:15:30"), wiring.get(LocalDateTime.class));
- assertArrayEquals("array".getBytes(), wiring.get(byte[].class));
+ // Test that sub-wirings created using 'with' share the same scopes
+ Wiring original = new Wiring(FactoryChains.forType(ConcreteClass.class, w -> instance));
+ original.singleton(ConcreteClass.class);
+ assertEquals(instance, original.with(FactoryChains.forType(ConcreteClass.class, w -> instance2),
+ w -> w.singleton(ConcreteClass.class)));
}
@Test
- public void constructWith() {
- Test7 instance = new Wiring()
- .add((Func0) () -> "value")
- .constructWith(Test7.class);
- assertEquals("default", instance.stringSupplier.call());
-
- instance = new Wiring()
- .add((Func0) () -> "value")
- .constructWith(Test7.class, Func0.class);
- assertEquals("value", instance.stringSupplier.call());
+ public void scopes() {
+ Wiring wiring = new Wiring(FactoryChains.forAnyConcreteClass());
+ ConcreteClass concreteClass = wiring.newInstance(ConcreteClass.class);
+ assertTrue(concreteClass != wiring.newInstance(ConcreteClass.class));
+
+ wiring = new Wiring(FactoryChains.forAnyConcreteClass());
+ concreteClass = wiring.singleton(ConcreteClass.class);
+ assertEquals(concreteClass, wiring.singleton(ConcreteClass.class));
+
+ wiring = new Wiring(FactoryChains.forAnyConcreteClass());
+ concreteClass = wiring.singleton(ConcreteClass.class, "qualifier");
+ assertEquals(concreteClass, wiring.singleton(ConcreteClass.class, "qualifier"));
+ assertTrue(concreteClass != wiring.singleton(ConcreteClass.class, "qualifier2"));
+
+ wiring = new Wiring(FactoryChains.forAnyConcreteClass());
+ concreteClass = wiring.singleton(ConcreteClass.class, "qualifier1", "qualifier2");
+ assertEquals(concreteClass, wiring.singleton(ConcreteClass.class, "qualifier1", "qualifier2"));
+ assertTrue(concreteClass != wiring.singleton(ConcreteClass.class, "qualifier2", "qualifier3"));
}
}
diff --git a/core/src/test/java/foundation/stack/datamill/configuration/impl/ConcreteClassFactoryTest.java b/core/src/test/java/foundation/stack/datamill/configuration/impl/ConcreteClassFactoryTest.java
new file mode 100644
index 0000000..aaf5459
--- /dev/null
+++ b/core/src/test/java/foundation/stack/datamill/configuration/impl/ConcreteClassFactoryTest.java
@@ -0,0 +1,201 @@
+package foundation.stack.datamill.configuration.impl;
+
+import foundation.stack.datamill.configuration.Named;
+import foundation.stack.datamill.configuration.PropertySources;
+import foundation.stack.datamill.configuration.Wiring;
+import foundation.stack.datamill.configuration.WiringException;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public class ConcreteClassFactoryTest {
+ private static final Logger logger = LoggerFactory.getLogger(ConcreteClassFactoryTest.class);
+
+ @Test
+ public void construction() {
+ assertTrue(new Wiring().singleton(ConcreteClass.class) instanceof ConcreteClass);
+
+ assertTrue(new Wiring().singleton(ConcreteClass2.class) instanceof ConcreteClass2);
+ assertTrue(new Wiring().singleton(ConcreteClass2.class).concreteClass instanceof ConcreteClass);
+
+ assertTrue(new Wiring().singleton(ConcreteClass3.class) instanceof ConcreteClass3);
+ assertTrue(new Wiring().singleton(ConcreteClass3.class).concreteClass instanceof ConcreteClass);
+ assertTrue(new Wiring().singleton(ConcreteClass3.class).concreteClass2 instanceof ConcreteClass2);
+
+ assertTrue(new Wiring(PropertySources.fromImmediate(s -> s.put("named", "12")))
+ .singleton(ConcreteClassWithNamedParameters.class).concreteClass instanceof ConcreteClass);
+ assertEquals(12, new Wiring(PropertySources.fromImmediate(s -> s.put("named", "12")))
+ .singleton(ConcreteClassWithNamedParameters.class).named);
+ }
+
+ @Test
+ public void failures() {
+ try {
+ new Wiring().singleton(FailureNoPublicConstructors.class);
+ fail("Should have failed to construct class with no public constructors!");
+ } catch (WiringException e) {
+ logger.debug("\n" + e.toString());
+ }
+
+ try {
+ new Wiring().singleton(FailureDependencyOnClassWithNoPublicConstructors.class);
+ fail("Should have failed to construct class which depends on another class with no public constructors!");
+ } catch (WiringException e) {
+ logger.debug("\n" + e.toString());
+ }
+
+ try {
+ new Wiring().singleton(FailureDependenciesOnClassesWithNoPublicConstructors.class);
+ fail("Should have failed to construct class which depends on other classes with no public constructors!");
+ } catch (WiringException e) {
+ logger.debug("\n" + e.toString());
+ }
+
+ try {
+ new Wiring().singleton(FailureUnsatisfiedDependencies.class);
+ fail("Should have failed to construct class which has unsatisfied dependencies!");
+ } catch (WiringException e) {
+ logger.debug("\n" + e.toString());
+ }
+
+ try {
+ new Wiring().singleton(FailureDependencyOnClassWithUnsatisfiedDependencies.class);
+ fail("Should have failed to construct class which depends on another class with unsatisfied dependencies!");
+ } catch (WiringException e) {
+ logger.debug("\n" + e.toString());
+ }
+
+ try {
+ new Wiring().singleton(FailureUnsatisfiedNamedParameters.class);
+ fail("Should have failed to construct class which has unsatisfied named parameters!");
+ } catch (WiringException e) {
+ logger.debug("\n" + e.toString());
+ }
+
+ try {
+ new Wiring().singleton(FailureDependencyOnClassWithUnsatisfiedNamedParameters.class);
+ fail("Should have failed to construct class which depends on another class with unsatisfied named parameters!");
+ } catch (WiringException e) {
+ logger.debug("\n" + e.toString());
+ }
+
+ try {
+ new Wiring(PropertySources.fromImmediate(s -> s.put("named", "abcd")))
+ .singleton(FailureInvalidNamedParameters.class);
+ fail("Should have failed to construct class which has an invalid named parameter value!");
+ } catch (WiringException e) {
+ logger.debug("\n" + e.toString());
+ }
+
+ try {
+ new Wiring().singleton(FailureThrowingConstructor.class);
+ fail("Should have failed to construct class with a throwing constructor!");
+ } catch (WiringException e) {
+ logger.debug("\n" + e.toString());
+ }
+ }
+
+ private static class ConcreteClass {
+ public ConcreteClass() {
+ }
+ }
+
+ private static class ConcreteClass2 {
+ private ConcreteClass concreteClass;
+
+ public ConcreteClass2(ConcreteClass concreteClass) {
+ this.concreteClass = concreteClass;
+ }
+ }
+
+ private static class ConcreteClass3 {
+ private ConcreteClass concreteClass;
+ private ConcreteClass2 concreteClass2;
+
+ public ConcreteClass3(ConcreteClass concreteClass, ConcreteClass2 concreteClass2) {
+ this.concreteClass = concreteClass;
+ this.concreteClass2 = concreteClass2;
+ }
+ }
+
+ private static class ConcreteClassWithNamedParameters {
+ private ConcreteClass concreteClass;
+ private int named;
+
+ public ConcreteClassWithNamedParameters(ConcreteClass concreteClass, @Named("named") int named) {
+ this.concreteClass = concreteClass;
+ this.named = named;
+ }
+ }
+
+ private static class FailureNoPublicConstructors {
+ private FailureNoPublicConstructors() {
+ }
+
+ protected FailureNoPublicConstructors(int __) {
+ }
+ }
+
+ private static class FailureDependencyOnClassWithNoPublicConstructors {
+ public FailureDependencyOnClassWithNoPublicConstructors(FailureNoPublicConstructors __) {
+ }
+ }
+
+ private static class FailureDependenciesOnClassesWithNoPublicConstructors {
+ public FailureDependenciesOnClassesWithNoPublicConstructors(FailureNoPublicConstructors __) {
+ }
+
+ public FailureDependenciesOnClassesWithNoPublicConstructors(FailureDependencyOnClassWithNoPublicConstructors __) {
+ }
+ }
+
+ private static class FailureUnsatisfiedDependencies {
+ public FailureUnsatisfiedDependencies(List> __) {
+ }
+
+ public FailureUnsatisfiedDependencies(String __, boolean ___) {
+
+ }
+ }
+
+ private static class FailureDependencyOnClassWithUnsatisfiedDependencies {
+ public FailureDependencyOnClassWithUnsatisfiedDependencies(FailureUnsatisfiedDependencies __) {
+ }
+ }
+
+ private static class FailureUnsatisfiedNamedParameters {
+ public FailureUnsatisfiedNamedParameters(@Named("named1") String __) {
+
+ }
+
+ public FailureUnsatisfiedNamedParameters(@Named("named2") boolean __) {
+
+ }
+ }
+
+ private static class FailureDependencyOnClassWithUnsatisfiedNamedParameters {
+ public FailureDependencyOnClassWithUnsatisfiedNamedParameters(FailureUnsatisfiedNamedParameters __) {
+ }
+ }
+
+ private static class FailureInvalidNamedParameters {
+ public FailureInvalidNamedParameters(@Named("named") int __) {
+
+ }
+ }
+
+ private static class FailureThrowingConstructor {
+ public FailureThrowingConstructor() {
+ throw new IllegalArgumentException("Error!");
+ }
+ }
+}
diff --git a/core/src/test/java/foundation/stack/datamill/configuration/impl/SimpleValueConverterTest.java b/core/src/test/java/foundation/stack/datamill/configuration/impl/SimpleValueConverterTest.java
new file mode 100644
index 0000000..1b283b5
--- /dev/null
+++ b/core/src/test/java/foundation/stack/datamill/configuration/impl/SimpleValueConverterTest.java
@@ -0,0 +1,53 @@
+package foundation.stack.datamill.configuration.impl;
+
+import foundation.stack.datamill.values.StringValue;
+import foundation.stack.datamill.values.Value;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Ravi Chodavarapu (rchodava@gmail.com)
+ */
+public class SimpleValueConverterTest {
+ @Test
+ public void convert() throws Exception {
+ assertEquals(true, SimpleValueConverter.convert(true, boolean.class));
+ assertEquals(12, (byte) SimpleValueConverter.convert(12, byte.class));
+ assertEquals('c', (char) SimpleValueConverter.convert('c', char.class));
+ assertEquals(12, (int) SimpleValueConverter.convert(12, int.class));
+ assertEquals(12, (short) SimpleValueConverter.convert(12, short.class));
+ assertEquals(12l, (long) SimpleValueConverter.convert(12, long.class));;
+ assertEquals(12f, SimpleValueConverter.convert(12f, float.class), 0e-32f);
+ assertEquals(12d, SimpleValueConverter.convert(12d, double.class), 0e-32f);
+
+ assertEquals(true, SimpleValueConverter.convert(true, Boolean.class));
+ assertEquals(12, (byte) SimpleValueConverter.convert(12, Byte.class));
+ assertEquals('c', (char) SimpleValueConverter.convert('c', Character.class));
+ assertEquals(12, (int) SimpleValueConverter.convert(12, Integer.class));
+ assertEquals(12, (short) SimpleValueConverter.convert(12, Short.class));
+ assertEquals(12l, (long) SimpleValueConverter.convert(12, Long.class));;
+ assertEquals(12f, SimpleValueConverter.convert(12f, Float.class), 0e-32f);
+ assertEquals(12d, SimpleValueConverter.convert(12d, Double.class), 0e-32f);
+
+ assertEquals(true, SimpleValueConverter.convert(new StringValue("true"), boolean.class));
+ assertEquals(12, (byte) SimpleValueConverter.convert(new StringValue("12"), byte.class));
+ assertEquals('c', (char) SimpleValueConverter.convert(new StringValue("c"), char.class));
+ assertEquals(12, (int) SimpleValueConverter.convert(new StringValue("12"), int.class));
+ assertEquals(12, (short) SimpleValueConverter.convert(new StringValue("12"), short.class));
+ assertEquals(12l, (long) SimpleValueConverter.convert(new StringValue("12"), long.class));;
+ assertEquals(12f, SimpleValueConverter.convert(new StringValue("12"), float.class), 0e-32f);
+ assertEquals(12d, SimpleValueConverter.convert(new StringValue("12"), double.class), 0e-32f);
+ assertEquals("Test", SimpleValueConverter.convert(new StringValue("Test"), String.class));
+ assertEquals("Test", SimpleValueConverter.convert("Test", Value.class).asString());
+
+ assertEquals(true, SimpleValueConverter.convert(new StringValue("true"), Boolean.class));
+ assertEquals(12, (byte) SimpleValueConverter.convert(new StringValue("12"), Byte.class));
+ assertEquals('c', (char) SimpleValueConverter.convert(new StringValue("c"), Character.class));
+ assertEquals(12, (int) SimpleValueConverter.convert(new StringValue("12"), Integer.class));
+ assertEquals(12, (short) SimpleValueConverter.convert(new StringValue("12"), Short.class));
+ assertEquals(12l, (long) SimpleValueConverter.convert(new StringValue("12"), Long.class));;
+ assertEquals(12f, SimpleValueConverter.convert(new StringValue("12"), Float.class), 0e-32f);
+ assertEquals(12d, SimpleValueConverter.convert(new StringValue("12"), Double.class), 0e-32f);
+ }
+}
diff --git a/cucumber/src/main/java/foundation/stack/datamill/cucumber/PlaceholderResolver.java b/cucumber/src/main/java/foundation/stack/datamill/cucumber/PlaceholderResolver.java
index 962651f..011e6e7 100644
--- a/cucumber/src/main/java/foundation/stack/datamill/cucumber/PlaceholderResolver.java
+++ b/cucumber/src/main/java/foundation/stack/datamill/cucumber/PlaceholderResolver.java
@@ -1,7 +1,7 @@
package foundation.stack.datamill.cucumber;
import com.jayway.jsonpath.JsonPath;
-import foundation.stack.datamill.configuration.Properties;
+import foundation.stack.datamill.configuration.PropertySources;
import foundation.stack.datamill.values.Value;
import foundation.stack.datamill.security.impl.BCrypt;
import org.slf4j.Logger;
@@ -129,7 +129,7 @@ private String resolveDateFormatterPlaceholder(String key) {
private String resolveSystemPlaceholder(String key) {
if (key.startsWith(SYSTEM_PLACEHOLDER_PREFIX)) {
key = key.substring(SYSTEM_PLACEHOLDER_PREFIX.length());
- return Properties.fromSystem().get(key).orElse(null);
+ return PropertySources.fromSystem().get(key).orElse(null);
}
return null;
diff --git a/lambda-api/src/main/java/foundation/stack/lambda/ApiHandler.java b/lambda-api/src/main/java/foundation/stack/lambda/ApiHandler.java
index bdc9f48..64360a4 100644
--- a/lambda-api/src/main/java/foundation/stack/lambda/ApiHandler.java
+++ b/lambda-api/src/main/java/foundation/stack/lambda/ApiHandler.java
@@ -1,6 +1,7 @@
package foundation.stack.lambda;
import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import com.google.common.base.Charsets;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
@@ -28,7 +29,7 @@
/**
* @author Ravi Chodavarapu (rchodava@gmail.com)
*/
-public abstract class ApiHandler {
+public abstract class ApiHandler implements RequestStreamHandler {
private static final String BODY_PROPERTY = "body";
private static final String LOG_LEVEL_PROPERTY = "logLevel";
private static final String HEADERS_PROPERTY = "headers";
@@ -188,7 +189,8 @@ private JSONObject handle(JSONObject requestJson) {
}
}
- public final void handle(InputStream requestStream, OutputStream responseStream, Context __) {
+ @Override
+ public final void handleRequest(InputStream requestStream, OutputStream responseStream, Context __) {
setLogLevel();
ServerRequest request = null;
diff --git a/lambda-api/src/test/java/foundation/stack/lambda/ApiHandlerTest.java b/lambda-api/src/test/java/foundation/stack/lambda/ApiHandlerTest.java
index e7d19f6..d549ad9 100644
--- a/lambda-api/src/test/java/foundation/stack/lambda/ApiHandlerTest.java
+++ b/lambda-api/src/test/java/foundation/stack/lambda/ApiHandlerTest.java
@@ -70,7 +70,7 @@ public void handle() {
// Test valid request
ByteArrayOutputStream output = new ByteArrayOutputStream();
TestApiHandler handler = new TestApiHandler();
- handler.handle(new ByteArrayInputStream(("{" +
+ handler.handleRequest(new ByteArrayInputStream(("{" +
"\"path\": \"/test\", " +
"\"httpMethod\": \"GET\", " +
"\"headers\": {" +
@@ -92,7 +92,7 @@ public void handle() {
// Request with no HTTP method
output = new ByteArrayOutputStream();
- handler.handle(new ByteArrayInputStream(("{ \"path\": \"/test\" }").getBytes(Charsets.UTF_8)), output, null);
+ handler.handleRequest(new ByteArrayInputStream(("{ \"path\": \"/test\" }").getBytes(Charsets.UTF_8)), output, null);
response = new JSONObject(new String(output.toByteArray(), Charsets.UTF_8));
assertEquals(400, response.optInt("statusCode"));
@@ -100,7 +100,7 @@ public void handle() {
// Test a route that returns a null response
output = new ByteArrayOutputStream();
- handler.handle(new ByteArrayInputStream(("{" +
+ handler.handleRequest(new ByteArrayInputStream(("{" +
"\"path\": \"/null\", " +
"\"httpMethod\": \"GET\"" +
"}")
@@ -112,7 +112,7 @@ public void handle() {
// Test a request with an uknown path
output = new ByteArrayOutputStream();
- handler.handle(new ByteArrayInputStream(("{" +
+ handler.handleRequest(new ByteArrayInputStream(("{" +
"\"path\": \"/other\", " +
"\"httpMethod\": \"GET\"" +
"}")
@@ -124,7 +124,7 @@ public void handle() {
// Test a route that results in an error being emitted, that isn't handled
output = new ByteArrayOutputStream();
- handler.handle(new ByteArrayInputStream(("{" +
+ handler.handleRequest(new ByteArrayInputStream(("{" +
"\"path\": \"/error\", " +
"\"httpMethod\": \"GET\"" +
"}")
@@ -136,7 +136,7 @@ public void handle() {
// Test a route that results in an error being thrown
output = new ByteArrayOutputStream();
- handler.handle(new ByteArrayInputStream(("{" +
+ handler.handleRequest(new ByteArrayInputStream(("{" +
"\"path\": \"/unhandled\", " +
"\"httpMethod\": \"GET\"" +
"}")