diff --git a/pom.xml b/pom.xml index 88fa5b8..1ad2ea4 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ org.slf4j slf4j-simple 1.7.21 + provided junit @@ -46,11 +47,6 @@ 1.3 test - - com.google.collections - google-collections - 1.0 - diff --git a/src/main/java/de/joshavg/simpledic/Instantiator.java b/src/main/java/de/joshavg/simpledic/Instantiator.java index 610d285..e17ccce 100644 --- a/src/main/java/de/joshavg/simpledic/Instantiator.java +++ b/src/main/java/de/joshavg/simpledic/Instantiator.java @@ -6,9 +6,11 @@ import java.lang.reflect.InvocationTargetException; class Instantiator { private final Class clz; + private final SdiContainerInterface container; - Instantiator(Class clz) { + Instantiator(Class clz, SdiContainerInterface container) { this.clz = clz; + this.container = container; } T createInstance() @@ -22,10 +24,14 @@ class Instantiator { for (int i = 0; i < parameterTypes.length; ++i) { @SuppressWarnings("unchecked") Class paramClz = (Class) parameterTypes[i]; - parameters[i] = new Instantiator<>(paramClz).createInstance(); + parameters[i] = container.getInstance(paramClz); } return constructor.newInstance(parameters); } + T createSingleton() { + return null; + } + } diff --git a/src/main/java/de/joshavg/simpledic/IntegrityCheck.java b/src/main/java/de/joshavg/simpledic/IntegrityCheck.java index ce0d5c2..6b0fb17 100644 --- a/src/main/java/de/joshavg/simpledic/IntegrityCheck.java +++ b/src/main/java/de/joshavg/simpledic/IntegrityCheck.java @@ -1,13 +1,14 @@ package de.joshavg.simpledic; -import com.google.common.annotations.VisibleForTesting; import de.joshavg.simpledic.exception.ClassNotRegistered; import de.joshavg.simpledic.exception.integrity.DependencyCycleDetected; import de.joshavg.simpledic.exception.integrity.DependencyNotSatisfied; import de.joshavg.simpledic.exception.integrity.DuplicatedServiceClassesFound; import de.joshavg.simpledic.exception.integrity.MoreThanOneConstructor; +import de.joshavg.simpledic.exception.integrity.NoVisibleConstructor; import de.joshavg.simpledic.exception.integrity.SdicClassNotFound; import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -91,10 +92,19 @@ class IntegrityCheck { } private void collectConstructors(ArrayList l, Constructor[] arr) { + if(arr.length == 0) { + return; + } + Class clz = arr[0].getDeclaringClass(); if (arr.length > 1) { throw new MoreThanOneConstructor(clz); } + + if(!Modifier.isPublic(arr[0].getModifiers())) { + throw new NoVisibleConstructor(clz); + } + l.add(arr[0]); findDefinition(clz).setConstructor(arr[0]); } @@ -131,9 +141,8 @@ class IntegrityCheck { } } - @VisibleForTesting static boolean isServiceName(String name) { - return !"service.".equals(name) && name.startsWith("service."); + return name.matches("service\\.[^.]+"); } List getDefinitions() { diff --git a/src/main/java/de/joshavg/simpledic/SdiContainer.java b/src/main/java/de/joshavg/simpledic/SdiContainer.java index af38e9f..26eab58 100644 --- a/src/main/java/de/joshavg/simpledic/SdiContainer.java +++ b/src/main/java/de/joshavg/simpledic/SdiContainer.java @@ -6,22 +6,27 @@ import de.joshavg.simpledic.exception.SdicInstantiationException; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Properties; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class SdiContainer { +public class SdiContainer implements SdiContainerInterface { private static final Logger LOG = LoggerFactory.getLogger(SdiContainer.class); private final Properties props; private final List definitions; + private final Map, Object> singletons; private SdiContainer(Properties props, List definitions) { this.props = props; this.definitions = definitions; + this.singletons = new HashMap<>(); } public static SdiContainer load() { @@ -51,21 +56,40 @@ public class SdiContainer { return definitions.stream().map(ServiceDefinition::getClz).collect(Collectors.toList()); } - public T createInstance(Class clz) { - if (clz == null) { - throw new NullPointerException(); - } - + @Override + public T getInstance(Class clz) { LOG.trace("instance ordered: ", clz); if (!serviceClasses().contains(clz)) { throw new ClassNotRegistered(clz); } + if (isStoredAsSingleton(clz)) { + return clz.cast(singletons.get(clz)); + } + try { - return new Instantiator<>(clz).createInstance(); + T instance = new Instantiator<>(clz, this).createInstance(); + handleSingleton(clz, instance); + return instance; } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { throw new SdicInstantiationException(e); } } + private void handleSingleton(Class clz, T instance) { + String key = props.entrySet().stream() + .filter(e -> Objects.equals(clz.getName(), String.valueOf(e.getValue()))) + .map(e -> e.getKey().toString()) + .findFirst() + .orElse(""); + + if (props.containsKey(key + ".singleton") && "true".equals(props.get(key + ".singleton"))) { + singletons.put(clz, instance); + } + } + + private boolean isStoredAsSingleton(Class clz) { + return singletons.containsKey(clz); + } + } diff --git a/src/main/java/de/joshavg/simpledic/SdiContainerInterface.java b/src/main/java/de/joshavg/simpledic/SdiContainerInterface.java new file mode 100644 index 0000000..70cb0bc --- /dev/null +++ b/src/main/java/de/joshavg/simpledic/SdiContainerInterface.java @@ -0,0 +1,6 @@ +package de.joshavg.simpledic; + +public interface SdiContainerInterface { + + T getInstance(Class clz); +} diff --git a/src/main/java/de/joshavg/simpledic/exception/integrity/NoVisibleConstructor.java b/src/main/java/de/joshavg/simpledic/exception/integrity/NoVisibleConstructor.java new file mode 100644 index 0000000..18d02e1 --- /dev/null +++ b/src/main/java/de/joshavg/simpledic/exception/integrity/NoVisibleConstructor.java @@ -0,0 +1,8 @@ +package de.joshavg.simpledic.exception.integrity; + +public class NoVisibleConstructor extends RuntimeException { + + public NoVisibleConstructor(Class clz) { + super(String.format("%s has no visible constructor", clz)); + } +} diff --git a/src/test/java/de/joshavg/simpledic/ErrorTests.java b/src/test/java/de/joshavg/simpledic/ErrorTests.java index 9ee50ac..6fb52ac 100644 --- a/src/test/java/de/joshavg/simpledic/ErrorTests.java +++ b/src/test/java/de/joshavg/simpledic/ErrorTests.java @@ -2,10 +2,10 @@ package de.joshavg.simpledic; import de.joshavg.simpledic.exception.ClassNotRegistered; import de.joshavg.simpledic.exception.ContainerInitException; -import de.joshavg.simpledic.exception.SdicInstantiationException; import de.joshavg.simpledic.exception.integrity.DependencyCycleDetected; import de.joshavg.simpledic.exception.integrity.DependencyNotSatisfied; import de.joshavg.simpledic.exception.integrity.DuplicatedServiceClassesFound; +import de.joshavg.simpledic.exception.integrity.NoVisibleConstructor; import de.joshavg.simpledic.services.Depends1; import de.joshavg.simpledic.services.PrivateConstructor; import java.util.Map; @@ -20,27 +20,22 @@ public class ErrorTests { @Test(expected = DependencyCycleDetected.class) public void testCycleDetection() { - SdiContainer.load("cycle.properties").createInstance(Depends1.class); + SdiContainer.load("cycle.properties").getInstance(Depends1.class); } @Test(expected = ClassNotRegistered.class) public void requestUnknownClass() { - SdiContainer.load("almostsane.properties").createInstance(Map.class); + SdiContainer.load("sane.properties").getInstance(Map.class); } @Test(expected = DependencyNotSatisfied.class) public void requestServiceWithUnregisteredDependency() { - SdiContainer.load("unknowndep.properties").createInstance(Depends1.class); + SdiContainer.load("unknowndep.properties").getInstance(Depends1.class); } - @Test(expected = SdicInstantiationException.class) + @Test(expected = NoVisibleConstructor.class) public void privateConstructor() { - SdiContainer.load("almostsane.properties").createInstance(PrivateConstructor.class); - } - - @Test(expected = NullPointerException.class) - public void requestNull() { - SdiContainer.load("almostsane.properties").createInstance(null); + SdiContainer.load("invisible.properties").getInstance(PrivateConstructor.class); } @Test(expected = ContainerInitException.class) diff --git a/src/test/java/de/joshavg/simpledic/IntegrityCheckTest.java b/src/test/java/de/joshavg/simpledic/IntegrityCheckTest.java index ade3d9e..61f257f 100644 --- a/src/test/java/de/joshavg/simpledic/IntegrityCheckTest.java +++ b/src/test/java/de/joshavg/simpledic/IntegrityCheckTest.java @@ -13,6 +13,7 @@ public class IntegrityCheckTest { assertThat(IntegrityCheck.isServiceName("service"), is(false)); assertThat(IntegrityCheck.isServiceName("service."), is(false)); assertThat(IntegrityCheck.isServiceName("service.a"), is(true)); + assertThat(IntegrityCheck.isServiceName("service.a.singleton"), is(false)); } } diff --git a/src/test/java/de/joshavg/simpledic/SimpleDependenciesTest.java b/src/test/java/de/joshavg/simpledic/SimpleDependenciesTest.java index 554e59e..5ea0872 100644 --- a/src/test/java/de/joshavg/simpledic/SimpleDependenciesTest.java +++ b/src/test/java/de/joshavg/simpledic/SimpleDependenciesTest.java @@ -5,7 +5,7 @@ import org.junit.Test; public class SimpleDependenciesTest { - private static final String FILENAME = "almostsane.properties"; + private static final String FILENAME = "sane.properties"; @Test public void testIntegrityCheck() { @@ -15,6 +15,6 @@ public class SimpleDependenciesTest { @Test public void createOneDependencyService() { SdiContainer container = SdiContainer.load(FILENAME); - container.createInstance(DependsOnNoDependencies.class); + container.getInstance(DependsOnNoDependencies.class); } } diff --git a/src/test/java/de/joshavg/simpledic/SingletonTest.java b/src/test/java/de/joshavg/simpledic/SingletonTest.java new file mode 100644 index 0000000..e743451 --- /dev/null +++ b/src/test/java/de/joshavg/simpledic/SingletonTest.java @@ -0,0 +1,18 @@ +package de.joshavg.simpledic; + +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.MatcherAssert.assertThat; + +import de.joshavg.simpledic.services.NoDependencies; +import org.junit.Test; + +public class SingletonTest { + + @Test + public void sameInstances() { + SdiContainer container = SdiContainer.load("singleton.properties"); + assertThat(container.getInstance(NoDependencies.class), + sameInstance(container.getInstance(NoDependencies.class))); + } + +} diff --git a/src/test/resources/invisible.properties b/src/test/resources/invisible.properties new file mode 100644 index 0000000..2d1d01d --- /dev/null +++ b/src/test/resources/invisible.properties @@ -0,0 +1 @@ +service.private: de.joshavg.simpledic.services.PrivateConstructor diff --git a/src/test/resources/almostsane.properties b/src/test/resources/sane.properties similarity index 65% rename from src/test/resources/almostsane.properties rename to src/test/resources/sane.properties index 3700df0..d04a390 100644 --- a/src/test/resources/almostsane.properties +++ b/src/test/resources/sane.properties @@ -1,3 +1,2 @@ service.one: de.joshavg.simpledic.services.DependsOnNoDependencies service.nodeps: de.joshavg.simpledic.services.NoDependencies -service.private: de.joshavg.simpledic.services.PrivateConstructor diff --git a/src/test/resources/singleton.properties b/src/test/resources/singleton.properties new file mode 100644 index 0000000..8a3b4b5 --- /dev/null +++ b/src/test/resources/singleton.properties @@ -0,0 +1,2 @@ +service.one: de.joshavg.simpledic.services.NoDependencies +service.one.singleton: true