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