1 /** 2 * Contains the implementation of the dependency container. 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-11 8 */ 9 10 module dango.inject.container; 11 12 private 13 { 14 import std.algorithm.iteration : map, filter; 15 import std.algorithm.searching : find, canFind; 16 import std.format : fmt = format; 17 import std.exception : assumeWontThrow; 18 import std.uni : toUpper; 19 import std.array : join, array; 20 21 import dango.inject.injection : Inject, Named, inject; 22 import dango.inject.factory : ComponentFactory; 23 import dango.inject.provider; 24 import dango.inject.exception; 25 } 26 27 28 /** 29 * The dependency container maintains all dependencies registered with it. 30 */ 31 class DependencyContainer 32 { 33 private @safe 34 { 35 Registration[][TypeInfo] _registrations; 36 Registration[] autowireStack; 37 } 38 39 /** 40 * Register a single value. 41 * 42 * A `ValueProvider` is used to provide the value. 43 */ 44 Registration value(T)(T value) nothrow @safe 45 { 46 return this.provider(new ValueProvider!T(value)); 47 } 48 49 /** 50 * Register a single value with name. 51 * 52 * A `ValueProvider` is used to provide the value. 53 */ 54 Registration value(T)(string name, T value) nothrow @safe 55 { 56 return this.provider(name, new ValueProvider!T(value)); 57 } 58 59 /** 60 * Register a Class. 61 * 62 * Instances are provided using a `ClassProvider` that injects dependencies using 63 * this container. 64 */ 65 Registration register(ConcreteType)() nothrow @safe 66 if (is(ConcreteType == class)) 67 { 68 return register!(ConcreteType, ConcreteType)(); 69 } 70 71 /** 72 * Register a Class. 73 * 74 * Instances are provided using a `ClassProvider` that injects dependencies using 75 * this container. 76 */ 77 Registration register(SuperType, ConcreteType : SuperType)() nothrow @safe 78 if (is(ConcreteType == class)) 79 { 80 Provider provider = new ClassProvider!(SuperType, ConcreteType)(this); 81 TypeInfo registeredType = provider.registeredType; 82 TypeInfo providedType = provider.providedType; 83 84 if (auto existingCandidates = registeredType in _registrations) 85 { 86 auto existingRegistration = assumeWontThrow(find!((r) @trusted { 87 return r.providedType == providedType; 88 })(*existingCandidates)); 89 if (existingRegistration.length) 90 return existingRegistration[0]; 91 } 92 93 return this.provider(provider); 94 } 95 96 /** 97 * Register a Class with name. 98 * 99 * Instances are provided using a `ClassProvider` that injects dependencies using 100 * this container. 101 */ 102 Registration register(ConcreteType)(string name) nothrow @safe 103 if (is(ConcreteType == class)) 104 { 105 return register!(ConcreteType, ConcreteType)(name); 106 } 107 108 /** 109 * Register a Class with name. 110 * 111 * Instances are provided using a `ClassProvider` that injects dependencies using 112 * this container. 113 */ 114 Registration register(SuperType, ConcreteType : SuperType)(string name) nothrow @safe 115 if (is(ConcreteType == class)) 116 { 117 Provider provider = new ClassProvider!(SuperType, ConcreteType)(this); 118 TypeInfo registeredType = provider.registeredType; 119 TypeInfo providedType = provider.providedType; 120 string uName = assumeWontThrow(name.toUpper); 121 122 if (auto existingCandidates = registeredType in _registrations) 123 { 124 auto existingRegistration = assumeWontThrow(find!((r) @safe { 125 return r.isNamed && r.name == uName; 126 })(*existingCandidates)); 127 if (existingRegistration.length) 128 return existingRegistration[0]; 129 } 130 131 return this.provider(name, provider); 132 } 133 134 /** 135 * Register a factory function for a type. 136 */ 137 Registration factory(F : ComponentFactory!(T, A), T, A...)(A args) nothrow @safe 138 { 139 auto freg = this.register!(ComponentFactory!(T, A), F).singleInstance(); 140 auto fact = assumeWontThrow(resolveInjectdInstance!F(freg)); 141 return this.provider(new FactoryProvider!(F, T, A)(this, fact, args)); 142 } 143 144 /** 145 * Register a factory function for a type. 146 */ 147 Registration factory(F : ComponentFactory!(T, A), T, A...)(string name, A args) nothrow @safe 148 { 149 auto freg = this.register!(ComponentFactory!(T, A), F)(name).singleInstance(); 150 auto fact = assumeWontThrow(resolveInjectdInstance!F(freg)); 151 return this.provider(name, new FactoryProvider!(F, T, A)(this, fact, args)); 152 } 153 154 /** 155 * Register a provider using the type returned by `provider.providedType`. 156 */ 157 Registration provider(Provider provider) nothrow @safe 158 { 159 auto newRegistration = new Registration(provider); 160 return addRegistration(provider.registeredType, newRegistration); 161 } 162 163 /** 164 * Register a provider using the type returned by `provider.providedType`. 165 */ 166 Registration provider(string name, Provider provider) nothrow @safe 167 { 168 auto newRegistration = new Registration(provider, assumeWontThrow(name.toUpper)); 169 return addRegistration(provider.registeredType, newRegistration); 170 } 171 172 /** 173 * Resolve dependencies using a qualifier. 174 * 175 * Dependencies can only resolved using this method if they are registered by super type. 176 * 177 * Resolved dependencies are automatically autowired before being returned. 178 */ 179 ResolveType resolve(ResolveType)() @safe 180 { 181 TypeInfo resolveType = typeid(ResolveType); 182 183 auto candidates = resolveType in _registrations; 184 if (!candidates) 185 throw new ResolveDangoException("Type not registered.", resolveType); 186 187 if (candidates.length > 1) 188 { 189 string candidateList = (*candidates).map!((c) => c.providedType.toString).join(','); 190 throw new ResolveDangoException("Multiple qualified candidates available: " ~ 191 candidateList ~ ". Please use a qualifier or name.", resolveType); 192 } 193 194 Registration registration = (*candidates)[0]; 195 return resolveInjectdInstance!ResolveType(registration); 196 } 197 198 /** 199 * Resolve all dependencies registered to a super type. 200 * 201 * Returns: 202 * An array of autowired instances is returned. The order is undetermined. 203 */ 204 ResolveType[] resolveAll(ResolveType)() @safe 205 { 206 TypeInfo resolveType = typeid(ResolveType); 207 208 auto candidates = resolveType in _registrations; 209 if (!candidates) 210 throw new ResolveDangoException("Type not registered.", resolveType); 211 212 ResolveType[] result; 213 foreach (registration; *candidates) 214 result ~= resolveInjectdInstance!ResolveType(registration); 215 216 return result; 217 } 218 219 /** 220 * Resolve dependencies using a qualifier. 221 * 222 * Dependencies can only resolved using this method if they are registered by super type. 223 * 224 * Resolved dependencies are automatically autowired before being returned. 225 */ 226 ResolveType resolve(ResolveType)(string name) @safe 227 { 228 TypeInfo resolveType = typeid(ResolveType); 229 string uName = name.toUpper; 230 231 auto candidates = resolveType in _registrations; 232 if (!candidates) 233 throw new ResolveDangoException("Type not registered.", resolveType); 234 235 auto namedCandidates = filter!((r) { 236 return r.isNamed && r.name == uName; 237 })(*candidates).array; 238 239 if (namedCandidates.length > 1) 240 { 241 string candidateList = namedCandidates.map!((c) => c.providedType.toString).join(','); 242 throw new ResolveDangoException("Multiple qualified candidates available: " ~ 243 candidateList ~ ".", resolveType); 244 } 245 else if (namedCandidates.length == 0) 246 throw new ResolveDangoException( 247 fmt!"Type not registered with name '%s'."(uName), resolveType); 248 249 Registration registration = namedCandidates[0]; 250 return resolveInjectdInstance!ResolveType(registration); 251 } 252 253 /** 254 * Resolve all dependencies registered to a super type. 255 * 256 * Returns: 257 * An array of autowired instances is returned. The order is undetermined. 258 */ 259 ResolveType[] resolveAll(ResolveType)(string name) @safe 260 { 261 TypeInfo resolveType = typeid(ResolveType); 262 string uName = name.toUpper; 263 264 auto candidates = resolveType in _registrations; 265 if (!candidates) 266 throw new ResolveDangoException("Type not registered.", resolveType); 267 268 auto namedCandidates = filter!((r) { 269 return r.isNamed && r.name == uName; 270 })(*candidates); 271 272 ResolveType[] result; 273 foreach (registration; namedCandidates) 274 result ~= resolveInjectdInstance!ResolveType(registration); 275 276 return result; 277 } 278 279 /** 280 * Resolve dependencies using a qualifier. 281 * 282 * Dependencies can only resolved using this method if they are registered by super type. 283 * 284 * Resolved dependencies are automatically autowired before being returned. 285 */ 286 QualifierType resolve(ResolveType, QualifierType : ResolveType)() @safe 287 { 288 TypeInfo resolveType = typeid(ResolveType); 289 TypeInfo qualifierType = typeid(QualifierType); 290 291 auto candidates = resolveType in _registrations; 292 if (!candidates) 293 throw new ResolveDangoException("Type not registered.", resolveType); 294 295 auto typedCandidates = filter!((r) @trusted { 296 return r.providedType == qualifierType; 297 })(*candidates).array; 298 299 if (typedCandidates.length > 1) 300 { 301 string candidateList = typedCandidates.map!((c) => c.providedType.toString).join(','); 302 throw new ResolveDangoException("Multiple qualified candidates available: " ~ 303 candidateList ~ ".", resolveType); 304 } 305 else if (typedCandidates.length == 0) 306 throw new ResolveDangoException("Type not registered.", resolveType); 307 308 Registration registration = typedCandidates[0]; 309 return resolveInjectdInstance!QualifierType(registration); 310 } 311 312 /** 313 * Resolve all dependencies registered to a super type. 314 * 315 * Returns: 316 * An array of autowired instances is returned. The order is undetermined. 317 */ 318 QualifierType[] resolveAll(ResolveType, QualifierType : ResolveType)() 319 { 320 TypeInfo resolveType = typeid(ResolveType); 321 TypeInfo qualifierType = typeid(QualifierType); 322 323 auto candidates = resolveType in _registrations; 324 if (!candidates) 325 throw new ResolveDangoException("Type not registered.", resolveType); 326 327 auto typedCandidates = filter!((r) @trusted { 328 return r.providedType == qualifierType; 329 })(*candidates); 330 331 ResolveType[] result; 332 foreach (registration; typedCandidates) 333 result ~= resolveInjectdInstance!ResolveType(registration); 334 335 return result; 336 } 337 338 339 private: 340 341 342 ResolveType resolveInjectdInstance(ResolveType)(Registration registration) @trusted 343 { 344 synchronized (this) 345 { 346 if (!(autowireStack.canFind(registration))) 347 { 348 autowireStack ~= registration; 349 scope (exit) 350 autowireStack = autowireStack[0 .. $-1]; 351 return registration.provide!ResolveType(true); 352 } 353 else 354 return registration.provide!ResolveType(false); 355 } 356 } 357 358 359 Registration addRegistration(TypeInfo registeredType, Registration registration) nothrow @safe 360 { 361 assumeWontThrow(() @safe { 362 synchronized (this) 363 _registrations[registeredType] ~= registration; 364 }()); 365 return registration; 366 } 367 } 368 369 @("Should work container with value") 370 @safe unittest 371 { 372 auto cont = new DependencyContainer(); 373 auto reg = cont.value(1); 374 assert (!reg.isNamed); 375 assert (cont.resolve!int == 1); 376 377 reg = cont.value("two", 2); 378 assert (reg.isNamed); 379 assert (reg.name == "TWO"); 380 381 assert (cont._registrations[typeid(int)].length == 2); 382 assert (cont.resolve!int("tWo") == 2); 383 384 cont.value("hello"); 385 assert (cont.resolve!string() == "hello"); 386 cont.value("name", "world"); 387 assertThrown!ResolveDangoException(cont.resolve!string); 388 assert (cont.resolve!string("name") == "world"); 389 } 390 391 version (unittest) 392 { 393 import std.exception : assertThrown; 394 interface Animal { string name() @safe; } 395 class Cat : Animal {string name() { return "cat"; }} 396 class Dog : Animal {string name() { return "dog"; }} 397 } 398 399 @("Should work container with concrete object") 400 @system unittest 401 { 402 auto cont = new DependencyContainer(); 403 auto reg1 = cont.register!Cat(); 404 auto reg2 = cont.register!Cat(); 405 assert (reg1 == reg2); 406 auto cat = cont.resolve!Cat; 407 assert (cat && cat.name == "cat"); 408 } 409 410 @("Should work container with qualified object") 411 @safe unittest 412 { 413 auto cont = new DependencyContainer(); 414 auto reg1 = cont.register!(Animal, Cat)(); 415 assertThrown(cont.resolve!(Animal, Dog)); 416 417 auto cat = cont.resolve!Animal; 418 assert (cat && cat.name == "cat"); 419 420 cat = cont.resolve!(Animal, Cat); 421 assert (cat && cat.name == "cat"); 422 } 423 424 @("Should work container with named object") 425 @safe unittest 426 { 427 auto cont = new DependencyContainer(); 428 auto reg1 = cont.register!(Animal, Cat)("bar"); 429 assertThrown(cont.resolve!(Animal, Dog)); 430 431 auto cat = cont.resolve!Animal; 432 assert (cat && cat.name == "cat"); 433 434 cat = cont.resolve!(Animal, Cat); 435 assert (cat && cat.name == "cat"); 436 437 cat = cont.resolve!(Animal)("bar"); 438 assert (cat && cat.name == "cat"); 439 } 440 441 @("Should work container with custom provider") 442 @safe unittest 443 { 444 struct ItemDB { int value; } 445 class CustomProvider : Provider 446 { 447 private int _initVal; 448 this(int initVal) { _initVal = initVal; } 449 TypeInfo providedType() const pure nothrow @safe { return typeid(ItemDB); } 450 TypeInfo registeredType() const pure nothrow @safe { return typeid(ItemDB); } 451 bool canSingleton() const pure nothrow @safe { return false; } 452 Provider originalProvider() pure nothrow @safe { return this; } 453 void withProvided(bool ii, void delegate(void*) @safe dg) @trusted 454 { 455 auto result = ItemDB(_initVal); 456 dg(&result); 457 } 458 } 459 460 auto cont = new DependencyContainer(); 461 cont.provider("one", new CustomProvider(1)); 462 cont.provider("two", new CustomProvider(2)); 463 assert (cont.resolve!ItemDB("one").value == 1); 464 assert (cont.resolve!ItemDB("two").value == 2); 465 } 466 467 @("Should work container with factory") 468 @safe unittest 469 { 470 interface Logger { int getLevel() @safe; } 471 class ConsoleLogger : Logger 472 { 473 private int _level; 474 this(int level) { _level = level; } 475 int getLevel() @safe { return _level; } 476 } 477 class CustomFactory : ComponentFactory!(Logger, int) 478 { 479 Logger createComponent(int lvl) { return new ConsoleLogger(lvl); } 480 } 481 482 auto cont = new DependencyContainer(); 483 cont.factory!CustomFactory(10); 484 485 auto ft = cont.resolve!(ComponentFactory!(Logger, int)); 486 assert (ft && ft.createComponent(11).getLevel == 11); 487 488 auto v = cont.resolve!Logger; 489 assert (v && v.getLevel == 10); 490 } 491