1 /**
2 This module contains the core of the Jumped framework.
3 
4 It contains all the required internal functionality in order to detect and
5 resolve any dependencies.
6 
7 For public usage, the main function is `jumpStart`, which will instantiate a
8 class, resolve dependencies, and executed `@startup` methods.
9 */
10 module jumped.beans;
11 
12 import jumped.introspection;
13 import jumped.attributes;
14 import jumped.errors;
15 import std.traits;
16 import std.typecons;
17 import std.meta;
18 import std.algorithm;
19 
20 private struct BeanInfo(alias FactoryMethod)
21 {
22 	alias Bean = ReturnType!FactoryMethod;
23 	static immutable string methodName = __traits(identifier, FactoryMethod);
24 	alias Parent = __traits(parent, FactoryMethod);
25 
26 	static if (isScalarType!bean)
27 	{
28 		alias isAnnotatedWith(Annotation) = Alias!false;
29 		alias getAnnotations(Annotation) = AliasSeq!();
30 	}
31 	else
32 	{
33 		alias isAnnotatedWith(Annotation) = hasAnnotation!(bean, Annotation);
34 		alias getAnnotations(Annotation) = getUDAs!(bean, Annotation);
35 	}
36 }
37 
38 /**
39 A container is a class that can perform dependency resolution at compile-time,
40 finding any dependencies through the template parameter.
41 */
42 class Container(T)
43 {
44 	private alias beans = DiscoverBeans!T;
45 	private alias DiscoverBeans(Type) = AliasSeq!(BeanInfo!createRootBean, AllBeansAccessableBy!(BeanInfo!createRootBean));
46 	private T _rootBean = null;
47 
48 	private T createRootBean()
49 	{
50 		return new T();
51 	}
52 
53 	private template AllBeansAccessableBy(Type...)
54 	{
55 		static if (Type.length == 1)
56 		{
57 			alias beans = BeansDirectlyAccessableBy!(Type[0].Bean);
58 			static if (beans.length == 0)
59 			{
60 				alias AllBeansAccessableBy = AliasSeq!();
61 			}
62 			else
63 			{
64 				alias AllBeansAccessableBy = AliasSeq!(
65 					beans,
66 					AllBeansAccessableBy!beans
67 				);
68 			}
69 		}
70 		else
71 		{
72 			alias AllBeansAccessableBy = AliasSeq!(
73 				AllBeansAccessableBy!(Type[0]),
74 				AllBeansAccessableBy!(Type[1..$])
75 			);
76 		}
77 	}
78 
79 	private template BeansDirectlyAccessableBy(Type)
80 	{
81 		static if (!isScalarType!Type && getMembersByAnnotation!(Type, bean).length > 0)
82 			alias BeansDirectlyAccessableBy = AliasSeq!(MapReturnTypes!(getMembersByAnnotation!(Type, bean)));
83 		else
84 			alias BeansDirectlyAccessableBy = AliasSeq!();
85 	}
86 
87 	private template MapReturnTypes(Values...)
88 	{
89 		static if (Values.length == 1)
90 		{
91 			alias MapReturnTypes = Alias!(BeanInfo!(Values[0]));
92 		}
93 		else
94 		{
95 			alias MapReturnTypes = AliasSeq!(
96 				MapReturnTypes!(Values[0]),
97 				MapReturnTypes!(Values[1..$])
98 			);
99 		}
100 	}
101 
102 	private template FindAnnotatedMembers(Annotation)
103 	{
104 		alias FindAnnotatedMembers = FindAnnotatedMembersInBeans!(Annotation, beans);
105 	}
106 
107 	private template FindAnnotatedMembersInBeans(Annotation, Beans...)
108 	{
109 		static if (Beans.length == 1)
110 		{
111 			alias bean = Beans[0].Bean;
112 			static if (isScalarType!(bean))
113 				alias FindAnnotatedMembersInBeans = AliasSeq!();
114 			else
115 				alias FindAnnotatedMembersInBeans = AliasSeq!(getSymbolsByUDA!(bean, Annotation));
116 		}
117 		else static if (Beans.length > 1)
118 		{
119 			alias FindAnnotatedMembersInBeans = AliasSeq!(
120 				FindAnnotatedMembersInBeans!(Annotation, Beans[0]),
121 				FindAnnotatedMembersInBeans!(Annotation, Beans[1..$])
122 			);
123 		}
124 		else
125 		{
126 			alias FindAnnotatedMembersInBeans = AliasSeq!();
127 		}
128 	}
129 
130 	/**
131 	Executes all methods with a specific annotation.
132 	Params:
133 		Annotation = The annotation to filter by.
134 	*/
135 	void executeAll(Annotation)()
136 	{
137 		static foreach (member; FindAnnotatedMembers!Annotation)
138 		{
139 			execute!(__traits(identifier, member))(resolve!(__traits(parent, member)));
140 		}
141 	}
142 
143 	/*void executeAll(Annotation)()
144 	{
145 		executeAll!(Annotation, Alias!true)();
146 	}*/
147 
148 	/// Executes a method, automatically resolving any required parameters.
149 	/// Params:
150 	///   member = The name of the member to execute.
151 	///   type = The object to call the method on.
152 	auto execute(string member, Type)(Type type)
153 	if (Parameters!(__traits(getMember, Type, member)).length == 0)
154 	{
155 		cast(void) type;
156 		return __traits(getMember, type, member)();
157 	}
158 
159 	/// Executes a method, automatically resolving any required parameters.
160 	/// Params:
161 	///   member = The name of the member to execute.
162 	///   type = The object to call the method on.
163 	auto execute(string member, Type)(Type type)
164 	if (Parameters!(__traits(getMember, Type, member)).length > 0)
165 	{
166 		cast(void) type;
167 		Tuple!(Parameters!(__traits(getMember, Type, member))) parameters;
168 		static foreach (i, parameter; Parameters!(__traits(getMember, Type, member)))
169 		{
170 			parameters[i] = resolve!parameter;
171 		}
172 		return __traits(getMember, type, member)(parameters.expand);
173 	}
174 
175 	/**
176 	Resolve a types and returns an instance of that type.
177 
178 	This version will detect the root bean, and use special instantiation for
179 	this specific bean.
180 	*/
181 	Type resolve(Type)()
182 	if (is(Type == T))
183 	{
184 		if (_rootBean is null)
185 			_rootBean = createRootBean();
186 		return _rootBean;
187 	}
188 
189 	/**
190 	Resolve a types and returns an instance of that type.
191 
192 	Resolves a bean that has an instantiator (a method annotated with `@bean`).
193 	*/
194 	Type resolve(Type)()
195 	if (!is(Type == T) && hasBeanInstantiator!Type)
196 	{
197 		static foreach (bean; beans)
198 		{
199 			static if (is(bean.Bean == Type))
200 			{
201 				pragma(msg, "@bean: " ~ bean.Parent.stringof ~ "#" ~ bean.methodName);
202 				bean.Parent parent = resolve!(bean.Parent);
203 				return execute!(bean.methodName)(parent);
204 			}
205 		}
206 	}
207 
208 	/**
209 	Resolve a types and returns an instance of that type.
210 
211 	Resolves a bean that does not have an instantiator, but is annotated with
212 	`@component`.
213 	*/
214 	Type resolve(Type)()
215 	if (!is(Type == T) && !hasBeanInstantiator!Type && hasAnnotation!(component, Type))
216 	{
217 		pragma(msg, "@component: " ~ Type.stringof);
218 		static if (__traits(hasMember, Type, "__ctor"))
219 		{
220 			Tuple!(Parameters!(__traits(getMember, Type, "__ctor"))) parameters;
221 			static foreach (i, parameter; Parameters!(__traits(getMember, Type, "__ctor")))
222 			{
223 				parameters[i] = resolve!parameter;
224 			}
225 			return new Type(parameters.expand);
226 		}
227 		else
228 		{
229 			return new Type();
230 		}
231 	}
232 
233 	/**
234 	Resolve a types and returns an instance of that type.
235 
236 	Catch-all if the bean cannot be found. Will simply cause a compile error.
237 	*/
238 	template resolve(Type)
239 	if (!is(Type == T) && !hasBeanInstantiator!Type && !hasAnnotation!(component, Type))
240 	{
241 		static assert(0, "Could not resolve bean '" ~ Type.stringof ~ "'");
242 	}
243 
244 	/**
245 	Checks if there is a `@bean` instantiator for the given `Type`.
246 	*/
247 	private template hasBeanInstantiator(Type)
248 	{
249 		private template isBean(Type, alias bean)
250 		{
251 			enum isBean = is(bean.Bean == Type);
252 		}
253 
254 		enum hasBeanInstantiator = anySatisfy!(ApplyLeft!(isBean, Type), beans);
255 	}
256 }
257 
258 /**
259 Starts the application.
260 
261 It will first create an instance of the application. After the application is
262 instantiated, it will resolve any dependencies required to execute all methods
263 annotated with `@startup`. Once all `@startup`-methods have finished execution,
264 it will execute all `@shutdown`-methods.
265 Params:
266 	T = The startup class
267 */
268 void jumpStart(T)()
269 {
270 	auto container = new Container!T;
271 
272 	try
273 	{
274 		container.executeAll!startup();
275 		container.executeAll!(shutdownOnSuccess);
276 	}
277 	catch (Exception e)
278 	{
279 		container.executeAll!(shutdownOnFailure);
280 	}
281 	finally
282 	{
283 		container.executeAll!(shutdown);
284 	}
285 }