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 }