1 /**
2  * Contains functionality for autowiring dependencies using a dependency container.
3  *
4  * This module is used in a dependency container for autowiring dependencies when resolving them.
5  * You typically only need this module if you want inject dependencies into a class instance not
6  * managed by a dependency container.
7  *
8  * Part of the Poodinis Dependency Injection framework.
9  *
10  * Authors:
11  *  Mike Bierlee, m.bierlee@lostmoment.com
12  * Copyright: 2014-2025 Mike Bierlee
13  * License:
14  *  This software is licensed under the terms of the MIT license.
15  *  The full terms of the license can be found in the LICENSE file.
16  */
17 
18 module poodinis.autowire;
19 
20 import poodinis.container : DependencyContainer, PreDestroy, ResolveException, ResolveOption;
21 import poodinis.registration : Registration, InstantiationContext;
22 import poodinis.factory : InstanceFactory, InstanceFactoryParameters, CreatesSingleton;
23 import poodinis.valueinjection : ValueInjector, ValueInjectionException,
24     ValueNotAvailableException, Value, MandatoryValue;
25 import poodinis.altphobos : isFunction;
26 import poodinis.imports : createImportsString;
27 
28 import std.exception : enforce;
29 import std.string : format;
30 import std.traits : BaseClassesTuple, FieldNameTuple, fullyQualifiedName, hasUDA, isDynamicArray;
31 import std.range : ElementType;
32 
33 debug {
34     import std.stdio : writeln;
35 }
36 
37 private struct UseMemberType {
38 }
39 
40 /**
41  * UDA for annotating class members as candidates for autowiring.
42  *
43  * Optionally a template parameter can be supplied to specify the type of a qualified class. The qualified type
44  * of a concrete class is used to autowire members declared by supertype. If no qualifier is supplied, the type
45  * of the member is used as qualifier.
46  *
47  * Note: @Autowire is considered legacy, but not deprecated. Using @Inject is preferred.
48  *
49  * Examples:
50  * Annotate member of class to be autowired:
51  * ---
52  * class Car {
53  *    @Autowire
54  *    Engine engine;
55  * }
56  * ---
57  *
58  * Annotate member of class with qualifier:
59  * ---
60  * class FuelEngine : Engine { ... }
61  * class ElectricEngine : Engine { ... }
62  *
63  * class HybridCar {
64  *    @Autowire!FuelEngine
65  *    Engine fuelEngine;
66  *
67  *    @Autowire!ElectricEngine
68  *    Engine electricEngine;
69  * }
70  * ---
71  * The members of an instance of "HybridCar" will now be autowired properly, because the autowire mechanism will
72  * autowire member "fuelEngine" as if it's of type "FuelEngine". This means that the members of instance "fuelEngine"
73  * will also be autowired because the autowire mechanism knows that member "fuelEngine" is an instance of "FuelEngine"
74  *
75  * See_Also: Inject
76  */
77 struct Autowire(QualifierType) {
78     QualifierType qualifier;
79 }
80 
81 /**
82  * UDA for marking autowired dependencies optional.
83  * Optional dependencies will not lead to a resolveException when there is no type registered for them.
84  * The member will remain null.
85  */
86 struct OptionalDependency {
87 }
88 
89 /**
90  * UDA for annotating class members to be autowired with a new instance regardless of their registration scope.
91  *
92  * Examples:
93  *---
94  * class Car {
95  *     @Autowire
96  *     @AssignNewInstance
97  *     Antenna antenna;
98  * }
99  *---
100  * antenna will always be assigned a new instance of class Antenna.
101  */
102 struct AssignNewInstance {
103 }
104 
105 private void printDebugAutowiredInstance(TypeInfo instanceType, void* instanceAddress) {
106     debug {
107         writeln(format("DEBUG: Autowiring members of [%s@%s]", instanceType, instanceAddress));
108     }
109 }
110 
111 /**
112  * Autowires members of a given instance using dependencies registered in the given container.
113  *
114  * All members of the given instance, which are annotated using the "Autowire" UDA, are autowired.
115  * Members can have any visibility (public, private, etc). All members are resolved using the given
116  * container. Qualifiers are used to determine the type of class to resolve for any member of instance.
117  *
118  * See_Also: Autowire
119  */
120 void autowire(Type)(shared(DependencyContainer) container, Type instance, bool isExistingInstance = false) {
121     debug (poodinisVerbose) {
122         printDebugAutowiredInstance(typeid(Type), &instance);
123     }
124 
125     // Recurse into base class if there are more between Type and Object in the hierarchy
126     static if (BaseClassesTuple!Type.length > 1) {
127         autowire!(BaseClassesTuple!Type[0])(container, instance, isExistingInstance);
128     }
129 
130     foreach (index, name; FieldNameTuple!Type) {
131         autowireMember!(name, index, Type)(container, instance, isExistingInstance);
132     }
133 }
134 
135 private void printDebugAutowiringCandidate(TypeInfo candidateInstanceType,
136     void* candidateInstanceAddress, TypeInfo instanceType, void* instanceAddress, string member) {
137     debug {
138         writeln(format("DEBUG: Autowired instance [%s@%s] to [%s@%s].%s", candidateInstanceType,
139                 candidateInstanceAddress, instanceType, instanceAddress, member));
140     }
141 }
142 
143 private void printDebugAutowiringArray(TypeInfo superTypeInfo,
144     TypeInfo instanceType, void* instanceAddress, string member) {
145     debug {
146         writeln(format("DEBUG: Autowired all registered instances of super type %s to [%s@%s].%s",
147                 superTypeInfo, instanceType, instanceAddress, member));
148     }
149 }
150 
151 private void autowireMember(string member, size_t memberIndex, Type)(
152     shared(DependencyContainer) container, Type instance, bool isExistingInstance) {
153     foreach (attribute; __traits(getAttributes, Type.tupleof[memberIndex])) {
154         static if (is(attribute == Autowire!T, T)) {
155             injectInstance!(member, memberIndex, typeof(attribute.qualifier))(container, instance);
156         } else static if (__traits(isSame, attribute, Autowire)) {
157             injectInstance!(member, memberIndex, UseMemberType)(container, instance);
158         } else static if (is(typeof(attribute) == Value) || is(typeof(attribute) == MandatoryValue)) {
159             if (isExistingInstance) {
160                 continue;
161             }
162             enum key = attribute.key;
163             enum isMandatory = is(typeof(attribute) == MandatoryValue);
164             injectValue!(member, memberIndex, key, isMandatory)(container, instance);
165         }
166     }
167 }
168 
169 private void injectInstance(string member, size_t memberIndex, QualifierType, Type)(
170     shared(DependencyContainer) container, Type instance) {
171     if (instance.tupleof[memberIndex] is null) {
172         alias MemberType = typeof(Type.tupleof[memberIndex]);
173         enum isOptional = hasUDA!(Type.tupleof[memberIndex], OptionalDependency);
174 
175         static if (isDynamicArray!MemberType) {
176             injectMultipleInstances!(member, memberIndex, isOptional, MemberType)(container,
177                 instance);
178         } else {
179             injectSingleInstance!(member, memberIndex, isOptional, MemberType, QualifierType)(container,
180                 instance);
181         }
182     }
183 }
184 
185 private void injectMultipleInstances(string member, size_t memberIndex,
186     bool isOptional, MemberType, Type)(shared(DependencyContainer) container, Type instance) {
187     alias MemberElementType = ElementType!MemberType;
188     static if (isOptional) {
189         auto instances = container.resolveAll!MemberElementType(ResolveOption.noResolveException);
190     } else {
191         auto instances = container.resolveAll!MemberElementType;
192     }
193 
194     instance.tupleof[memberIndex] = instances;
195     debug (poodinisVerbose) {
196         printDebugAutowiringArray(typeid(MemberElementType), typeid(Type), &instance, member);
197     }
198 }
199 
200 private void injectSingleInstance(string member, size_t memberIndex,
201     bool isOptional, MemberType, QualifierType, Type)(
202     shared(DependencyContainer) container, Type instance) {
203     debug (poodinisVerbose) {
204         TypeInfo qualifiedInstanceType = typeid(MemberType);
205     }
206 
207     enum assignNewInstance = hasUDA!(Type.tupleof[memberIndex], AssignNewInstance);
208 
209     MemberType qualifiedInstance;
210     static if (!is(QualifierType == UseMemberType)) {
211         qualifiedInstance = createOrResolveInstance!(MemberType, QualifierType,
212             assignNewInstance, isOptional)(container);
213         debug (poodinisVerbose) {
214             qualifiedInstanceType = typeid(QualifierType);
215         }
216     } else {
217         qualifiedInstance = createOrResolveInstance!(MemberType, MemberType,
218             assignNewInstance, isOptional)(container);
219     }
220 
221     instance.tupleof[memberIndex] = qualifiedInstance;
222 
223     debug (poodinisVerbose) {
224         printDebugAutowiringCandidate(qualifiedInstanceType,
225             &qualifiedInstance, typeid(Type), &instance, member);
226     }
227 }
228 
229 private QualifierType createOrResolveInstance(MemberType, QualifierType,
230     bool createNew, bool isOptional)(shared(DependencyContainer) container) {
231     static if (createNew) {
232         auto instanceFactory = new InstanceFactory();
233         instanceFactory.factoryParameters = InstanceFactoryParameters(typeid(MemberType),
234             CreatesSingleton.no);
235         return cast(MemberType) instanceFactory.getInstance();
236     } else {
237         static if (isOptional) {
238             return container.resolve!(MemberType, QualifierType)(ResolveOption.noResolveException);
239         } else {
240             return container.resolve!(MemberType, QualifierType);
241         }
242     }
243 }
244 
245 private void injectValue(string member, size_t memberIndex, string key, bool mandatory, Type)(
246     shared(DependencyContainer) container, Type instance) {
247     alias MemberType = typeof(Type.tupleof[memberIndex]);
248     try {
249         auto injector = container.resolve!(ValueInjector!MemberType);
250         instance.tupleof[memberIndex] = injector.get(key);
251         debug (poodinisVerbose) {
252             printDebugValueInjection(typeid(Type), &instance, member, typeid(MemberType), key);
253         }
254     } catch (ResolveException e) {
255         throw new ValueInjectionException(format(
256                 "Could not inject value of type %s into %s.%s: value injector is missing for this type.",
257                 typeid(MemberType), typeid(Type), member));
258     } catch (ValueNotAvailableException e) {
259         static if (mandatory) {
260             throw new ValueInjectionException(format("Could not inject value of type %s into %s.%s",
261                     typeid(MemberType), typeid(Type), member), e);
262         }
263     }
264 }
265 
266 private void printDebugValueInjection(TypeInfo instanceType,
267     void* instanceAddress, string member, TypeInfo valueType, string key) {
268     debug {
269         writeln(format("DEBUG: Injected value with key '%s' of type %s into [%s@%s].%s",
270                 key, valueType, instanceType, instanceAddress, member));
271     }
272 }
273 
274 /**
275  * Autowire the given instance using the globally available dependency container.
276  *
277  * See_Also: DependencyContainer
278  * Deprecated: Using the global container is undesired. See DependencyContainer.getInstance().
279  */
280 deprecated void globalAutowire(Type)(Type instance) {
281     DependencyContainer.getInstance().autowire(instance);
282 }
283 
284 class AutowiredRegistration(RegistrationType : Object) : Registration {
285     private shared(DependencyContainer) container;
286 
287     this(TypeInfo registeredType, InstanceFactory instanceFactory,
288         shared(DependencyContainer) originatingContainer) {
289         super(registeredType, typeid(RegistrationType), instanceFactory, originatingContainer);
290     }
291 
292     override Object getInstance(
293         InstantiationContext context = new AutowireInstantiationContext()) {
294         enforce(!(originatingContainer is null),
295             "The registration's originating container is null. There is no way to resolve autowire dependencies.");
296 
297         RegistrationType instance = cast(RegistrationType) super.getInstance(context);
298 
299         AutowireInstantiationContext autowireContext = cast(AutowireInstantiationContext) context;
300         enforce(!(autowireContext is null),
301             "Given instantiation context type could not be cast to an AutowireInstantiationContext. If you relied on using the default assigned context: make sure you're calling getInstance() on an instance of type AutowiredRegistration!");
302         if (autowireContext.autowireInstance) {
303             bool isExistingInstance = instanceFactory.factoryParameters
304                 .existingInstance !is null;
305             originatingContainer.autowire(instance, isExistingInstance);
306         }
307 
308         this.preDestructor = getPreDestructor(instance);
309 
310         return instance;
311     }
312 
313     private void delegate() getPreDestructor(RegistrationType instance) {
314         void delegate() preDestructor = null;
315         static foreach (memberName; __traits(allMembers, RegistrationType)) {
316             static if (__traits(compiles, __traits(getOverloads, RegistrationType, memberName))) {
317                 static foreach (overload; __traits(getOverloads, RegistrationType, memberName)) {
318                     static if (__traits(compiles, __traits(getProtection, overload))
319                         && __traits(getProtection, overload) == "public"
320                         && isFunction!overload
321                         && hasUDA!(overload, PreDestroy)) {
322                         preDestructor = &__traits(getMember, instance, memberName);
323                     }
324                 }
325             }
326         }
327 
328         return preDestructor;
329     }
330 }
331 
332 class AutowireInstantiationContext : InstantiationContext {
333     bool autowireInstance = true;
334 }
335 
336 version (unittest)  :  //
337 
338 import poodinis;
339 import poodinis.testclasses;
340 import std.exception;
341 
342 // Test autowiring concrete type to existing instance
343 unittest {
344     auto container = new shared DependencyContainer();
345     container.register!ComponentA;
346     auto componentB = new ComponentB();
347     container.autowire(componentB);
348     assert(componentB !is null, "Autowirable dependency failed to autowire");
349 }
350 
351 // Test autowiring interface type to existing instance
352 unittest {
353     auto container = new shared DependencyContainer();
354     container.register!(InterfaceA, ComponentC);
355     auto componentD = new ComponentD();
356     container.autowire(componentD);
357     assert(componentD.componentC !is null, "Autowirable dependency failed to autowire");
358 }
359 
360 // Test autowiring private members
361 unittest {
362     auto container = new shared DependencyContainer();
363     container.register!(InterfaceA, ComponentC);
364     auto componentD = new ComponentD();
365     container.autowire(componentD);
366     assert(componentD.privateComponentC is componentD.componentC,
367         "Autowire private dependency failed");
368 }
369 
370 // Test autowiring will only happen once
371 unittest {
372     auto container = new shared DependencyContainer();
373     container.register!(InterfaceA, ComponentC).newInstance();
374     auto componentD = new ComponentD();
375     container.autowire(componentD);
376     auto expectedComponent = componentD.componentC;
377     container.autowire(componentD);
378     auto actualComponent = componentD.componentC;
379     assert(expectedComponent is actualComponent,
380         "Autowiring the second time wired a different instance");
381 }
382 
383 // Test autowiring unregistered type
384 unittest {
385     auto container = new shared DependencyContainer();
386     auto componentD = new ComponentD();
387     assertThrown!(ResolveException)(container.autowire(componentD),
388         "Autowiring unregistered type should throw ResolveException");
389 }
390 
391 // Test autowiring member with non-autowire attribute does not autowire
392 unittest {
393     auto container = new shared DependencyContainer();
394     auto componentE = new ComponentE();
395     container.autowire(componentE);
396     assert(componentE.componentC is null,
397         "Autowiring should not occur for members with attributes other than @Autowire");
398 }
399 
400 // Test autowire class with alias declaration
401 unittest {
402     auto container = new shared DependencyContainer();
403     container.register!ComponentA;
404     auto componentDeclarationCocktail = new ComponentDeclarationCocktail();
405 
406     container.autowire(componentDeclarationCocktail);
407 
408     assert(componentDeclarationCocktail.componentA !is null,
409         "Autowiring class with non-assignable declarations failed");
410 }
411 
412 // Test autowire class with qualifier
413 unittest {
414     auto container = new shared DependencyContainer();
415     container.register!(InterfaceA, ComponentC);
416     container.register!(InterfaceA, ComponentX);
417     auto componentX = container.resolve!(InterfaceA, ComponentX);
418 
419     auto monkeyShine = new MonkeyShine();
420     container.autowire(monkeyShine);
421 
422     assert(monkeyShine.component is componentX, "Autowiring class with qualifier failed");
423 }
424 
425 // Test autowire class with multiple qualifiers
426 unittest {
427     auto container = new shared DependencyContainer();
428     container.register!(InterfaceA, ComponentC);
429     container.register!(InterfaceA, ComponentX);
430     auto componentC = container.resolve!(InterfaceA, ComponentC);
431     auto componentX = container.resolve!(InterfaceA, ComponentX);
432 
433     auto bootstrapBootstrap = new BootstrapBootstrap();
434     container.autowire(bootstrapBootstrap);
435 
436     assert(bootstrapBootstrap.componentX is componentX,
437         "Autowiring class with multiple qualifiers failed");
438     assert(bootstrapBootstrap.componentC is componentC,
439         "Autowiring class with multiple qualifiers failed");
440 }
441 
442 // Test getting instance from autowired registration will autowire instance
443 unittest {
444     auto container = new shared DependencyContainer();
445     container.register!ComponentA;
446 
447     auto registration = new AutowiredRegistration!ComponentB(typeid(ComponentB),
448         new InstanceFactory(), container).initializeFactoryType().singleInstance();
449     auto instance = cast(ComponentB) registration.getInstance(
450         new AutowireInstantiationContext());
451 
452     assert(instance.componentA !is null);
453 }
454 
455 // Test autowiring a dynamic array with all qualified types
456 unittest {
457     auto container = new shared DependencyContainer();
458     container.register!(InterfaceA, ComponentC);
459     container.register!(InterfaceA, ComponentX);
460 
461     auto lord = new LordOfTheComponents();
462     container.autowire(lord);
463 
464     assert(lord.components.length == 2, "Dynamic array was not autowired");
465 }
466 
467 // Test autowiring new instance of singleinstance registration with newInstance UDA
468 unittest {
469     auto container = new shared DependencyContainer();
470     container.register!ComponentA;
471 
472     auto regularComponentA = container.resolve!ComponentA;
473     auto charlie = new ComponentCharlie();
474 
475     container.autowire(charlie);
476 
477     assert(charlie.componentA !is regularComponentA,
478         "Autowiring class with AssignNewInstance did not yield a different instance");
479 }
480 
481 // Test autowiring members from base class
482 unittest {
483     auto container = new shared DependencyContainer();
484     container.register!ComponentA;
485     container.register!ComponentB;
486     container.register!ComponentZ;
487 
488     auto instance = new ComponentZ();
489     container.autowire(instance);
490 
491     assert(instance.componentA !is null);
492 }
493 
494 // Test autowiring optional dependencies
495 unittest {
496     auto container = new shared DependencyContainer();
497     auto instance = new OuttaTime();
498 
499     container.autowire(instance);
500 
501     assert(instance.interfaceA is null);
502     assert(instance.componentA is null);
503     assert(instance.componentCs is null);
504 }
505 
506 // Test autowiring class using value injection
507 unittest {
508     auto container = new shared DependencyContainer();
509 
510     container.register!(ValueInjector!int, TestInjector);
511     container.register!ComponentA;
512     auto instance = new ValuedClass();
513 
514     container.autowire(instance);
515 
516     assert(instance.intValue == 8);
517     assert(instance.unrelated !is null);
518 }
519 
520 // Test that @Value is not injected at all for existingInstance (default value case)
521 unittest {
522     auto container = new shared DependencyContainer();
523     container.register!(ValueInjector!int, TestInjector);
524     container.register!ComponentA;
525     auto instance1 = new ValuedClass();
526     container.register!ValuedClass.existingInstance(instance1);
527     auto resolved1 = container.resolve!ValuedClass;
528     assert(resolved1 is instance1, "Should resolve to the same instance");
529     assert(resolved1.intValue == int.init, "@Value should not inject for existingInstance, even if default");
530     assert(resolved1.unrelated !is null, "Other dependencies should still be autowired");
531 }
532 
533 // Test that @Value is not injected at all for existingInstance (non-default value case)
534 unittest {
535     auto container = new shared DependencyContainer();
536     container.register!(ValueInjector!int, TestInjector);
537     container.register!ComponentA;
538     auto instance2 = new ValuedClass();
539     instance2.intValue = 42;
540     container.register!ValuedClass.existingInstance(instance2);
541     auto resolved2 = container.resolve!ValuedClass;
542     assert(resolved2 is instance2, "Should resolve to the same instance");
543     assert(resolved2.intValue == 42, "@Value should not inject for existingInstance, even if non-default");
544     assert(resolved2.unrelated !is null, "Other dependencies should still be autowired");
545 }