1 /**
2 This module provides fluent api based configuration of components with custom
3 configuration errors.
4 
5 License:
6 	Boost Software License - Version 1.0 - August 17th, 2003
7 
8     Permission is hereby granted, free of charge, to any person or organization
9     obtaining a copy of the software and accompanying documentation covered by
10     this license (the "Software") to use, reproduce, display, distribute,
11     execute, and transmit the Software, and to prepare derivative works of the
12     Software, and to permit third-parties to whom the Software is furnished to
13     do so, all subject to the following:
14 
15     The copyright notices in the Software and this entire statement, including
16     the above license grant, this restriction and the following disclaimer,
17     must be included in all copies of the Software, in whole or in part, and
18     all derivative works of the Software, unless such copies or derivative
19     works are solely in the form of machine-executable object code generated by
20     a source language processor.
21 
22     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24     FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
25     SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
26     FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
27     ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28     DEALINGS IN THE SOFTWARE.
29 
30 Authors:
31 	Alexandru Ermicioi
32 **/
33 module aermicioi.aedi.configurer.register.factory_configurer;
34 
35 
36 import aermicioi.aedi.configurer.register.configuration_context_factory;
37 import aermicioi.aedi.configurer.register.context : ValueRegistrationContext;
38 import aermicioi.aedi.container.container;
39 import aermicioi.aedi.container.proxy_container;
40 import aermicioi.aedi.exception;
41 import aermicioi.aedi.factory;
42 import aermicioi.aedi.storage.allocator_aware;
43 import aermicioi.aedi.storage.decorator;
44 import aermicioi.aedi.storage.locator;
45 import aermicioi.aedi.storage.storage;
46 import aermicioi.aedi.util.traits;
47 public import aermicioi.aedi.factory.reference : lref, anonymous;
48 
49 import std.meta;
50 import std.traits;
51 import std.algorithm;
52 import std.range;
53 
54 @safe:
55 
56 /**
57 Construct component using args.
58 
59 Constructs component using args, that are passed to function.
60 The function will attempt to find at least one construct that
61 can accept passed argument list. If it fails, compiler will
62 produce error, with respective problems.
63 The argument list can contain beside simple values, references
64 to other components in locator. Arguments that are references to other components
65 won't be type checked.
66 
67 Params:
68 	factory = the factory which will call constructor with passed arguments.
69 	args = a list of arguments that will be passed to constructor.
70 
71 Returns:
72 	Z.
73 **/
74 
75 Z construct(Z : InstanceFactoryAware!T, T, Args...)(Z factory, auto ref Args args) {
76     factory.setInstanceFactory(constructorBasedFactory!T(args));
77 
78     return factory;
79 }
80 
81 /**
82 Invoke T's method to create component of type X.
83 
84 Configures component's factory to call method of factoryMethod with args,
85 in order to create component of type X.
86 In case when method is not a static member, the function requires to
87 pass a instance of factoryMethod or a reference to it.
88 The algorithm will check for args compatiblity with parameters of
89 factory method. No type check is done for arguments that are references
90 at compile time.
91 
92 Params:
93     factory = component's factory that is configured to call factoryMethod methods to spawn component
94     factoryMethod = instance of factory method that will be used to instantiate component
95     args = a list of arguments passed to factory method
96     T = type of factoryMethod
97     method = the method that is called from T to instantiate component
98     W = either LocatorReference or T
99     X = the return type of T.method member
100 **/
101 Z factoryMethod(T, string method, Z : InstanceFactoryAware!X, X, W, Args...)(Z factory, auto ref W factoryMethod, auto ref Args args)
102     if (
103         isNonStaticMethodCompatible!(T, method, Args) &&
104         (is(W : T) || is(W : RuntimeReference))
105     ) {
106     factory.setInstanceFactory(factoryMethodBasedFactory!(T, method)(factoryMethod, args));
107 
108     return factory;
109 }
110 
111 /**
112 ditto
113 **/
114 Z factoryMethod(T, string method, Z : InstanceFactoryAware!X, X, Args...)(Z factory, auto ref Args args)
115     if (
116         isStaticMethodCompatible!(T, method, Args)
117     ) {
118 
119     factory.setInstanceFactory(factoryMethodBasedFactory!(T, method)(args));
120     return factory;
121 }
122 
123 /**
124 ditto
125 **/
126 Z factoryMethod(Dg, Z : InstanceFactoryAware!X, X, Args...)(Z factory, Dg func, auto ref Args args)
127     if (isSomeFunction!Dg && is(ReturnType!Dg == X)) {
128 
129     factory.setInstanceFactory(functionInstanceFactory(func, args));
130     return factory;
131 }
132 
133 /**
134 Invoke component's method with supplied args.
135 
136 Configures component's factory to call specified method with passed args.
137 The function will check if the arguments passed to it are compatible with at
138 least one method from possible overload set.
139 The args list can contain references to other objects in locator as well, though
140 no type compatibility checks will be performed at compile time.
141 
142 Params:
143 	factory = the factory which will be configured to invoke method.
144 	args = the arguments that will be used to invoke method on the new object.
145 
146 Returns:
147 	Z.
148 **/
149 Z set(string property, Z : PropertyConfigurersAware!T, T, Args...)(Z factory, auto ref Args args)
150     if (!isField!(T, property)) {
151     mixin assertObjectMethodCompatible!(T, property, Args);
152 
153     factory.addPropertyConfigurer(methodConfigurer!(property, T)(args));
154 
155     return factory;
156 }
157 
158 /**
159 Set component's public field to passed arg.
160 
161 Configures component's factory to set specified field to passed arg.
162 The function will check if passed argument is type compatible with specified field.
163 The argument can be a reference as well. In case of argument being reference to another component
164 in container, no type compatiblity checking will be done.
165 
166 Params
167     factory = the factory which will be configured to set property.
168 	arg = the value of property to be set, or reference to component in container.
169 
170 Returns:
171 	Z.
172 **/
173 Z set(string property, Z : PropertyConfigurersAware!T, T, Arg)(Z factory, auto ref Arg arg)
174     if (isField!(T, property)) {
175     mixin assertFieldCompatible!(T, property, Arg);
176 
177     factory.addPropertyConfigurer(fieldConfigurer!(property, T)(arg));
178 
179     return factory;
180 }
181 
182 /**
183 Construct component using a delegate.
184 
185 Constructs component using a delegate, and a list of arguments passed to delegate.
186 
187 Params:
188 	factory = the factory which will use delegate to construct component.
189 	dg = the delegate that is responsible for creating component, given a list of arguments.
190 	args = the arguments that will be used by delegate to construct component.
191 
192 Returns:
193 	Z.
194 **/
195 Z callback(Z : InstanceFactoryAware!T, T, Args...)(Z factory, T delegate(RCIAllocator, Locator!(), Args) dg, auto ref Args args) {
196     factory.setInstanceFactory(callbackFactory!T(dg, args));
197 
198     return factory;
199 }
200 
201 /**
202 ditto
203 **/
204 Z callback(Z : InstanceFactoryAware!T, T, Args...)(Z factory, T function(RCIAllocator, Locator!(), Args) dg, auto ref Args args) {
205     factory.setInstanceFactory(callbackFactory!T(dg, args));
206 
207     return factory;
208 }
209 
210 /**
211 Call dg on a component that is in configuration phase.
212 
213 Call dg on component to perform some modifications, using args as input.
214 
215 Params:
216     factory = factory which will call dg with args.
217     dg = delegate that will perform some modifications on component using passed args.
218     args = a list of arguments passed to dg.
219 
220 Returns:
221     Z
222 **/
223 Z callback(Z : PropertyConfigurersAware!T, T, Args...)(Z factory, void delegate(Locator!(), T, Args) dg, auto ref Args args) {
224     factory.addPropertyConfigurer(callbackConfigurer!T(dg, args));
225 
226     return factory;
227 }
228 
229 /**
230 ditto
231 **/
232 Z callback(Z : PropertyConfigurersAware!T, T, Args...)(Z factory, void function(Locator!(), T, Args) dg, auto ref Args args) {
233     factory.addPropertyConfigurer(callbackConfigurer!T(dg, args));
234 
235     return factory;
236 }
237 
238 /**
239 ditto
240 **/
241 Z callback(Z : PropertyConfigurersAware!T, T, Args...)(Z factory, void delegate(Locator!(), ref T, Args) dg, auto ref Args args) {
242     factory.addPropertyConfigurer(callbackConfigurer!T(dg, args));
243 
244     return factory;
245 }
246 
247 /**
248 ditto
249 **/
250 Z callback(Z : PropertyConfigurersAware!T, T, Args...)(Z factory, void function(Locator!(), ref T, Args) dg, auto ref Args args) {
251     factory.addPropertyConfigurer(callbackConfigurer!T(dg, args));
252 
253     return factory;
254 }
255 
256 /**
257 Autowire a constructor, field or a method.
258 
259 Autowire a constructor, field or a method.
260 To autowire a constructor simply call autowire with no template arguments.
261 For autowiring a field or a method, call autowire with the name of field or argument as first template argument.
262 Autowiring process will attempt to find all parameters by their name first, and by type afterwards, in order to
263 inject the dependencies into the component. Failing to find a suitable candidate for injection, will fail in an exception
264 during container instantiation.
265 Note: There are no guarantees as of which method or constructor from an overload set will be selected
266 as such it is advised to use autowire only on members that are not part of an overload set.
267 
268 Params:
269     T = the component type
270     member = field or method of component T
271     factory = ConfigurationContextFactory where to inject the constructor or method configurer
272 
273 Returns:
274     Z
275 **/
276 Z autowire(Z : InstanceFactoryAware!T, T)(Z factory)
277     if (getMembersWithProtection!(T, "__ctor", "public").length > 0) {
278 
279     alias ctor = getMembersWithProtection!(T, "__ctor", "public")[0];
280 
281     return factory.construct(makeFunctionParameterReferences!ctor.expand);
282 }
283 
284 /**
285 ditto
286 **/
287 Z autowire(string member, Z : PropertyConfigurersAware!T, T)(Z factory)
288     if (getMembersWithProtection!(T, member, "public").length > 0) {
289     alias method = getMembersWithProtection!(T, member, "public")[0];
290 
291     auto arguments = makeFunctionParameterReferences!method;
292 
293     static if (arguments.length == 1) {
294         arguments[0] = __traits(identifier, method).lref.alternate(arguments[0]);
295     }
296 
297     return factory.set!(member)(arguments.expand);
298 }
299 
300 /**
301 ditto
302 **/
303 Z autowire(string member, Z : PropertyConfigurersAware!T, T)(Z factory)
304     if (isField!(T, member)) {
305 
306     alias field = getMember!(T, member);
307     RuntimeReference reference = __traits(identifier, field).lref.alternate(lref!(typeof(field)));
308 
309     return factory.set!(member)(reference);
310 }
311 
312 /**
313 Instantiates a component using a value as basis.
314 
315 Instantiates a component using a value as basis.
316 As a consequence, any reference based type will
317 point to same content when it is instantiated
318 multiple times.
319 
320 Params:
321     T = the component type
322     factory = ConfigurationContextFactory where to inject the constructor or method configurer
323     value = default value used to instantiate component
324 **/
325 Z value(Z : InstanceFactoryAware!T, T)(Z factory, auto ref T value) {
326     factory.setInstanceFactory(new ValueInstanceFactory!T(value));
327 
328     return factory;
329 }
330 
331 /**
332 Instantiates a component using as basis some third party factory.
333 
334 Params:
335     T = the type of component that is to be configured
336     factory = factory that uses the parent factory for component instantiation
337     delegated = the factory used by factory to instantiate an object.
338 **/
339 Z parent(Z : InstanceFactoryAware!T, T, X : Factory!W, W : T)(Z factory, X delegated) {
340     factory.setInstanceFactory(new DelegatingInstanceFactory!(T, W)(delegated));
341 
342     return factory;
343 }
344 /**
345 Tag constructed component with some information.
346 
347 Tag constructed component with some information.
348 Description
349 
350 Params:
351 	factory = factory for constructed component
352 	tag = tag with which to tag factory.
353 
354 Returns:
355 	factory
356 **/
357 W tag(Z, W : ConfigurableFactory!(T, Policies), T, Policies...)(W factory, auto ref Z tag) {
358     static if (ContainsPolicy!(WrapperStorePolicy, Policies)) {
359         Taggable!Z taggable;
360 
361         import std.range : chain, only;
362         import aermicioi.aedi.util.range : filterByInterface;
363 
364         auto candidates = factory.wrapper
365             .decorators!ObjectFactory
366             .filterByInterface!(Taggable!Z)
367             .chain(factory.decorated.only.filterByInterface!(Taggable!Z));
368 
369         if (candidates.empty) {
370             auto taggableDecorator = new TaggableFactoryDecorator!(Object, Z);
371             taggableDecorator.decorated = factory.wrapper;
372             factory.wrapper = taggableDecorator;
373 
374             taggable = taggableDecorator;
375             factory.storage.set(factory.wrapper, factory.identity);
376         } else {
377             taggable = candidates.front;
378         }
379 
380         taggable.tag(tag);
381 
382         return factory;
383     } else {
384         static assert(false, "Cannot tag component, expected for configurable factory to implement " ~ fullyQualifiedName!WrapperStorePolicy);
385     }
386 }
387 
388 /**
389 Register a description for component
390 
391 Params:
392     factory = configuration context for component
393     instance = configuration context for comopnent that is already insantiated
394     title = title for component
395     description = description for component
396 
397 Returns:
398     Configuration context
399 **/
400 W describe(W : ConfigurableFactory!(T, Policies), T, Policies...)(W factory, string title, string description = null) {
401     static if (ContainsPolicy!(StoragePolicy, Policies)) {
402         import aermicioi.aedi.container.describing_container : IdentityDescriber;
403 
404         IdentityDescriber!() describer = factory.locator.locate!(IdentityDescriber!());
405         describer.register(factory.identity, title, description);
406 
407         return factory;
408     } else {
409         static assert(false, "Cannot describe component, expected for configurable factory to implement " ~ fullyQualifiedName!StoragePolicy);
410     }
411 }
412 
413 /**
414 ditto
415 **/
416 ValueRegistrationContext.ValueContext describe(ValueRegistrationContext.ValueContext instance, string title, string description = null) {
417     import aermicioi.aedi.container.describing_container : IdentityDescriber;
418 
419     IdentityDescriber!() describer = instance.locator.locate!(IdentityDescriber!());
420     describer.register(instance.identity, title, description);
421 
422     return instance;
423 }
424 
425 /**
426 Run annotation processor over a component and scan it for configuration or new components.
427 
428 Params:
429     factory = component factory to run scanning over
430 
431 Returns:
432     factory
433 **/
434 W scan(W : GenericFactory!T, T)(W factory) {
435     import aermicioi.aedi.configurer.annotation.component_scan : ConfiguratorPolicyImpl;
436     ConfiguratorPolicyImpl.configure(factory, factory.locator);
437 
438     return factory;
439 }
440 
441 /**
442 ditto
443 **/
444 W scan(W : ConfigurableContainer!(T, Policies), T, Policies...)(W factory) {
445     static if (ContainsPolicy!(StoragePolicy, Policies)) {
446         import aermicioi.aedi.configurer.annotation.component_scan : ContainerAdderImpl, ConfiguratorPolicyImpl;
447         ContainerAdderImpl!().scan!T(factory.locator, factory.storage);
448         ConfiguratorPolicyImpl.configure(factory, factory.locator);
449 
450         return factory;
451     } else {
452         static assert(false, "Cannot scan component, expected for configurable factory to implement " ~ fullyQualifiedName!StoragePolicy);
453     }
454 }
455 
456 /**
457 Mark constructed object to be provided through a proxy instead of directly doing so.
458 Object will be proxied only in case when the storage where it is stored support
459 storing of proxy object factories.
460 
461 Params:
462 	factory = factory for constructed object
463 
464 Returns:
465 	factory
466 **/
467 W proxy(W : ConfigurableFactory!(T, Policies), T, Policies...)(W factory) @trusted {
468     import aermicioi.aedi.factory.proxy_factory : ProxyFactory, ProxyObjectFactory;
469     import aermicioi.aedi.container.proxy_container : ProxyContainer;
470     import aermicioi.aedi.util.range : filterByInterface;
471 
472     auto candidates = factory.storage.decorators!Container.filterByInterface!ProxyContainer;
473 
474     if (!candidates.empty) {
475         candidates.front.set(
476             new ProxyObjectWrappingFactory!T(
477                 new ProxyFactory!T(factory.identity, candidates.front.decorated)
478             ),
479             factory.identity,
480         );
481     }
482 
483     return factory;
484 }
485 
486 /**
487 Use delegate T for destruction of component.
488 
489 Params:
490     factory = component factory which will use delegate to destroy component
491     dg = destruction delegate
492     args = optional arguments to delegate
493 
494 Returns:
495     factory
496 **/
497 Z destructor(Z : InstanceDestructorAware!T, T, Args...)(
498     Z factory,
499     void delegate(RCIAllocator, ref T, Args) dg,
500     Args args
501 ) {
502     factory.setInstanceDestructor(callbackInstanceDestructor!T(dg, args));
503 
504     return factory;
505 }
506 
507 /**
508 Use method of destructor to destroy component.
509 
510 Use method of destructor to destroy component. By convention it is assumed that first argument is destroyed compnent followed by
511 optional arguments.
512 
513 Params:
514     method = destructor's method used to destroy component
515     factory = component factory
516     destructor = actual destructor that will destroy object
517     args = arguments passed to destructor
518 Returns:
519     factory
520 **/
521 Z destructor(string method, X, Z : InstanceDestructorAware!T, T, Args...)(
522     Z factory,
523     X destructor,
524     Args args
525 ) {
526     factory.setInstanceDestructor(factoryMethodInstanceDestructor!(T, method, X, Args)(destructor, args));
527 
528     return factory;
529 }
530 
531 /**
532 ditto
533 **/
534 Z destructor(string method, X, Z : InstanceDestructorAware!T, T, Args...)(
535     Z factory,
536     Args args
537 ) {
538     factory.setInstanceDestructor(factoryMethodInstanceDestructor!(T, method, X, Args)(args));
539 
540     return factory;
541 }