Skip to content

Commit

Permalink
Merge pull request #2 from ref-humbold/resolver
Browse files Browse the repository at this point in the history
Resolver
  • Loading branch information
R. Kaleta authored Jul 21, 2020
2 parents 4d7dab2 + dd69518 commit a2c3f3b
Show file tree
Hide file tree
Showing 62 changed files with 2,270 additions and 952 deletions.
7 changes: 4 additions & 3 deletions build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<format property="date.version" pattern="yyMMdd" locale="en,GB" />
</tstamp>

<property name="major.version" value="2" />
<property name="minor.version" value="2" />
<property name="major.version" value="3" />
<property name="minor.version" value="0" />
<property name="spec.version" value="${major.version}.${minor.version}" />
<property name="jar.version" value="${major.version}.${minor.version}.${date.version}" />
<property name="vendor" value="Rafał Kaleta" />
Expand Down Expand Up @@ -86,7 +86,7 @@

<target name="test">
<mkdir dir="${junit.result.dir}" />
<junitlauncher printsummary="true">
<junitlauncher printsummary="true" failureproperty="junit.failure">
<classpath refid="project.classpath" />
<testclasses outputdir="${junit.result.dir}">
<fileset dir="${build.dir}" includes="**/*Test.class" />
Expand All @@ -98,6 +98,7 @@
<fileset dir="${junit.result.dir}" includes="TEST-*.xml" />
<report format="frames" todir="${junit.report.dir}" />
</junitreport>
<fail if="junit.failure" message="JUnit test(s) failed" />
</target>

<target name="docs">
Expand Down
7 changes: 6 additions & 1 deletion src/dicontainer/ConstructionPolicy.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,10 @@

public enum ConstructionPolicy
{
CONSTRUCT, SINGLETON
CONSTRUCT, SINGLETON;

public static ConstructionPolicy getDefault()
{
return CONSTRUCT;
}
}
28 changes: 0 additions & 28 deletions src/dicontainer/ConstructorComparator.java

This file was deleted.

221 changes: 16 additions & 205 deletions src/dicontainer/DIContainer.java
Original file line number Diff line number Diff line change
@@ -1,49 +1,41 @@
package dicontainer;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Stack;

import dicontainer.annotation.Dependency;
import dicontainer.exception.*;
import dicontainer.dictionary.DIDictionary;
import dicontainer.resolver.DIResolver;

public final class DIContainer
{
private final TypesContainer typesContainer = new TypesContainer();
private final DIDictionary diDictionary;
private final DIResolver diResolver;

public DIContainer()
{
diDictionary = new DIDictionary();
diResolver = new DIResolver(diDictionary);
}

/**
* Register concrete type class in the container.
* @param type type class
* @return {@code this} for method chaining
* @throws AbstractTypeException if type is an abstract class or an interface
*/
public <T> DIContainer registerType(Class<T> type)
{
return registerType(type, ConstructionPolicy.CONSTRUCT);
diDictionary.addType(type);
return this;
}

/**
* Register concrete type class in the container with singleton specification.
* @param type type class
* @param policy construction policy of instances
* @return {@code this} for method chaining
* @throws AbstractTypeException if type is an abstract class or an interface
*/
public <T> DIContainer registerType(Class<T> type, ConstructionPolicy policy)
{
if(TypesContainer.isAbstractType(type))
throw new AbstractTypeException(
String.format("Type %s is abstract, so it cannot be self registered.",
type.getSimpleName()));

return registerType(type, type, policy);
diDictionary.addType(type, policy);
return this;
}

/**
Expand All @@ -54,7 +46,8 @@ public <T> DIContainer registerType(Class<T> type, ConstructionPolicy policy)
*/
public <T> DIContainer registerType(Class<T> supertype, Class<? extends T> subtype)
{
return registerType(supertype, subtype, ConstructionPolicy.CONSTRUCT);
diDictionary.addType(supertype, subtype);
return this;
}

/**
Expand All @@ -67,11 +60,7 @@ public <T> DIContainer registerType(Class<T> supertype, Class<? extends T> subty
public <T> DIContainer registerType(Class<T> supertype, Class<? extends T> subtype,
ConstructionPolicy policy)
{
if(policy == ConstructionPolicy.SINGLETON)
typesContainer.addSingletonSubtype(supertype, subtype);
else
typesContainer.addSubtype(supertype, subtype);

diDictionary.addType(supertype, subtype, policy);
return this;
}

Expand All @@ -83,13 +72,7 @@ public <T> DIContainer registerType(Class<T> supertype, Class<? extends T> subty
*/
public <T> DIContainer registerInstance(Class<T> type, T instance)
{
if(instance == null)
throw new NullInstanceException(
String.format("Given instance to register for %s is null.",
type.getSimpleName()));

typesContainer.addInstance(type, instance);

diDictionary.addInstance(type, instance);
return this;
}

Expand All @@ -101,7 +84,7 @@ public <T> DIContainer registerInstance(Class<T> type, T instance)
*/
public <T> T resolve(Class<T> type)
{
return resolveType(type, new Stack<>());
return diResolver.construct(type);
}

/**
Expand All @@ -112,178 +95,6 @@ public <T> T resolve(Class<T> type)
*/
public <T> T buildUp(T instance)
{
buildUpObject(instance, new Stack<>());

return instance;
}

private boolean isSetter(Method method)
{
return method.getReturnType() == void.class && method.getName().startsWith("set")
&& method.getParameterCount() == 1;
}

private <T> T resolveType(Class<T> type, Stack<Class<?>> resolved)
{
T object = typesContainer.getInstance(type);

if(object == null)
object = resolveConstructor(type, resolved);

buildUpObject(object, resolved);

return object;
}

private <T> T resolveConstructor(Class<T> type, Stack<Class<?>> resolved)
{
resolved.push(type);

Class<? extends T> mappedClass = findRegisteredConcreteType(type);
Constructor<? extends T>[] constructors = getConstructors(mappedClass);

Arrays.sort(constructors, new ConstructorComparator());

if(constructors.length > 1)
if(constructors[1].isAnnotationPresent(Dependency.class))
throw new MultipleAnnotatedConstructorsException(
"Only one constructor can be annotated as dependency.");

T object = null;
DIException lastException = null;

for(Constructor<? extends T> ctor : constructors)
{
try
{
object = createInstance(ctor, resolved);
}
catch(DIException e)
{
lastException = e;
}

if(object != null || ctor.isAnnotationPresent(Dependency.class))
break;
}

resolved.pop();

if(object == null)
throw lastException != null ? lastException : new NoInstanceCreatedException(
String.format(
"No instance produced for %s, although all possibilities have been "
+ "checked.", type.getSimpleName()));

typesContainer.updateInstance(type, object);

return object;
}

private <T> void resolveSetter(T object, Method setter, Stack<Class<?>> resolved)
{
ArrayList<Object> paramObjects = new ArrayList<>();

for(Class<?> p : setter.getParameterTypes())
paramObjects.add(resolveType(p, resolved));

try
{
setter.invoke(object, paramObjects.toArray());
}
catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new DIException(e.getMessage(), e);
}
}

private <T> void buildUpObject(T obj, Stack<Class<?>> resolved)
{
ArrayList<Method> setters = new ArrayList<>();

for(Method m : obj.getClass().getMethods())
if(m.isAnnotationPresent(Dependency.class))
{
if(!isSetter(m))
throw new IncorrectDependencySetterException(
"Dependency method must have exactly one argument, void return type "
+ "and name starting with 'set'.");

setters.add(m);
}

for(Method setter : setters)
resolveSetter(obj, setter, resolved);
}

private <T> T createInstance(Constructor<? extends T> ctor, Stack<Class<?>> resolved)
{
ArrayList<Object> params = new ArrayList<>();

for(Class<?> type : ctor.getParameterTypes())
{
if(resolved.contains(type))
throw new CircularDependenciesException("Dependencies resolving detected a cycle.");

if(!typesContainer.containsType(type))
throw new MissingDependenciesException("No dependency found when resolving.");

params.add(resolveType(type, resolved));
}

try
{
return ctor.newInstance(params.toArray());
}
catch(InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new DIException(e.getMessage(), e);
}
}

private <T> Class<? extends T> findRegisteredConcreteType(Class<T> type)
{
Class<? extends T> subtype = type;

do
{
if(!typesContainer.containsSubtype(subtype))
{
if(TypesContainer.isAbstractType(subtype))
throw new MissingDependenciesException(
String.format("Abstract type %s has no registered concrete subclass.",
subtype.getSimpleName()));

typesContainer.addSubtype(subtype);
}

subtype = typesContainer.getSubtype(subtype);
} while(TypesContainer.isAbstractType(subtype));

return subtype;
}

@SuppressWarnings("unchecked")
private <T> Constructor<? extends T>[] getConstructors(Class<? extends T> type)
{
Constructor<?>[] constructors;

try
{
constructors = type.getConstructors();
}
catch(SecurityException e)
{
throw new NoSuitableConstructorException(
String.format("No dependency constructor found for class %s",
type.getSimpleName()), e);
}

if(constructors.length == 0)
throw new NoSuitableConstructorException(
String.format("No dependency constructor found for class %s",
type.getSimpleName()));

return (Constructor<? extends T>[])constructors;
return diResolver.inject(instance);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dicontainer.exception;
package dicontainer;

public class DIException
extends RuntimeException
Expand Down
Loading

0 comments on commit a2c3f3b

Please sign in to comment.