diff --git a/LICENSE.txt b/LICENSE.txt index cf6bb88..277c5a6 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,19 +1,19 @@ -Copyright (c) 2014-2015 Mike Bierlee - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +Copyright (c) 2014-2016 Mike Bierlee + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index d985074..c45e937 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Poodinis Dependency Injection Framework ======================================= Version 6.0.0 -Copyright 2014-2015 Mike Bierlee +Copyright 2014-2016 Mike Bierlee Licensed under the terms of the MIT license - See [LICENSE.txt](LICENSE.txt) Master: [![Build Status](https://api.travis-ci.org/mbierlee/poodinis.png?branch=master)](https://travis-ci.org/mbierlee/poodinis) - Dev: [![Build Status](https://api.travis-ci.org/mbierlee/poodinis.png?branch=develop)](https://travis-ci.org/mbierlee/poodinis) diff --git a/dub.json b/dub.json index e76b1bf..a8b81fd 100644 --- a/dub.json +++ b/dub.json @@ -3,7 +3,7 @@ "description" : "A dependency injection framework with support for autowiring.", "homepage": "http://lostmoment.com/open-source/poodinis", "authors": ["Mike Bierlee"], - "copyright": "Copyright 2015 Mike Bierlee", + "copyright": "Copyright 2014-2016 Mike Bierlee", "license": "MIT", "configurations": [ { diff --git a/example/annotations/app.d b/example/annotations/app.d index 8d1dba2..fb2de8c 100644 --- a/example/annotations/app.d +++ b/example/annotations/app.d @@ -1,50 +1,50 @@ -/** - * Poodinis Dependency Injection Framework - * Copyright 2014-2015 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. - */ - -import poodinis; - -import std.random; -import std.digest.md; -import std.stdio; -import std.conv; - -class SuperSecurityDevice { - private int seed; - - public this() { - auto randomGenerator = Random(unpredictableSeed); - seed = uniform(0, 999, randomGenerator); - } - - public string getPassword() { - return to!string(seed) ~ "t1m3sp13!!:"; - } -} - -class SecurityManager { - @Autowire - public SuperSecurityDevice levelOneSecurity; - - @Autowire - @AssignNewInstance - public SuperSecurityDevice levelTwoSecurity; -} - -void main() { - auto dependencies = DependencyContainer.getInstance(); - dependencies.register!SuperSecurityDevice; // Registered with the default "Single instance" scope - dependencies.register!SecurityManager; - - auto manager = dependencies.resolve!SecurityManager; - - writeln("Password for user one: " ~ manager.levelOneSecurity.getPassword()); - writeln("Password for user two: " ~ manager.levelTwoSecurity.getPassword()); - - if (manager.levelOneSecurity is manager.levelTwoSecurity) { - writeln("SECURITY BREACH!!!!!"); - } -} +/** + * Poodinis Dependency Injection Framework + * Copyright 2014-2016 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. + */ + +import poodinis; + +import std.random; +import std.digest.md; +import std.stdio; +import std.conv; + +class SuperSecurityDevice { + private int seed; + + public this() { + auto randomGenerator = Random(unpredictableSeed); + seed = uniform(0, 999, randomGenerator); + } + + public string getPassword() { + return to!string(seed) ~ "t1m3sp13!!:"; + } +} + +class SecurityManager { + @Autowire + public SuperSecurityDevice levelOneSecurity; + + @Autowire + @AssignNewInstance + public SuperSecurityDevice levelTwoSecurity; +} + +void main() { + auto dependencies = DependencyContainer.getInstance(); + dependencies.register!SuperSecurityDevice; // Registered with the default "Single instance" scope + dependencies.register!SecurityManager; + + auto manager = dependencies.resolve!SecurityManager; + + writeln("Password for user one: " ~ manager.levelOneSecurity.getPassword()); + writeln("Password for user two: " ~ manager.levelTwoSecurity.getPassword()); + + if (manager.levelOneSecurity is manager.levelTwoSecurity) { + writeln("SECURITY BREACH!!!!!"); + } +} diff --git a/example/applicationcontext/app.d b/example/applicationcontext/app.d index 207e1c4..52cc753 100644 --- a/example/applicationcontext/app.d +++ b/example/applicationcontext/app.d @@ -1,68 +1,68 @@ -/** - * Poodinis Dependency Injection Framework - * Copyright 2014-2015 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. - */ - -import poodinis; - -import std.stdio; - -class TownSquare { - - @Autowire - public MarketStall marketStall; - - public void makeSound() { - marketStall.announceGoodsForSale(); - } - -} - -interface Goods { - public string getGoodsName(); -} - -class Fish : Goods { - public override string getGoodsName() { - return "Fish"; - } -} - -class MarketStall { - private Goods goods; - - this(Goods goods) { - this.goods = goods; - } - - public void announceGoodsForSale() { - writeln(goods.getGoodsName() ~ " for sale!"); - } -} - -class ExampleApplicationContext : ApplicationContext { - - @Autowire - public Goods goods; - - public override void registerDependencies(shared(DependencyContainer) container) { - container.register!(Goods, Fish); - container.register!TownSquare; - } - - @Component - public MarketStall marketStall() { - return new MarketStall(goods); - } - -} - -void main() { - auto container = DependencyContainer.getInstance(); - container.registerContext!ExampleApplicationContext; - - auto townSquare = container.resolve!TownSquare; - townSquare.makeSound(); -} +/** + * Poodinis Dependency Injection Framework + * Copyright 2014-2016 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. + */ + +import poodinis; + +import std.stdio; + +class TownSquare { + + @Autowire + public MarketStall marketStall; + + public void makeSound() { + marketStall.announceGoodsForSale(); + } + +} + +interface Goods { + public string getGoodsName(); +} + +class Fish : Goods { + public override string getGoodsName() { + return "Fish"; + } +} + +class MarketStall { + private Goods goods; + + this(Goods goods) { + this.goods = goods; + } + + public void announceGoodsForSale() { + writeln(goods.getGoodsName() ~ " for sale!"); + } +} + +class ExampleApplicationContext : ApplicationContext { + + @Autowire + public Goods goods; + + public override void registerDependencies(shared(DependencyContainer) container) { + container.register!(Goods, Fish); + container.register!TownSquare; + } + + @Component + public MarketStall marketStall() { + return new MarketStall(goods); + } + +} + +void main() { + auto container = DependencyContainer.getInstance(); + container.registerContext!ExampleApplicationContext; + + auto townSquare = container.resolve!TownSquare; + townSquare.makeSound(); +} diff --git a/example/arraycompletion/app.d b/example/arraycompletion/app.d index a288767..89d8c95 100644 --- a/example/arraycompletion/app.d +++ b/example/arraycompletion/app.d @@ -1,54 +1,54 @@ -/** - * Poodinis Dependency Injection Framework - * Copyright 2014-2015 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. - */ - -import poodinis; - -import std.stdio; - -interface Pie { - public void eat(); -} - -class BlueBerryPie : Pie { - public override void eat() { - writeln("Nom nom nom. I like this one!"); - } -} - -class ApplePie : Pie { - public override void eat() { - writeln("Nom nom nom. These aren't real apples..."); - } -} - -class CardboardBoxPie : Pie { - public override void eat() { - writeln("Nom nom nom. This... is not a pie."); - } -} - -class PieEater { - @Autowire - public Pie[] pies; - - public void eatThemAll() { - foreach(pie ; pies) { - pie.eat(); - } - } -} - -void main() { - auto dependencies = DependencyContainer.getInstance(); - dependencies.register!(Pie, BlueBerryPie); - dependencies.register!(Pie, ApplePie); - dependencies.register!(Pie, CardboardBoxPie); - dependencies.register!(PieEater); - - auto eater = dependencies.resolve!PieEater; - eater.eatThemAll(); -} +/** + * Poodinis Dependency Injection Framework + * Copyright 2014-2016 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. + */ + +import poodinis; + +import std.stdio; + +interface Pie { + public void eat(); +} + +class BlueBerryPie : Pie { + public override void eat() { + writeln("Nom nom nom. I like this one!"); + } +} + +class ApplePie : Pie { + public override void eat() { + writeln("Nom nom nom. These aren't real apples..."); + } +} + +class CardboardBoxPie : Pie { + public override void eat() { + writeln("Nom nom nom. This... is not a pie."); + } +} + +class PieEater { + @Autowire + public Pie[] pies; + + public void eatThemAll() { + foreach(pie ; pies) { + pie.eat(); + } + } +} + +void main() { + auto dependencies = DependencyContainer.getInstance(); + dependencies.register!(Pie, BlueBerryPie); + dependencies.register!(Pie, ApplePie); + dependencies.register!(Pie, CardboardBoxPie); + dependencies.register!(PieEater); + + auto eater = dependencies.resolve!PieEater; + eater.eatThemAll(); +} diff --git a/example/qualifiers/app.d b/example/qualifiers/app.d index 971c800..4aefb16 100644 --- a/example/qualifiers/app.d +++ b/example/qualifiers/app.d @@ -1,57 +1,57 @@ -/** - * Poodinis Dependency Injection Framework - * Copyright 2014-2015 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. - */ - -import poodinis; - -import std.stdio; - -interface Engine { - public void engage(); -} - -class FuelEngine : Engine { - public void engage() { - writeln("VROOOOOOM!"); - } -} - -class ElectricEngine : Engine { - public void engage() { - writeln("hummmmmmmm...."); - } -} - -class HybridCar { - alias KilometersPerHour = int; - - @Autowire!FuelEngine - public Engine fuelEngine; - - @Autowire!ElectricEngine - public Engine electricEngine; - - public void moveAtSpeed(KilometersPerHour speed) { - if (speed <= 45) { - electricEngine.engage(); - } else { - fuelEngine.engage(); - } - } -} - -void main() { - auto dependencies = DependencyContainer.getInstance(); - - dependencies.register!HybridCar; - dependencies.register!(Engine, FuelEngine); - dependencies.register!(Engine, ElectricEngine); - - auto car = dependencies.resolve!HybridCar; - - car.moveAtSpeed(10); // Should print "hummmmmmmm...." - car.moveAtSpeed(50); // Should print "VROOOOOOM!" -} +/** + * Poodinis Dependency Injection Framework + * Copyright 2014-2016 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. + */ + +import poodinis; + +import std.stdio; + +interface Engine { + public void engage(); +} + +class FuelEngine : Engine { + public void engage() { + writeln("VROOOOOOM!"); + } +} + +class ElectricEngine : Engine { + public void engage() { + writeln("hummmmmmmm...."); + } +} + +class HybridCar { + alias KilometersPerHour = int; + + @Autowire!FuelEngine + public Engine fuelEngine; + + @Autowire!ElectricEngine + public Engine electricEngine; + + public void moveAtSpeed(KilometersPerHour speed) { + if (speed <= 45) { + electricEngine.engage(); + } else { + fuelEngine.engage(); + } + } +} + +void main() { + auto dependencies = DependencyContainer.getInstance(); + + dependencies.register!HybridCar; + dependencies.register!(Engine, FuelEngine); + dependencies.register!(Engine, ElectricEngine); + + auto car = dependencies.resolve!HybridCar; + + car.moveAtSpeed(10); // Should print "hummmmmmmm...." + car.moveAtSpeed(50); // Should print "VROOOOOOM!" +} diff --git a/example/quickstart/app.d b/example/quickstart/app.d index 2881d3b..eec27fc 100644 --- a/example/quickstart/app.d +++ b/example/quickstart/app.d @@ -1,24 +1,24 @@ -/** - * Poodinis Dependency Injection Framework - * Copyright 2014-2015 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. - */ - -import poodinis; - -interface Database{}; -class RelationalDatabase : Database {} - -class DataWriter { - @Autowire - public Database database; // Automatically injected when class is resolved -} - -void main() { - auto dependencies = DependencyContainer.getInstance(); - dependencies.register!DataWriter; - dependencies.register!(Database, RelationalDatabase); - - auto writer = dependencies.resolve!DataWriter; -} +/** + * Poodinis Dependency Injection Framework + * Copyright 2014-2016 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. + */ + +import poodinis; + +interface Database{}; +class RelationalDatabase : Database {} + +class DataWriter { + @Autowire + public Database database; // Automatically injected when class is resolved +} + +void main() { + auto dependencies = DependencyContainer.getInstance(); + dependencies.register!DataWriter; + dependencies.register!(Database, RelationalDatabase); + + auto writer = dependencies.resolve!DataWriter; +} diff --git a/source/poodinis/autowire.d b/source/poodinis/autowire.d index 6aa2d86..4e95cd0 100644 --- a/source/poodinis/autowire.d +++ b/source/poodinis/autowire.d @@ -1,206 +1,206 @@ -/** - * Contains functionality for autowiring dependencies using a dependency container. - * - * This module is used in a dependency container for autowiring dependencies when resolving them. - * You typically only need this module if you want inject dependencies into a class instance not - * managed by a dependency container. - * - * Part of the Poodinis Dependency Injection framework. - * - * Authors: - * Mike Bierlee, m.bierlee@lostmoment.com - * Copyright: 2014-2015 Mike Bierlee - * License: - * 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.autowire; - -import poodinis.container; -import poodinis.registration; - -import std.exception; -import std.stdio; -import std.string; -import std.traits; -import std.range; - -struct UseMemberType {}; - -/** - * UDA for annotating class members as candidates for autowiring. - * - * Optionally a template parameter can be supplied to specify the type of a qualified class. The qualified type - * of a concrete class is used to autowire members declared by supertype. If no qualifier is supplied, the type - * of the member is used as qualifier. - * - * Examples: - * Annotate member of class to be autowired: - * --- - * class Car { - * @Autowire - * public Engine engine; - * } - * --- - * - * Annotate member of class with qualifier: - * --- - * class FuelEngine : Engine { ... } - * class ElectricEngine : Engine { ... } - * - * class HybridCar { - * @Autowire!FuelEngine - * public Engine fuelEngine; - * - * @Autowire!ElectricEngine - * public Engine electricEngine; - * } - * --- - * The members of an instance of "HybridCar" will now be autowired properly, because the autowire mechanism will - * autowire member "fuelEngine" as if it's of type "FuelEngine". This means that the members of instance "fuelEngine" - * will also be autowired because the autowire mechanism knows that member "fuelEngine" is an instance of "FuelEngine" - */ -struct Autowire(QualifierType = UseMemberType) { - QualifierType qualifier; -}; - -/** - * UDA for annotating class members to be autowired with a new instance regardless of their registration scope. - * - * Examples: - *--- - * class Car { - * @Autowire - * @AssignNewInstance - * public Antenna antenna; - * } - *--- - * antenna will always be assigned a new instance of class Antenna. - */ -struct AssignNewInstance {} - -private void printDebugAutowiredInstance(TypeInfo instanceType, void* instanceAddress) { - writeln(format("DEBUG: Autowiring members of [%s@%s]", instanceType, instanceAddress)); -} - -/** - * Autowires members of a given instance using dependencies registered in the given container. - * - * All public members of the given instance, which are annotated using the "Autowire" UDA, are autowired. - * All members are resolved using the given container. Qualifiers are used to determine the type of class to - * resolve for any member of instance. - * - * Note that private members will not be autowired because the autowiring mechanism is not able to by-pass - * member visibility protection. - * - * See_Also: Autowire - */ -public void autowire(Type)(shared(DependencyContainer) container, Type instance) { - debug(poodinisVerbose) { - printDebugAutowiredInstance(typeid(Type), &instance); - } - - foreach (member ; __traits(allMembers, Type)) { - autowireMember!member(container, instance); - } -} - -private void printDebugAutowiringCandidate(TypeInfo candidateInstanceType, void* candidateInstanceAddress, TypeInfo instanceType, void* instanceAddress, string member) { - writeln(format("DEBUG: Autowired instance [%s@%s] to [%s@%s].%s", candidateInstanceType, candidateInstanceAddress, instanceType, instanceAddress, member)); -} - -private void printDebugAutowiringArray(TypeInfo superTypeInfo, TypeInfo instanceType, void* instanceAddress, string member) { - writeln(format("DEBUG: Autowired all registered instances of super type %s to [%s@%s].%s", superTypeInfo, instanceType, instanceAddress, member)); -} - -private void autowireMember(string member, Type)(shared(DependencyContainer) container, Type instance) { - static if(__traits(compiles, __traits(getMember, instance, member)) && __traits(compiles, __traits(getAttributes, __traits(getMember, instance, member)))) { - foreach(autowireAttribute; __traits(getAttributes, __traits(getMember, instance, member))) { - static if (__traits(isSame, autowireAttribute, Autowire) || is(autowireAttribute == Autowire!T, T)) { - if (__traits(getMember, instance, member) is null) { - alias MemberType = typeof(__traits(getMember, instance, member)); - - enum assignNewInstance = hasUDA!(__traits(getMember, instance, member), AssignNewInstance); - - static if (isDynamicArray!MemberType) { - alias MemberElementType = ElementType!MemberType; - auto instances = container.resolveAll!MemberElementType; - __traits(getMember, instance, member) = instances; - debug(poodinisVerbose) { - printDebugAutowiringArray(typeid(MemberElementType), typeid(Type), &instance, member); - } - } else { - debug(poodinisVerbose) { - TypeInfo qualifiedInstanceType = typeid(MemberType); - } - - MemberType qualifiedInstance; - static if (is(autowireAttribute == Autowire!T, T) && !is(autowireAttribute.qualifier == UseMemberType)) { - alias QualifierType = typeof(autowireAttribute.qualifier); - qualifiedInstance = createOrResolveInstance!(MemberType, QualifierType, assignNewInstance)(container); - debug(poodinisVerbose) { - qualifiedInstanceType = typeid(QualifierType); - } - } else { - qualifiedInstance = createOrResolveInstance!(MemberType, MemberType, assignNewInstance)(container); - } - - __traits(getMember, instance, member) = qualifiedInstance; - - debug(poodinisVerbose) { - printDebugAutowiringCandidate(qualifiedInstanceType, &qualifiedInstance, typeid(Type), &instance, member); - } - } - } - - break; - } - } - } -} - -private QualifierType createOrResolveInstance(MemberType, QualifierType, bool createNew)(shared(DependencyContainer) container) { - static if (createNew) { - auto instanceFactory = new InstanceFactory(typeid(MemberType), CreatesSingleton.no, null); - return cast(MemberType) instanceFactory.getInstance(); - } else { - return container.resolve!(MemberType, QualifierType); - } -} - -/** - * Autowire the given instance using the globally available dependency container. - * - * See_Also: DependencyContainer - */ -public void globalAutowire(Type)(Type instance) { - DependencyContainer.getInstance().autowire(instance); -} - -class AutowiredRegistration(RegistrationType : Object) : Registration { - private shared(DependencyContainer) container; - - public this(TypeInfo registeredType, shared(DependencyContainer) container) { - enforce(!(container is null), "Argument 'container' is null. Autowired registrations need to autowire using a container."); - this.container = container; - super(registeredType, typeid(RegistrationType)); - } - - public override Object getInstance(InstantiationContext context = new AutowireInstantiationContext()) { - RegistrationType instance = cast(RegistrationType) super.getInstance(context); - - AutowireInstantiationContext autowireContext = cast(AutowireInstantiationContext) context; - enforce(!(autowireContext is null), "Given instantiation context type could not be cast to an AutowireInstantiationContext. If you relied on using the default assigned context: make sure you're calling getInstance() on an instance of type AutowiredRegistration!"); - if (autowireContext.autowireInstance) { - container.autowire(instance); - } - - return instance; - } - -} - -class AutowireInstantiationContext : InstantiationContext { - public bool autowireInstance = true; -} +/** + * Contains functionality for autowiring dependencies using a dependency container. + * + * This module is used in a dependency container for autowiring dependencies when resolving them. + * You typically only need this module if you want inject dependencies into a class instance not + * managed by a dependency container. + * + * Part of the Poodinis Dependency Injection framework. + * + * Authors: + * Mike Bierlee, m.bierlee@lostmoment.com + * Copyright: 2014-2016 Mike Bierlee + * License: + * 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.autowire; + +import poodinis.container; +import poodinis.registration; + +import std.exception; +import std.stdio; +import std.string; +import std.traits; +import std.range; + +struct UseMemberType {}; + +/** + * UDA for annotating class members as candidates for autowiring. + * + * Optionally a template parameter can be supplied to specify the type of a qualified class. The qualified type + * of a concrete class is used to autowire members declared by supertype. If no qualifier is supplied, the type + * of the member is used as qualifier. + * + * Examples: + * Annotate member of class to be autowired: + * --- + * class Car { + * @Autowire + * public Engine engine; + * } + * --- + * + * Annotate member of class with qualifier: + * --- + * class FuelEngine : Engine { ... } + * class ElectricEngine : Engine { ... } + * + * class HybridCar { + * @Autowire!FuelEngine + * public Engine fuelEngine; + * + * @Autowire!ElectricEngine + * public Engine electricEngine; + * } + * --- + * The members of an instance of "HybridCar" will now be autowired properly, because the autowire mechanism will + * autowire member "fuelEngine" as if it's of type "FuelEngine". This means that the members of instance "fuelEngine" + * will also be autowired because the autowire mechanism knows that member "fuelEngine" is an instance of "FuelEngine" + */ +struct Autowire(QualifierType = UseMemberType) { + QualifierType qualifier; +}; + +/** + * UDA for annotating class members to be autowired with a new instance regardless of their registration scope. + * + * Examples: + *--- + * class Car { + * @Autowire + * @AssignNewInstance + * public Antenna antenna; + * } + *--- + * antenna will always be assigned a new instance of class Antenna. + */ +struct AssignNewInstance {} + +private void printDebugAutowiredInstance(TypeInfo instanceType, void* instanceAddress) { + writeln(format("DEBUG: Autowiring members of [%s@%s]", instanceType, instanceAddress)); +} + +/** + * Autowires members of a given instance using dependencies registered in the given container. + * + * All public members of the given instance, which are annotated using the "Autowire" UDA, are autowired. + * All members are resolved using the given container. Qualifiers are used to determine the type of class to + * resolve for any member of instance. + * + * Note that private members will not be autowired because the autowiring mechanism is not able to by-pass + * member visibility protection. + * + * See_Also: Autowire + */ +public void autowire(Type)(shared(DependencyContainer) container, Type instance) { + debug(poodinisVerbose) { + printDebugAutowiredInstance(typeid(Type), &instance); + } + + foreach (member ; __traits(allMembers, Type)) { + autowireMember!member(container, instance); + } +} + +private void printDebugAutowiringCandidate(TypeInfo candidateInstanceType, void* candidateInstanceAddress, TypeInfo instanceType, void* instanceAddress, string member) { + writeln(format("DEBUG: Autowired instance [%s@%s] to [%s@%s].%s", candidateInstanceType, candidateInstanceAddress, instanceType, instanceAddress, member)); +} + +private void printDebugAutowiringArray(TypeInfo superTypeInfo, TypeInfo instanceType, void* instanceAddress, string member) { + writeln(format("DEBUG: Autowired all registered instances of super type %s to [%s@%s].%s", superTypeInfo, instanceType, instanceAddress, member)); +} + +private void autowireMember(string member, Type)(shared(DependencyContainer) container, Type instance) { + static if(__traits(compiles, __traits(getMember, instance, member)) && __traits(compiles, __traits(getAttributes, __traits(getMember, instance, member)))) { + foreach(autowireAttribute; __traits(getAttributes, __traits(getMember, instance, member))) { + static if (__traits(isSame, autowireAttribute, Autowire) || is(autowireAttribute == Autowire!T, T)) { + if (__traits(getMember, instance, member) is null) { + alias MemberType = typeof(__traits(getMember, instance, member)); + + enum assignNewInstance = hasUDA!(__traits(getMember, instance, member), AssignNewInstance); + + static if (isDynamicArray!MemberType) { + alias MemberElementType = ElementType!MemberType; + auto instances = container.resolveAll!MemberElementType; + __traits(getMember, instance, member) = instances; + debug(poodinisVerbose) { + printDebugAutowiringArray(typeid(MemberElementType), typeid(Type), &instance, member); + } + } else { + debug(poodinisVerbose) { + TypeInfo qualifiedInstanceType = typeid(MemberType); + } + + MemberType qualifiedInstance; + static if (is(autowireAttribute == Autowire!T, T) && !is(autowireAttribute.qualifier == UseMemberType)) { + alias QualifierType = typeof(autowireAttribute.qualifier); + qualifiedInstance = createOrResolveInstance!(MemberType, QualifierType, assignNewInstance)(container); + debug(poodinisVerbose) { + qualifiedInstanceType = typeid(QualifierType); + } + } else { + qualifiedInstance = createOrResolveInstance!(MemberType, MemberType, assignNewInstance)(container); + } + + __traits(getMember, instance, member) = qualifiedInstance; + + debug(poodinisVerbose) { + printDebugAutowiringCandidate(qualifiedInstanceType, &qualifiedInstance, typeid(Type), &instance, member); + } + } + } + + break; + } + } + } +} + +private QualifierType createOrResolveInstance(MemberType, QualifierType, bool createNew)(shared(DependencyContainer) container) { + static if (createNew) { + auto instanceFactory = new InstanceFactory(typeid(MemberType), CreatesSingleton.no, null); + return cast(MemberType) instanceFactory.getInstance(); + } else { + return container.resolve!(MemberType, QualifierType); + } +} + +/** + * Autowire the given instance using the globally available dependency container. + * + * See_Also: DependencyContainer + */ +public void globalAutowire(Type)(Type instance) { + DependencyContainer.getInstance().autowire(instance); +} + +class AutowiredRegistration(RegistrationType : Object) : Registration { + private shared(DependencyContainer) container; + + public this(TypeInfo registeredType, shared(DependencyContainer) container) { + enforce(!(container is null), "Argument 'container' is null. Autowired registrations need to autowire using a container."); + this.container = container; + super(registeredType, typeid(RegistrationType)); + } + + public override Object getInstance(InstantiationContext context = new AutowireInstantiationContext()) { + RegistrationType instance = cast(RegistrationType) super.getInstance(context); + + AutowireInstantiationContext autowireContext = cast(AutowireInstantiationContext) context; + enforce(!(autowireContext is null), "Given instantiation context type could not be cast to an AutowireInstantiationContext. If you relied on using the default assigned context: make sure you're calling getInstance() on an instance of type AutowiredRegistration!"); + if (autowireContext.autowireInstance) { + container.autowire(instance); + } + + return instance; + } + +} + +class AutowireInstantiationContext : InstantiationContext { + public bool autowireInstance = true; +} diff --git a/source/poodinis/container.d b/source/poodinis/container.d index f947e87..56d707d 100644 --- a/source/poodinis/container.d +++ b/source/poodinis/container.d @@ -1,361 +1,361 @@ -/** - * Contains the implementation of the dependency container. - * - * Part of the Poodinis Dependency Injection framework. - * - * Authors: - * Mike Bierlee, m.bierlee@lostmoment.com - * Copyright: 2014-2015 Mike Bierlee - * License: - * 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.algorithm; -import std.concurrency; - -debug { - import std.stdio; -} - -import poodinis.registration; -import poodinis.autowire; -import poodinis.context; - -/** - * Exception thrown when errors occur while resolving a type in a dependency container. - */ -class ResolveException : Exception { - this(string message, TypeInfo resolveType) { - super(format("Exception while resolving type %s: %s", resolveType.toString(), message)); - } -} - -/** - * Exception thrown when errors occur while registering a type in a dependency container. - */ -class RegistrationException : Exception { - this(string message, TypeInfo registrationType) { - super(format("Exception while registering type %s: %s", registrationType.toString(), message)); - } -} - -/** - * Options which influence the process of registering dependencies - */ -public enum RegistrationOption { - /** - * Prevent a concrete type being registered on itself. With this option you will always need - * to use the supertype as the type of the dependency. - */ - DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION -} - -/** - * The dependency container maintains all dependencies registered with it. - * - * Dependencies registered by a container can be resolved as long as they are still registered with the container. - * Upon resolving a dependency, an instance is fetched according to a specific scope which dictates how instances of - * dependencies are created. Resolved dependencies will be autowired before being returned. - * - * In most cases you want to use a global singleton dependency container provided by getInstance() to manage all dependencies. - * You can still create new instances of this class for exceptional situations. - */ -synchronized class DependencyContainer { - private Registration[][TypeInfo] registrations; - - private Registration[] autowireStack; - - /** - * Register a dependency by concrete class type. - * - * A dependency registered by concrete class type can only be resolved by concrete class type. - * No qualifiers can be used when resolving dependencies which are registered by concrete type. - * - * The default registration scope is "single instance" scope. - * - * Returns: - * A registration is returned which can be used to change the registration scope. - * - * Examples: - * Register and resolve a class by concrete type: - * --- - * class Cat : Animal { ... } - * container.register!Cat; - * --- - * - * See_Also: singleInstance, newInstance, existingInstance - */ - public Registration register(ConcreteType)() { - return register!(ConcreteType, ConcreteType)(); - } - - /** - * Register a dependency by super type. - * - * A dependency registered by super type can only be resolved by super type. A qualifier is typically - * used to resolve dependencies registered by super type. - * - * The default registration scope is "single instance" scope. - * - * Examples: - * Register and resolve by super type - * --- - * class Cat : Animal { ... } - * container.register!(Animal, Cat); - * --- - * - * See_Also: singleInstance, newInstance, existingInstance, RegistrationOptions - */ - public Registration register(SuperType, ConcreteType : SuperType, RegistrationOptionsTuple...)(RegistrationOptionsTuple options) { - TypeInfo registeredType = typeid(SuperType); - TypeInfo_Class concreteType = typeid(ConcreteType); - - debug(poodinisVerbose) { - writeln(format("DEBUG: Register type %s (as %s)", concreteType.toString(), registeredType.toString())); - } - - auto existingRegistration = getExistingRegistration(registeredType, concreteType); - if (existingRegistration) { - return existingRegistration; - } - - auto newRegistration = new AutowiredRegistration!ConcreteType(registeredType, this); - newRegistration.singleInstance(); - - if (!hasOption(options, RegistrationOption.DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION)) { - static if (!is(SuperType == ConcreteType)) { - auto concreteTypeRegistration = register!ConcreteType; - concreteTypeRegistration.linkTo(newRegistration); - } - } - - registrations[registeredType] ~= cast(shared(Registration)) newRegistration; - return newRegistration; - } - - private bool hasOption(RegistrationOptionsTuple...)(RegistrationOptionsTuple options, RegistrationOption option) { - foreach(presentOption ; options) { - if (presentOption == option) { - return true; - } - } - - return false; - } - - private Registration getExistingRegistration(TypeInfo registrationType, TypeInfo qualifierType) { - auto existingCandidates = registrationType in registrations; - if (existingCandidates) { - return getRegistration(cast(Registration[]) *existingCandidates, qualifierType); - } - - return null; - } - - private Registration getRegistration(Registration[] candidates, TypeInfo concreteType) { - foreach(existingRegistration ; candidates) { - if (existingRegistration.instanceType == concreteType) { - return existingRegistration; - } - } - - return null; - } - - /** - * Resolve dependencies. - * - * Dependencies can only resolved using this method if they are registered by concrete type or the only - * concrete type registered by super type. - * - * Resolved dependencies are automatically autowired before being returned. - * - * Returns: - * An instance is returned which is created according to the registration scope with which they are registered. - * - * Throws: - * ResolveException when type is not registered. - * - * Examples: - * Resolve dependencies registered by super type and concrete type: - * --- - * class Cat : Animal { ... } - * class Dog : Animal { ... } - * - * container.register!(Animal, Cat); - * container.register!Dog; - * - * container.resolve!Animal; - * container.resolve!Dog; - * --- - * You cannot resolve a dependency when it is registered by multiple super types: - * --- - * class Cat : Animal { ... } - * class Dog : Animal { ... } - * - * container.register!(Animal, Cat); - * container.register!(Animal, Dog); - * - * container.resolve!Animal; // Error: multiple candidates for type "Animal" - * container.resolve!Dog; // Error: No type is registered by concrete type "Dog", only by super type "Animal" - * --- - * You need to use the resolve method which allows you to specify a qualifier. - */ - public RegistrationType resolve(RegistrationType)() { - return resolve!(RegistrationType, RegistrationType)(); - } - - /** - * Resolve dependencies using a qualifier. - * - * Dependencies can only resolved using this method if they are registered by super type. - * - * Resolved dependencies are automatically autowired before being returned. - * - * Returns: - * An instance is returned which is created according to the registration scope with which they are registered. - * - * Throws: - * ResolveException when type is not registered or there are multiple candidates available for type. - * - * Examples: - * Resolve dependencies registered by super type: - * --- - * class Cat : Animal { ... } - * class Dog : Animal { ... } - * - * container.register!(Animal, Cat); - * container.register!(Animal, Dog); - * - * container.resolve!(Animal, Cat); - * container.resolve!(Animal, Dog); - * --- - */ - 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, cast(Registration[]) *candidates); - return resolveAutowiredInstance!QualifierType(registration); - } - - private QualifierType resolveAutowiredInstance(QualifierType)(Registration registration) { - QualifierType instance; - if (!(cast(Registration[]) autowireStack).canFind(registration)) { - autowireStack ~= cast(shared(Registration)) registration; - instance = cast(QualifierType) registration.getInstance(new AutowireInstantiationContext()); - autowireStack = autowireStack[0 .. $-1]; - } else { - auto autowireContext = new AutowireInstantiationContext(); - autowireContext.autowireInstance = false; - instance = cast(QualifierType) registration.getInstance(autowireContext); - } - return instance; - } - - /** - * Resolve all dependencies registered to a super type. - * - * Returns: - * An array of autowired instances is returned. The order is undetermined. - * - * Examples: - * --- - * class Cat : Animal { ... } - * class Dog : Animal { ... } - * - * container.register!(Animal, Cat); - * container.register!(Animal, Dog); - * - * Animal[] animals = container.resolveAll!Animal; - * --- - */ - public RegistrationType[] resolveAll(RegistrationType)() { - RegistrationType[] instances; - TypeInfo resolveType = typeid(RegistrationType); - - auto qualifiedRegistrations = resolveType in registrations; - if (!qualifiedRegistrations) { - throw new ResolveException("Type not registered.", resolveType); - } - - foreach(registration ; cast(Registration[]) *qualifiedRegistrations) { - instances ~= resolveAutowiredInstance!RegistrationType(registration); - } - - return instances; - } - - 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); - } - - /** - * Clears all dependency registrations managed by this container. - */ - public void clearAllRegistrations() { - registrations.destroy(); - } - - /** - * Removes a registered dependency by type. - * - * A dependency can be removed either by super type or concrete type, depending on how they are registered. - * - * Examples: - * --- - * container.removeRegistration!Animal; - * --- - */ - public void removeRegistration(RegistrationType)() { - registrations.remove(typeid(RegistrationType)); - } - - /** - * Register dependencies through an application context. - * - * An application context allows you to fine-tune dependency set-up and instantiation. - * It is mostly used for dependencies which come from an external library or when you don't - * want to use annotations to set-up dependencies in your classes. - */ - public void registerContext(Context : ApplicationContext)() { - auto context = new Context(); - context.registerDependencies(this); - context.registerContextComponents(this); - this.register!(ApplicationContext, Context)().existingInstance(context); - autowire(this, context); - } - - /** - * Returns a global singleton instance of a dependency container. - */ - public static shared(DependencyContainer) getInstance() { - static shared DependencyContainer instance; - if (instance is null) { - instance = new DependencyContainer(); - } - return instance; - } -} +/** + * Contains the implementation of the dependency container. + * + * Part of the Poodinis Dependency Injection framework. + * + * Authors: + * Mike Bierlee, m.bierlee@lostmoment.com + * Copyright: 2014-2016 Mike Bierlee + * License: + * 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.algorithm; +import std.concurrency; + +debug { + import std.stdio; +} + +import poodinis.registration; +import poodinis.autowire; +import poodinis.context; + +/** + * Exception thrown when errors occur while resolving a type in a dependency container. + */ +class ResolveException : Exception { + this(string message, TypeInfo resolveType) { + super(format("Exception while resolving type %s: %s", resolveType.toString(), message)); + } +} + +/** + * Exception thrown when errors occur while registering a type in a dependency container. + */ +class RegistrationException : Exception { + this(string message, TypeInfo registrationType) { + super(format("Exception while registering type %s: %s", registrationType.toString(), message)); + } +} + +/** + * Options which influence the process of registering dependencies + */ +public enum RegistrationOption { + /** + * Prevent a concrete type being registered on itself. With this option you will always need + * to use the supertype as the type of the dependency. + */ + DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION +} + +/** + * The dependency container maintains all dependencies registered with it. + * + * Dependencies registered by a container can be resolved as long as they are still registered with the container. + * Upon resolving a dependency, an instance is fetched according to a specific scope which dictates how instances of + * dependencies are created. Resolved dependencies will be autowired before being returned. + * + * In most cases you want to use a global singleton dependency container provided by getInstance() to manage all dependencies. + * You can still create new instances of this class for exceptional situations. + */ +synchronized class DependencyContainer { + private Registration[][TypeInfo] registrations; + + private Registration[] autowireStack; + + /** + * Register a dependency by concrete class type. + * + * A dependency registered by concrete class type can only be resolved by concrete class type. + * No qualifiers can be used when resolving dependencies which are registered by concrete type. + * + * The default registration scope is "single instance" scope. + * + * Returns: + * A registration is returned which can be used to change the registration scope. + * + * Examples: + * Register and resolve a class by concrete type: + * --- + * class Cat : Animal { ... } + * container.register!Cat; + * --- + * + * See_Also: singleInstance, newInstance, existingInstance + */ + public Registration register(ConcreteType)() { + return register!(ConcreteType, ConcreteType)(); + } + + /** + * Register a dependency by super type. + * + * A dependency registered by super type can only be resolved by super type. A qualifier is typically + * used to resolve dependencies registered by super type. + * + * The default registration scope is "single instance" scope. + * + * Examples: + * Register and resolve by super type + * --- + * class Cat : Animal { ... } + * container.register!(Animal, Cat); + * --- + * + * See_Also: singleInstance, newInstance, existingInstance, RegistrationOptions + */ + public Registration register(SuperType, ConcreteType : SuperType, RegistrationOptionsTuple...)(RegistrationOptionsTuple options) { + TypeInfo registeredType = typeid(SuperType); + TypeInfo_Class concreteType = typeid(ConcreteType); + + debug(poodinisVerbose) { + writeln(format("DEBUG: Register type %s (as %s)", concreteType.toString(), registeredType.toString())); + } + + auto existingRegistration = getExistingRegistration(registeredType, concreteType); + if (existingRegistration) { + return existingRegistration; + } + + auto newRegistration = new AutowiredRegistration!ConcreteType(registeredType, this); + newRegistration.singleInstance(); + + if (!hasOption(options, RegistrationOption.DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION)) { + static if (!is(SuperType == ConcreteType)) { + auto concreteTypeRegistration = register!ConcreteType; + concreteTypeRegistration.linkTo(newRegistration); + } + } + + registrations[registeredType] ~= cast(shared(Registration)) newRegistration; + return newRegistration; + } + + private bool hasOption(RegistrationOptionsTuple...)(RegistrationOptionsTuple options, RegistrationOption option) { + foreach(presentOption ; options) { + if (presentOption == option) { + return true; + } + } + + return false; + } + + private Registration getExistingRegistration(TypeInfo registrationType, TypeInfo qualifierType) { + auto existingCandidates = registrationType in registrations; + if (existingCandidates) { + return getRegistration(cast(Registration[]) *existingCandidates, qualifierType); + } + + return null; + } + + private Registration getRegistration(Registration[] candidates, TypeInfo concreteType) { + foreach(existingRegistration ; candidates) { + if (existingRegistration.instanceType == concreteType) { + return existingRegistration; + } + } + + return null; + } + + /** + * Resolve dependencies. + * + * Dependencies can only resolved using this method if they are registered by concrete type or the only + * concrete type registered by super type. + * + * Resolved dependencies are automatically autowired before being returned. + * + * Returns: + * An instance is returned which is created according to the registration scope with which they are registered. + * + * Throws: + * ResolveException when type is not registered. + * + * Examples: + * Resolve dependencies registered by super type and concrete type: + * --- + * class Cat : Animal { ... } + * class Dog : Animal { ... } + * + * container.register!(Animal, Cat); + * container.register!Dog; + * + * container.resolve!Animal; + * container.resolve!Dog; + * --- + * You cannot resolve a dependency when it is registered by multiple super types: + * --- + * class Cat : Animal { ... } + * class Dog : Animal { ... } + * + * container.register!(Animal, Cat); + * container.register!(Animal, Dog); + * + * container.resolve!Animal; // Error: multiple candidates for type "Animal" + * container.resolve!Dog; // Error: No type is registered by concrete type "Dog", only by super type "Animal" + * --- + * You need to use the resolve method which allows you to specify a qualifier. + */ + public RegistrationType resolve(RegistrationType)() { + return resolve!(RegistrationType, RegistrationType)(); + } + + /** + * Resolve dependencies using a qualifier. + * + * Dependencies can only resolved using this method if they are registered by super type. + * + * Resolved dependencies are automatically autowired before being returned. + * + * Returns: + * An instance is returned which is created according to the registration scope with which they are registered. + * + * Throws: + * ResolveException when type is not registered or there are multiple candidates available for type. + * + * Examples: + * Resolve dependencies registered by super type: + * --- + * class Cat : Animal { ... } + * class Dog : Animal { ... } + * + * container.register!(Animal, Cat); + * container.register!(Animal, Dog); + * + * container.resolve!(Animal, Cat); + * container.resolve!(Animal, Dog); + * --- + */ + 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, cast(Registration[]) *candidates); + return resolveAutowiredInstance!QualifierType(registration); + } + + private QualifierType resolveAutowiredInstance(QualifierType)(Registration registration) { + QualifierType instance; + if (!(cast(Registration[]) autowireStack).canFind(registration)) { + autowireStack ~= cast(shared(Registration)) registration; + instance = cast(QualifierType) registration.getInstance(new AutowireInstantiationContext()); + autowireStack = autowireStack[0 .. $-1]; + } else { + auto autowireContext = new AutowireInstantiationContext(); + autowireContext.autowireInstance = false; + instance = cast(QualifierType) registration.getInstance(autowireContext); + } + return instance; + } + + /** + * Resolve all dependencies registered to a super type. + * + * Returns: + * An array of autowired instances is returned. The order is undetermined. + * + * Examples: + * --- + * class Cat : Animal { ... } + * class Dog : Animal { ... } + * + * container.register!(Animal, Cat); + * container.register!(Animal, Dog); + * + * Animal[] animals = container.resolveAll!Animal; + * --- + */ + public RegistrationType[] resolveAll(RegistrationType)() { + RegistrationType[] instances; + TypeInfo resolveType = typeid(RegistrationType); + + auto qualifiedRegistrations = resolveType in registrations; + if (!qualifiedRegistrations) { + throw new ResolveException("Type not registered.", resolveType); + } + + foreach(registration ; cast(Registration[]) *qualifiedRegistrations) { + instances ~= resolveAutowiredInstance!RegistrationType(registration); + } + + return instances; + } + + 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); + } + + /** + * Clears all dependency registrations managed by this container. + */ + public void clearAllRegistrations() { + registrations.destroy(); + } + + /** + * Removes a registered dependency by type. + * + * A dependency can be removed either by super type or concrete type, depending on how they are registered. + * + * Examples: + * --- + * container.removeRegistration!Animal; + * --- + */ + public void removeRegistration(RegistrationType)() { + registrations.remove(typeid(RegistrationType)); + } + + /** + * Register dependencies through an application context. + * + * An application context allows you to fine-tune dependency set-up and instantiation. + * It is mostly used for dependencies which come from an external library or when you don't + * want to use annotations to set-up dependencies in your classes. + */ + public void registerContext(Context : ApplicationContext)() { + auto context = new Context(); + context.registerDependencies(this); + context.registerContextComponents(this); + this.register!(ApplicationContext, Context)().existingInstance(context); + autowire(this, context); + } + + /** + * Returns a global singleton instance of a dependency container. + */ + public static shared(DependencyContainer) getInstance() { + static shared DependencyContainer instance; + if (instance is null) { + instance = new DependencyContainer(); + } + return instance; + } +} diff --git a/source/poodinis/context.d b/source/poodinis/context.d index 8f67f58..f9850bb 100644 --- a/source/poodinis/context.d +++ b/source/poodinis/context.d @@ -1,68 +1,68 @@ -/** - * Contains the implementation of application context setup. - * - * Part of the Poodinis Dependency Injection framework. - * - * Authors: - * Mike Bierlee, m.bierlee@lostmoment.com - * Copyright: 2014-2015 Mike Bierlee - * License: - * 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.context; - -import poodinis.container; -import poodinis.registration; - -import std.traits; - -class ApplicationContext { - public void registerDependencies(shared(DependencyContainer) container) {} -} - -/** -* A component annotation is used for specifying which factory methods produce components in -* an application context. -*/ -struct Component {} - -/** -* This annotation allows you to specify by which super type the component should be registered. This -* enables you to use type-qualified alternatives for dependencies. -*/ -struct RegisterByType(Type) { - Type type; -} - -/** -* Components with the prototype registration will be scoped as dependencies which will created -* new instances every time they are resolved. The factory method will be called repeatedly. -*/ -struct Prototype {} - -public void registerContextComponents(ApplicationContextType : ApplicationContext)(ApplicationContextType context, shared(DependencyContainer) container) { - foreach (member ; __traits(allMembers, ApplicationContextType)) { - static if (hasUDA!(__traits(getMember, context, member), Component)) { - - auto factoryMethod = &__traits(getMember, context, member); - Registration registration = null; - auto createsSingleton = CreatesSingleton.yes; - - foreach(attribute; __traits(getAttributes, __traits(getMember, context, member))) { - static if (is(attribute == RegisterByType!T, T)) { - registration = container.register!(typeof(attribute.type), ReturnType!factoryMethod); - } else static if (__traits(isSame, attribute, Prototype)) { - createsSingleton = CreatesSingleton.no; - } - } - - if (registration is null) { - registration = container.register!(ReturnType!factoryMethod); - } - - registration.instanceFactory = new InstanceFactory(registration.instanceType, createsSingleton, null, factoryMethod); - } - } -} +/** + * Contains the implementation of application context setup. + * + * Part of the Poodinis Dependency Injection framework. + * + * Authors: + * Mike Bierlee, m.bierlee@lostmoment.com + * Copyright: 2014-2016 Mike Bierlee + * License: + * 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.context; + +import poodinis.container; +import poodinis.registration; + +import std.traits; + +class ApplicationContext { + public void registerDependencies(shared(DependencyContainer) container) {} +} + +/** +* A component annotation is used for specifying which factory methods produce components in +* an application context. +*/ +struct Component {} + +/** +* This annotation allows you to specify by which super type the component should be registered. This +* enables you to use type-qualified alternatives for dependencies. +*/ +struct RegisterByType(Type) { + Type type; +} + +/** +* Components with the prototype registration will be scoped as dependencies which will created +* new instances every time they are resolved. The factory method will be called repeatedly. +*/ +struct Prototype {} + +public void registerContextComponents(ApplicationContextType : ApplicationContext)(ApplicationContextType context, shared(DependencyContainer) container) { + foreach (member ; __traits(allMembers, ApplicationContextType)) { + static if (hasUDA!(__traits(getMember, context, member), Component)) { + + auto factoryMethod = &__traits(getMember, context, member); + Registration registration = null; + auto createsSingleton = CreatesSingleton.yes; + + foreach(attribute; __traits(getAttributes, __traits(getMember, context, member))) { + static if (is(attribute == RegisterByType!T, T)) { + registration = container.register!(typeof(attribute.type), ReturnType!factoryMethod); + } else static if (__traits(isSame, attribute, Prototype)) { + createsSingleton = CreatesSingleton.no; + } + } + + if (registration is null) { + registration = container.register!(ReturnType!factoryMethod); + } + + registration.instanceFactory = new InstanceFactory(registration.instanceType, createsSingleton, null, factoryMethod); + } + } +} diff --git a/source/poodinis/package.d b/source/poodinis/package.d index 94954e0..2c9c67f 100644 --- a/source/poodinis/package.d +++ b/source/poodinis/package.d @@ -1,17 +1,17 @@ -/** - * Package module for the Poodinis Dependency Injection framework. - * - * Authors: - * Mike Bierlee, m.bierlee@lostmoment.com - * Copyright: 2014-2015 Mike Bierlee - * License: - * 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; - -public import poodinis.autowire; -public import poodinis.container; -public import poodinis.registration; -public import poodinis.context; +/** + * Package module for the Poodinis Dependency Injection framework. + * + * Authors: + * Mike Bierlee, m.bierlee@lostmoment.com + * Copyright: 2014-2016 Mike Bierlee + * License: + * 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; + +public import poodinis.autowire; +public import poodinis.container; +public import poodinis.registration; +public import poodinis.context; diff --git a/source/poodinis/registration.d b/source/poodinis/registration.d index a538777..aa52796 100644 --- a/source/poodinis/registration.d +++ b/source/poodinis/registration.d @@ -1,145 +1,145 @@ -/** - * This module contains objects for defining and scoping dependency registrations. - * - * Part of the Poodinis Dependency Injection framework. - * - * Authors: - * Mike Bierlee, m.bierlee@lostmoment.com - * Copyright: 2014-2015 Mike Bierlee - * License: - * 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.registration; - -import std.typecons; -import std.exception; - -debug { - import std.stdio; - import std.string; -} - -class InstanceCreationException : Exception { - this(string message, string file = __FILE__, size_t line = __LINE__) { - super(message, file, line); - } -} - -class Registration { - private TypeInfo _registeredType = null; - private TypeInfo_Class _instanceType = null; - private Registration linkedRegistration; - - public @property registeredType() { - return _registeredType; - } - - public @property instanceType() { - return _instanceType; - } - - public InstanceFactory instanceFactory = null; - - this(TypeInfo registeredType, TypeInfo_Class instanceType) { - this._registeredType = registeredType; - this._instanceType = instanceType; - } - - public Object getInstance(InstantiationContext context = new InstantiationContext()) { - if (linkedRegistration !is null) { - return linkedRegistration.getInstance(context); - } - - - if (instanceFactory is null) { - throw new InstanceCreationException("No instance factory defined for registration of type " ~ registeredType.toString()); - } - - return instanceFactory.getInstance(); - } - - public Registration linkTo(Registration registration) { - this.linkedRegistration = registration; - return this; - } -} - -alias CreatesSingleton = Flag!"CreatesSingleton"; -alias InstanceFactoryMethod = Object delegate(); - -class InstanceFactory { - private TypeInfo_Class instanceType = null; - private Object instance = null; - private CreatesSingleton createsSingleton; - private InstanceFactoryMethod factoryMethod; - - this(TypeInfo_Class instanceType, CreatesSingleton createsSingleton = CreatesSingleton.yes, Object existingInstance = null, InstanceFactoryMethod factoryMethod = null) { - this.instanceType = instanceType; - this.createsSingleton = existingInstance !is null ? CreatesSingleton.yes : createsSingleton; - this.instance = existingInstance; - this.factoryMethod = factoryMethod !is null ? factoryMethod : &this.createInstance; - } - - public Object getInstance() { - if (createsSingleton && instance !is null) { - debug(poodinisVerbose) { - writeln(format("DEBUG: Existing instance returned of type %s", instanceType.toString())); - } - - return instance; - } - - debug(poodinisVerbose) { - writeln(format("DEBUG: Creating new instance of type %s", instanceType.toString())); - } - - instance = factoryMethod(); - return instance; - } - - private Object createInstance() { - enforce!InstanceCreationException(instanceType, "Instance type is not defined, cannot create instance without knowing its type."); - return instanceType.create(); - } -} - -/** - * Scopes registrations to return the same instance every time a given registration is resolved. - * - * Effectively makes the given registration a singleton. - */ -public Registration singleInstance(Registration registration) { - registration.instanceFactory = new InstanceFactory(registration.instanceType, CreatesSingleton.yes, null); - return registration; -} - -/** - * Scopes registrations to return a new instance every time the given registration is resolved. - */ -public Registration newInstance(Registration registration) { - registration.instanceFactory = new InstanceFactory(registration.instanceType, CreatesSingleton.no, null); - return registration; -} - -/** - * Scopes registrations to return the given instance every time the given registration is resolved. - */ -public Registration existingInstance(Registration registration, Object instance) { - registration.instanceFactory = new InstanceFactory(registration.instanceType, CreatesSingleton.yes, instance); - return registration; -} - -public string toConcreteTypeListString(Registration[] registrations) { - auto concreteTypeListString = ""; - foreach (registration ; registrations) { - if (concreteTypeListString.length > 0) { - concreteTypeListString ~= ", "; - } - concreteTypeListString ~= registration.instanceType.toString(); - } - return concreteTypeListString; -} - -class InstantiationContext {} +/** + * This module contains objects for defining and scoping dependency registrations. + * + * Part of the Poodinis Dependency Injection framework. + * + * Authors: + * Mike Bierlee, m.bierlee@lostmoment.com + * Copyright: 2014-2016 Mike Bierlee + * License: + * 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.registration; + +import std.typecons; +import std.exception; + +debug { + import std.stdio; + import std.string; +} + +class InstanceCreationException : Exception { + this(string message, string file = __FILE__, size_t line = __LINE__) { + super(message, file, line); + } +} + +class Registration { + private TypeInfo _registeredType = null; + private TypeInfo_Class _instanceType = null; + private Registration linkedRegistration; + + public @property registeredType() { + return _registeredType; + } + + public @property instanceType() { + return _instanceType; + } + + public InstanceFactory instanceFactory = null; + + this(TypeInfo registeredType, TypeInfo_Class instanceType) { + this._registeredType = registeredType; + this._instanceType = instanceType; + } + + public Object getInstance(InstantiationContext context = new InstantiationContext()) { + if (linkedRegistration !is null) { + return linkedRegistration.getInstance(context); + } + + + if (instanceFactory is null) { + throw new InstanceCreationException("No instance factory defined for registration of type " ~ registeredType.toString()); + } + + return instanceFactory.getInstance(); + } + + public Registration linkTo(Registration registration) { + this.linkedRegistration = registration; + return this; + } +} + +alias CreatesSingleton = Flag!"CreatesSingleton"; +alias InstanceFactoryMethod = Object delegate(); + +class InstanceFactory { + private TypeInfo_Class instanceType = null; + private Object instance = null; + private CreatesSingleton createsSingleton; + private InstanceFactoryMethod factoryMethod; + + this(TypeInfo_Class instanceType, CreatesSingleton createsSingleton = CreatesSingleton.yes, Object existingInstance = null, InstanceFactoryMethod factoryMethod = null) { + this.instanceType = instanceType; + this.createsSingleton = existingInstance !is null ? CreatesSingleton.yes : createsSingleton; + this.instance = existingInstance; + this.factoryMethod = factoryMethod !is null ? factoryMethod : &this.createInstance; + } + + public Object getInstance() { + if (createsSingleton && instance !is null) { + debug(poodinisVerbose) { + writeln(format("DEBUG: Existing instance returned of type %s", instanceType.toString())); + } + + return instance; + } + + debug(poodinisVerbose) { + writeln(format("DEBUG: Creating new instance of type %s", instanceType.toString())); + } + + instance = factoryMethod(); + return instance; + } + + private Object createInstance() { + enforce!InstanceCreationException(instanceType, "Instance type is not defined, cannot create instance without knowing its type."); + return instanceType.create(); + } +} + +/** + * Scopes registrations to return the same instance every time a given registration is resolved. + * + * Effectively makes the given registration a singleton. + */ +public Registration singleInstance(Registration registration) { + registration.instanceFactory = new InstanceFactory(registration.instanceType, CreatesSingleton.yes, null); + return registration; +} + +/** + * Scopes registrations to return a new instance every time the given registration is resolved. + */ +public Registration newInstance(Registration registration) { + registration.instanceFactory = new InstanceFactory(registration.instanceType, CreatesSingleton.no, null); + return registration; +} + +/** + * Scopes registrations to return the given instance every time the given registration is resolved. + */ +public Registration existingInstance(Registration registration, Object instance) { + registration.instanceFactory = new InstanceFactory(registration.instanceType, CreatesSingleton.yes, instance); + return registration; +} + +public string toConcreteTypeListString(Registration[] registrations) { + auto concreteTypeListString = ""; + foreach (registration ; registrations) { + if (concreteTypeListString.length > 0) { + concreteTypeListString ~= ", "; + } + concreteTypeListString ~= registration.instanceType.toString(); + } + return concreteTypeListString; +} + +class InstantiationContext {} diff --git a/test/poodinis/autowiretest.d b/test/poodinis/autowiretest.d index ac1db5e..65ef43c 100644 --- a/test/poodinis/autowiretest.d +++ b/test/poodinis/autowiretest.d @@ -1,191 +1,191 @@ -/** - * Poodinis Dependency Injection Framework - * Copyright 2014-2015 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. - */ - -import poodinis; - -import std.exception; - -version(unittest) { - class ComponentA {} - - class ComponentB { - public @Autowire ComponentA componentA; - } - - interface InterfaceA {} - - class ComponentC : InterfaceA {} - - class ComponentD { - public @Autowire InterfaceA componentC = null; - } - - class DummyAttribute{}; - - class ComponentE { - @DummyAttribute - public ComponentC componentC; - } - - class ComponentDeclarationCocktail { - alias noomer = int; - - @Autowire - public ComponentA componentA; - - public void doesNothing() { - } - - ~this(){ - } - } - - class ComponentX : InterfaceA {} - - class MonkeyShine { - @Autowire!ComponentX - public InterfaceA component; - } - - class BootstrapBootstrap { - @Autowire!ComponentX - public InterfaceA componentX; - - @Autowire!ComponentC - public InterfaceA componentC; - } - - class LordOfTheComponents { - @Autowire - public InterfaceA[] components; - } - class ComponentCharlie { - @Autowire - @AssignNewInstance - public ComponentA componentA; - } - - // Test autowiring concrete type to existing instance - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!ComponentA; - auto componentB = new ComponentB(); - container.autowire!(ComponentB)(componentB); - assert(componentB !is null, "Autowirable dependency failed to autowire"); - } - - // Test autowiring interface type to existing instance - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(InterfaceA, ComponentC); - auto componentD = new ComponentD(); - container.autowire!(ComponentD)(componentD); - assert(componentD.componentC !is null, "Autowirable dependency failed to autowire"); - } - - // Test autowiring will only happen once - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(InterfaceA, ComponentC).newInstance(); - auto componentD = new ComponentD(); - container.autowire!(ComponentD)(componentD); - auto expectedComponent = componentD.componentC; - container.autowire!(ComponentD)(componentD); - auto actualComponent = componentD.componentC; - assert(expectedComponent is actualComponent, "Autowiring the second time wired a different instance"); - } - - // Test autowiring unregistered type - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - auto componentD = new ComponentD(); - assertThrown!(ResolveException)(container.autowire!(ComponentD)(componentD), "Autowiring unregistered type should throw ResolveException"); - } - - // Test autowiring member with non-autowire attribute does not autowire - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - auto componentE = new ComponentE(); - container.autowire!ComponentE(componentE); - assert(componentE.componentC is null, "Autowiring should not occur for members with attributes other than @Autowire"); - } - - // Test autowire class with alias declaration - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!ComponentA; - auto componentDeclarationCocktail = new ComponentDeclarationCocktail(); - - container.autowire(componentDeclarationCocktail); - - assert(componentDeclarationCocktail.componentA !is null, "Autowiring class with non-assignable declarations failed"); - } - - // Test autowire class with qualifier - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(InterfaceA, ComponentC); - container.register!(InterfaceA, ComponentX); - auto componentX = container.resolve!(InterfaceA, ComponentX); - - auto monkeyShine = new MonkeyShine(); - container.autowire(monkeyShine); - - assert(monkeyShine.component is componentX, "Autowiring class with qualifier failed"); - } - - // Test autowire class with multiple qualifiers - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(InterfaceA, ComponentC); - container.register!(InterfaceA, ComponentX); - auto componentC = container.resolve!(InterfaceA, ComponentC); - auto componentX = container.resolve!(InterfaceA, ComponentX); - - auto bootstrapBootstrap = new BootstrapBootstrap(); - container.autowire(bootstrapBootstrap); - - assert(bootstrapBootstrap.componentX is componentX, "Autowiring class with multiple qualifiers failed"); - assert(bootstrapBootstrap.componentC is componentC, "Autowiring class with multiple qualifiers failed"); - } - - // Test getting instance from autowired registration will autowire instance - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!ComponentA; - - auto registration = new AutowiredRegistration!ComponentB(typeid(ComponentB), container).singleInstance(); - auto instance = cast(ComponentB) registration.getInstance(new AutowireInstantiationContext()); - - assert(instance.componentA !is null); - } - - // Test autowiring a dynamic array with all qualified types - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(InterfaceA, ComponentC); - container.register!(InterfaceA, ComponentX); - - auto lord = new LordOfTheComponents(); - container.autowire(lord); - - assert(lord.components.length == 2, "Dynamic array was not autowired"); - } - - // Test autowiring new instance of singleinstance registration with newInstance UDA - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!ComponentA; - - auto regularComponentA = container.resolve!ComponentA; - auto charlie = new ComponentCharlie(); - - container.autowire(charlie); - - assert(charlie.componentA !is regularComponentA, "Autowiring class with AssignNewInstance did not yield a different instance"); - } -} +/** + * Poodinis Dependency Injection Framework + * Copyright 2014-2016 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. + */ + +import poodinis; + +import std.exception; + +version(unittest) { + class ComponentA {} + + class ComponentB { + public @Autowire ComponentA componentA; + } + + interface InterfaceA {} + + class ComponentC : InterfaceA {} + + class ComponentD { + public @Autowire InterfaceA componentC = null; + } + + class DummyAttribute{}; + + class ComponentE { + @DummyAttribute + public ComponentC componentC; + } + + class ComponentDeclarationCocktail { + alias noomer = int; + + @Autowire + public ComponentA componentA; + + public void doesNothing() { + } + + ~this(){ + } + } + + class ComponentX : InterfaceA {} + + class MonkeyShine { + @Autowire!ComponentX + public InterfaceA component; + } + + class BootstrapBootstrap { + @Autowire!ComponentX + public InterfaceA componentX; + + @Autowire!ComponentC + public InterfaceA componentC; + } + + class LordOfTheComponents { + @Autowire + public InterfaceA[] components; + } + class ComponentCharlie { + @Autowire + @AssignNewInstance + public ComponentA componentA; + } + + // Test autowiring concrete type to existing instance + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!ComponentA; + auto componentB = new ComponentB(); + container.autowire!(ComponentB)(componentB); + assert(componentB !is null, "Autowirable dependency failed to autowire"); + } + + // Test autowiring interface type to existing instance + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(InterfaceA, ComponentC); + auto componentD = new ComponentD(); + container.autowire!(ComponentD)(componentD); + assert(componentD.componentC !is null, "Autowirable dependency failed to autowire"); + } + + // Test autowiring will only happen once + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(InterfaceA, ComponentC).newInstance(); + auto componentD = new ComponentD(); + container.autowire!(ComponentD)(componentD); + auto expectedComponent = componentD.componentC; + container.autowire!(ComponentD)(componentD); + auto actualComponent = componentD.componentC; + assert(expectedComponent is actualComponent, "Autowiring the second time wired a different instance"); + } + + // Test autowiring unregistered type + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + auto componentD = new ComponentD(); + assertThrown!(ResolveException)(container.autowire!(ComponentD)(componentD), "Autowiring unregistered type should throw ResolveException"); + } + + // Test autowiring member with non-autowire attribute does not autowire + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + auto componentE = new ComponentE(); + container.autowire!ComponentE(componentE); + assert(componentE.componentC is null, "Autowiring should not occur for members with attributes other than @Autowire"); + } + + // Test autowire class with alias declaration + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!ComponentA; + auto componentDeclarationCocktail = new ComponentDeclarationCocktail(); + + container.autowire(componentDeclarationCocktail); + + assert(componentDeclarationCocktail.componentA !is null, "Autowiring class with non-assignable declarations failed"); + } + + // Test autowire class with qualifier + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(InterfaceA, ComponentC); + container.register!(InterfaceA, ComponentX); + auto componentX = container.resolve!(InterfaceA, ComponentX); + + auto monkeyShine = new MonkeyShine(); + container.autowire(monkeyShine); + + assert(monkeyShine.component is componentX, "Autowiring class with qualifier failed"); + } + + // Test autowire class with multiple qualifiers + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(InterfaceA, ComponentC); + container.register!(InterfaceA, ComponentX); + auto componentC = container.resolve!(InterfaceA, ComponentC); + auto componentX = container.resolve!(InterfaceA, ComponentX); + + auto bootstrapBootstrap = new BootstrapBootstrap(); + container.autowire(bootstrapBootstrap); + + assert(bootstrapBootstrap.componentX is componentX, "Autowiring class with multiple qualifiers failed"); + assert(bootstrapBootstrap.componentC is componentC, "Autowiring class with multiple qualifiers failed"); + } + + // Test getting instance from autowired registration will autowire instance + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!ComponentA; + + auto registration = new AutowiredRegistration!ComponentB(typeid(ComponentB), container).singleInstance(); + auto instance = cast(ComponentB) registration.getInstance(new AutowireInstantiationContext()); + + assert(instance.componentA !is null); + } + + // Test autowiring a dynamic array with all qualified types + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(InterfaceA, ComponentC); + container.register!(InterfaceA, ComponentX); + + auto lord = new LordOfTheComponents(); + container.autowire(lord); + + assert(lord.components.length == 2, "Dynamic array was not autowired"); + } + + // Test autowiring new instance of singleinstance registration with newInstance UDA + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!ComponentA; + + auto regularComponentA = container.resolve!ComponentA; + auto charlie = new ComponentCharlie(); + + container.autowire(charlie); + + assert(charlie.componentA !is regularComponentA, "Autowiring class with AssignNewInstance did not yield a different instance"); + } +} diff --git a/test/poodinis/containertest.d b/test/poodinis/containertest.d index 663777b..905e086 100644 --- a/test/poodinis/containertest.d +++ b/test/poodinis/containertest.d @@ -1,588 +1,588 @@ -/** - * Poodinis Dependency Injection Framework - * Copyright 2014-2015 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. - */ - -import poodinis; - -import std.exception; -import core.thread; - -version(unittest) { - class TestContext : ApplicationContext { - public override void registerDependencies(shared(DependencyContainer) container) { - container.register!TestClass; - } - - @Component - public UnrelatedClass unrelated() { - return new UnrelatedClass(); - } - } - - class AutowiredTestContext : ApplicationContext { - - @Autowire - public UnrelatedClass unrelatedClass; - - @Component - public ClassWrapper wrapper() { - return new ClassWrapper(unrelatedClass); - } - } - - class ComplexAutowiredTestContext : ApplicationContext { - - @Autowire - public UnrelatedClass unrelatedClass; - - @Autowire - public ClassWrapper classWrapper; - - public override void registerDependencies(shared(DependencyContainer) container) { - container.register!UnrelatedClass; - } - - @Component - public ClassWrapper wrapper() { - return new ClassWrapper(unrelatedClass); - } - - @Component - public ClassWrapperWrapper wrapperWrapper() { - return new ClassWrapperWrapper(classWrapper); - } - - } - - interface TestInterface { - } - - class TestClass : TestInterface { - } - - class TestClassDeux : TestInterface { - @Autowire - public UnrelatedClass unrelated; - } - - 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 { - } - - class Spiders { - @Autowire - public TestInterface testMember; - } - - class Recursive { - @Autowire - public Recursive recursive; - } - - class Moolah {} - - class Wants { - @Autowire - public Moolah moolah; - } - - class John { - @Autowire - public Wants wants; - } - - class ClassWrapper { - public Object someClass; - - this(Object someClass) { - this.someClass = someClass; - } - } - - class ClassWrapperWrapper { - public ClassWrapper wrapper; - - this(ClassWrapper wrapper) { - this.wrapper = wrapper; - } - } - - // Test register concrete type - unittest { - shared(DependencyContainer) 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 { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(TestClass)(); - TestClass actualInstance = container.resolve!(TestClass)(); - 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 { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(TestInterface, TestClass)(); - TestInterface actualInstance = container.resolve!(TestInterface)(); - 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 { - shared(DependencyContainer) container = new DependencyContainer(); - assertThrown!ResolveException(container.resolve!(TestClass)(), "Resolving non-registered type does not fail"); - } - - // Test clear registrations - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(TestClass)(); - 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 { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(TestClass)().singleInstance(); - auto instance1 = container.resolve!(TestClass); - 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 { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(TestClass)().newInstance(); - auto instance1 = container.resolve!(TestClass); - 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 { - shared(DependencyContainer) container = new DependencyContainer(); - auto expectedInstance = new TestClass(); - container.register!(TestClass)().existingInstance(expectedInstance); - 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 { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!AutowiredClass; - container.register!ComponentClass; - auto componentInstance = container.resolve!ComponentClass; - auto autowiredInstance = container.resolve!AutowiredClass; - assert(componentInstance.autowiredClass is autowiredInstance, "Member is not autowired upon resolving"); - } - - // Test circular autowiring - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!ComponentMouse; - container.register!ComponentCat; - auto mouse = container.resolve!ComponentMouse; - 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 { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!TestClass; - container.removeRegistration!TestClass; - assertThrown!ResolveException(container.resolve!TestClass); - } - - // Test autowiring does not autowire member where instance is non-null - unittest { - shared(DependencyContainer) 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 { - shared(DependencyContainer) 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 { - shared(DependencyContainer) 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 { - shared(DependencyContainer) 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 - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!Banana; - container.register!Bittie; - container.register!(SuperInterface, SuperImplementation); - - SuperImplementation superInstance = cast(SuperImplementation) container.resolve!SuperInterface; - - assert(!(superInstance.banana is null), "Instance which was resolved by interface type was not autowired."); - } - - // Test reusing a container after clearing all registrations - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!Banana; - container.clearAllRegistrations(); - try { - container.resolve!Banana; - } catch (ResolveException e) { - container.register!Banana; - return; - } - assert(false); - } - - // Test register multiple concrete classess to same interface type - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(Color, Blue); - container.register!(Color, Red); - } - - // Test removing all registrations for type with multiple registrations. - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(Color, Blue); - container.register!(Color, Red); - container.removeRegistration!Color; - } - - // Test registering same registration again - unittest { - shared(DependencyContainer) 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 { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(Color, Blue); - container.register!(Color, Red); - try { - container.resolve!Color; - } catch (ResolveException e) { - return; - } - assert(false); - } - - // Test resolve registration with multiple qualifiers using a qualifier - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(Color, Blue); - 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"); - } - - // Test autowire of unqualified member typed by interface. - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!Spiders; - container.register!(TestInterface, TestClass); - - auto instance = container.resolve!Spiders; - - assert(!(instance is null), "Container failed to autowire member by interface"); - } - - // Register existing registration - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - - auto firstRegistration = container.register!TestClass; - auto secondRegistration = container.register!TestClass; - - assert(firstRegistration is secondRegistration, "Registering the same registration twice registers the dependencies twice."); - } - - // Register existing registration by supertype - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - - auto firstRegistration = container.register!(TestInterface, TestClass); - auto secondRegistration = container.register!(TestInterface, TestClass); - - assert(firstRegistration is secondRegistration, "Registering the same registration by super type twice registers the dependencies twice."); - } - - // Resolve dependency depending on itself - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!Recursive; - - auto instance = container.resolve!Recursive; - - assert(instance.recursive is instance, "Resolving dependency that depends on itself fails."); - assert(instance.recursive.recursive is instance, "Resolving dependency that depends on itself fails."); - } - - // Test autowire stack pop-back - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!Moolah; - container.register!Wants.newInstance(); - container.register!John; - - container.resolve!Wants; - auto john = container.resolve!John; - - assert(john.wants.moolah !is null, "Autowire stack did not clear entries properly"); - } - - // Test resolving registration registered in different thread - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - - auto thread = new Thread(delegate() { - container.register!TestClass; - }); - thread.start(); - thread.join(); - - container.resolve!TestClass; - } - - // Test resolving instance previously resolved in different thread - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - shared(TestClass) actualTestClass; - - container.register!TestClass; - - auto thread = new Thread(delegate() { - actualTestClass = cast(shared(TestClass)) container.resolve!TestClass; - }); - thread.start(); - thread.join(); - - shared(TestClass) expectedTestClass = cast(shared(TestClass)) container.resolve!TestClass; - - assert(expectedTestClass is actualTestClass, "Instance resolved in main thread is not the one resolved in thread"); - } - - // Test registering type with option DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(TestInterface, TestClass)(RegistrationOption.DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION); - - auto firstInstance = container.resolve!TestInterface; - assertThrown!ResolveException(container.resolve!TestClass); - } - - // Test registering type will register by contrete type by default - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(TestInterface, TestClass); - - auto firstInstance = container.resolve!TestInterface; - auto secondInstance = container.resolve!TestClass; - - assert(firstInstance is secondInstance); - } - - // Test resolving all registrations to an interface - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!(Color, Blue); - container.register!(Color, Red); - - auto colors = container.resolveAll!Color; - - assert(colors.length == 2, "resolveAll did not yield all instances of interface type"); - } - - // Test autowiring instances resolved in array - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!UnrelatedClass; - container.register!(TestInterface, TestClassDeux); - - auto instances = container.resolveAll!TestInterface; - auto instance = cast(TestClassDeux) instances[0]; - - assert(instance.unrelated !is null); - } - - // Test setting up simple dependencies through application context - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.registerContext!TestContext; - auto instance = container.resolve!TestClass; - - assert(instance !is null); - } - - // Test resolving dependency from registered application context - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.registerContext!TestContext; - auto instance = container.resolve!UnrelatedClass; - - assert(instance !is null); - } - - // Test autowiring application context - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.register!UnrelatedClass; - container.registerContext!AutowiredTestContext; - auto instance = container.resolve!ClassWrapper; - - assert(instance !is null); - assert(instance.someClass !is null); - } - - // Test autowiring application context with dependencies registered in same context - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.registerContext!ComplexAutowiredTestContext; - auto instance = container.resolve!ClassWrapperWrapper; - auto wrapper = container.resolve!ClassWrapper; - auto someClass = container.resolve!UnrelatedClass; - - assert(instance !is null); - assert(instance.wrapper is wrapper); - assert(instance.wrapper.someClass is someClass); - } - - // Test resolving registered context - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - container.registerContext!TestContext; - container.resolve!ApplicationContext; - } -} +/** + * Poodinis Dependency Injection Framework + * Copyright 2014-2016 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. + */ + +import poodinis; + +import std.exception; +import core.thread; + +version(unittest) { + class TestContext : ApplicationContext { + public override void registerDependencies(shared(DependencyContainer) container) { + container.register!TestClass; + } + + @Component + public UnrelatedClass unrelated() { + return new UnrelatedClass(); + } + } + + class AutowiredTestContext : ApplicationContext { + + @Autowire + public UnrelatedClass unrelatedClass; + + @Component + public ClassWrapper wrapper() { + return new ClassWrapper(unrelatedClass); + } + } + + class ComplexAutowiredTestContext : ApplicationContext { + + @Autowire + public UnrelatedClass unrelatedClass; + + @Autowire + public ClassWrapper classWrapper; + + public override void registerDependencies(shared(DependencyContainer) container) { + container.register!UnrelatedClass; + } + + @Component + public ClassWrapper wrapper() { + return new ClassWrapper(unrelatedClass); + } + + @Component + public ClassWrapperWrapper wrapperWrapper() { + return new ClassWrapperWrapper(classWrapper); + } + + } + + interface TestInterface { + } + + class TestClass : TestInterface { + } + + class TestClassDeux : TestInterface { + @Autowire + public UnrelatedClass unrelated; + } + + 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 { + } + + class Spiders { + @Autowire + public TestInterface testMember; + } + + class Recursive { + @Autowire + public Recursive recursive; + } + + class Moolah {} + + class Wants { + @Autowire + public Moolah moolah; + } + + class John { + @Autowire + public Wants wants; + } + + class ClassWrapper { + public Object someClass; + + this(Object someClass) { + this.someClass = someClass; + } + } + + class ClassWrapperWrapper { + public ClassWrapper wrapper; + + this(ClassWrapper wrapper) { + this.wrapper = wrapper; + } + } + + // Test register concrete type + unittest { + shared(DependencyContainer) 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 { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(TestClass)(); + TestClass actualInstance = container.resolve!(TestClass)(); + 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 { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(TestInterface, TestClass)(); + TestInterface actualInstance = container.resolve!(TestInterface)(); + 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 { + shared(DependencyContainer) container = new DependencyContainer(); + assertThrown!ResolveException(container.resolve!(TestClass)(), "Resolving non-registered type does not fail"); + } + + // Test clear registrations + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(TestClass)(); + 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 { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(TestClass)().singleInstance(); + auto instance1 = container.resolve!(TestClass); + 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 { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(TestClass)().newInstance(); + auto instance1 = container.resolve!(TestClass); + 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 { + shared(DependencyContainer) container = new DependencyContainer(); + auto expectedInstance = new TestClass(); + container.register!(TestClass)().existingInstance(expectedInstance); + 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 { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!AutowiredClass; + container.register!ComponentClass; + auto componentInstance = container.resolve!ComponentClass; + auto autowiredInstance = container.resolve!AutowiredClass; + assert(componentInstance.autowiredClass is autowiredInstance, "Member is not autowired upon resolving"); + } + + // Test circular autowiring + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!ComponentMouse; + container.register!ComponentCat; + auto mouse = container.resolve!ComponentMouse; + 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 { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!TestClass; + container.removeRegistration!TestClass; + assertThrown!ResolveException(container.resolve!TestClass); + } + + // Test autowiring does not autowire member where instance is non-null + unittest { + shared(DependencyContainer) 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 { + shared(DependencyContainer) 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 { + shared(DependencyContainer) 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 { + shared(DependencyContainer) 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 + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!Banana; + container.register!Bittie; + container.register!(SuperInterface, SuperImplementation); + + SuperImplementation superInstance = cast(SuperImplementation) container.resolve!SuperInterface; + + assert(!(superInstance.banana is null), "Instance which was resolved by interface type was not autowired."); + } + + // Test reusing a container after clearing all registrations + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!Banana; + container.clearAllRegistrations(); + try { + container.resolve!Banana; + } catch (ResolveException e) { + container.register!Banana; + return; + } + assert(false); + } + + // Test register multiple concrete classess to same interface type + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(Color, Blue); + container.register!(Color, Red); + } + + // Test removing all registrations for type with multiple registrations. + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(Color, Blue); + container.register!(Color, Red); + container.removeRegistration!Color; + } + + // Test registering same registration again + unittest { + shared(DependencyContainer) 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 { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(Color, Blue); + container.register!(Color, Red); + try { + container.resolve!Color; + } catch (ResolveException e) { + return; + } + assert(false); + } + + // Test resolve registration with multiple qualifiers using a qualifier + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(Color, Blue); + 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"); + } + + // Test autowire of unqualified member typed by interface. + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!Spiders; + container.register!(TestInterface, TestClass); + + auto instance = container.resolve!Spiders; + + assert(!(instance is null), "Container failed to autowire member by interface"); + } + + // Register existing registration + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + + auto firstRegistration = container.register!TestClass; + auto secondRegistration = container.register!TestClass; + + assert(firstRegistration is secondRegistration, "Registering the same registration twice registers the dependencies twice."); + } + + // Register existing registration by supertype + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + + auto firstRegistration = container.register!(TestInterface, TestClass); + auto secondRegistration = container.register!(TestInterface, TestClass); + + assert(firstRegistration is secondRegistration, "Registering the same registration by super type twice registers the dependencies twice."); + } + + // Resolve dependency depending on itself + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!Recursive; + + auto instance = container.resolve!Recursive; + + assert(instance.recursive is instance, "Resolving dependency that depends on itself fails."); + assert(instance.recursive.recursive is instance, "Resolving dependency that depends on itself fails."); + } + + // Test autowire stack pop-back + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!Moolah; + container.register!Wants.newInstance(); + container.register!John; + + container.resolve!Wants; + auto john = container.resolve!John; + + assert(john.wants.moolah !is null, "Autowire stack did not clear entries properly"); + } + + // Test resolving registration registered in different thread + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + + auto thread = new Thread(delegate() { + container.register!TestClass; + }); + thread.start(); + thread.join(); + + container.resolve!TestClass; + } + + // Test resolving instance previously resolved in different thread + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + shared(TestClass) actualTestClass; + + container.register!TestClass; + + auto thread = new Thread(delegate() { + actualTestClass = cast(shared(TestClass)) container.resolve!TestClass; + }); + thread.start(); + thread.join(); + + shared(TestClass) expectedTestClass = cast(shared(TestClass)) container.resolve!TestClass; + + assert(expectedTestClass is actualTestClass, "Instance resolved in main thread is not the one resolved in thread"); + } + + // Test registering type with option DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(TestInterface, TestClass)(RegistrationOption.DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION); + + auto firstInstance = container.resolve!TestInterface; + assertThrown!ResolveException(container.resolve!TestClass); + } + + // Test registering type will register by contrete type by default + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(TestInterface, TestClass); + + auto firstInstance = container.resolve!TestInterface; + auto secondInstance = container.resolve!TestClass; + + assert(firstInstance is secondInstance); + } + + // Test resolving all registrations to an interface + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(Color, Blue); + container.register!(Color, Red); + + auto colors = container.resolveAll!Color; + + assert(colors.length == 2, "resolveAll did not yield all instances of interface type"); + } + + // Test autowiring instances resolved in array + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!UnrelatedClass; + container.register!(TestInterface, TestClassDeux); + + auto instances = container.resolveAll!TestInterface; + auto instance = cast(TestClassDeux) instances[0]; + + assert(instance.unrelated !is null); + } + + // Test setting up simple dependencies through application context + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.registerContext!TestContext; + auto instance = container.resolve!TestClass; + + assert(instance !is null); + } + + // Test resolving dependency from registered application context + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.registerContext!TestContext; + auto instance = container.resolve!UnrelatedClass; + + assert(instance !is null); + } + + // Test autowiring application context + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!UnrelatedClass; + container.registerContext!AutowiredTestContext; + auto instance = container.resolve!ClassWrapper; + + assert(instance !is null); + assert(instance.someClass !is null); + } + + // Test autowiring application context with dependencies registered in same context + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.registerContext!ComplexAutowiredTestContext; + auto instance = container.resolve!ClassWrapperWrapper; + auto wrapper = container.resolve!ClassWrapper; + auto someClass = container.resolve!UnrelatedClass; + + assert(instance !is null); + assert(instance.wrapper is wrapper); + assert(instance.wrapper.someClass is someClass); + } + + // Test resolving registered context + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.registerContext!TestContext; + container.resolve!ApplicationContext; + } +} diff --git a/test/poodinis/contexttest.d b/test/poodinis/contexttest.d index 2d3c2f5..166a55b 100644 --- a/test/poodinis/contexttest.d +++ b/test/poodinis/contexttest.d @@ -1,141 +1,141 @@ -/** - * Poodinis Dependency Injection Framework - * Copyright 2014-2015 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. - */ - -import poodinis; - -import std.exception; - -version(unittest) { - - interface Fruit { - string getShape(); - } - - interface Animal { - string getYell(); - } - - class Banana { - public string color; - - this(string color) { - this.color = color; - } - } - - class Apple {} - - class Pear : Fruit { - public override string getShape() { - return "Pear shaped"; - } - } - - class Rabbit : Animal { - public override string getYell() { - return "Squeeeeeel"; - } - } - - class Wolf : Animal { - public override string getYell() { - return "Wooooooooooo"; - } - } - - class PieChart {} - - class TestContext : ApplicationContext { - - @Component - public Banana banana() { - return new Banana("Yellow"); - } - - public Apple apple() { - return new Apple(); - } - - @Component - @RegisterByType!Fruit - public Pear pear() { - return new Pear(); - } - - @Component - @RegisterByType!Animal - public Rabbit rabbit() { - return new Rabbit(); - } - - @Component - @RegisterByType!Animal - public Wolf wolf() { - return new Wolf(); - } - - @Component - @Prototype - public PieChart pieChart() { - return new PieChart(); - } - } - - //Test register component registrations from context - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - auto context = new TestContext(); - context.registerContextComponents(container); - auto bananaInstance = container.resolve!Banana; - - assert(bananaInstance.color == "Yellow"); - } - - //Test non-annotated methods are not registered - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - auto context = new TestContext(); - context.registerContextComponents(container); - assertThrown!ResolveException(container.resolve!Apple); - } - - //Test register component by base type - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - auto context = new TestContext(); - context.registerContextComponents(container); - auto instance = container.resolve!Fruit; - assert(instance.getShape() == "Pear shaped"); - } - - //Test register components with multiple candidates - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - auto context = new TestContext(); - context.registerContextComponents(container); - - auto rabbit = container.resolve!(Animal, Rabbit); - assert(rabbit.getYell() == "Squeeeeeel"); - - auto wolf = container.resolve!(Animal, Wolf); - assert(wolf.getYell() == "Wooooooooooo"); - } - - //Test register component as prototype - unittest { - shared(DependencyContainer) container = new DependencyContainer(); - auto context = new TestContext(); - context.registerContextComponents(container); - - auto firstInstance = container.resolve!PieChart; - auto secondInstance = container.resolve!PieChart; - - assert(firstInstance !is null && secondInstance !is null); - assert(firstInstance !is secondInstance); - } - -} +/** + * Poodinis Dependency Injection Framework + * Copyright 2014-2016 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. + */ + +import poodinis; + +import std.exception; + +version(unittest) { + + interface Fruit { + string getShape(); + } + + interface Animal { + string getYell(); + } + + class Banana { + public string color; + + this(string color) { + this.color = color; + } + } + + class Apple {} + + class Pear : Fruit { + public override string getShape() { + return "Pear shaped"; + } + } + + class Rabbit : Animal { + public override string getYell() { + return "Squeeeeeel"; + } + } + + class Wolf : Animal { + public override string getYell() { + return "Wooooooooooo"; + } + } + + class PieChart {} + + class TestContext : ApplicationContext { + + @Component + public Banana banana() { + return new Banana("Yellow"); + } + + public Apple apple() { + return new Apple(); + } + + @Component + @RegisterByType!Fruit + public Pear pear() { + return new Pear(); + } + + @Component + @RegisterByType!Animal + public Rabbit rabbit() { + return new Rabbit(); + } + + @Component + @RegisterByType!Animal + public Wolf wolf() { + return new Wolf(); + } + + @Component + @Prototype + public PieChart pieChart() { + return new PieChart(); + } + } + + //Test register component registrations from context + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + auto context = new TestContext(); + context.registerContextComponents(container); + auto bananaInstance = container.resolve!Banana; + + assert(bananaInstance.color == "Yellow"); + } + + //Test non-annotated methods are not registered + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + auto context = new TestContext(); + context.registerContextComponents(container); + assertThrown!ResolveException(container.resolve!Apple); + } + + //Test register component by base type + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + auto context = new TestContext(); + context.registerContextComponents(container); + auto instance = container.resolve!Fruit; + assert(instance.getShape() == "Pear shaped"); + } + + //Test register components with multiple candidates + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + auto context = new TestContext(); + context.registerContextComponents(container); + + auto rabbit = container.resolve!(Animal, Rabbit); + assert(rabbit.getYell() == "Squeeeeeel"); + + auto wolf = container.resolve!(Animal, Wolf); + assert(wolf.getYell() == "Wooooooooooo"); + } + + //Test register component as prototype + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + auto context = new TestContext(); + context.registerContextComponents(container); + + auto firstInstance = container.resolve!PieChart; + auto secondInstance = container.resolve!PieChart; + + assert(firstInstance !is null && secondInstance !is null); + assert(firstInstance !is secondInstance); + } + +} diff --git a/test/poodinis/registrationtest.d b/test/poodinis/registrationtest.d index 0723e25..6dc198b 100644 --- a/test/poodinis/registrationtest.d +++ b/test/poodinis/registrationtest.d @@ -1,123 +1,123 @@ -/** - * Poodinis Dependency Injection Framework - * Copyright 2014-2015 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. - */ - -import poodinis; - -import std.exception; - -version(unittest) { - class TestType {} - - interface TestInterface {} - - class TestImplementation : TestInterface { - public string someContent = ""; - } - - // Test getting instance without scope defined throws exception - unittest { - Registration registration = new Registration(typeid(TestType), null); - assertThrown!(InstanceCreationException)(registration.getInstance()); - } - - // Test set single instance scope using scope setter - unittest { - Registration registration = new Registration(null, typeid(TestType)); - auto chainedRegistration = registration.singleInstance(); - auto instance1 = registration.getInstance(); - auto instance2 = registration.getInstance(); - assert(instance1 is instance2, "Registration with single instance scope did not return the same instance"); - assert(registration is chainedRegistration, "Registration returned by scope setting is not the same as the registration being set"); - } - - // Test set new instance scope using scope setter - unittest { - Registration registration = new Registration(null, typeid(TestType)); - auto chainedRegistration = registration.newInstance(); - auto instance1 = registration.getInstance(); - auto instance2 = registration.getInstance(); - assert(instance1 !is instance2, "Registration with new instance scope did not return a different instance"); - assert(registration is chainedRegistration, "Registration returned by scope setting is not the same as the registration being set"); - } - - // Test set existing instance scope using scope setter - unittest { - Registration registration = new Registration(null, null); - auto expectedInstance = new TestType(); - auto chainedRegistration = registration.existingInstance(expectedInstance); - auto actualInstance = registration.getInstance(); - assert(expectedInstance is expectedInstance, "Registration with existing instance scope did not return the same instance"); - assert(registration is chainedRegistration, "Registration returned by scope setting is not the same as the registration being set"); - } - - // Test linking registrations - unittest { - Registration firstRegistration = new Registration(typeid(TestInterface), typeid(TestImplementation)).singleInstance(); - Registration secondRegistration = new Registration(typeid(TestImplementation), typeid(TestImplementation)).singleInstance().linkTo(firstRegistration); - - auto firstInstance = firstRegistration.getInstance(); - auto secondInstance = secondRegistration.getInstance(); - - assert(firstInstance is secondInstance); - } - - // Test instance factory with singletons - unittest { - auto factory = new InstanceFactory(typeid(TestImplementation), CreatesSingleton.yes, null); - auto instanceOne = factory.getInstance(); - auto instanceTwo = factory.getInstance(); - - assert(instanceOne !is null, "Created factory instance is null"); - assert(instanceOne is instanceTwo, "Created factory instance is not the same"); - } - - // Test instance factory with new instances - unittest { - auto factory = new InstanceFactory(typeid(TestImplementation), CreatesSingleton.no, null); - auto instanceOne = factory.getInstance(); - auto instanceTwo = factory.getInstance(); - - assert(instanceOne !is null, "Created factory instance is null"); - assert(instanceOne !is instanceTwo, "Created factory instance is the same"); - } - - // Test instance factory with existing instances - unittest { - auto existingInstance = new TestImplementation(); - auto factory = new InstanceFactory(typeid(TestImplementation), CreatesSingleton.yes, existingInstance); - auto instanceOne = factory.getInstance(); - auto instanceTwo = factory.getInstance(); - - assert(instanceOne is existingInstance, "Created factory instance is not the existing instance"); - assert(instanceTwo is existingInstance, "Created factory instance is not the existing instance when called again"); - } - - // Test instance factory with existing instances when setting singleton flag to "no" - unittest { - auto existingInstance = new TestImplementation(); - auto factory = new InstanceFactory(typeid(TestImplementation), CreatesSingleton.no, existingInstance); - auto instance = factory.getInstance(); - - assert(instance is existingInstance, "Created factory instance is not the existing instance"); - } - - // Test creating instance using custom factory method - unittest { - Object factoryMethod() { - auto instance = new TestImplementation(); - instance.someContent = "Ducks!"; - return instance; - } - - auto factory = new InstanceFactory(null, CreatesSingleton.yes, null, &factoryMethod); - auto instance = cast(TestImplementation) factory.getInstance(); - - assert(instance !is null, "No instance was created by factory or could not be cast to expected type"); - assert(instance.someContent == "Ducks!"); - } - -} +/** + * Poodinis Dependency Injection Framework + * Copyright 2014-2016 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. + */ + +import poodinis; + +import std.exception; + +version(unittest) { + class TestType {} + + interface TestInterface {} + + class TestImplementation : TestInterface { + public string someContent = ""; + } + + // Test getting instance without scope defined throws exception + unittest { + Registration registration = new Registration(typeid(TestType), null); + assertThrown!(InstanceCreationException)(registration.getInstance()); + } + + // Test set single instance scope using scope setter + unittest { + Registration registration = new Registration(null, typeid(TestType)); + auto chainedRegistration = registration.singleInstance(); + auto instance1 = registration.getInstance(); + auto instance2 = registration.getInstance(); + assert(instance1 is instance2, "Registration with single instance scope did not return the same instance"); + assert(registration is chainedRegistration, "Registration returned by scope setting is not the same as the registration being set"); + } + + // Test set new instance scope using scope setter + unittest { + Registration registration = new Registration(null, typeid(TestType)); + auto chainedRegistration = registration.newInstance(); + auto instance1 = registration.getInstance(); + auto instance2 = registration.getInstance(); + assert(instance1 !is instance2, "Registration with new instance scope did not return a different instance"); + assert(registration is chainedRegistration, "Registration returned by scope setting is not the same as the registration being set"); + } + + // Test set existing instance scope using scope setter + unittest { + Registration registration = new Registration(null, null); + auto expectedInstance = new TestType(); + auto chainedRegistration = registration.existingInstance(expectedInstance); + auto actualInstance = registration.getInstance(); + assert(expectedInstance is expectedInstance, "Registration with existing instance scope did not return the same instance"); + assert(registration is chainedRegistration, "Registration returned by scope setting is not the same as the registration being set"); + } + + // Test linking registrations + unittest { + Registration firstRegistration = new Registration(typeid(TestInterface), typeid(TestImplementation)).singleInstance(); + Registration secondRegistration = new Registration(typeid(TestImplementation), typeid(TestImplementation)).singleInstance().linkTo(firstRegistration); + + auto firstInstance = firstRegistration.getInstance(); + auto secondInstance = secondRegistration.getInstance(); + + assert(firstInstance is secondInstance); + } + + // Test instance factory with singletons + unittest { + auto factory = new InstanceFactory(typeid(TestImplementation), CreatesSingleton.yes, null); + auto instanceOne = factory.getInstance(); + auto instanceTwo = factory.getInstance(); + + assert(instanceOne !is null, "Created factory instance is null"); + assert(instanceOne is instanceTwo, "Created factory instance is not the same"); + } + + // Test instance factory with new instances + unittest { + auto factory = new InstanceFactory(typeid(TestImplementation), CreatesSingleton.no, null); + auto instanceOne = factory.getInstance(); + auto instanceTwo = factory.getInstance(); + + assert(instanceOne !is null, "Created factory instance is null"); + assert(instanceOne !is instanceTwo, "Created factory instance is the same"); + } + + // Test instance factory with existing instances + unittest { + auto existingInstance = new TestImplementation(); + auto factory = new InstanceFactory(typeid(TestImplementation), CreatesSingleton.yes, existingInstance); + auto instanceOne = factory.getInstance(); + auto instanceTwo = factory.getInstance(); + + assert(instanceOne is existingInstance, "Created factory instance is not the existing instance"); + assert(instanceTwo is existingInstance, "Created factory instance is not the existing instance when called again"); + } + + // Test instance factory with existing instances when setting singleton flag to "no" + unittest { + auto existingInstance = new TestImplementation(); + auto factory = new InstanceFactory(typeid(TestImplementation), CreatesSingleton.no, existingInstance); + auto instance = factory.getInstance(); + + assert(instance is existingInstance, "Created factory instance is not the existing instance"); + } + + // Test creating instance using custom factory method + unittest { + Object factoryMethod() { + auto instance = new TestImplementation(); + instance.someContent = "Ducks!"; + return instance; + } + + auto factory = new InstanceFactory(null, CreatesSingleton.yes, null, &factoryMethod); + auto instance = cast(TestImplementation) factory.getInstance(); + + assert(instance !is null, "No instance was created by factory or could not be cast to expected type"); + assert(instance.someContent == "Ducks!"); + } + +}