1 /**
2  * This module contains instance factory facilities
3  *
4  * Authors:
5  *  Mike Bierlee, m.bierlee@lostmoment.com
6  * Copyright: 2014-2022 Mike Bierlee
7  * License:
8  *  This software is licensed under the terms of the MIT license.
9  *  The full terms of the license can be found in the LICENSE file.
10  */
11 
12 module poodinis.factory;
13 
14 import poodinis.container : DependencyContainer;
15 import poodinis.imports : createImportsString;
16 
17 import std.typecons : Flag;
18 import std.exception : enforce;
19 import std.traits : Parameters, isBuiltinType, fullyQualifiedName;
20 import std.string : format;
21 
22 debug
23 {
24     import std.stdio : writeln;
25 }
26 
27 alias CreatesSingleton = Flag!"CreatesSingleton";
28 alias InstanceFactoryMethod = Object delegate();
29 alias InstanceEventHandler = void delegate(Object instance);
30 
31 class InstanceCreationException : Exception
32 {
33     this(string message, string file = __FILE__, size_t line = __LINE__)
34     {
35         super(message, file, line);
36     }
37 }
38 
39 struct InstanceFactoryParameters
40 {
41     TypeInfo_Class instanceType;
42     CreatesSingleton createsSingleton = CreatesSingleton.yes;
43     Object existingInstance;
44     InstanceFactoryMethod factoryMethod;
45 }
46 
47 class InstanceFactory
48 {
49     private Object instance = null;
50     private InstanceFactoryParameters _factoryParameters;
51     private InstanceEventHandler _constructionHandler;
52 
53     this()
54     {
55         factoryParameters = InstanceFactoryParameters();
56     }
57 
58     public @property void factoryParameters(InstanceFactoryParameters factoryParameters)
59     {
60         if (factoryParameters.factoryMethod is null)
61         {
62             factoryParameters.factoryMethod = &this.createInstance;
63         }
64 
65         if (factoryParameters.existingInstance !is null)
66         {
67             factoryParameters.createsSingleton = CreatesSingleton.yes;
68             this.instance = factoryParameters.existingInstance;
69         }
70 
71         _factoryParameters = factoryParameters;
72     }
73 
74     public @property InstanceFactoryParameters factoryParameters()
75     {
76         return _factoryParameters;
77     }
78 
79     public Object getInstance()
80     {
81         if (_factoryParameters.createsSingleton && instance !is null)
82         {
83             debug (poodinisVerbose)
84             {
85                 printDebugUseExistingInstance();
86             }
87 
88             return instance;
89         }
90 
91         debug (poodinisVerbose)
92         {
93             printDebugCreateNewInstance();
94         }
95 
96         instance = _factoryParameters.factoryMethod();
97         if (_constructionHandler !is null)
98         {
99             _constructionHandler(instance);
100         }
101 
102         return instance;
103     }
104 
105     void onConstructed(InstanceEventHandler handler)
106     {
107         _constructionHandler = handler;
108     }
109 
110     private void printDebugUseExistingInstance()
111     {
112         debug
113         {
114             if (_factoryParameters.instanceType !is null)
115             {
116                 writeln(format("DEBUG: Existing instance returned of type %s",
117                         _factoryParameters.instanceType.toString()));
118             }
119             else
120             {
121                 writeln("DEBUG: Existing instance returned from custom factory method");
122             }
123         }
124     }
125 
126     private void printDebugCreateNewInstance()
127     {
128         debug
129         {
130             if (_factoryParameters.instanceType !is null)
131             {
132                 writeln(format("DEBUG: Creating new instance of type %s",
133                         _factoryParameters.instanceType.toString()));
134             }
135             else
136             {
137                 writeln("DEBUG: Creating new instance from custom factory method");
138             }
139         }
140     }
141 
142     protected Object createInstance()
143     {
144         enforce!InstanceCreationException(_factoryParameters.instanceType,
145                 "Instance type is not defined, cannot create instance without knowing its type.");
146         return _factoryParameters.instanceType.create();
147     }
148 }
149 
150 class ConstructorInjectingInstanceFactory(InstanceType) : InstanceFactory
151 {
152     private shared DependencyContainer container;
153     private bool isBeingInjected = false;
154 
155     this(shared DependencyContainer container)
156     {
157         this.container = container;
158     }
159 
160     private static string createArgumentList(Params...)()
161     {
162         string argumentList = "";
163         foreach (param; Params)
164         {
165             if (argumentList.length > 0)
166             {
167                 argumentList ~= ",";
168             }
169 
170             argumentList ~= "container.resolve!(" ~ param.stringof ~ ")";
171         }
172         return argumentList;
173     }
174 
175     private static string createImportList(Params...)()
176     {
177         string importList = "";
178         foreach (param; Params)
179         {
180             importList ~= createImportsString!param;
181         }
182         return importList;
183     }
184 
185     private static bool parametersAreValid(Params...)()
186     {
187         bool isValid = true;
188         foreach (param; Params)
189         {
190             if (isBuiltinType!param || is(param == struct))
191             {
192                 isValid = false;
193                 break;
194             }
195         }
196 
197         return isValid;
198     }
199 
200     protected override Object createInstance()
201     {
202         enforce!InstanceCreationException(container,
203                 "A dependency container is not defined. Cannot perform constructor injection without one.");
204         enforce!InstanceCreationException(!isBeingInjected,
205                 format("%s is already being created and injected; possible circular dependencies in constructors?",
206                     InstanceType.stringof));
207 
208         Object instance = null;
209         static if (__traits(compiles, __traits(getOverloads, InstanceType, `__ctor`)))
210         {
211             foreach (ctor; __traits(getOverloads, InstanceType, `__ctor`))
212             {
213                 static if (parametersAreValid!(Parameters!ctor))
214                 {
215                     isBeingInjected = true;
216                     mixin(createImportsString!InstanceType ~ createImportList!(
217                             Parameters!ctor) ~ `
218                         instance = new ` ~ fullyQualifiedName!InstanceType ~ `(` ~ createArgumentList!(
219                             Parameters!ctor) ~ `);
220                     `);
221                     isBeingInjected = false;
222                     break;
223                 }
224             }
225         }
226 
227         if (instance is null)
228         {
229             instance = typeid(InstanceType).create();
230         }
231 
232         enforce!InstanceCreationException(instance !is null,
233                 "Unable to create instance of type" ~ InstanceType.stringof
234                 ~ ", does it have injectable constructors?");
235 
236         return instance;
237     }
238 }