1 /**
2  * Модуль работы с компонентами и фабриками к ним
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-22
8  */
9 
10 module dango.inject.factory;
11 
12 private
13 {
14     import std.meta : Filter;
15     import std.format : fmt = format;
16     import std.traits : hasMember;
17 
18     import bolts : isFunctionOver;
19 
20     import dango.inject.container : DependencyContainer;
21     import dango.inject.injection : inject;
22 }
23 
24 
25 /**
26  * Интерфейс фабрики для создания компонентов системы
27  * Params:
28  * I - Конструируемый тип
29  * A - Типы аргументов
30  */
31 interface ComponentFactory(C, A...)
32 {
33     alias ComponentType = C;
34 
35     /**
36      * Создает компонент
37      */
38     C createComponent(A args) @safe;
39 }
40 
41 
42 /**
43  * Метод позволяет создавать компоненты на основе анализа конструкторов
44  * Params:
45  * C = Тип создаваемого объекта
46  * args = Принимаемые аргументы
47  */
48 C createComponentByCtor(C, A...)(A args)
49 {
50     enum hasValidCtor(alias ctor) = isFunctionOver!(ctor, args);
51 
52     static if (hasMember!(C, "__ctor"))
53     {
54         alias pCtors = Filter!(hasValidCtor,
55                 __traits(getOverloads, C, "__ctor"));
56         static if(pCtors.length)
57             return new C(args);
58         else static if (!A.length)
59             return new C();
60         else
61             static assert(false, fmt!"Component %s is not create using argument types (%s)"(
62                     C.stringof, A.stringof));
63     }
64     else
65         return new C();
66 }
67 
68 
69 version (unittest)
70 {
71     interface Server
72     {
73         string host() @safe;
74         ushort port() @safe;
75     }
76 
77     class HTTPServer : Server
78     {
79         private 
80         {
81             string _host;
82             ushort _port;
83         }
84 
85         this() @safe {}
86 
87         this(string host, ushort port) @safe
88         {
89             this._host = host;
90             this._port = port;
91         }
92 
93         string host() @safe { return _host; }
94         ushort port() @safe { return _port; }
95     }
96 
97     class ServerFactory : ComponentFactory!(Server, string, ushort)
98     {
99         Server createComponent(string host, ushort port)
100         {
101             return new HTTPServer(host, port);
102         }
103     }
104 }
105 
106 @("Should work createComponentByCtor method")
107 @safe unittest
108 {
109 
110     auto server = createComponentByCtor!(HTTPServer, string, ushort)("host", 3);
111     assert (server.host == "host");
112     assert (server.port == 3);
113 
114     server = createComponentByCtor!(HTTPServer)();
115     assert (server.host == "");
116     assert (server.port == 0);
117 }
118 
119 
120 /**
121  * Фабрика автосгенерирована на основе конструктора компонента
122  */
123 template ComponentFactoryCtor(C, CT : C, A...)
124 {
125     class ComponentFactoryCtor : ComponentFactory!(C, DependencyContainer, A)
126     {
127         alias ConcreteType = CT;
128 
129         /**
130          * See_Also: ComponentFactory.createComponent
131          */
132         C createComponent(DependencyContainer container, A args) @safe
133         {
134             auto result = createComponentByCtor!(CT, A)(args);
135             inject!CT(container, result);
136             return result;
137         }
138     }
139 }
140 
141 @("Should work ComponentFactoryCtor")
142 @safe unittest
143 {
144     auto cont = new DependencyContainer();
145     auto factory = new ComponentFactoryCtor!(Server, HTTPServer, string, ushort)();
146     auto server = factory.createComponent(cont, "host", 3);
147     assert (server.host == "host");
148     assert (server.port == 3);
149 }
150 
151 
152 /**
153  * Оборачивание существующей фабрики в фабрику создающую 
154  * объект с инъекцией зависимости
155  */
156 template WrapDependencyFactory(F : ComponentFactory!(C, A), C, A...)
157 {
158     static if (A.length && is(A[0] == DependencyContainer))
159         alias AA = A[1..$];
160     else
161         alias AA = A;
162 
163     class WrapDependencyFactory : ComponentFactory!(C, DependencyContainer, AA)
164     {
165         /**
166          * See_Also: ComponentFactory.createComponent
167          */
168         C createComponent(DependencyContainer container, AA args) @safe
169         {
170             auto factory = new F();
171             inject!F(container, factory);            
172             static if (A.length && is(A[0] == DependencyContainer))
173                 return factory.createComponent(container, args);
174             else
175                 return factory.createComponent(args);
176         }
177     }
178 }
179 
180 @("Should work wrapDependencyFactory")
181 @safe unittest
182 {
183     alias F = ComponentFactoryCtor!(Server, HTTPServer, string, ushort);
184     auto cont = new DependencyContainer();
185     auto factory = new WrapDependencyFactory!(F)();
186     auto server = factory.createComponent(cont, "host", 3);
187     assert (server.host == "host");
188     assert (server.port == 3);
189 
190     auto factory2 = new WrapDependencyFactory!ServerFactory();
191     server = factory.createComponent(cont, "host", 3);
192     assert (server.host == "host");
193     assert (server.port == 3);
194 }
195