Skip to content

Commit

Permalink
Break recursion when a factory calls Wiring again to construct an ins…
Browse files Browse the repository at this point in the history
…tance

Super of isn't necessarily going to return an instance of a class derived from the class it is registered with - for example (registering super of Class1, but a call to the factory can request construction of an interface of Class1, for which the factory returns Class2, which implements the interface but is not a sub-class of Class1)
Add additional unit tests
Fixed bug so that the the singleton scope respects qualifiers
Added lots of documentation
  • Loading branch information
rchodava committed Apr 16, 2017
1 parent ef38c25 commit b4af376
Show file tree
Hide file tree
Showing 16 changed files with 313 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
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<Object, Object> {
/**
* 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.
*/
Expand All @@ -18,7 +26,7 @@ public interface FactoryChain extends QualifyingFactory<Object, Object> {
* 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.
*/
<T, R extends T> FactoryChain thenForSuperOf(Class<T> type, Factory<T, R> factory);
<T> FactoryChain thenForSuperOf(Class<T> type, Factory<T, ?> factory);

/**
* Add a factory for a specific type. Note that this factory is only invoked if the type being constructed matches
Expand All @@ -36,7 +44,7 @@ public interface FactoryChain extends QualifyingFactory<Object, Object> {
/**
* @see #thenForSuperOf(Class, Factory)
*/
<T, R extends T> FactoryChain thenForSuperOf(Class<T> type, TypeLessFactory<R> factory);
<T> FactoryChain thenForSuperOf(Class<T> type, TypeLessFactory<?> factory);

/**
* @see #thenForType(Class, Factory)
Expand All @@ -55,7 +63,7 @@ public interface FactoryChain extends QualifyingFactory<Object, Object> {
*
* @see #thenForSuperOf(Class, Factory)
*/
<T, R extends T> FactoryChain thenForSuperOf(Class<T> type, QualifyingFactory<T, R> factory);
<T> FactoryChain thenForSuperOf(Class<T> type, QualifyingFactory<T, ?> factory);

/**
* Add a qualifying factory for a specific type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,51 @@
import foundation.stack.datamill.configuration.impl.FactoryChainImpl;

/**
* <p>
* Use this class to create{@link FactoryChain}s. For example, consider a chain composed and used as follows:
* <p/>
* <pre>
* chain = FactoryChains.forType(DatabaseClient.class, w -> createDatabaseClient())
* .thenForSuperOf(UserRepository.class, w -> new UserRepository(...))
* .thenForAny(Core.FACTORY)
* .thenForAnyConcreteClass();
* </pre>
* <p>
* 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.
* <p/>
* <p>
* 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.
* </p>
* <p>
* Note that it is easy to construct factory chains that result in infinite recursion. For example, consider the
* following:
* </p>
* <pre>
* chain = FactoryChains.forSuperOf(EmailService.class, w -> w.singleton(EmailService.class))
* .thenForAnyConcreteClass();
*
* new Wiring(chain).singleton(EmailService.class);
* </pre>
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
*
* @author Ravi Chodavarapu (rchodava@gmail.com)
*/
public class FactoryChains {
Expand All @@ -24,7 +69,7 @@ public static <T, R extends T> FactoryChain forAny(Factory<T, R> factory) {
/**
* @see FactoryChain#thenForSuperOf(Class, Factory)
*/
public static <T, R extends T> FactoryChain forSuperOf(Class<T> type, Factory<T, R> factory) {
public static FactoryChain forSuperOf(Class<?> type, Factory<?, ?> factory) {
return new FactoryChainImpl(FactoryChainImpl.isSuperOf(type), factory);
}

Expand All @@ -45,7 +90,7 @@ public static <T, R extends T> FactoryChain forAny(QualifyingFactory<T, R> facto
/**
* @see FactoryChain#thenForSuperOf(Class, QualifyingFactory)
*/
public static <T, R extends T> FactoryChain forSuperOf(Class<T> type, QualifyingFactory<T, R> factory) {
public static FactoryChain forSuperOf(Class<?> type, QualifyingFactory<?, ?> factory) {
return new FactoryChainImpl(FactoryChainImpl.isSuperOf(type), factory);
}

Expand All @@ -66,7 +111,7 @@ public static <R> FactoryChain forAny(TypeLessFactory<R> factory) {
/**
* @see FactoryChain#thenForSuperOf(Class, TypeLessFactory)
*/
public static <T, R extends T> FactoryChain forSuperOf(Class<T> type, TypeLessFactory<R> factory) {
public static FactoryChain forSuperOf(Class<?> type, TypeLessFactory<?> factory) {
return new FactoryChainImpl(FactoryChainImpl.isSuperOf(type), factory);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,10 @@
*/
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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@
* transformers.
* </p>
* <p>
* Note that 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:
* 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:
* </p>
* <pre>
* chain1 = COMMON.orEnvironment(PropertyNameTransformers.LOWER_CAMEL_TO_UPPER_UNDERSCORE);
* chain2 = COMMON.orFile("defaults.properties");
* chain1 = common.orEnvironment(PropertyNameTransformers.LOWER_CAMEL_TO_UPPER_UNDERSCORE);
* chain2 = common.orFile("defaults.properties");
* </pre>
* <p>
* Each of these chains is a separate chain that has some common section.
Expand Down Expand Up @@ -127,7 +127,9 @@ public static PropertySourceChain fromImmediate(Action1<ImmediatePropertySource>
/**
* @see PropertySourceChain#orImmediate(Action1, Func1)
*/
public static PropertySourceChain fromImmediate(Action1<ImmediatePropertySource> initializer, Func1<String, String> transformer) {
public static PropertySourceChain fromImmediate(
Action1<ImmediatePropertySource> initializer,
Func1<String, String> transformer) {
ImmediatePropertySourceImpl immediateSource = new ImmediatePropertySourceImpl();
initializer.call(immediateSource);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

/**
* 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
* 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)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,57 @@
package foundation.stack.datamill.configuration;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.*;

/**
* @author Ravi Chodavarapu (rchodava@gmail.com)
*/
public class SingletonScope implements Scope {
private Map<Class<?>, Object> constructed = new HashMap<>();
private Map<QualifiedType, Object> constructed = new HashMap<>();

@Override
public <T, R extends T> R resolve(
Wiring wiring,
QualifyingFactory<T, R> factory,
Class<? extends T> type,
Collection<String> qualifiers) {
Object instance = constructed.get(type);
Object instance = constructed.get(new QualifiedType(type, qualifiers));
if (instance == null) {
instance = factory.call(wiring, type, qualifiers);
if (instance != null) {
constructed.put(type, instance);
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<String> 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;
}
}
}

0 comments on commit b4af376

Please sign in to comment.