1 module aermicioi.aedi.factory.deferring_factory;
2 
3 import aermicioi.aedi.factory.factory;
4 import aermicioi.aedi.factory.generic_factory;
5 import aermicioi.aedi.storage.decorator;
6 import aermicioi.aedi.factory.decorating_factory : DecoratableGenericFactory;
7 import aermicioi.aedi.util.range : exceptions, filterByInterface;
8 import aermicioi.aedi.util.typecons : AllArgsConstructor, ArgsConstructor;
9 import aermicioi.aedi.exception.circular_reference_exception : CircularReferenceException;
10 import aermicioi.aedi.exception.di_exception : AediException;
11 
12 /**
13 Context for configuration actions that were delayed. Automatically will run them once the original component requested is fetched back.
14 **/
15 @safe class DeferralContext
16 {
17 
18     private
19     {
20 
21         /**
22         A list of deffered configuration actions.
23         **/
24         void delegate() @safe[] deferred;
25 
26         /**
27         Amount of nested requests for components. Used to track when deffered actions are required to run.
28         **/
29         size_t requests;
30     }
31 
32     public
33     {
34         @property bool pending() inout {
35             import std.range : empty;
36 
37             return requests == 0 && !deferred.empty;
38         }
39 
40         void track()
41         {
42             ++this.requests;
43         }
44 
45         void release()
46         in(requests > 0, "Cannot decrease component request count since it is already at 0.")
47         {
48             --this.requests;
49         }
50 
51         void execute()
52         in (requests == 0, "Cannot execute deferred actions while construction of components involved in circular dependency chain is in progress.") {
53             import std.algorithm : reverse;
54             auto pending = this.deferred.reverse;
55             deferred = null;
56 
57             foreach (action; pending)
58             {
59                 action();
60             }
61         }
62 
63         typeof(this) register(void delegate() @safe deferred)
64         {
65             this.deferred ~= deferred;
66 
67             return this;
68         }
69     }
70 }
71 
72 @safe class DeferringFactory(T) : DecoratableGenericFactory!T
73 {
74 
75     this(
76         GenericFactory!T decorated,
77         DeferralContext context,
78     ) {
79         this.decorated = decorated;
80         this.context = context;
81     }
82 
83     private DeferralContext context;
84 
85     public
86     {
87         /**
88 		Instantiates something of type T.
89 
90 		Returns:
91 			T instantiated component of type T.
92 		**/
93         override T factory() @safe {
94             this.context.track;
95             scope(exit) this.context.release;
96             return super.factory();
97         }
98 
99         /**
100         Adds an configurer to the PropertyConfigurersAware.
101 
102         Params:
103         	configurer = a configurer that will be invoked after factory of an object.
104 
105     	Returns:
106     		The PropertyConfigurersAware instance
107         **/
108         override GenericFactory!T addPropertyConfigurer(PropertyConfigurer!T configurer) @safe
109         {
110             super.addPropertyConfigurer(new DeferringPropertyConfigurer!T(configurer, this.context));
111 
112             return this;
113         }
114     }
115 }
116 
117 @safe class DeferringPropertyConfigurer(T) : PropertyConfigurer!T, Decorator!(PropertyConfigurer!T) {
118     import aermicioi.aedi.storage.locator : Locator;
119     mixin MutableDecoratorMixin!(PropertyConfigurer!T);
120     private DeferralContext context;
121 
122     this (
123         PropertyConfigurer!T configurer,
124         DeferralContext context
125     ) {
126         this.decorated = configurer;
127         this.context = context;
128     }
129 
130     public {
131 
132         /**
133         Accepts a reference to an object that is to be configured by the configurer.
134 
135         Params:
136         	object = An object of type T, that will be configured
137         **/
138         void configure(ref T object) @safe {
139             try {
140                 this.decorated.configure(object);
141             } catch (AediException e) {
142                 if (context !is null) {
143                     foreach (CircularReferenceException exception; e.exceptions.filterByInterface!CircularReferenceException) {
144                         T closure = object;
145                         context.register(() => this.decorated.configure(closure));
146                     }
147                 }
148             }
149         }
150 
151         /**
152 		Set a locator to object.
153 
154 		Params:
155 			locator = the locator that is set to oject.
156 
157 		Returns:
158 			LocatorAware.
159 		**/
160 		@property typeof(this) locator(Locator!() locator) @safe nothrow {
161             this.decorated.locator = locator;
162 
163             return this;
164         }
165     }
166 }