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