1 /**
2  * Contains the implementation of the dependency container.
3  *
4  * Copyright: (c) 2015-2020, Milofon Project.
5  * License: Subject to the terms of the BSD 3-Clause License, as written in the included LICENSE.md file.
6  * Author: <m.galanin@milofon.pro> Maksim Galanin
7  * Date: 2020-04-11
8  */
9 
10 module dango.inject.container;
11 
12 private
13 {
14     import std.algorithm.iteration : map, filter;
15     import std.algorithm.searching : find, canFind;
16     import std.format : fmt = format;
17     import std.exception : assumeWontThrow;
18     import std.uni : toUpper;
19     import std.array : join, array;
20 
21     import dango.inject.injection : Inject, Named, inject;
22     import dango.inject.factory : ComponentFactory;
23     import dango.inject.provider;
24     import dango.inject.exception;
25 }
26 
27 
28 /**
29  * The dependency container maintains all dependencies registered with it.
30  */
31 class DependencyContainer
32 {
33     private @safe
34     {
35         Registration[][TypeInfo] _registrations;
36         Registration[] autowireStack;
37     }
38 
39     /**
40      * Register a single value.
41      *
42      * A `ValueProvider` is used to provide the value.
43      */
44     Registration value(T)(T value) nothrow @safe
45     {
46         return this.provider(new ValueProvider!T(value));
47     }
48 
49     /**
50      * Register a single value with name.
51      *
52      * A `ValueProvider` is used to provide the value.
53      */
54     Registration value(T)(string name, T value) nothrow @safe
55     {
56         return this.provider(name, new ValueProvider!T(value));
57     }
58 
59     /**
60      * Register a Class.
61      *
62      * Instances are provided using a `ClassProvider` that injects dependencies using
63      * this container.
64      */
65     Registration register(ConcreteType)() nothrow @safe
66         if (is(ConcreteType == class))
67     {
68         return register!(ConcreteType, ConcreteType)();
69     }
70 
71     /**
72      * Register a Class.
73      *
74      * Instances are provided using a `ClassProvider` that injects dependencies using
75      * this container.
76      */
77     Registration register(SuperType, ConcreteType : SuperType)() nothrow @safe
78         if (is(ConcreteType == class))
79     {
80         Provider provider = new ClassProvider!(SuperType, ConcreteType)(this);
81         TypeInfo registeredType = provider.registeredType;
82         TypeInfo providedType = provider.providedType;
83 
84         if (auto existingCandidates = registeredType in _registrations)
85         {
86             auto existingRegistration = assumeWontThrow(find!((r) @trusted {
87                         return r.providedType == providedType;
88                     })(*existingCandidates));
89             if (existingRegistration.length)
90                 return existingRegistration[0];
91         }
92 
93         return this.provider(provider);
94     }
95 
96     /**
97      * Register a Class with name.
98      *
99      * Instances are provided using a `ClassProvider` that injects dependencies using
100      * this container.
101      */
102     Registration register(ConcreteType)(string name) nothrow @safe
103         if (is(ConcreteType == class))
104     {
105         return register!(ConcreteType, ConcreteType)(name);
106     }
107 
108     /**
109      * Register a Class with name.
110      *
111      * Instances are provided using a `ClassProvider` that injects dependencies using
112      * this container.
113      */
114     Registration register(SuperType, ConcreteType : SuperType)(string name) nothrow @safe
115         if (is(ConcreteType == class))
116     {
117         Provider provider = new ClassProvider!(SuperType, ConcreteType)(this);
118         TypeInfo registeredType = provider.registeredType;
119         TypeInfo providedType = provider.providedType;
120         string uName = assumeWontThrow(name.toUpper);
121 
122         if (auto existingCandidates = registeredType in _registrations)
123         {
124             auto existingRegistration = assumeWontThrow(find!((r) @safe {
125                         return r.isNamed && r.name == uName;
126                     })(*existingCandidates));
127             if (existingRegistration.length)
128                 return existingRegistration[0];
129         }
130 
131         return this.provider(name, provider);
132     }
133 
134     /**
135      * Register a factory function for a type.
136      */
137     Registration factory(F : ComponentFactory!(T, A), T, A...)(A args) nothrow @safe
138     {
139         auto freg = this.register!(ComponentFactory!(T, A), F).singleInstance();
140         auto fact = assumeWontThrow(resolveInjectdInstance!F(freg));
141         return this.provider(new FactoryProvider!(F, T, A)(this, fact, args));
142     }
143 
144     /**
145      * Register a factory function for a type.
146      */
147     Registration factory(F : ComponentFactory!(T, A), T, A...)(string name, A args) nothrow @safe
148     {
149         auto freg = this.register!(ComponentFactory!(T, A), F)(name).singleInstance();
150         auto fact = assumeWontThrow(resolveInjectdInstance!F(freg));
151         return this.provider(name, new FactoryProvider!(F, T, A)(this, fact, args));
152     }
153 
154     /**
155      * Register a provider using the type returned by `provider.providedType`.
156      */
157     Registration provider(Provider provider) nothrow @safe
158     {
159         auto newRegistration = new Registration(provider);
160         return addRegistration(provider.registeredType, newRegistration);
161     }
162 
163     /**
164      * Register a provider using the type returned by `provider.providedType`.
165      */
166     Registration provider(string name, Provider provider) nothrow @safe
167     {
168         auto newRegistration = new Registration(provider, assumeWontThrow(name.toUpper));
169         return addRegistration(provider.registeredType, newRegistration);
170     }
171 
172     /**
173      * Resolve dependencies using a qualifier.
174      *
175      * Dependencies can only resolved using this method if they are registered by super type.
176      *
177      * Resolved dependencies are automatically autowired before being returned.
178      */
179     ResolveType resolve(ResolveType)() @safe
180     {
181         TypeInfo resolveType = typeid(ResolveType);
182 
183         auto candidates = resolveType in _registrations;
184         if (!candidates)
185             throw new ResolveDangoException("Type not registered.", resolveType);
186 
187         if (candidates.length > 1)
188         {
189             string candidateList = (*candidates).map!((c) => c.providedType.toString).join(',');
190             throw new ResolveDangoException("Multiple qualified candidates available: " ~
191                     candidateList ~ ". Please use a qualifier or name.", resolveType);
192         }
193 
194         Registration registration = (*candidates)[0];
195         return resolveInjectdInstance!ResolveType(registration);
196     }
197 
198     /**
199      * Resolve all dependencies registered to a super type.
200      *
201      * Returns:
202      * An array of autowired instances is returned. The order is undetermined.
203      */
204     ResolveType[] resolveAll(ResolveType)() @safe
205     {
206         TypeInfo resolveType = typeid(ResolveType);
207 
208         auto candidates = resolveType in _registrations;
209         if (!candidates)
210             throw new ResolveDangoException("Type not registered.", resolveType);
211 
212         ResolveType[] result;
213         foreach (registration; *candidates)
214             result ~= resolveInjectdInstance!ResolveType(registration);
215 
216         return result;
217     }
218 
219     /**
220      * Resolve dependencies using a qualifier.
221      *
222      * Dependencies can only resolved using this method if they are registered by super type.
223      *
224      * Resolved dependencies are automatically autowired before being returned.
225      */
226     ResolveType resolve(ResolveType)(string name) @safe
227     {
228         TypeInfo resolveType = typeid(ResolveType);
229         string uName = name.toUpper;
230 
231         auto candidates = resolveType in _registrations;
232         if (!candidates)
233             throw new ResolveDangoException("Type not registered.", resolveType);
234 
235         auto namedCandidates = filter!((r) {
236                 return r.isNamed && r.name == uName;
237             })(*candidates).array;
238 
239         if (namedCandidates.length > 1)
240         {
241             string candidateList = namedCandidates.map!((c) => c.providedType.toString).join(',');
242             throw new ResolveDangoException("Multiple qualified candidates available: " ~
243                     candidateList ~ ".", resolveType);
244         }
245         else if (namedCandidates.length == 0)
246             throw new ResolveDangoException(
247                     fmt!"Type not registered with name '%s'."(uName), resolveType);
248 
249         Registration registration = namedCandidates[0];
250         return resolveInjectdInstance!ResolveType(registration);
251     }
252 
253     /**
254      * Resolve all dependencies registered to a super type.
255      *
256      * Returns:
257      * An array of autowired instances is returned. The order is undetermined.
258      */
259     ResolveType[] resolveAll(ResolveType)(string name) @safe
260     {
261         TypeInfo resolveType = typeid(ResolveType);
262         string uName = name.toUpper;
263 
264         auto candidates = resolveType in _registrations;
265         if (!candidates)
266             throw new ResolveDangoException("Type not registered.", resolveType);
267 
268         auto namedCandidates = filter!((r) {
269                 return r.isNamed && r.name == uName;
270             })(*candidates);
271 
272         ResolveType[] result;
273         foreach (registration; namedCandidates)
274             result ~= resolveInjectdInstance!ResolveType(registration);
275 
276         return result;
277     }
278 
279     /**
280      * Resolve dependencies using a qualifier.
281      *
282      * Dependencies can only resolved using this method if they are registered by super type.
283      *
284      * Resolved dependencies are automatically autowired before being returned.
285      */
286     QualifierType resolve(ResolveType, QualifierType : ResolveType)() @safe
287     {
288         TypeInfo resolveType = typeid(ResolveType);
289         TypeInfo qualifierType = typeid(QualifierType);
290 
291         auto candidates = resolveType in _registrations;
292         if (!candidates)
293             throw new ResolveDangoException("Type not registered.", resolveType);
294 
295         auto typedCandidates = filter!((r) @trusted {
296                 return r.providedType == qualifierType;
297             })(*candidates).array;
298 
299         if (typedCandidates.length > 1)
300         {
301             string candidateList = typedCandidates.map!((c) => c.providedType.toString).join(',');
302             throw new ResolveDangoException("Multiple qualified candidates available: " ~
303                     candidateList ~ ".", resolveType);
304         }
305         else if (typedCandidates.length == 0)
306             throw new ResolveDangoException("Type not registered.", resolveType);
307 
308         Registration registration = typedCandidates[0];
309         return resolveInjectdInstance!QualifierType(registration);
310     }
311 
312     /**
313      * Resolve all dependencies registered to a super type.
314      *
315      * Returns:
316      * An array of autowired instances is returned. The order is undetermined.
317      */
318     QualifierType[] resolveAll(ResolveType, QualifierType : ResolveType)()
319     {
320         TypeInfo resolveType = typeid(ResolveType);
321         TypeInfo qualifierType = typeid(QualifierType);
322 
323         auto candidates = resolveType in _registrations;
324         if (!candidates)
325             throw new ResolveDangoException("Type not registered.", resolveType);
326 
327         auto typedCandidates = filter!((r) @trusted {
328                 return r.providedType == qualifierType;
329             })(*candidates);
330 
331         ResolveType[] result;
332         foreach (registration; typedCandidates)
333             result ~= resolveInjectdInstance!ResolveType(registration);
334 
335         return result;
336     }
337 
338 
339 private:
340 
341 
342     ResolveType resolveInjectdInstance(ResolveType)(Registration registration) @trusted
343     {
344         synchronized (this)
345         {
346             if (!(autowireStack.canFind(registration)))
347             {
348                 autowireStack ~= registration;
349                 scope (exit)
350                     autowireStack = autowireStack[0 .. $-1];
351                 return registration.provide!ResolveType(true);
352             }
353             else
354                 return registration.provide!ResolveType(false);
355         }
356     }
357 
358 
359     Registration addRegistration(TypeInfo registeredType, Registration registration) nothrow @safe 
360     {
361         assumeWontThrow(() @safe {
362             synchronized (this)
363                 _registrations[registeredType] ~= registration;
364             }());
365         return registration;
366     }
367 }
368 
369 @("Should work container with value")
370 @safe unittest
371 {
372     auto cont = new DependencyContainer();
373     auto reg = cont.value(1); 
374     assert (!reg.isNamed);
375     assert (cont.resolve!int == 1);
376 
377     reg = cont.value("two", 2);
378     assert (reg.isNamed);
379     assert (reg.name == "TWO");
380 
381     assert (cont._registrations[typeid(int)].length == 2);
382     assert (cont.resolve!int("tWo") == 2);
383 
384     cont.value("hello");
385     assert (cont.resolve!string() == "hello");    
386     cont.value("name", "world");
387     assertThrown!ResolveDangoException(cont.resolve!string);
388     assert (cont.resolve!string("name") == "world");
389 }
390 
391 version (unittest)
392 {
393     import std.exception : assertThrown;
394     interface Animal { string name() @safe; }
395     class Cat : Animal {string name() { return "cat"; }}
396     class Dog : Animal {string name() { return "dog"; }}
397 }
398 
399 @("Should work container with concrete object")
400 @system unittest
401 {
402     auto cont = new DependencyContainer();
403     auto reg1 = cont.register!Cat();
404     auto reg2 = cont.register!Cat();
405     assert (reg1 == reg2);
406     auto cat = cont.resolve!Cat;
407     assert (cat && cat.name == "cat");
408 }
409 
410 @("Should work container with qualified object")
411 @safe unittest
412 {
413     auto cont = new DependencyContainer();
414     auto reg1 = cont.register!(Animal, Cat)();
415     assertThrown(cont.resolve!(Animal, Dog));
416 
417     auto cat = cont.resolve!Animal;
418     assert (cat && cat.name == "cat");
419     
420     cat = cont.resolve!(Animal, Cat);
421     assert (cat && cat.name == "cat");
422 }
423 
424 @("Should work container with named object")
425 @safe unittest
426 {
427     auto cont = new DependencyContainer();
428     auto reg1 = cont.register!(Animal, Cat)("bar");
429     assertThrown(cont.resolve!(Animal, Dog));
430 
431     auto cat = cont.resolve!Animal;
432     assert (cat && cat.name == "cat");
433 
434     cat = cont.resolve!(Animal, Cat);
435     assert (cat && cat.name == "cat");
436 
437     cat = cont.resolve!(Animal)("bar");
438     assert (cat && cat.name == "cat");
439 }
440 
441 @("Should work container with custom provider")
442 @safe unittest
443 {
444     struct ItemDB { int value; }
445     class CustomProvider : Provider
446     {
447         private int _initVal;
448         this(int initVal) { _initVal = initVal; }
449         TypeInfo providedType() const pure nothrow @safe { return typeid(ItemDB); }
450         TypeInfo registeredType() const pure nothrow @safe { return typeid(ItemDB); }
451         bool canSingleton() const pure nothrow @safe { return false; }
452         Provider originalProvider() pure nothrow @safe { return this; }
453         void withProvided(bool ii, void delegate(void*) @safe dg) @trusted
454         {
455             auto result = ItemDB(_initVal);
456             dg(&result);
457         }
458     }
459 
460     auto cont = new DependencyContainer();
461     cont.provider("one", new CustomProvider(1));
462     cont.provider("two", new CustomProvider(2));
463     assert (cont.resolve!ItemDB("one").value == 1);
464     assert (cont.resolve!ItemDB("two").value == 2);
465 }
466 
467 @("Should work container with factory")
468 @safe unittest
469 {
470     interface Logger { int getLevel() @safe; }
471     class ConsoleLogger : Logger
472     {
473         private int _level;
474         this(int level) { _level = level; }
475         int getLevel() @safe { return _level; }
476     }
477     class CustomFactory : ComponentFactory!(Logger, int)
478     {
479         Logger createComponent(int lvl) { return new ConsoleLogger(lvl); }
480     }
481 
482     auto cont = new DependencyContainer();
483     cont.factory!CustomFactory(10);
484 
485     auto ft = cont.resolve!(ComponentFactory!(Logger, int));
486     assert (ft && ft.createComponent(11).getLevel == 11);
487 
488     auto v = cont.resolve!Logger;
489     assert (v && v.getLevel == 10);
490 }
491