From 7bd0795b3fa3eafe4f5a9ac45f96d9af8914d352 Mon Sep 17 00:00:00 2001 From: Mike Bierlee Date: Wed, 6 Jan 2016 20:28:25 +0100 Subject: [PATCH] Fix line delimiters --- CHANGES.md | 160 ++-- README.md | 138 ++-- TUTORIAL.md | 396 +++++----- example/annotations/app.d | 100 +-- example/applicationcontext/app.d | 136 ++-- example/arraycompletion/app.d | 108 +-- example/qualifiers/app.d | 114 +-- example/quickstart/app.d | 48 +- source/poodinis/autowire.d | 412 +++++------ source/poodinis/container.d | 722 +++++++++--------- source/poodinis/context.d | 136 ++-- source/poodinis/package.d | 34 +- source/poodinis/registration.d | 290 ++++---- test/poodinis/autowiretest.d | 382 +++++----- test/poodinis/containertest.d | 1176 +++++++++++++++--------------- test/poodinis/contexttest.d | 282 +++---- test/poodinis/registrationtest.d | 246 +++---- 17 files changed, 2440 insertions(+), 2440 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4692848..2922d34 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,80 +1,80 @@ -Poodinis Changelog -================== -**Version 6.0.0** -* CHANGE registration scopes are replaced by a single factory implementation. If you were not doing anything with the internal scope mechanism, you -should not be affected by this change. -* ADD application contexts. You can register dependencies within an application context which allow you to fine-tune the creation of dependency instances. -* CHANGE all public poodinis imports to private. This should not affect you if you use the package import "poodinis" instead of individual modules. -* REMOVE deprecated ADD_CONCRETE_TYPE_REGISTRATION registration option. -* REMOVE deprecated RegistrationOptions alias. - -**Version 5.0.0** -* DEPRECATE ADD_CONCRETE_TYPE_REGISTRATION registration option. It basically does nothing anymore. See next point. -* CHANGE adding registrations by super type always registers them by concrete type as well now. (Previously done with ADD_CONCRETE_TYPE_REGISTRATION). See DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION for the reverse behaviour. -* CHANGE RegistrationOptions enum name to RegistrationOption -* DEPRECATE Usage of RegistrationOptions, please use RegistrationOption instead. - -**Version 4.0.0** -* REMOVE deprecated module "dependency.d" - -**Version 3.0.0** -This version is only compatible with DMD 2.068.0 or higher! -* ADD UDA which always resolved a new instance to an autowired member, regardless of registration scope. - -**Version 2.2.0** -* ADD canonical package module "package.d". Use "import poodinis;" to import the project. -* DEPRECATE module "dependency.d". Please use the canonical package module. See previous point. -* ADD autowiring of dynamic arrays. All registered instances of the element type of the array will be assigned to it. - -**Version 2.1.0** -* ADD option for registering a class by concrete type when registering that class by supertype. - -**Version 2.0.0** -This version introduces changes which might be incompatible with your current codebase -* CHANGE dependency container to be synchronized. Sharing a dependency container between threads is now possible. -The implication is that all dependency container instances must be shared now. -You don't have to change anything if you were only using the singleton dependency container. - -**Version 1.0.0** -This version introduces changes which are incompatible with previous versions -* REMOVE deprecated autowire constructor -* REMOVE deprecated container alias -* ADD documentation for public API -* REMOVE @Autowired UDA. Use @Autowire instead. -* ADD quickstart from readme to compilable example project. -* ADD example project for the use of qualifiers - -**Version 0.3.1** -* FIX issue where autowiring members which are declared by interface or supertype would get autowired incorrectly. - -**Version 0.3.0** -* ADD alternative workaround to readme for autowire limitation -* CHANGE returning of resolved instances by returning them by qualifier type instead -* ADD debug specifier to reduce verbosity of debug output - -**Version 0.2.0** -* ADD ability to register type with multiple concrete types. They can be correctly resolved using qualifiers. -* DEPRECATE template for autowiring in constructor. This workaround is buggy. Use qualifiers instead. - -**Version 0.1.4** -* Make Poodinis compatible with D 2.066.0 and DUB 0.9.22 -* FIX incorrect clearing of registrations -This release should be backwards compatible with the previous versions of D and DUB, but please note that there are no more separate -configurations for release and debug builds. You have to specify a build type in DUB. - -**Version 0.1.3** -* ADD global autowire function for convenience -* CHANGE workaround to be more simple -* FIX autowiring classes which contain non-symbolic declarations such as aliases. As a result, only variables are attempted to be autowired. - -**Version 0.1.2** -* ADD workaround for failing to autowire types registered by supertype or interface - -**Version 0.1.1** -* FIX: Also auto-wire members from base classes - -**Version 0.1.0** -* Initial open-source release -* ADD support for registering and resolving -* ADD registration scopes -* ADD autowiring +Poodinis Changelog +================== +**Version 6.0.0** +* CHANGE registration scopes are replaced by a single factory implementation. If you were not doing anything with the internal scope mechanism, you +should not be affected by this change. +* ADD application contexts. You can register dependencies within an application context which allow you to fine-tune the creation of dependency instances. +* CHANGE all public poodinis imports to private. This should not affect you if you use the package import "poodinis" instead of individual modules. +* REMOVE deprecated ADD_CONCRETE_TYPE_REGISTRATION registration option. +* REMOVE deprecated RegistrationOptions alias. + +**Version 5.0.0** +* DEPRECATE ADD_CONCRETE_TYPE_REGISTRATION registration option. It basically does nothing anymore. See next point. +* CHANGE adding registrations by super type always registers them by concrete type as well now. (Previously done with ADD_CONCRETE_TYPE_REGISTRATION). See DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION for the reverse behaviour. +* CHANGE RegistrationOptions enum name to RegistrationOption +* DEPRECATE Usage of RegistrationOptions, please use RegistrationOption instead. + +**Version 4.0.0** +* REMOVE deprecated module "dependency.d" + +**Version 3.0.0** +This version is only compatible with DMD 2.068.0 or higher! +* ADD UDA which always resolved a new instance to an autowired member, regardless of registration scope. + +**Version 2.2.0** +* ADD canonical package module "package.d". Use "import poodinis;" to import the project. +* DEPRECATE module "dependency.d". Please use the canonical package module. See previous point. +* ADD autowiring of dynamic arrays. All registered instances of the element type of the array will be assigned to it. + +**Version 2.1.0** +* ADD option for registering a class by concrete type when registering that class by supertype. + +**Version 2.0.0** +This version introduces changes which might be incompatible with your current codebase +* CHANGE dependency container to be synchronized. Sharing a dependency container between threads is now possible. +The implication is that all dependency container instances must be shared now. +You don't have to change anything if you were only using the singleton dependency container. + +**Version 1.0.0** +This version introduces changes which are incompatible with previous versions +* REMOVE deprecated autowire constructor +* REMOVE deprecated container alias +* ADD documentation for public API +* REMOVE @Autowired UDA. Use @Autowire instead. +* ADD quickstart from readme to compilable example project. +* ADD example project for the use of qualifiers + +**Version 0.3.1** +* FIX issue where autowiring members which are declared by interface or supertype would get autowired incorrectly. + +**Version 0.3.0** +* ADD alternative workaround to readme for autowire limitation +* CHANGE returning of resolved instances by returning them by qualifier type instead +* ADD debug specifier to reduce verbosity of debug output + +**Version 0.2.0** +* ADD ability to register type with multiple concrete types. They can be correctly resolved using qualifiers. +* DEPRECATE template for autowiring in constructor. This workaround is buggy. Use qualifiers instead. + +**Version 0.1.4** +* Make Poodinis compatible with D 2.066.0 and DUB 0.9.22 +* FIX incorrect clearing of registrations +This release should be backwards compatible with the previous versions of D and DUB, but please note that there are no more separate +configurations for release and debug builds. You have to specify a build type in DUB. + +**Version 0.1.3** +* ADD global autowire function for convenience +* CHANGE workaround to be more simple +* FIX autowiring classes which contain non-symbolic declarations such as aliases. As a result, only variables are attempted to be autowired. + +**Version 0.1.2** +* ADD workaround for failing to autowire types registered by supertype or interface + +**Version 0.1.1** +* FIX: Also auto-wire members from base classes + +**Version 0.1.0** +* Initial open-source release +* ADD support for registering and resolving +* ADD registration scopes +* ADD autowiring diff --git a/README.md b/README.md index c45e937..89fe1e1 100644 --- a/README.md +++ b/README.md @@ -1,69 +1,69 @@ -Poodinis Dependency Injection Framework -======================================= -Version 6.0.0 -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) - -Poodinis is a dependency injection framework for the D programming language. It is inspired by the [Spring Framework] and [Hypodermic] IoC container for C++. Poodinis supports registering and resolving classes either by concrete type or interface. Automatic injection of dependencies is supported through the use of UDAs (Referred to as autowiring). - -Developed for D 2.069.2 -Uses the Phobos standard library -Can be built with DUB 0.9.24 - -History -------- -For a full overview of changes, see [CHANGES.md](CHANGES.md) - -Getting started ---------------- -###DUB Dependency -See the Poodinis [DUB project page] for instructions on how to include Poodinis into your project. - -###Quickstart -The following example shows the typical usage of Poodinis: -```d -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; -} -``` -For more examples, see the [examples](example) directory. - -###Tutorial -For an extended tutorial walking you through all functionality offered by Poodinis, see [TUTORIAL.md](TUTORIAL.md) - -Documentation -------------- -You can generate Public API documentation from the source code using DUB: -``` -dub build --build=ddox -``` -The documentation can then be found in docs/ - -Future Work ------------ -* Component scan (auto-registration) -* Phobos collections autowiring -* Constructor injection -* Named qualifiers - -[Spring Framework]: http://projects.spring.io/spring-framework/ -[Hypodermic]: https://github.com/ybainier/hypodermic/ -[DUB]: http://code.dlang.org/ -[DUB project page]: http://code.dlang.org/packages/poodinis -[Github issue tracker]: https://github.com/mbierlee/poodinis/issues +Poodinis Dependency Injection Framework +======================================= +Version 6.0.0 +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) + +Poodinis is a dependency injection framework for the D programming language. It is inspired by the [Spring Framework] and [Hypodermic] IoC container for C++. Poodinis supports registering and resolving classes either by concrete type or interface. Automatic injection of dependencies is supported through the use of UDAs (Referred to as autowiring). + +Developed for D 2.069.2 +Uses the Phobos standard library +Can be built with DUB 0.9.24 + +History +------- +For a full overview of changes, see [CHANGES.md](CHANGES.md) + +Getting started +--------------- +###DUB Dependency +See the Poodinis [DUB project page] for instructions on how to include Poodinis into your project. + +###Quickstart +The following example shows the typical usage of Poodinis: +```d +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; +} +``` +For more examples, see the [examples](example) directory. + +###Tutorial +For an extended tutorial walking you through all functionality offered by Poodinis, see [TUTORIAL.md](TUTORIAL.md) + +Documentation +------------- +You can generate Public API documentation from the source code using DUB: +``` +dub build --build=ddox +``` +The documentation can then be found in docs/ + +Future Work +----------- +* Component scan (auto-registration) +* Phobos collections autowiring +* Constructor injection +* Named qualifiers + +[Spring Framework]: http://projects.spring.io/spring-framework/ +[Hypodermic]: https://github.com/ybainier/hypodermic/ +[DUB]: http://code.dlang.org/ +[DUB project page]: http://code.dlang.org/packages/poodinis +[Github issue tracker]: https://github.com/mbierlee/poodinis/issues diff --git a/TUTORIAL.md b/TUTORIAL.md index b806898..dc29180 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -1,199 +1,199 @@ -Poodinis Tutorial -================= -This tutorial will give you an overview of all functionality offered by Poodinis and how to use them. - -The container -------------- -To register a class, a new dependency container must be instantiated: -```d -// Register a private container -auto dependencies = new DependencyContainer(); -// Or use the singleton container -dependencies = DependencyContainer.getInstance(); -``` -###Registering dependencies -To make dependencies available, they have to be registered: -```d -// Register concrete class -dependencies.register!ExampleClass; -// Register by super type -dependencies.register!(ExampleInterface, ExampleClass); -``` -In the above example, dependencies on the concrete class and interface will resolve an instance of class ExampleClass. A dependency registered by super type will automatically be registered by concrete type. - -Resolving dependencies ----------------------- -To manually resolve a dependency, all you have to do is resolve the dependency's type using the container in which it is registered: -```d -auto exampleClassInstance = dependencies.resolve!ExampleClass; -``` -If the class is registered by interface and not by concrete type, you can still resolve the class by concrete type: - -```d -auto exampleClassInstance = dependencies.resolve!ExampleInterface; -auto exampleClassInstance2 = dependencies.resolve!ExampleClass; -assert(exampleClassInstance is exampleClassInstance2); -``` -If you want to prevent registrations from being both registered by interface and concrete type, use the DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION option when registering: -```d -dependencies.register!(ExampleInterface, ExampleClass)(RegistrationOptions.DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION); -auto exampleClassInstance = dependencies.resolve!ExampleInterface; -auto exampleClassInstance2 = dependencies.resolve!ExampleClass; // A ResolveException is thrown -``` - -Dependency creation behaviour ------------------ -You can control how a dependency is resolved by specifying a creation scope during registration. The scope determines which instance is returned, be it the same each time or a new one. The following scopes are available: - -* Resolve a dependency using a single instance (default): - -```d -dependencies.register!(ExampleClass).singleInstance(); -``` -* Resolve a dependency with a new instance each time it is resolved: - -```d -dependencies.register!(ExampleClass).newInstance(); -``` -* Resolve a dependency using a pre-existing instance - -```d -auto preExistingInstance = new ExampleClass(); -dependencies.register!(ExampleClass).existingInstance(preExistingInstance); -``` - -Autowiring ----------- -The real value of any dependency injection framework comes from its ability to autowire dependencies. Poodinis supports autowiring by simply applying the **@Autowire** UDA to a member of a class: -```d -class ExampleClassA {} - -class ExampleClassB { - @Autowire - public ExampleClassA dependency; -} - -dependencies.register!ExampleClassA; -auto exampleInstance = new ExampleClassB(); -dependencies.autowire(exampleInstance); -assert(exampleInstance.dependency !is null); -``` -At the moment, it is only possible to autowire public members or properties. - -Dependencies are automatically autowired when a class is resolved. So when you register ExampleClassB, its member, *dependency*, is automatically autowired: -```d -dependencies.register!ExampleClassA; -dependencies.register!ExampleClassB; -auto instance = dependencies.resolve!ExampleClassB; -assert(instance.dependency !is null); -``` -If an interface is to be autowired, you must register a concrete class by interface. Any class registered by concrete type can only be injected when a dependency on a concrete type is autowired. - -Circular dependencies ---------------------- -Poodinis can autowire circular dependencies when they are registered with singleInstance or existingInstance registration scopes. Circular dependencies in registrations with newInstance scopes will not be autowired, as this would cause an endless loop. - -Registering and resolving using qualifiers ------------------------------------------- -You can register multiple concrete types to a super type. When doing so, you will need to specify a qualifier when resolving that type: -```d -// Color is an interface, Blue and Red are classes implementing that interface -dependencies.register!(Color, Blue); -dependencies.register!(Color, Red); -auto blueInstance = dependencies.resolve!(Color, Blue); -``` -If you want to autowire a type registered to multiple concrete types, specify a qualified type as template argument: -```d -class BluePaint { - @Autowire!Blue - public Color color; -} -``` -If you registered multiple concrete types to the same supertype and you do not resolve using a qualifier, a ResolveException is thrown stating that there are multiple candidates for the type to be resolved. - -Autowiring all registered instances to an array ------------------------------------------------ -If you have registered multiple concrete types to a super type, you can autowire them all to an array, in which case you can easily operate on them all: -```d -// Color is an interface, Blue and Red are classes implementing that interface - -class ColorMixer { - @Autowire - public Color[] colors; -} - -dependencies.register!(Color, Blue); -dependencies.register!(Color, Red); -auto mixer = dependencies.resolve!ColorMixer; -``` -Member mixer.colors will now contain instances of Blue and Red. The order of the instances is not guarenteed to be that of the order in which they were registered. - -Application Contexts --------------------- -You can fine-tune dependency configuration using application contexts. Application contexts allow you to centralize all dependency configuration as well as define -how instances of certain classes should be constructed using factory methods. -###Defining and using application contexts -An application context is defined as follows: -```d -class Context : ApplicationContext { - public override void registerDependencies(shared(DependencyContainer) container) { - container.register!SomeClass; - container.register!(SomeInterface, SomeOtherClass).newInstance(); - } - - @Component - public SomeLibraryClass libraryClass() { - return new SomeLibraryClass("This class needs constructor parameters so I have to register it through an application context"); - } -} -``` -In the override *registerDependencies()* you can register all dependencies which do not need complex set-up, just like you would do when directly using the dependency container. -This override is optional. You can still register simple dependencies outside of the context (or in another context). -Complex dependencies are registered through member methods of the context. These member methods serve as factory methods which will be called when a dependency is resolved. -They are annotated with the *@Component* UDA to let the container know that these methods should be registered as dependencies. The type of the registration is the same as the return type of the method. -Factory methods are useful when you have to deal with dependencies which require constructor arguments or elaborate set-up after instantiation. -Application contexts have to be registered with a dependency container. They are registered as follows: -```d -container.registerContext!Context; -``` -All registered dependencies can now be resolved by the same dependency container. Registering a context will also register it as a dependency, meaning you can autowire the application context in other classes. -You can register as many types of application contexts as you like. - -###Autowiring application contexts -Application contexts can make use of autowired dependencies like any other dependency. When registering an application context, all its components are registered first after which the application context is autowired. -This means that after the registration of an application context some dependencies will already be resolved and instantiated. The following example illustrates how autowired members can be used in a context: -```d -class Context : ApplicationContext { - - @Autowire - public SomeClass someClass; - - @Autowire - public SomeOtherClass someOtherClass; - - public override void registerDependencies(shared(DependencyContainer) container) { - container.register!SomeClass; - } - - @Component - public SomeLibraryClass libraryClass() { - return new SomeLibraryClass(someClass, someOtherClass); - } -} -``` -As you can see, autowired dependencies can be used within factory methods. When *SomeLibraryClass* is resolved, it will be created with a resolved instance of *SomeClass* and *SomeOtherClass*. As shown, autowired dependencies can be registered within the same -application context, but don't neccesarily have to be. You can even autowire dependencies which are created within a factory method within the same application context. -Application contexts are directly autowired after they have been registered. This means that all autowired dependencies which are not registered in the application context itself need to be registered before registering the application context. - -###Controlling component registration -You can further influence how components are registered and created with additional UDAs: -```d -class Context : ApplicationContext { - @Component - @Prototype // Will create a new instance every time the dependency is resolved. - @RegisterByType!SomeInterface // Registers the dependency by the specified super type instead of the return type - public SomeClass someClass() { - return new SomeClass(); - } -} +Poodinis Tutorial +================= +This tutorial will give you an overview of all functionality offered by Poodinis and how to use them. + +The container +------------- +To register a class, a new dependency container must be instantiated: +```d +// Register a private container +auto dependencies = new DependencyContainer(); +// Or use the singleton container +dependencies = DependencyContainer.getInstance(); +``` +###Registering dependencies +To make dependencies available, they have to be registered: +```d +// Register concrete class +dependencies.register!ExampleClass; +// Register by super type +dependencies.register!(ExampleInterface, ExampleClass); +``` +In the above example, dependencies on the concrete class and interface will resolve an instance of class ExampleClass. A dependency registered by super type will automatically be registered by concrete type. + +Resolving dependencies +---------------------- +To manually resolve a dependency, all you have to do is resolve the dependency's type using the container in which it is registered: +```d +auto exampleClassInstance = dependencies.resolve!ExampleClass; +``` +If the class is registered by interface and not by concrete type, you can still resolve the class by concrete type: + +```d +auto exampleClassInstance = dependencies.resolve!ExampleInterface; +auto exampleClassInstance2 = dependencies.resolve!ExampleClass; +assert(exampleClassInstance is exampleClassInstance2); +``` +If you want to prevent registrations from being both registered by interface and concrete type, use the DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION option when registering: +```d +dependencies.register!(ExampleInterface, ExampleClass)(RegistrationOptions.DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION); +auto exampleClassInstance = dependencies.resolve!ExampleInterface; +auto exampleClassInstance2 = dependencies.resolve!ExampleClass; // A ResolveException is thrown +``` + +Dependency creation behaviour +----------------- +You can control how a dependency is resolved by specifying a creation scope during registration. The scope determines which instance is returned, be it the same each time or a new one. The following scopes are available: + +* Resolve a dependency using a single instance (default): + +```d +dependencies.register!(ExampleClass).singleInstance(); +``` +* Resolve a dependency with a new instance each time it is resolved: + +```d +dependencies.register!(ExampleClass).newInstance(); +``` +* Resolve a dependency using a pre-existing instance + +```d +auto preExistingInstance = new ExampleClass(); +dependencies.register!(ExampleClass).existingInstance(preExistingInstance); +``` + +Autowiring +---------- +The real value of any dependency injection framework comes from its ability to autowire dependencies. Poodinis supports autowiring by simply applying the **@Autowire** UDA to a member of a class: +```d +class ExampleClassA {} + +class ExampleClassB { + @Autowire + public ExampleClassA dependency; +} + +dependencies.register!ExampleClassA; +auto exampleInstance = new ExampleClassB(); +dependencies.autowire(exampleInstance); +assert(exampleInstance.dependency !is null); +``` +At the moment, it is only possible to autowire public members or properties. + +Dependencies are automatically autowired when a class is resolved. So when you register ExampleClassB, its member, *dependency*, is automatically autowired: +```d +dependencies.register!ExampleClassA; +dependencies.register!ExampleClassB; +auto instance = dependencies.resolve!ExampleClassB; +assert(instance.dependency !is null); +``` +If an interface is to be autowired, you must register a concrete class by interface. Any class registered by concrete type can only be injected when a dependency on a concrete type is autowired. + +Circular dependencies +--------------------- +Poodinis can autowire circular dependencies when they are registered with singleInstance or existingInstance registration scopes. Circular dependencies in registrations with newInstance scopes will not be autowired, as this would cause an endless loop. + +Registering and resolving using qualifiers +------------------------------------------ +You can register multiple concrete types to a super type. When doing so, you will need to specify a qualifier when resolving that type: +```d +// Color is an interface, Blue and Red are classes implementing that interface +dependencies.register!(Color, Blue); +dependencies.register!(Color, Red); +auto blueInstance = dependencies.resolve!(Color, Blue); +``` +If you want to autowire a type registered to multiple concrete types, specify a qualified type as template argument: +```d +class BluePaint { + @Autowire!Blue + public Color color; +} +``` +If you registered multiple concrete types to the same supertype and you do not resolve using a qualifier, a ResolveException is thrown stating that there are multiple candidates for the type to be resolved. + +Autowiring all registered instances to an array +----------------------------------------------- +If you have registered multiple concrete types to a super type, you can autowire them all to an array, in which case you can easily operate on them all: +```d +// Color is an interface, Blue and Red are classes implementing that interface + +class ColorMixer { + @Autowire + public Color[] colors; +} + +dependencies.register!(Color, Blue); +dependencies.register!(Color, Red); +auto mixer = dependencies.resolve!ColorMixer; +``` +Member mixer.colors will now contain instances of Blue and Red. The order of the instances is not guarenteed to be that of the order in which they were registered. + +Application Contexts +-------------------- +You can fine-tune dependency configuration using application contexts. Application contexts allow you to centralize all dependency configuration as well as define +how instances of certain classes should be constructed using factory methods. +###Defining and using application contexts +An application context is defined as follows: +```d +class Context : ApplicationContext { + public override void registerDependencies(shared(DependencyContainer) container) { + container.register!SomeClass; + container.register!(SomeInterface, SomeOtherClass).newInstance(); + } + + @Component + public SomeLibraryClass libraryClass() { + return new SomeLibraryClass("This class needs constructor parameters so I have to register it through an application context"); + } +} +``` +In the override *registerDependencies()* you can register all dependencies which do not need complex set-up, just like you would do when directly using the dependency container. +This override is optional. You can still register simple dependencies outside of the context (or in another context). +Complex dependencies are registered through member methods of the context. These member methods serve as factory methods which will be called when a dependency is resolved. +They are annotated with the *@Component* UDA to let the container know that these methods should be registered as dependencies. The type of the registration is the same as the return type of the method. +Factory methods are useful when you have to deal with dependencies which require constructor arguments or elaborate set-up after instantiation. +Application contexts have to be registered with a dependency container. They are registered as follows: +```d +container.registerContext!Context; +``` +All registered dependencies can now be resolved by the same dependency container. Registering a context will also register it as a dependency, meaning you can autowire the application context in other classes. +You can register as many types of application contexts as you like. + +###Autowiring application contexts +Application contexts can make use of autowired dependencies like any other dependency. When registering an application context, all its components are registered first after which the application context is autowired. +This means that after the registration of an application context some dependencies will already be resolved and instantiated. The following example illustrates how autowired members can be used in a context: +```d +class Context : ApplicationContext { + + @Autowire + public SomeClass someClass; + + @Autowire + public SomeOtherClass someOtherClass; + + public override void registerDependencies(shared(DependencyContainer) container) { + container.register!SomeClass; + } + + @Component + public SomeLibraryClass libraryClass() { + return new SomeLibraryClass(someClass, someOtherClass); + } +} +``` +As you can see, autowired dependencies can be used within factory methods. When *SomeLibraryClass* is resolved, it will be created with a resolved instance of *SomeClass* and *SomeOtherClass*. As shown, autowired dependencies can be registered within the same +application context, but don't neccesarily have to be. You can even autowire dependencies which are created within a factory method within the same application context. +Application contexts are directly autowired after they have been registered. This means that all autowired dependencies which are not registered in the application context itself need to be registered before registering the application context. + +###Controlling component registration +You can further influence how components are registered and created with additional UDAs: +```d +class Context : ApplicationContext { + @Component + @Prototype // Will create a new instance every time the dependency is resolved. + @RegisterByType!SomeInterface // Registers the dependency by the specified super type instead of the return type + public SomeClass someClass() { + return new SomeClass(); + } +} ``` \ No newline at end of file diff --git a/example/annotations/app.d b/example/annotations/app.d index fb2de8c..aaac6fb 100644 --- a/example/annotations/app.d +++ b/example/annotations/app.d @@ -1,50 +1,50 @@ -/** - * 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!!!!!"); - } -} +/** + * 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 52cc753..a7c4575 100644 --- a/example/applicationcontext/app.d +++ b/example/applicationcontext/app.d @@ -1,68 +1,68 @@ -/** - * 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(); -} +/** + * 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 89d8c95..93fed84 100644 --- a/example/arraycompletion/app.d +++ b/example/arraycompletion/app.d @@ -1,54 +1,54 @@ -/** - * 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(); -} +/** + * 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 4aefb16..bc07ea3 100644 --- a/example/qualifiers/app.d +++ b/example/qualifiers/app.d @@ -1,57 +1,57 @@ -/** - * 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!" -} +/** + * 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 eec27fc..965c68f 100644 --- a/example/quickstart/app.d +++ b/example/quickstart/app.d @@ -1,24 +1,24 @@ -/** - * 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; -} +/** + * 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 4e95cd0..39a9049 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-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; -} +/** + * 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 56d707d..8c39736 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-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; - } -} +/** + * 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 f9850bb..8893efa 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-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); - } - } -} +/** + * 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 2c9c67f..30db364 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-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; +/** + * 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 aa52796..ba23c2d 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-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 {} +/** + * 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 65ef43c..e723d0f 100644 --- a/test/poodinis/autowiretest.d +++ b/test/poodinis/autowiretest.d @@ -1,191 +1,191 @@ -/** - * 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"); - } -} +/** + * 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 905e086..fca0e83 100644 --- a/test/poodinis/containertest.d +++ b/test/poodinis/containertest.d @@ -1,588 +1,588 @@ -/** - * 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; - } -} +/** + * 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 166a55b..9f9bdb9 100644 --- a/test/poodinis/contexttest.d +++ b/test/poodinis/contexttest.d @@ -1,141 +1,141 @@ -/** - * 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); - } - -} +/** + * 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 6dc198b..20be117 100644 --- a/test/poodinis/registrationtest.d +++ b/test/poodinis/registrationtest.d @@ -1,123 +1,123 @@ -/** - * 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!"); - } - -} +/** + * 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!"); + } + +}