1 module skadi.container.container; 2 3 import std.string; 4 import std.algorithm; 5 import std.concurrency; 6 7 debug { 8 import std.stdio; 9 } 10 11 public import skadi.container.registration; 12 public import skadi.container.inject; 13 14 /** 15 * Exception thrown when errors occur while resolving a type in a dependency container. 16 */ 17 class ResolveException : Exception 18 { 19 this(string message, TypeInfo resolveType) 20 { 21 super(format("Exception while resolving type %s: %s", resolveType.toString(), message)); 22 } 23 } 24 25 /** 26 * Exception thrown when errors occur while registering a type in a dependency container. 27 */ 28 class RegistrationException : Exception 29 { 30 this(string message, TypeInfo registrationType) 31 { 32 super(format("Exception while registering type %s: %s", registrationType.toString(), message)); 33 } 34 } 35 36 /** 37 * Options which influence the process of registering dependencies 38 */ 39 public enum RegistrationOptions 40 { 41 /** 42 * When registering a type by its supertype, providing this option will also register 43 * a linked registration to the type itself. 44 * 45 * This allows you to resolve that type both by super type and concrete type using the 46 * same registration scope (and instance managed by this scope). 47 */ 48 ADD_CONCRETE_TYPE_REGISTRATION 49 } 50 51 /** 52 * The dependency container maintains all dependencies registered with it. 53 * 54 * Dependencies registered by a container can be resolved as long as they are still registered with the container. 55 * Upon resolving a dependency, an instance is fetched according to a specific scope which dictates how instances of 56 * dependencies are created. Resolved dependencies will be injected before being returned. 57 * 58 * In most cases you want to use a global singleton dependency container provided by getInstance() to manage all dependencies. 59 * You can still create new instances of this class for exceptional situations. 60 */ 61 synchronized class Container 62 { 63 private Registration[][TypeInfo] registrations; 64 65 private Registration[] injectStack; 66 67 /** 68 * Register a dependency by concrete class type. 69 * 70 * A dependency registered by concrete class type can only be resolved by concrete class type. 71 * No qualifiers can be used when resolving dependencies which are registered by concrete type. 72 * 73 * The default registration scope is "single instance" scope. 74 * 75 * Returns: 76 * A registration is returned which can be used to change the registration scope. 77 */ 78 public Registration register(ConcreteType)() 79 { 80 return register!(ConcreteType, ConcreteType)(); 81 } 82 83 /** 84 * Register a dependency by super type. 85 * 86 * A dependency registered by super type can only be resolved by super type. A qualifier is typically 87 * used to resolve dependencies registered by super type. 88 * 89 * The default registration scope is "single instance" scope. 90 * 91 * See_Also: singleInstance, newInstance, existingInstance, RegistrationOptions 92 */ 93 public Registration register(SuperType, ConcreteType : SuperType, RegistrationOptionsTuple...)(RegistrationOptionsTuple options) 94 { 95 TypeInfo registeredType = typeid(SuperType); 96 TypeInfo_Class concreteType = typeid(ConcreteType); 97 98 debug(poodinisVerbose) { 99 writeln(format("DEBUG: Register type %s (as %s)", concreteType.toString(), registeredType.toString())); 100 } 101 102 auto existingRegistration = getExistingRegistration(registeredType, concreteType); 103 if (existingRegistration) { 104 return existingRegistration; 105 } 106 107 auto newRegistration = new InjectedRegistration!ConcreteType(registeredType, this); 108 newRegistration.singleInstance(); 109 110 if (hasOption(options, RegistrationOptions.ADD_CONCRETE_TYPE_REGISTRATION)) { 111 static if (!is(SuperType == ConcreteType)) { 112 auto concreteTypeRegistration = register!ConcreteType; 113 concreteTypeRegistration.linkTo(newRegistration); 114 } else { 115 throw new RegistrationException("Option ADD_CONCRETE_TYPE_REGISTRATION cannot be used when registering a concrete type registration", concreteType); 116 } 117 } 118 119 registrations[registeredType] ~= cast(shared(Registration)) newRegistration; 120 return newRegistration; 121 } 122 123 private bool hasOption(RegistrationOptionsTuple...)(RegistrationOptionsTuple options, RegistrationOptions option) 124 { 125 foreach(presentOption ; options) { 126 if (presentOption == option) { 127 return true; 128 } 129 } 130 131 return false; 132 } 133 134 private Registration getExistingRegistration(TypeInfo registrationType, TypeInfo qualifierType) 135 { 136 auto existingCandidates = registrationType in registrations; 137 if (existingCandidates) { 138 return getRegistration(cast(Registration[]) *existingCandidates, qualifierType); 139 } 140 141 return null; 142 } 143 144 private Registration getRegistration(Registration[] candidates, TypeInfo concreteType) 145 { 146 foreach(existingRegistration ; candidates) { 147 if (existingRegistration.instantiatableType == concreteType) { 148 return existingRegistration; 149 } 150 } 151 152 return null; 153 } 154 155 /** 156 * Resolve dependencies. 157 * 158 * Dependencies can only resolved using this method if they are registered by concrete type or the only 159 * concrete type registered by super type. 160 * 161 * Resolved dependencies are automatically injected before being returned. 162 * 163 * Returns: 164 * An instance is returned which is created according to the registration scope with which they are registered. 165 * 166 * Throws: 167 * ResolveException when type is not registered. 168 169 * You need to use the resolve method which allows you to specify a qualifier. 170 */ 171 public RegistrationType resolve(RegistrationType)() 172 { 173 return resolve!(RegistrationType, RegistrationType)(); 174 } 175 176 /** 177 * Resolve dependencies using a qualifier. 178 * 179 * Dependencies can only resolved using this method if they are registered by super type. 180 * 181 * Resolved dependencies are automatically injected before being returned. 182 * 183 * Returns: 184 * An instance is returned which is created according to the registration scope with which they are registered. 185 * 186 * Throws: 187 * ResolveException when type is not registered or there are multiple candidates available for type. 188 */ 189 public QualifierType resolve(RegistrationType, QualifierType : RegistrationType)() 190 { 191 TypeInfo resolveType = typeid(RegistrationType); 192 TypeInfo qualifierType = typeid(QualifierType); 193 194 debug(poodinisVerbose) { 195 writeln("DEBUG: Resolving type " ~ resolveType.toString() ~ " with qualifier " ~ qualifierType.toString()); 196 } 197 198 auto candidates = resolveType in registrations; 199 if (!candidates) { 200 throw new ResolveException("Type not registered.", resolveType); 201 } 202 203 Registration registration = getQualifiedRegistration(resolveType, qualifierType, cast(Registration[]) *candidates); 204 return resolveInjectedInstance!QualifierType(registration); 205 } 206 207 private QualifierType resolveInjectedInstance(QualifierType)(Registration registration) 208 { 209 QualifierType instance; 210 if (!(cast(Registration[]) injectStack).canFind(registration)) { 211 injectStack ~= cast(shared(Registration)) registration; 212 instance = cast(QualifierType) registration.getInstance(new InjectInstantiationContext()); 213 injectStack = injectStack[0 .. $-1]; 214 } else { 215 auto injectContext = new InjectInstantiationContext(); 216 injectContext.injectInstance = false; 217 instance = cast(QualifierType) registration.getInstance(injectContext); 218 } 219 return instance; 220 } 221 222 /** 223 * Resolve all dependencies registered to a super type. 224 * 225 * Returns: 226 * An array of injected instances is returned. The order is undetermined. 227 */ 228 public RegistrationType[] resolveAll(RegistrationType)() 229 { 230 RegistrationType[] instances; 231 TypeInfo resolveType = typeid(RegistrationType); 232 233 auto qualifiedRegistrations = resolveType in registrations; 234 if (!qualifiedRegistrations) { 235 throw new ResolveException("Type not registered.", resolveType); 236 } 237 238 foreach(registration ; cast(Registration[]) *qualifiedRegistrations) { 239 instances ~= resolveInjectedInstance!RegistrationType(registration); 240 } 241 242 return instances; 243 } 244 245 private Registration getQualifiedRegistration(TypeInfo resolveType, TypeInfo qualifierType, Registration[] candidates) 246 { 247 if (resolveType == qualifierType) { 248 if (candidates.length > 1) { 249 string candidateList = candidates.toConcreteTypeListString(); 250 throw new ResolveException("Multiple qualified candidates available: " ~ candidateList ~ ". Please use a qualifier.", resolveType); 251 } 252 253 return candidates[0]; 254 } 255 256 return getRegistration(candidates, qualifierType); 257 } 258 259 /** 260 * Clears all dependency registrations managed by this container. 261 */ 262 public void clearAllRegistrations() 263 { 264 registrations.destroy(); 265 } 266 267 /** 268 * Removes a registered dependency by type. 269 * 270 * A dependency can be removed either by super type or concrete type, depending on how they are registered. 271 * 272 */ 273 public void removeRegistration(RegistrationType)() 274 { 275 registrations.remove(typeid(RegistrationType)); 276 } 277 278 /** 279 * Returns a global singleton instance of a dependency container. 280 */ 281 public static shared(Container) getInstance() 282 { 283 static shared Container instance; 284 if (instance is null) { 285 instance = new Container(); 286 } 287 return instance; 288 } 289 290 }