1 module skadi.container.inject; 2 3 public import skadi.container.container; 4 5 import std.exception; 6 import std.stdio; 7 import std.string; 8 import std.traits; 9 import std.range; 10 11 struct UseMemberType {}; 12 13 /** 14 * UDA for annotating class members as candidates for Injecting. 15 * 16 * Optionally a template parameter can be supplied to specify the type of a qualified class. The qualified type 17 * of a concrete class is used to inject members declared by supertype. If no qualifier is supplied, the type 18 * of the member is used as qualifier. 19 * 20 * The members of an instance of "HybridCar" will now be injectd properly, because the inject mechanism will 21 * inject member "fuelEngine" as if it's of type "FuelEngine". This means that the members of instance "fuelEngine" 22 * will also be injectd because the inject mechanism knows that member "fuelEngine" is an instance of "FuelEngine" 23 */ 24 struct Inject(QualifierType = UseMemberType) 25 { 26 QualifierType qualifier; 27 }; 28 29 /** 30 * UDA for annotating class members to be injectd with a new instance regardless of their registration scope. 31 * 32 * antenna will always be assigned a new instance of class Antenna. 33 */ 34 struct AssignNewInstance {} 35 36 private void printDebugInjectedInstance(TypeInfo instanceType, void* instanceAddress) 37 { 38 writeln(format("DEBUG: Injecting members of [%s@%s]", instanceType, instanceAddress)); 39 } 40 41 /** 42 * Injects members of a given instance using dependencies registered in the given container. 43 * 44 * All public members of the given instance, which are annotated using the "Inject" UDA, are injectd. 45 * All members are resolved using the given container. Qualifiers are used to determine the type of class to 46 * resolve for any member of instance. 47 * 48 * Note that private members will not be injectd because the autowiring mechanism is not able to by-pass 49 * member visibility protection. 50 * 51 * See_Also: Inject 52 */ 53 public void inject(Type)(shared(Container) container, Type instance) 54 { 55 debug(poodinisVerbose) { 56 printDebugInjectedInstance(typeid(Type), &instance); 57 } 58 59 foreach (member ; __traits(allMembers, Type)) { 60 injectMember!member(container, instance); 61 } 62 } 63 64 private void printDebugInjectingCandidate(TypeInfo candidateInstanceType, void* candidateInstanceAddress, TypeInfo instanceType, void* instanceAddress, string member) 65 { 66 writeln(format("DEBUG: Injected instance [%s@%s] to [%s@%s].%s", candidateInstanceType, candidateInstanceAddress, instanceType, instanceAddress, member)); 67 } 68 69 private void printDebugInjectingArray(TypeInfo superTypeInfo, TypeInfo instanceType, void* instanceAddress, string member) 70 { 71 writeln(format("DEBUG: Inject all registered instances of super type %s to [%s@%s].%s", superTypeInfo, instanceType, instanceAddress, member)); 72 } 73 74 private void injectMember(string member, Type)(shared(Container) container, Type instance) 75 { 76 static if(__traits(compiles, __traits(getMember, instance, member)) && __traits(compiles, __traits(getAttributes, __traits(getMember, instance, member)))) { 77 foreach(injectAttribute; __traits(getAttributes, __traits(getMember, instance, member))) { 78 static if (__traits(isSame, injectAttribute, Inject) || is(injectAttribute == Inject!T, T)) { 79 if (__traits(getMember, instance, member) is null) { 80 alias MemberType = typeof(__traits(getMember, instance, member)); 81 82 enum assignNewInstance = hasUDA!(__traits(getMember, instance, member), AssignNewInstance); 83 84 static if (isDynamicArray!MemberType) { 85 alias MemberElementType = ElementType!MemberType; 86 auto instances = container.resolveAll!MemberElementType; 87 __traits(getMember, instance, member) = instances; 88 debug(poodinisVerbose) { 89 printDebugInjectingArray(typeid(MemberElementType), typeid(Type), &instance, member); 90 } 91 } else { 92 debug(poodinisVerbose) { 93 TypeInfo qualifiedInstanceType = typeid(MemberType); 94 } 95 96 MemberType qualifiedInstance; 97 static if (is(injectAttribute == Inject!T, T) && !is(injectAttribute.qualifier == UseMemberType)) { 98 alias QualifierType = typeof(injectAttribute.qualifier); 99 qualifiedInstance = createOrResolveInstance!(MemberType, QualifierType, assignNewInstance)(container); 100 debug(poodinisVerbose) { 101 qualifiedInstanceType = typeid(QualifierType); 102 } 103 } else { 104 qualifiedInstance = createOrResolveInstance!(MemberType, MemberType, assignNewInstance)(container); 105 } 106 107 __traits(getMember, instance, member) = qualifiedInstance; 108 109 debug(poodinisVerbose) { 110 printDebugInjectingCandidate(qualifiedInstanceType, &qualifiedInstance, typeid(Type), &instance, member); 111 } 112 } 113 } 114 115 break; 116 } 117 } 118 } 119 } 120 121 private QualifierType createOrResolveInstance(MemberType, QualifierType, bool createNew)(shared(Container) container) 122 { 123 static if (createNew) { 124 auto instanceFactory = new NewInstanceScope(typeid(MemberType)); 125 return cast(MemberType) instanceFactory.getInstance(); 126 } else { 127 return container.resolve!(MemberType, QualifierType); 128 } 129 } 130 131 /** 132 * Inject the given instance using the globally available dependency container. 133 * 134 * See_Also: Container 135 */ 136 public void globalInject(Type)(Type instance) 137 { 138 Container.getInstance().inject(instance); 139 } 140 141 class InjectedRegistration(RegistrationType : Object) : Registration 142 { 143 private shared(Container) container; 144 145 public this(TypeInfo registeredType, shared(Container) container) 146 { 147 enforce(!(container is null), "Argument 'container' is null. Injected registrations need to inject using a container."); 148 this.container = container; 149 super(registeredType, typeid(RegistrationType)); 150 } 151 152 public override Object getInstance(InstantiationContext context = new InjectInstantiationContext()) 153 { 154 RegistrationType instance = cast(RegistrationType) super.getInstance(context); 155 156 InjectInstantiationContext injectContext = cast(InjectInstantiationContext) context; 157 enforce(!(injectContext is null), "Given instantiation context type could not be cast to an InjectInstantiationContext. If you relied on using the default assigned context: make sure you're calling getInstance() on an instance of type InjectedRegistration!"); 158 if (injectContext.injectInstance) { 159 container.inject(instance); 160 } 161 162 return instance; 163 } 164 165 } 166 167 class InjectInstantiationContext : InstantiationContext 168 { 169 public bool injectInstance = true; 170 }