1 /**
2 License:
3     Boost Software License - Version 1.0 - August 17th, 2003
4 
5     Permission is hereby granted, free of charge, to any person or organization
6     obtaining a copy of the software and accompanying documentation covered by
7     this license (the "Software") to use, reproduce, display, distribute,
8     execute, and transmit the Software, and to prepare derivative works of the
9     Software, and to permit third-parties to whom the Software is furnished to
10     do so, all subject to the following:
11 
12     The copyright notices in the Software and this entire statement, including
13     the above license grant, this restriction and the following disclaimer,
14     must be included in all copies of the Software, in whole or in part, and
15     all derivative works of the Software, unless such copies or derivative
16     works are solely in the form of machine-executable object code generated by
17     a source language processor.
18 
19     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21     FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22     SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23     FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24     ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25     DEALINGS IN THE SOFTWARE.
26 
27 Authors:
28     Alexandru Ermicioi
29 **/
30 module aermicioi.aedi_property_reader.core.document;
31 
32 import aermicioi.aedi;
33 import aermicioi.aedi_property_reader.core.exception : ConvertorException;
34 import aermicioi.aedi_property_reader.core.convertor;
35 import aermicioi.aedi_property_reader.core.placeholder;
36 import aermicioi.aedi.storage.wrapper;
37 import std.meta;
38 import std.conv;
39 import std.experimental.allocator;
40 import std.exception : enforce;
41 import aermicioi.aedi_property_reader.core.accessor;
42 import aermicioi.aedi_property_reader.core.type_guesser;
43 import std.algorithm;
44 import std.array;
45 import std.experimental.logger;
46 
47 /**
48 An implementation of Container interface from aedi IoC providing access to
49 components that are stored into a document.
50 
51 An implementation of Container interface from aedi IoC providing access to
52 components that are stored into a document where a document could be anything
53 that is accessable by a PropertyAccessor implementation. Components stored in
54 document will be converted according to convertor associated to property
55 path from document.
56 **/
57 class DocumentContainer(DocumentType, FieldType = DocumentType) :
58     Container, Storage!(Convertor, string),
59     AllocatorAware!(),
60     Convertor
61 {
62 
63     mixin AllocatorAwareMixin!(typeof(this));
64 
65     private {
66         Convertor[string] convertors;
67         PropertyAccessor!(DocumentType, FieldType) accessor_;
68         TypeGuesser!FieldType guesser_;
69 
70         DocumentType document_;
71         Object[string] components;
72     }
73 
74     public {
75 
76         /**
77         Constructor for a container for document.
78 
79         Params:
80             document = document stored in container.
81         **/
82         this(DocumentType document) {
83             this.document = document;
84         }
85 
86         @property {
87 
88             /**
89             Set guesser
90 
91             Params:
92                 guesser = guesser uset to guess the D type of document.
93 
94             Returns:
95                 typeof(this)
96             **/
97             typeof(this) guesser(TypeGuesser!FieldType guesser) @safe nothrow pure {
98                 this.guesser_ = guesser;
99 
100                 return this;
101             }
102 
103             /**
104             Get guesser
105 
106             Returns:
107                 TypeGuesser!FieldType
108             **/
109             inout(TypeGuesser!FieldType) guesser() @safe nothrow pure inout {
110                 return this.guesser_;
111             }
112             /**
113             Set document
114 
115             Params:
116                 document = document containing valuable components
117 
118             Returns:
119                 typeof(this)
120             **/
121             typeof(this) document(DocumentType document) @safe nothrow {
122                 this.document_ = document;
123 
124                 return this;
125             }
126 
127             /**
128             Get document
129 
130             Returns:
131                 DocumentType
132             **/
133             inout(DocumentType) document() @safe nothrow pure inout {
134                 return this.document_;
135             }
136 
137             /**
138             Set accessor
139 
140             Params:
141                 accessor = accessor used to navigate through document
142             Returns:
143                 typeof(this)
144             **/
145             typeof(this) accessor(PropertyAccessor!(DocumentType, FieldType) accessor) @safe nothrow pure {
146                 this.accessor_ = accessor;
147 
148                 return this;
149             }
150 
151             /**
152             Get accessor
153 
154             Returns:
155                 PropertyAccessor!(DocumentType, FieldType)
156             **/
157             inout(PropertyAccessor!(DocumentType, FieldType)) accessor() @safe nothrow pure inout {
158                 return this.accessor_;
159             }
160         }
161 
162         /**
163 		Save an element in Storage by key identity.
164 
165 		Params:
166 			identity = identity of element in Storage.
167 			element = element which is to be saved in Storage.
168 
169 		Return:
170 			Storage
171 		**/
172         typeof(this) set(Convertor element, string identity) {
173             this.convertors[identity] = element;
174 
175             return this;
176         }
177 
178         /**
179         Remove an element from Storage with identity.
180 
181         Remove an element from Storage with identity. If there is no element by provided identity, then no action is performed.
182 
183         Params:
184         	identity = the identity of element to be removed.
185 
186     	Return:
187     		Storage
188         **/
189         typeof(this) remove(string identity) {
190             this.convertors.remove(identity);
191 
192             return this;
193         }
194 
195         /**
196         Sets up the internal state of container.
197 
198         Sets up the internal state of container (Ex, for singleton container it will spawn all objects that locator contains).
199         **/
200         Container instantiate() {
201             foreach (identity, convertor; this.convertors) {
202                 if (this.accessor.has(this.document, identity)) {
203                     this.get(identity);
204                 }
205             }
206 
207             return this;
208         }
209 
210         /**
211         Destruct all managed components.
212 
213         Destruct all managed components. The method denotes the end of container lifetime, and therefore destruction of all managed components
214         by it.
215         **/
216         Container terminate() {
217             foreach (identity, convertor; this.convertors) {
218                 if (auto component = identity in this.components) {
219                     convertor.destruct(*component, this.allocator);
220                 }
221             }
222 
223             return this;
224         }
225 
226         /**
227 		Get a component that is associated with key.
228 
229 		Params:
230 			identity = the element id.
231 
232 		Throws:
233 			NotFoundException in case if the element wasn't found.
234 
235 		Returns:
236 			Object element if it is available.
237 		**/
238         Object get(string identity) {
239             debug(trace) trace("Searching for \"", identity, '"');
240 
241             Object converted;
242 
243             if (auto peeked = identity in this.components) {
244                 debug(trace) trace("Found already converted \"", identity, '"');
245 
246                 return *peeked;
247             }
248 
249             if (!this.accessor.has(this.document, identity)) {
250                 throw new NotFoundException(text("Could not find \"", identity, "\" in document of type ", typeid(DocumentType)));
251             }
252 
253             static if (is(FieldType : Object)) {
254 
255                 FieldType document = this.accessor.access(this.document, identity);
256             } else {
257                 import std.typecons : scoped;
258                 auto document = scoped!(PlaceholderImpl!FieldType)(this.accessor.access(this.document, identity));
259             }
260 
261             debug(trace) trace("Searching for suitable convertor for \"", identity, "\" of ", typeid(FieldType));
262 
263             if (auto convertor = identity in this.convertors) {
264                 if (convertor.to !is typeid(void)) {
265                     debug(trace) trace("Found convertor for \"", identity, "\" commencing conversion to ", convertor.to);
266 
267                     converted = (*convertor).convert(document, convertor.to, this.allocator);
268                     this.components[identity] = converted;
269                     return converted;
270                 }
271             }
272 
273             debug(trace) trace("No suitable convertor found, attempting to guess the desired type.");
274             static if (is(FieldType : Object)) {
275 
276                 TypeInfo guess = this.guesser.guess(document);
277             } else {
278 
279                 TypeInfo guess = this.guesser.guess(document.value);
280             }
281             debug(trace) trace("Guessed ", guess, " type, commencing conversion");
282             return this.convert(document, guess, this.allocator);
283         }
284 
285         /**
286         Check if an element is present in Locator by key id.
287 
288         Note:
289         	This check should be done for elements that locator actually contains, and
290         	not in chained locator.
291         Params:
292         	identity = identity of element.
293 
294     	Returns:
295     		bool true if an element by key is present in Locator.
296         **/
297         bool has(string identity) inout {
298             if (identity in this.components) {
299 
300                 return true;
301             }
302 
303             return this.accessor.has(this.document, identity);
304         }
305 
306         @property {
307 
308             /**
309             Get the type info of component that convertor can convert from.
310 
311             Get the type info of component that convertor can convert from.
312             The method is returning the default type that it is able to convert,
313             though it is not necessarily limited to this type only. More generalistic
314             checks should be done by convertsFrom method.
315 
316             Returns:
317                 type info of component that convertor is able to convert.
318             **/
319             TypeInfo from() const nothrow {
320                 return typeid(FieldType);
321             }
322 
323             /**
324             Get the type info of component that convertor is able to convert to.
325 
326             Get the type info of component that convertor is able to convert to.
327             The method is returning the default type that is able to convert,
328             though it is not necessarily limited to this type only. More generalistic
329             checks should be done by convertsTo method.
330 
331             Returns:
332                 type info of component that can be converted to.
333             **/
334             TypeInfo to() const nothrow {
335                 return typeid(void);
336             }
337         }
338 
339         /**
340         Check whether convertor is able to convert from.
341 
342         Check whether convertor is able to convert from.
343         The intent of method is to implement customized type checking
344         is not limited immediatly to supported default from component.
345 
346         Params:
347             from = the type info of component that could potentially be converted by convertor.
348         Returns:
349             true if it is able to convert from, or false otherwise.
350         **/
351         bool convertsFrom(TypeInfo from) const nothrow {
352             return this.convertors.byValue.canFind!(convertor => convertor.convertsFrom(from));
353         }
354 
355         /**
356         Check whether convertor is able to convert from.
357 
358         Check whether convertor is able to convert from.
359         The method will try to extract type info out of from
360         object and use for subsequent type checking.
361         The intent of method is to implement customized type checking
362         is not limited immediatly to supported default from component.
363 
364         Params:
365             from = the type info of component that could potentially be converted by convertor.
366         Returns:
367             true if it is able to convert from, or false otherwise.
368         **/
369         bool convertsFrom(in Object from) const nothrow {
370             return this.convertors.byValue.canFind!(convertor => convertor.convertsFrom(from));
371         }
372 
373         /**
374         Check whether convertor is able to convert to.
375 
376         Check whether convertor is able to convert to.
377         The intent of the method is to implement customized type checking
378         that is not limited immediatly to supported default to component.
379 
380         Params:
381             to = type info of component that convertor could potentially convert to.
382 
383         Returns:
384             true if it is able to convert to, false otherwise.
385         **/
386         bool convertsTo(TypeInfo to) const nothrow {
387             return this.convertors.byValue.canFind!(convertor => convertor.convertsTo(to));
388         }
389 
390         /**
391         Check whether convertor is able to convert to.
392 
393         Check whether convertor is able to convert to.
394         The method will try to extract type info out of to object and use
395         for subsequent type checking.
396         The intent of the method is to implement customized type checking
397         that is not limited immediatly to supported default to component.
398 
399         Params:
400             to = type info of component that convertor could potentially convert to.
401 
402         Returns:
403             true if it is able to convert to, false otherwise.
404         **/
405         bool convertsTo(in Object to) const nothrow {
406             return this.convertors.byValue.canFind!(convertor => convertor.convertsTo(to));
407         }
408 
409         /**
410         Convert from component to component.
411 
412         Params:
413             from = original component that is to be converted.
414             to = destination object that will be constructed out for original one.
415             allocator = optional allocator that could be used to construct to component.
416         Throws:
417             ConvertorException when there is a converting error
418             InvalidArgumentException when arguments passed are not of right type or state
419         Returns:
420             Resulting converted component.
421         **/
422         Object convert(in Object from, TypeInfo to, RCIAllocator allocator = theAllocator) {
423             debug(trace) trace("Searching for convertor for ", from.identify, " to ", to);
424             auto convertors = this.convertors.byValue.filter!(
425                 c => c.convertsTo(to) && c.convertsFrom(from)
426             );
427 
428             if (!convertors.empty) {
429                 debug(trace) trace("Found convertor ", convertors.front.classinfo, " for ", from.identify, " to ", to);
430 
431                 return convertors.front.convert(from, to, allocator);
432             }
433 
434             debug(trace) trace("No suitable convertor found for ", from.identify, " to ", to);
435             throw new ConvertorException(text("Could not convert ", from.identify, " to ", to));
436         }
437 
438         /**
439         Destroy component created using this convertor.
440 
441         Destroy component created using this convertor.
442         Since convertor could potentially allocate memory for
443         converted component, only itself is containing history of allocation,
444         and therefore it is responsible as well to destroy and free allocated
445         memory with allocator.
446 
447         Params:
448             converted = component that should be destroyed.
449             allocator = allocator used to allocate converted component.
450         **/
451         void destruct(ref Object converted, RCIAllocator allocator = theAllocator) {
452             auto destructors = this.convertors.byValue.filter!(convertor => convertor.convertsTo(converted));
453 
454             enforce!ConvertorException(!destructors.empty, text("Could not destroy ", converted.identify, ", no suitable convertor found."));
455 
456             destructors.front.destruct(converted, allocator);
457         }
458     }
459 }
460 
461 /**
462 An implementation of document container that holds an advised convertor along the document for usage as default constructor for convertors
463 in configuration api.
464 **/
465 class AdvisedDocumentContainer(DocumentType, FieldType, alias AdvisedConvertor) : DocumentContainer!(DocumentType, FieldType) {
466 
467     public {
468 
469         this(DocumentType document) {
470             super(document);
471         }
472     }
473 }