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 }