diff --git a/Injector/.gitignore b/Injector/.gitignore new file mode 100644 index 0000000..345e61a --- /dev/null +++ b/Injector/.gitignore @@ -0,0 +1,49 @@ +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties diff --git a/Injector/.idea/compiler.xml b/Injector/.idea/compiler.xml new file mode 100644 index 0000000..bec0f06 --- /dev/null +++ b/Injector/.idea/compiler.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Injector/.idea/misc.xml b/Injector/.idea/misc.xml new file mode 100644 index 0000000..e8942bd --- /dev/null +++ b/Injector/.idea/misc.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/Injector/.idea/modules.xml b/Injector/.idea/modules.xml new file mode 100644 index 0000000..f21519f --- /dev/null +++ b/Injector/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Injector/Injector.iml b/Injector/Injector.iml new file mode 100644 index 0000000..dc9a8a2 --- /dev/null +++ b/Injector/Injector.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Injector/pom.xml b/Injector/pom.xml new file mode 100644 index 0000000..6ca3d01 --- /dev/null +++ b/Injector/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + ru.spbau.mit.kazakov.Injector + Injector + 1.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + + junit + junit + 4.12 + + + org.jetbrains + annotations + 13.0 + + + + \ No newline at end of file diff --git a/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/AmbiguousImplementationException.java b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/AmbiguousImplementationException.java new file mode 100644 index 0000000..eaf04b7 --- /dev/null +++ b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/AmbiguousImplementationException.java @@ -0,0 +1,7 @@ +package ru.spbau.mit.kazakov.Injector; + +/** + * Thrown when found multiple implementations for a dependency. + */ +public class AmbiguousImplementationException extends Exception { +} diff --git a/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/ImplementationNotFoundException.java b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/ImplementationNotFoundException.java new file mode 100644 index 0000000..5b4afea --- /dev/null +++ b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/ImplementationNotFoundException.java @@ -0,0 +1,7 @@ +package ru.spbau.mit.kazakov.Injector; + +/** + * Thrown if found no implementation for a dependency. + */ +public class ImplementationNotFoundException extends Exception { +} diff --git a/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/InjectionCycleException.java b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/InjectionCycleException.java new file mode 100644 index 0000000..e61306c --- /dev/null +++ b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/InjectionCycleException.java @@ -0,0 +1,7 @@ +package ru.spbau.mit.kazakov.Injector; + +/** + * Thrown if found a circular dependency. + */ +public class InjectionCycleException extends Exception { +} diff --git a/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/Injector.java b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/Injector.java new file mode 100644 index 0000000..ab57151 --- /dev/null +++ b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/Injector.java @@ -0,0 +1,103 @@ +package ru.spbau.mit.kazakov.Injector; + +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import java.util.*; + + +/** + * A class for creating an object of specified class using given classes. + */ +public class Injector { + /** + * Creates an object of specified class using classes from specified container. + * + * @param rootClassName specified class + * @param implementations for creating an object + * @return created object + * @throws AmbiguousImplementationException when found multiple implementations for a dependency + * @throws ImplementationNotFoundException if found no implementation for a dependency + * @throws InjectionCycleException if found a circular dependency + * @throws ClassNotFoundException if found not existing class + */ + @NotNull + public static Object initialize(@NotNull String rootClassName, @NotNull List implementations) throws Exception { + return create(rootClassName, implementations, new TreeSet<>(), new HashMap<>()); + } + + /** + * Creates an object of specified class using classes from specified container. + * + * @param className specified class + * @param implementations for creating an object + * @param demanded already required dependencies + * @param created already created objects + * @return created object + * @throws AmbiguousImplementationException when found multiple implementations for a dependency + * @throws ImplementationNotFoundException if found no implementation for a dependency + * @throws InjectionCycleException if found a circular dependency + * @throws ClassNotFoundException if found not existing class + */ + @NotNull + private static Object create(@NotNull String className, @NotNull List implementations, + @NotNull Set demanded, + @NotNull Map created) throws Exception { + if (demanded.contains(className)) { + if (created.containsKey(className)) { + return created.get(className); + } else { + throw new InjectionCycleException(); + } + } + demanded.add(className); + + Class toCreate = Class.forName(className); + if (Modifier.isAbstract(toCreate.getModifiers()) || toCreate.isInterface()) { + String foundImplementation = null; + for (String implementation : implementations) { + Class implementationClass = Class.forName(implementation); + if (toCreate.isAssignableFrom(implementationClass) && !toCreate.equals(implementationClass)) { + if (foundImplementation != null) { + throw new AmbiguousImplementationException(); + } else { + foundImplementation = implementation; + } + } + } + if (foundImplementation == null) { + throw new ImplementationNotFoundException(); + } + Object implementationObject = create(foundImplementation, implementations, demanded, created); + created.put(className, implementationObject); + return implementationObject; + } + + Constructor constructor = toCreate.getConstructors()[0]; + Class[] parameters = constructor.getParameterTypes(); + ArrayList createdParameters = new ArrayList<>(); + for (Class parameter : parameters) { + String parameterName = parameter.getCanonicalName(); + Class paramClass = Class.forName(parameterName); + if (!paramClass.isInterface() && !Modifier.isAbstract(paramClass.getModifiers())) { + boolean found = false; + for (String implementationName : implementations) { + if (parameterName.equals(implementationName)) { + found = true; + break; + } + } + if (!found) { + throw new ImplementationNotFoundException(); + } + } + createdParameters.add(create(parameterName, implementations, + demanded, created)); + } + Object classInstance = constructor.newInstance(createdParameters.toArray()); + created.put(className, classInstance); + return classInstance; + } +} + diff --git a/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithCycleDependency1.java b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithCycleDependency1.java new file mode 100644 index 0000000..82561cf --- /dev/null +++ b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithCycleDependency1.java @@ -0,0 +1,14 @@ +package ru.spbau.mit.kazakov.Injector.testClasses; + +import static org.junit.Assert.*; + +public class ClassWithCycleDependency1 { + public final ClassWithCycleDependency2 dependency; + static int numberOfObjects = 0; + + public ClassWithCycleDependency1(ClassWithCycleDependency2 dependency) { + this.dependency = dependency; + numberOfObjects++; + assertTrue(numberOfObjects <= 1); + } +} diff --git a/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithCycleDependency2.java b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithCycleDependency2.java new file mode 100644 index 0000000..544155f --- /dev/null +++ b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithCycleDependency2.java @@ -0,0 +1,14 @@ +package ru.spbau.mit.kazakov.Injector.testClasses; + +import static org.junit.Assert.assertTrue; + +public class ClassWithCycleDependency2 { + public final ClassWithCycleDependency3 dependency; + static int numberOfObjects = 0; + + public ClassWithCycleDependency2(ClassWithCycleDependency3 dependency) { + this.dependency = dependency; + numberOfObjects++; + assertTrue(numberOfObjects <= 1); + } +} diff --git a/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithCycleDependency3.java b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithCycleDependency3.java new file mode 100644 index 0000000..2134a50 --- /dev/null +++ b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithCycleDependency3.java @@ -0,0 +1,14 @@ +package ru.spbau.mit.kazakov.Injector.testClasses; + +import static org.junit.Assert.assertTrue; + +public class ClassWithCycleDependency3 { + public final ClassWithCycleDependency1 dependency; + static int numberOfObjects = 0; + + public ClassWithCycleDependency3(ClassWithCycleDependency1 dependency) { + this.dependency = dependency; + numberOfObjects++; + assertTrue(numberOfObjects <= 1); + } +} diff --git a/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithOneClassDependency.java b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithOneClassDependency.java new file mode 100644 index 0000000..b6626c1 --- /dev/null +++ b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithOneClassDependency.java @@ -0,0 +1,14 @@ +package ru.spbau.mit.kazakov.Injector.testClasses; + +import static org.junit.Assert.assertTrue; + +public class ClassWithOneClassDependency { + public final ClassWithoutDependencies dependency; + static int numberOfObjects = 0; + + public ClassWithOneClassDependency(ClassWithoutDependencies dependency) { + this.dependency = dependency; + numberOfObjects++; + assertTrue(numberOfObjects <= 1); + } +} diff --git a/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithOneInterfaceDependency.java b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithOneInterfaceDependency.java new file mode 100644 index 0000000..f20af20 --- /dev/null +++ b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithOneInterfaceDependency.java @@ -0,0 +1,14 @@ +package ru.spbau.mit.kazakov.Injector.testClasses; + +import static org.junit.Assert.assertTrue; + +public class ClassWithOneInterfaceDependency { + public final Interface dependency; + static int numberOfObjects = 0; + + public ClassWithOneInterfaceDependency(Interface dependency) { + this.dependency = dependency; + numberOfObjects++; + assertTrue(numberOfObjects <= 1); + } +} diff --git a/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithoutDependencies.java b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithoutDependencies.java new file mode 100644 index 0000000..0b8b272 --- /dev/null +++ b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/ClassWithoutDependencies.java @@ -0,0 +1,5 @@ +package ru.spbau.mit.kazakov.Injector.testClasses; + +public class ClassWithoutDependencies { + +} diff --git a/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/Interface.java b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/Interface.java new file mode 100644 index 0000000..fb63868 --- /dev/null +++ b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/Interface.java @@ -0,0 +1,4 @@ +package ru.spbau.mit.kazakov.Injector.testClasses; + +public interface Interface { +} diff --git a/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/InterfaceImpl.java b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/InterfaceImpl.java new file mode 100644 index 0000000..2871613 --- /dev/null +++ b/Injector/src/main/java/ru/spbau/mit/kazakov/Injector/testClasses/InterfaceImpl.java @@ -0,0 +1,4 @@ +package ru.spbau.mit.kazakov.Injector.testClasses; + +public class InterfaceImpl implements Interface { +} diff --git a/Injector/src/test/java/ru/spbau/mit/kazakov/Injector/InjectorTest.java b/Injector/src/test/java/ru/spbau/mit/kazakov/Injector/InjectorTest.java new file mode 100644 index 0000000..8832e2c --- /dev/null +++ b/Injector/src/test/java/ru/spbau/mit/kazakov/Injector/InjectorTest.java @@ -0,0 +1,67 @@ +package ru.spbau.mit.kazakov.Injector; + +import org.junit.Test; +import ru.spbau.mit.kazakov.Injector.testClasses.ClassWithOneClassDependency; +import ru.spbau.mit.kazakov.Injector.testClasses.ClassWithOneInterfaceDependency; +import ru.spbau.mit.kazakov.Injector.testClasses.ClassWithoutDependencies; +import ru.spbau.mit.kazakov.Injector.testClasses.InterfaceImpl; + +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.Assert.*; + +public class InjectorTest { + private static final String TEST_CLASS_PREFIX = "ru.spbau.mit.kazakov.Injector.testClasses."; + + @Test + public void injectorShouldInitializeClassWithoutDependencies() + throws Exception { + Object object = Injector.initialize(TEST_CLASS_PREFIX + "ClassWithoutDependencies", + Collections.emptyList()); + assertTrue(object instanceof ClassWithoutDependencies); + } + + @Test + public void injectorShouldInitializeClassWithOneClassDependency() + throws Exception { + Object object = Injector.initialize( + TEST_CLASS_PREFIX + "ClassWithOneClassDependency", + Collections.singletonList(TEST_CLASS_PREFIX + "ClassWithoutDependencies") + ); + assertTrue(object instanceof ClassWithOneClassDependency); + ClassWithOneClassDependency instance = (ClassWithOneClassDependency) object; + assertTrue(instance.dependency != null); + } + + @Test + public void injectorShouldInitializeClassWithOneInterfaceDependency() + throws Exception { + Object object = Injector.initialize( + TEST_CLASS_PREFIX + "ClassWithOneInterfaceDependency", + Collections.singletonList(TEST_CLASS_PREFIX + "InterfaceImpl") + ); + assertTrue(object instanceof ClassWithOneInterfaceDependency); + ClassWithOneInterfaceDependency instance = (ClassWithOneInterfaceDependency) object; + assertTrue(instance.dependency instanceof InterfaceImpl); + } + + @Test(expected = AmbiguousImplementationException.class) + public void injectorThrowsAmbiguousImplementationsException() throws Exception { + Object object = Injector.initialize(TEST_CLASS_PREFIX + "ClassWithOneInterfaceDependency", + Arrays.asList(TEST_CLASS_PREFIX + "InterfaceImpl", TEST_CLASS_PREFIX + "InterfaceImpl")); + } + + @Test(expected = ImplementationNotFoundException.class) + public void injectorThrowsImplementationNotFoundException() throws Exception { + Object object = Injector.initialize(TEST_CLASS_PREFIX + "ClassWithOneInterfaceDependency", + Collections.singletonList(TEST_CLASS_PREFIX + "ClassWithoutDependencies")); + } + + @Test(expected = InjectionCycleException.class) + public void injectorThrowsInjectionCycleException() throws Exception { + Object object = Injector.initialize(TEST_CLASS_PREFIX + "ClassWithCycleDependency1", + Arrays.asList(TEST_CLASS_PREFIX + "ClassWithCycleDependency1", + TEST_CLASS_PREFIX + "ClassWithCycleDependency2", TEST_CLASS_PREFIX + "ClassWithCycleDependency3")); + } +} \ No newline at end of file