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 }