1 /**
2  * Contains functionality for autowiring dependencies using a 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-16
8  */
9 
10 module dango.inject.injection;
11 
12 private
13 {
14     import std.traits;
15 
16     import dango.inject.container;
17 }
18 
19 
20 struct Inject(QualifierType) {}
21 
22 
23 struct Named
24 {
25     string name;
26 }
27 
28 
29 void inject(Type)(DependencyContainer container, Type instance) @safe
30 {
31     static if (BaseClassesTuple!Type.length > 1)
32         inject!(BaseClassesTuple!Type[0])(container, instance);
33 
34     static foreach (name; FieldNameTuple!Type)
35         injectMember!(name, Type)(container, instance);
36 }
37 
38 version (unittest)
39 {
40     interface Printer { string title() @safe; }
41     class LaserPrinter : Printer {
42         private string _title;
43         @Inject
44         this(@Named("printer") string title) { _title = title; }
45         string title() @safe { return _title; }
46     }
47     interface Report {}
48     abstract class SimpleReport : Report {
49         @Inject!LaserPrinter 
50         private Printer _printer;
51         @Inject @Named("pages")
52         private int _pages;
53         @Inject @Named("report")
54         private string _title;
55         @Inject
56         private ubyte[] _code;
57         @Inject
58         private Printer _printer2 = new LaserPrinter("superlaser");
59     }
60     class AdditionalReport : SimpleReport {}
61 }
62 
63 @("Should work inject to object")
64 @safe unittest
65 {
66     auto cont = new DependencyContainer();
67     cont.register!(Printer, LaserPrinter);  
68     cont.value!int("pages", 2);
69     cont.value!string("printer", "laser");
70     cont.value!string("report", "matrix");
71     cont.value!string("other");
72     cont.value!ubyte(1);
73     cont.value!ubyte(2);
74     cont.register!(Report, AdditionalReport);
75     auto rep = cast(SimpleReport)cont.resolve!Report();
76     assert (rep);
77     assert (rep._printer2.title == "superlaser");
78     assert (rep._printer && rep._printer.title == "laser");
79     assert (rep._title == "matrix");
80     assert (rep._code == [1, 2]);
81 }
82 
83 
84 private:
85 
86 
87 void injectMember(string name, Type)(DependencyContainer container, Type instance) @safe
88 {
89     alias member = __traits(getMember, Type, name);
90     static foreach (attribute; __traits(getAttributes, member))
91     {
92         static if (is(attribute == Inject!T, T : typeof(member)))
93             injectMemberType!(name, Type, typeof(member), T)(container, instance);
94         else static if (__traits(isSame, attribute, Inject))
95             injectMemberType!(name, Type, typeof(member))(container, instance);
96     }
97 }
98 
99 
100 void injectMemberType(string name, Type, MemberTypes...)(DependencyContainer container, Type instance) @safe
101 {
102     alias MemberType = MemberTypes[0];
103     static if (is(MemberType == class) || is(MemberType == interface))
104         if (__traits(getMember, instance, name) !is null)
105             return;
106 
107     static if (isDynamicArray!MemberType && !isSomeString!MemberType)
108         injectMultiple!(name, Type, MemberTypes)(container, instance);
109     else
110         injectSingle!(name, Type, MemberTypes)(container, instance);
111 }
112 
113 
114 void injectMultiple(string name, Type, MemberTypes...)(DependencyContainer container, Type instance) @safe
115 {
116     alias MemberElementType = ForeachType!(MemberTypes[0]);
117     enum namedUDAs = getUDAs!(__traits(getMember, Type, name), Named);
118     static if (namedUDAs.length)
119         __traits(getMember, instance, name) = container.resolveAll!(MemberElementType)(namedUDAs[0].name);
120     else
121         __traits(getMember, instance, name) = container.resolveAll!(MemberElementType)();
122 }
123 
124 
125 void injectSingle(string name, Type, MemberTypes...)(DependencyContainer container, Type instance) @safe
126 {
127     enum namedUDAs = getUDAs!(__traits(getMember, Type, name), Named);
128     static if (namedUDAs.length)
129         __traits(getMember, instance, name) = container.resolve!(MemberTypes)(namedUDAs[0].name);
130     else
131     {
132         auto ins = container.resolve!(MemberTypes)();
133         assert (ins);
134         __traits(getMember, instance, name) = container.resolve!(MemberTypes)();
135     }
136 }
137