1 /**
2 Aedi, a dependency injection library.
3 
4 Aedi is a dependency injection library. It does provide a set of containers that do
5 IoC, and an interface to configure application components (structs, objects, etc.)
6 
7 Aim:
8 
9 The aim of library is to provide a dependency injection solution that is
10 feature rich, easy to use, easy to learn, and easy to extend up to your needs.
11 
12 Extending factories tutorial explained that library can be extended in two ways,
13 factory direction and container direction. In AEDI framework containers have at least
14 two responsibilities. The first one is to serve components, and the second one is to
15 manage life time of served components.
16 
17 Due to the first responsibility, AEDI has two basic containers, singleton and prototype. The
18 behavior of those two containers can be refound in other implementations of DIF like Spring frame-
19 work. Singleton container will serve same component no matter how many times a caller asks
20 it for a component associated with desired identity. Prototype container will serve new instance of
21 component each time it is asked by caller. Switchable decorating container presented decorated containers tutorial
22 limits in some sense the lifetime of it’s components only to the period when it is enabled.
23 
24 A custom container should be implemented, only when existing ones are not providing features
25 required by the third party code (code that uses the framework). As stated, the purpose of a container
26 is to serve and manage components, and therefore a new implementation of a container should be
27 concerned with those tasks.
28 
29 Compared to implementing a factory component, implementing a new container, requires it
30 to implement several interfaces. Each interface provides some functionality that can be used by
31 framework to enable some additional features related to container functionality. Implementation of
32 those interfaces is not imposed. The only requirement is to implement the basic Container interface
33 that exposes component serving abilities, along with option to instantiate them before their actual
34 usage. The following interfaces are allowed to be implemented by custom container:
35 
36 $(UL
37     $(LI $(D_INLINECODE Container) – exposes serving component features.)
38     $(LI $(D_INLINECODE Storage) – exposes component factory storing capabilities.)
39     $(LI $(D_INLINECODE FactoryLocator) – exposes component factory locating capabilities)
40     $(LI $(D_INLINECODE AliasAware) – exposes aliasing of component’s identity to another identity)
41     $(LI $(D_INLINECODE ConfigurableContainer) – exposes component factory storing capabilities along FactoryLocator and AliasAware)
42 )
43 
44 Container:
45 
46 Container interface is the minimal requirement for a custom container to implement, since it
47 offers component serving capabilities. Listing below presents the interface that is to be implemented
48 by custom containers. It is meaningful to implement only this interface when, container will not
49 integrate with any advanced features that framework provides.
50 
51 ------------------
52 interface Container : Locator!(Object, string) {
53 
54     public {
55 
56         Container instantiate();
57 
58         Container terminate();
59     }
60 }
61 ------------------
62 
63 Storage:
64 
65 By implementing Storage interface, a container declares it’s ability to store component
66 factories in it. Listing below displays abilities required to be implemented by container. Once a
67 container implements the interface, the component registration system can use the container to store
68 factories in it. In other words it is possible to use $(D_INLINECODE register) method on custom container. The
69 interface does not enforce, how stored components are used internally by container. They can never
70 be used, or used for other purposes, rather than serving created components to caller. Though,
71 by convention it is assumed that stored factory components are used for right purpose of creating
72 components served by container, and deviating from this convention, will lead in ambiguity in code
73 api that must be documented exhaustive.
74 
75 ------------------
76 interface Storage(Type, KeyType) {
77 
78     public {
79 
80         Storage set(Type element, KeyType identity);
81 
82         Storage remove(KeyType identity);
83     }
84 }
85 ------------------
86 
87 FactoryLocator:
88 
89 FactoryLocator interface, implemented by a container, exposes ability for exterior code to
90 access stored factory components in a container. Listing below shows the abilities that a container must implement.
91 
92 Such functionality is useful for postprocessing of component factories after they are registered
93 into container, for various purposes. One of them, is dynamically register tagged components as a
94 dependency to an event emmiter component. The FactoryLocator functionality allows for third party
95 library developers, to implement more complicated and useful features based on postprocessing of
96 factory components.
97 
98 ------------------
99 interface FactoryLocator(T : Factory!Z, Z) {
100     import std.range.interfaces;
101     import std.typecons;
102 
103     public {
104 
105         T getFactory(string identity);
106 
107         InputRange!(Tuple!(T, string)) getFactories();
108     }
109 }
110 ------------------
111 
112 AliasAware:
113 
114 AliasAware implemented by a containers, exposes an interface to exterior by which aliasing
115 of identities becomes possible. Listing below shows the methods that a container should implement.
116 The aliasing of component identities can become handy, when a library provides a container with
117 components, and another third party code is reliant on a component from this container, yet the
118 dependency is referenced with another identity. Aliasing will allow the referenced identity to be
119 aliased to right identity of component. Such cases can occur when a component from a library is
120 deprecated and removed, and it’s functionality is provided by another one identified differently. In
121 the end the aliasing mechanism allows a component from container to be associated with multiple
122 identities.
123 
124 ------------------
125 interface AliasAware(Type) {
126 
127     public {
128 
129         AliasAware link(Type identity, Type alias_);
130 
131         AliasAware unlink(Type alias_);
132 
133         const(Type) resolve(in Type alias_) const;
134     }
135 }
136 ------------------
137 
138 ConfigurableContainer:
139 
140 ConfigurableContainer interface is amalgation of previosly defined interfaces. Instead of
141 declaring entire list of interfaces a container should implement, it is easier to replace it with ConfigurableCo
142 interface. Listing below shows the abilities that a container must implement
143 
144 ------------------
145 interface ConfigurableContainer : Container, Storage!(ObjectFactory, string), AliasAware!(string), FactoryLocator!ObjectFactory {
146 
147 }
148 ------------------
149 
150 Try to run example. Modify it, run it again to understand how to implement
151 your own container.
152 
153 License:
154 	Boost Software License - Version 1.0 - August 17th, 2003
155 
156 	Permission is hereby granted, free of charge, to any person or organization
157 	obtaining a copy of the software and accompanying documentation covered by
158 	this license (the "Software") to use, reproduce, display, distribute,
159 	execute, and transmit the Software, and to prepare derivative works of the
160 	Software, and to permit third-parties to whom the Software is furnished to
161 	do so, all subject to the following:
162 
163 	The copyright notices in the Software and this entire statement, including
164 	the above license grant, this restriction and the following disclaimer,
165 	must be included in all copies of the Software, in whole or in part, and
166 	all derivative works of the Software, unless such copies or derivative
167 	works are solely in the form of machine-executable object code generated by
168 	a source language processor.
169 
170 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
171 	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
172 	FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
173 	SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
174 	FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
175 	ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
176 	DEALINGS IN THE SOFTWARE.
177 
178 Authors:
179 	aermicioi
180 **/
181 
182 module extending_containers;
183 
184 import aermicioi.aedi;
185 import aermicioi.aedi.util.typecons : Pair, pair;
186 import std.stdio;
187 import std.range;
188 import std.typecons;
189 
190 @safe class MySingletonContainer : ConfigurableContainer {
191     import std.stdio;
192     private {
193 
194         Object[string] singletons;
195         ObjectFactory[string] factories;
196 
197         string[string] aliasings;
198         size_t levels;
199     }
200 
201     public {
202 
203         MySingletonContainer set(ObjectFactory object, string key) {
204 
205             write("Registering component by ", key, " of type ", object.type.toString(), ": ");
206             this.factories[key] = object;
207             writeln("[..OK..]");
208 
209             return this;
210         }
211 
212         MySingletonContainer remove(string key) {
213             this.factories.remove(key);
214             this.singletons.remove(key);
215 
216             return this;
217         }
218 
219         Object get(string key) {
220             writeln('\t'.repeat(levels), "Serving ", key, " component.");
221 
222             if ((this.resolve(key) in this.singletons) is null) {
223                 writeln('\t'.repeat(levels), key, " component not found.");
224 
225                 if ((this.resolve(key) in this.factories) is null) {
226                     throw new NotFoundException("Object with id ${identity} not found.", key);
227                 }
228 
229                 writeln('\t'.repeat(levels), "Instantiating ", key, " component {");
230                 levels++;
231 
232                 this.singletons[this.resolve(key)] = this.factories[this.resolve(key)].factory();
233                 levels--;
234                 writeln('\t'.repeat(levels), "}");
235             }
236 
237             return this.singletons[key];
238         }
239 
240         bool has(in string key) inout {
241             return (key in this.factories) !is null;
242         }
243 
244         MySingletonContainer instantiate() {
245             import std.algorithm : filter;
246 
247             writeln("Booting container");
248             foreach (pair; this.factories.byKeyValue.filter!((pair) => (pair.key in this.singletons) is null)) {
249 
250                 writeln("Instantiating component ", pair.key, " {");
251                 levels++;
252                 this.singletons[pair.key] = pair.value.factory;
253                 levels--;
254                 writeln("}");
255             }
256 
257             return this;
258         }
259 
260         MySingletonContainer terminate() {
261             import std.algorithm : filter;
262 
263             writeln("Shutting down container");
264             foreach (pair; this.factories.byKeyValue.filter!(pair => (pair.key in this.singletons) !is null)) {
265 
266                 writeln("Destroying component ", pair.key);
267                 pair.value.destruct(this.singletons[pair.key]);
268             }
269 
270             (() @trusted => this.singletons.clear())();
271 
272             return this;
273         }
274 
275         MySingletonContainer link(string key, string alias_) {
276             this.aliasings[alias_] = key;
277 
278             return this;
279         }
280 
281         MySingletonContainer unlink(string alias_) {
282             this.remove(alias_);
283 
284             return this;
285         }
286 
287         const(string) resolve(in string alias_) const {
288             import std.typecons : Rebindable;
289             Rebindable!(const(string)) aliased = alias_;
290 
291             while ((aliased in this.aliasings) !is null) {
292                 writeln("Resolving alias ", aliased, " to ", this.aliasings[aliased]);
293                 aliased = this.aliasings[aliased];
294             }
295 
296             return aliased;
297         }
298 
299 
300         ObjectFactory getFactory(string identity) {
301             return this.factories[identity];
302         }
303 
304         InputRange!(Pair!(ObjectFactory, string)) getFactories() {
305             import std.algorithm;
306 
307             return this.factories.byKeyValue.map!(
308                 a => pair(a.value, a.key)
309             ).inputRangeObject;
310         }
311     }
312 }
313 
314 /**
315 A struct that should be managed by container.
316 **/
317 struct Color {
318     ubyte r;
319     ubyte g;
320     ubyte b;
321 }
322 
323 /**
324 Size of a car.
325 **/
326 struct Size {
327 
328     ulong width;
329     ulong height;
330     ulong length;
331 }
332 
333 /**
334 Interface for engines.
335 
336 An engine should implement it, in order to be installable in a car.
337 **/
338 interface Engine {
339 
340     public {
341 
342         void turnOn();
343         void run();
344         void turnOff();
345         @property string vendor();
346     }
347 }
348 
349 /**
350 A concrete implementation of Engine that uses gasoline for propelling.
351 **/
352 class GasolineEngine : Engine {
353 
354     private {
355         string vendor_;
356     }
357 
358     public {
359         @property {
360         	GasolineEngine vendor(string vendor) @safe nothrow {
361         		this.vendor_ = vendor;
362 
363         		return this;
364         	}
365 
366         	string vendor() @safe nothrow {
367         		return this.vendor_;
368         	}
369         }
370 
371         void turnOn() {
372             writeln("pururukVrooomVrrr");
373 
374         }
375 
376         void run() {
377             writeln("vrooom");
378         }
379 
380         void turnOff() {
381             writeln("vrrrPrrft");
382         }
383     }
384 }
385 
386 /**
387 A concrete implementation of Engine that uses diesel for propelling.
388 **/
389 class DieselEngine : Engine {
390 
391     private {
392         string vendor_;
393     }
394 
395     public {
396         @property {
397         	DieselEngine vendor(string vendor) @safe nothrow {
398         		this.vendor_ = vendor;
399 
400         		return this;
401         	}
402 
403         	string vendor() @safe nothrow {
404         		return this.vendor_;
405         	}
406         }
407 
408         void turnOn() {
409             writeln("pururukVruumVrrr");
410 
411         }
412 
413         void run() {
414             writeln("vruum");
415         }
416 
417         void turnOff() {
418             writeln("vrrrPft");
419         }
420     }
421 }
422 
423 /**
424 A class representing a car.
425 **/
426 class Car {
427 
428     private {
429         Color color_; // Car color
430         Size size_; // Car size
431         Engine engine_; // Car engine
432     }
433 
434     public {
435 
436         /**
437         Constructor of car.
438 
439         Constructs a car with a set of sizes. A car cannot of course have
440         undefined sizes, so we should provide it during construction.
441 
442         Params:
443             size = size of a car.
444         **/
445         this(Size size, Engine engine) {
446             this.size_ = size;
447             this.engine = engine;
448         }
449 
450         @property {
451 
452             /**
453             Set color of car
454 
455             Set color of car. A car can live with undefined color (physics allow it).
456 
457             Params:
458             	color = color of a car.
459 
460             Returns:
461             	Car
462             **/
463             Car color(Color color) @safe nothrow {
464             	this.color_ = color;
465 
466             	return this;
467             }
468 
469             Color color() @safe nothrow {
470             	return this.color_;
471             }
472 
473             Size size() @safe nothrow {
474             	return this.size_;
475             }
476 
477             /**
478             Set engine used in car.
479 
480             Params:
481             	engine = engine used in car to propel it.
482 
483             Returns:
484             	Car
485             **/
486             Car engine(Engine engine) @safe nothrow {
487             	this.engine_ = engine;
488 
489             	return this;
490             }
491 
492             Engine engine() @safe nothrow {
493             	return this.engine_;
494             }
495         }
496 
497         void start() {
498             engine.turnOn();
499         }
500 
501         void run() {
502             engine.run();
503         }
504 
505         void stop() {
506             engine.turnOff();
507         }
508     }
509 }
510 
511 void drive(Car car, string name) {
512     writeln("Uuh, what a nice car, ", name," with following specs:");
513     writeln("Size:", car.size());
514     writeln("Color:", car.color());
515 
516     writeln("Let's turn it on");
517     car.start();
518 
519     writeln("What a nice sound! We should make a test drive!");
520     car.run();
521 
522     writeln("Umm the test drive was awesome, let's get home and turn it off.");
523     car.run();
524     car.stop();
525 }
526 
527 void main() {
528 
529     MySingletonContainer container = new MySingletonContainer;
530     scope(exit) container.terminate();
531 
532     with (container.configure) {
533 
534         register!Color("color.green") // Register "green" color into container.
535             .set!"r"(cast(ubyte) 0)
536             .set!"g"(cast(ubyte) 255)
537             .set!"b"(cast(ubyte) 0);
538 
539         register!Size("size.sedan") // Register a size of a generic "sedan" into container
540             .set!"width"(200UL)
541             .set!"height"(150UL)
542             .set!"length"(500UL);
543 
544         register!(Engine, GasolineEngine)
545             .set!"vendor"("Mundane motors"); // Register a gasoline engine as default implementation of an engine
546         register!GasolineEngine
547             .set!"vendor"("Elite motors"); // Register a gasoline engine. Note: this engine is not the same gasoline engine from default implementation.
548         register!DieselEngine
549             .set!"vendor"("Hardcore motors"); // Register a diesel engine
550 
551         register!Car("sedan.engine.default") // Register a car with a default engine
552             .construct("size.sedan".lref, lref!Engine)
553             .set!"color"("color.green".lref);
554 
555         register!Car("sedan.engine.gasoline") // Register a car with gasoline engine
556             .construct("size.sedan".lref, lref!GasolineEngine)
557             .set!"color"("color.green".lref);
558 
559         register!Car("sedan.engine.diesel") // Register a car with diesel engine
560             .construct("size.sedan".lref, lref!DieselEngine)
561             .set!"color"("color.green".lref);
562 
563     }
564 
565     container.instantiate(); // Boot container (or prepare managed code/data).
566 
567     container.locate!Car("sedan.engine.default").drive("Default car");
568     container.locate!Car("sedan.engine.gasoline").drive("Gasoline car");
569     container.locate!Car("sedan.engine.diesel").drive("Diesel car");
570 }