diff --git a/source/poodinis/autowire.d b/source/poodinis/autowire.d index 24f84fb..cd3d9c2 100644 --- a/source/poodinis/autowire.d +++ b/source/poodinis/autowire.d @@ -7,7 +7,7 @@ module poodinis.autowire; -public import poodinis.dependency; +public import poodinis.container; import std.typetuple; @@ -26,13 +26,13 @@ alias Autowired = Autowire; public void autowire(Type)(DependencyContainer container, Type instance) { // For the love of god, refactor this! - + debug(poodinisVerbose) { auto instanceType = typeid(Type); auto instanceAddress = &instance; writeln(format("DEBUG: Autowiring members of [%s@%s]", instanceType, instanceAddress)); } - + foreach (member ; __traits(allMembers, Type)) { static if(__traits(compiles, __traits(getMember, Type, member)) && __traits(compiles, __traits(getAttributes, __traits(getMember, Type, member)))) { foreach(autowireAttribute; __traits(getAttributes, __traits(getMember, Type, member))) { @@ -40,11 +40,11 @@ public void autowire(Type)(DependencyContainer container, Type instance) { if (__traits(getMember, instance, member) is null) { alias memberReference = TypeTuple!(__traits(getMember, instance, member)); alias MemberType = typeof(memberReference)[0]; - + debug(poodinisVerbose) { string qualifiedInstanceTypeString = typeid(MemberType).toString; } - + MemberType qualifiedInstance; static if (is(autowireAttribute == Autowire!T, T) && !is(autowireAttribute.qualifier == UseMemberType)) { alias QualifierType = typeof(autowireAttribute.qualifier); @@ -55,15 +55,15 @@ public void autowire(Type)(DependencyContainer container, Type instance) { } else { qualifiedInstance = container.resolve!(typeof(memberReference)); } - + __traits(getMember, instance, member) = qualifiedInstance; - + debug(poodinisVerbose) { auto qualifiedInstanceAddress = &qualifiedInstance; writeln(format("DEBUG: Autowired instance [%s@%s] to [%s@%s].%s", qualifiedInstanceTypeString, qualifiedInstanceAddress, instanceType, instanceAddress, member)); } } - + break; } } @@ -80,4 +80,4 @@ mixin template AutowireConstructor() { public void globalAutowire(Type)(Type instance) { DependencyContainer.getInstance().autowire(instance); -} \ No newline at end of file +} diff --git a/source/poodinis/container.d b/source/poodinis/container.d index 6fd0eec..d46e378 100644 --- a/source/poodinis/container.d +++ b/source/poodinis/container.d @@ -1,12 +1,136 @@ -/** - * Poodinis Dependency Injection Framework - * Copyright 2014 Mike Bierlee - * This software is licensed under the terms of the MIT license. - * The full terms of the license can be found in the LICENSE file. - */ - -module poodinis.container; - -pragma(msg, "Module \"container\" has been renamed to \"dependency\". The use of the \"container\" module is deprecated"); - -public import poodinis.dependency; +/** + * Poodinis Dependency Injection Framework + * Copyright 2014 Mike Bierlee + * This software is licensed under the terms of the MIT license. + * The full terms of the license can be found in the LICENSE file. + */ + +module poodinis.container; + +import std.string; +import std.array; +import std.algorithm; + +debug { + import std.stdio; +} + +public import poodinis.registration; +public import poodinis.autowire; + +class RegistrationException : Exception { + this(string message, TypeInfo registeredType, TypeInfo_Class concreteType) { + super(format("Exception while registering type %s to %s: %s", registeredType.toString(), concreteType.name, message)); + } +} + +class ResolveException : Exception { + this(string message, TypeInfo resolveType) { + super(format("Exception while resolving type %s: %s", resolveType.toString(), message)); + } +} + +deprecated("Container has been renamed to DependencyContainer") +alias Container = DependencyContainer; + +class DependencyContainer { + + private static DependencyContainer instance; + + private Registration[][TypeInfo] registrations; + + private Registration[] autowireStack; + + public Registration register(ConcreteType)() { + return register!(ConcreteType, ConcreteType)(); + } + + public Registration register(InterfaceType, ConcreteType : InterfaceType)() { + TypeInfo registeredType = typeid(InterfaceType); + TypeInfo_Class concreteType = typeid(ConcreteType); + + debug(poodinisVerbose) { + writeln(format("DEBUG: Register type %s (as %s)", concreteType.toString(), registeredType.toString())); + } + + auto existingCandidates = registeredType in registrations; + if (existingCandidates) { + auto existingRegistration = getRegistration(*existingCandidates, concreteType); + if (existingRegistration) { + return existingRegistration; + } + } + + Registration newRegistration = new Registration(registeredType, concreteType); + newRegistration.singleInstance(); + registrations[registeredType] ~= newRegistration; + return newRegistration; + } + + private Registration getRegistration(Registration[] candidates, TypeInfo concreteType) { + foreach(existingRegistration ; candidates) { + if (existingRegistration.instantiatableType == concreteType) { + return existingRegistration; + } + } + + return null; + } + + public RegistrationType resolve(RegistrationType)() { + return resolve!(RegistrationType, RegistrationType)(); + } + + public QualifierType resolve(RegistrationType, QualifierType : RegistrationType)() { + TypeInfo resolveType = typeid(RegistrationType); + TypeInfo qualifierType = typeid(QualifierType); + + debug(poodinisVerbose) { + writeln("DEBUG: Resolving type " ~ resolveType.toString() ~ " with qualifier " ~ qualifierType.toString()); + } + + auto candidates = resolveType in registrations; + if (!candidates) { + throw new ResolveException("Type not registered.", resolveType); + } + + Registration registration = getQualifiedRegistration(resolveType, qualifierType, *candidates); + QualifierType instance = cast(QualifierType) registration.getInstance(); + + if (!autowireStack.canFind(registration)) { + autowireStack ~= registration; + this.autowire!(QualifierType)(instance); + autowireStack.popBack(); + } + + return instance; + } + + private Registration getQualifiedRegistration(TypeInfo resolveType, TypeInfo qualifierType, Registration[] candidates) { + if (resolveType == qualifierType) { + if (candidates.length > 1) { + string candidateList = candidates.toConcreteTypeListString(); + throw new ResolveException("Multiple qualified candidates available: " ~ candidateList ~ ". Please use a qualifier.", resolveType); + } + + return candidates[0]; + } + + return getRegistration(candidates, qualifierType); + } + + public void clearAllRegistrations() { + registrations.destroy(); + } + + public void removeRegistration(RegistrationType)() { + registrations.remove(typeid(RegistrationType)); + } + + public static DependencyContainer getInstance() { + if (instance is null) { + instance = new DependencyContainer(); + } + return instance; + } +} diff --git a/source/poodinis/dependency.d b/source/poodinis/dependency.d deleted file mode 100644 index e01fe9f..0000000 --- a/source/poodinis/dependency.d +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Poodinis Dependency Injection Framework - * Copyright 2014 Mike Bierlee - * This software is licensed under the terms of the MIT license. - * The full terms of the license can be found in the LICENSE file. - */ - -module poodinis.dependency; - -import std.string; -import std.array; -import std.algorithm; - -debug { - import std.stdio; -} - -public import poodinis.registration; -public import poodinis.autowire; - -class RegistrationException : Exception { - this(string message, TypeInfo registeredType, TypeInfo_Class concreteType) { - super(format("Exception while registering type %s to %s: %s", registeredType.toString(), concreteType.name, message)); - } -} - -class ResolveException : Exception { - this(string message, TypeInfo resolveType) { - super(format("Exception while resolving type %s: %s", resolveType.toString(), message)); - } -} - -deprecated("Container has been renamed to DependencyContainer") -alias Container = DependencyContainer; - -class DependencyContainer { - - private static DependencyContainer instance; - - private Registration[][TypeInfo] registrations; - - private Registration[] autowireStack; - - public Registration register(ConcreteType)() { - return register!(ConcreteType, ConcreteType)(); - } - - public Registration register(InterfaceType, ConcreteType : InterfaceType)() { - TypeInfo registeredType = typeid(InterfaceType); - TypeInfo_Class concreteType = typeid(ConcreteType); - - debug(poodinisVerbose) { - writeln(format("DEBUG: Register type %s (as %s)", concreteType.toString(), registeredType.toString())); - } - - auto existingCandidates = registeredType in registrations; - if (existingCandidates) { - auto existingRegistration = getRegistration(*existingCandidates, concreteType); - if (existingRegistration) { - return existingRegistration; - } - } - - Registration newRegistration = new Registration(registeredType, concreteType); - newRegistration.singleInstance(); - registrations[registeredType] ~= newRegistration; - return newRegistration; - } - - private Registration getRegistration(Registration[] candidates, TypeInfo concreteType) { - foreach(existingRegistration ; candidates) { - if (existingRegistration.instantiatableType == concreteType) { - return existingRegistration; - } - } - - return null; - } - - public RegistrationType resolve(RegistrationType)() { - return resolve!(RegistrationType, RegistrationType)(); - } - - public QualifierType resolve(RegistrationType, QualifierType : RegistrationType)() { - TypeInfo resolveType = typeid(RegistrationType); - TypeInfo qualifierType = typeid(QualifierType); - - debug(poodinisVerbose) { - writeln("DEBUG: Resolving type " ~ resolveType.toString() ~ " with qualifier " ~ qualifierType.toString()); - } - - auto candidates = resolveType in registrations; - if (!candidates) { - throw new ResolveException("Type not registered.", resolveType); - } - - Registration registration = getQualifiedRegistration(resolveType, qualifierType, *candidates); - QualifierType instance = cast(QualifierType) registration.getInstance(); - - if (!autowireStack.canFind(registration)) { - autowireStack ~= registration; - this.autowire!(QualifierType)(instance); - autowireStack.popBack(); - } - - return instance; - } - - private Registration getQualifiedRegistration(TypeInfo resolveType, TypeInfo qualifierType, Registration[] candidates) { - if (resolveType == qualifierType) { - if (candidates.length > 1) { - string candidateList = candidates.toConcreteTypeListString(); - throw new ResolveException("Multiple qualified candidates available: " ~ candidateList ~ ". Please use a qualifier.", resolveType); - } - - return candidates[0]; - } - - return getRegistration(candidates, qualifierType); - } - - public void clearAllRegistrations() { - registrations.destroy(); - } - - public void removeRegistration(RegistrationType)() { - registrations.remove(typeid(RegistrationType)); - } - - public static DependencyContainer getInstance() { - if (instance is null) { - instance = new DependencyContainer(); - } - return instance; - } -} diff --git a/test/poodinis/dependencytest.d b/test/poodinis/containertest.d similarity index 95% rename from test/poodinis/dependencytest.d rename to test/poodinis/containertest.d index 7eaa5e2..9153c6b 100644 --- a/test/poodinis/dependencytest.d +++ b/test/poodinis/containertest.d @@ -5,98 +5,98 @@ * The full terms of the license can be found in the LICENSE file. */ -import poodinis.dependency; +import poodinis.container; import std.exception; version(unittest) { interface TestInterface { } - + class TestClass : TestInterface { } - + class UnrelatedClass{ } - + class FailOnCreationClass { this() { throw new Exception("This class should not be instantiated"); } } - + class AutowiredClass { } - + class ComponentClass { @Autowire public AutowiredClass autowiredClass; } - + class ComponentCat { @Autowire public ComponentMouse mouse; } - + class ComponentMouse { @Autowire public ComponentCat cat; } - + class Eenie { @Autowire public Meenie meenie; } - + class Meenie { @Autowire public Moe moe; } - + class Moe { @Autowire public Eenie eenie; } - + class Ittie { @Autowire public Bittie bittie; } - + class Bittie { @Autowire public Banana banana; } - + class Banana { @Autowire public Bittie bittie; } - + interface SuperInterface { } - + class SuperImplementation : SuperInterface { @Autowire public Banana banana; } - + interface Color { } - + class Blue : Color { } - + class Red : Color { } - + // Test register concrete type unittest { auto container = new DependencyContainer(); auto registration = container.register!(TestClass)(); assert(registration.registeredType == typeid(TestClass), "Type of registered type not the same"); } - + // Test resolve registered type unittest { auto container = new DependencyContainer(); @@ -105,7 +105,7 @@ version(unittest) { assert(actualInstance !is null, "Resolved type is null"); assert(cast(TestClass) actualInstance, "Resolved class is not the same type as expected"); } - + // Test register interface unittest { auto container = new DependencyContainer(); @@ -114,13 +114,13 @@ version(unittest) { assert(actualInstance !is null, "Resolved type is null"); assert(cast(TestInterface) actualInstance, "Resolved class is not the same type as expected"); } - + // Test resolve non-registered type unittest { auto container = new DependencyContainer(); assertThrown!ResolveException(container.resolve!(TestClass)(), "Resolving non-registered type does not fail"); } - + // Test clear registrations unittest { auto container = new DependencyContainer(); @@ -128,14 +128,14 @@ version(unittest) { container.clearAllRegistrations(); assertThrown!ResolveException(container.resolve!(TestClass)(), "Resolving cleared type does not fail"); } - + // Test get singleton of container unittest { auto instance1 = DependencyContainer.getInstance(); auto instance2 = DependencyContainer.getInstance(); assert(instance1 is instance2, "getInstance does not return the same instance"); } - + // Test resolve single instance for type unittest { auto container = new DependencyContainer(); @@ -144,7 +144,7 @@ version(unittest) { auto instance2 = container.resolve!(TestClass); assert(instance1 is instance2, "Resolved instance from single instance scope is not the each time it is resolved"); } - + // Test resolve new instance for type unittest { auto container = new DependencyContainer(); @@ -153,7 +153,7 @@ version(unittest) { auto instance2 = container.resolve!(TestClass); assert(instance1 !is instance2, "Resolved instance from new instance scope is the same each time it is resolved"); } - + // Test resolve existing instance for type unittest { auto container = new DependencyContainer(); @@ -162,7 +162,7 @@ version(unittest) { auto actualInstance = container.resolve!(TestClass); assert(expectedInstance is actualInstance, "Resolved instance from existing instance scope is not the same as the registered instance"); } - + // Test autowire resolved instances unittest { auto container = new DependencyContainer(); @@ -172,7 +172,7 @@ version(unittest) { auto autowiredInstance = container.resolve!AutowiredClass; assert(componentInstance.autowiredClass is autowiredInstance, "Member is not autowired upon resolving"); } - + // Test circular autowiring unittest { auto container = new DependencyContainer(); @@ -182,7 +182,7 @@ version(unittest) { auto cat = container.resolve!ComponentCat; assert(mouse.cat is cat && cat.mouse is mouse && mouse !is cat, "Circular dependencies should be autowirable"); } - + // Test remove registration unittest { auto container = new DependencyContainer(); @@ -190,69 +190,69 @@ version(unittest) { container.removeRegistration!TestClass; assertThrown!ResolveException(container.resolve!TestClass); } - + // Test autowiring does not autowire member where instance is non-null unittest { auto container = new DependencyContainer(); auto existingA = new AutowiredClass(); auto existingB = new ComponentClass(); existingB.autowiredClass = existingA; - + container.register!AutowiredClass; container.register!(ComponentClass).existingInstance(existingB); auto resolvedA = container.resolve!AutowiredClass; auto resolvedB = container.resolve!ComponentClass; - + assert(resolvedB.autowiredClass is existingA && resolvedA !is existingA, "Autowiring shouldn't rewire member when it is already wired to an instance"); } - + // Test autowiring circular dependency by third-degree unittest { auto container = new DependencyContainer(); container.register!Eenie; container.register!Meenie; container.register!Moe; - + auto eenie = container.resolve!Eenie; - + assert(eenie.meenie.moe.eenie is eenie, "Autowiring third-degree circular dependency failed"); } - + // Test autowiring deep circular dependencies unittest { auto container = new DependencyContainer(); container.register!Ittie; container.register!Bittie; container.register!Banana; - + auto ittie = container.resolve!Ittie; - + assert(ittie.bittie is ittie.bittie.banana.bittie, "Autowiring deep dependencies failed."); } - + // Test autowiring deep circular dependencies with newInstance scope does not autowire new instance second time unittest { auto container = new DependencyContainer(); container.register!(Ittie).newInstance(); container.register!(Bittie).newInstance(); container.register!(Banana).newInstance(); - + auto ittie = container.resolve!Ittie; - + assert(ittie.bittie.banana.bittie.banana is null, "Autowiring deep dependencies with newInstance scope autowired a reoccuring type."); } - + // Test autowiring type registered by interface fails (BUG test case) unittest { auto container = new DependencyContainer(); container.register!Banana; container.register!(SuperInterface, SuperImplementation); - + SuperImplementation superInstance = cast(SuperImplementation) container.resolve!SuperInterface; - + assert(superInstance.banana is null, "Autowire instance which was resolved by interface type, which was not expected to be possible"); } - + // Test reusing a container after clearing all registrations unittest { auto container = new DependencyContainer(); @@ -266,14 +266,14 @@ version(unittest) { } assert(false); } - + // Test register multiple concrete classess to same interface type unittest { auto container = new DependencyContainer(); container.register!(Color, Blue); container.register!(Color, Red); } - + // Test removing all registrations for type with multiple registrations. unittest { auto container = new DependencyContainer(); @@ -281,16 +281,16 @@ version(unittest) { container.register!(Color, Red); container.removeRegistration!Color; } - + // Test registering same registration again unittest { auto container = new DependencyContainer(); auto firstRegistration = container.register!(Color, Blue); auto secondRegistration = container.register!(Color, Blue); - + assert(firstRegistration is secondRegistration, "First registration is not the same as the second of equal types"); } - + // Test resolve registration with multiple qualifiers unittest { auto container = new DependencyContainer(); @@ -303,7 +303,7 @@ version(unittest) { } assert(false); } - + // Test resolve registration with multiple qualifiers using a qualifier unittest { auto container = new DependencyContainer(); @@ -311,9 +311,9 @@ version(unittest) { container.register!(Color, Red); auto blueInstance = container.resolve!(Color, Blue); auto redInstance = container.resolve!(Color, Red); - + assert(blueInstance !is redInstance, "Resolving type with multiple, different registrations yielded the same instance"); assert(blueInstance !is null, "Resolved blue instance to null"); assert(redInstance !is null, "Resolved red instance to null"); } -} \ No newline at end of file +}