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 : Container, AediAllocatorAware = AllocatorAware, component, autowired, AllocatorAwareMixin, AediNotFoundException = NotFoundException;
33 import aermicioi.aedi_property_reader.convertor.exception : ConvertorException;
34 import aermicioi.aedi_property_reader.convertor.convertor;
35 import aermicioi.aedi_property_reader.convertor.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.convertor.accessor;
42 import aermicioi.aedi_property_reader.convertor.type_guesser;
43 import std.algorithm;
44 import std.array;
45 import std.experimental.logger;
46 import std.traits : Unqual;
47 
48 /**
49 Locator for properties that weren't registered in container, however are present in the root document.
50 
51 Since the type of the properties is not known at the runtime,
52 this locator will employ a type guesser to guess the type of element
53 it is requested from root document. If guessing fails, then raw type will
54 be returned per type guesser semantics.
55 **/
56 @component
57 @safe class DocumentLocator(DocumentType) : Container, AediAllocatorAware!()
58 {
59 
60     private {
61         Convertor convertor_;
62         PropertyAccessor!(DocumentType, Object) accessor_;
63         TypeGuesser!Object guesser_;
64         Object[string] components;
65 
66         TypeWrapper document;
67     }
68 
69     mixin AllocatorAwareMixin!(typeof(this));
70 
71     public {
72 
73         /**
74         Constructor for a container for document.
75 
76         Params:
77             document = document stored in container.
78             convertor = convertor storing any other one registered in document container.
79         **/
80         @autowired
81         this(DocumentType document, RCIAllocator allocator = theAllocator) {
82             this.document = TypeWrapper(document);
83             this.allocator = allocator;
84         }
85 
86         @property {
87 
88             /**
89             Set convertor
90 
91             Params:
92                 convertor = combined convertor used in converting from type to another
93 
94             Returns:
95                 typeof(this)
96             **/
97             @autowired
98             typeof(this) convertor(Convertor convertor) @safe nothrow pure {
99                 this.convertor_ = convertor;
100 
101                 return this;
102             }
103 
104             /**
105             Get convertor
106 
107             Returns:
108                 CombinedConvertor
109             **/
110             inout(Convertor) convertor() @safe nothrow pure inout {
111                 return this.convertor_;
112             }
113 
114             /**
115             Set guesser
116 
117             Params:
118                 guesser = guesser uset to guess the D type of document.
119 
120             Returns:
121                 typeof(this)
122             **/
123             @autowired
124             typeof(this) guesser(TypeGuesser!Object guesser) @safe nothrow pure {
125                 this.guesser_ = guesser;
126 
127                 return this;
128             }
129 
130             /**
131             Get guesser
132 
133             Returns:
134                 TypeGuesser!Object
135             **/
136             inout(TypeGuesser!Object) guesser() @safe nothrow pure inout {
137                 return this.guesser_;
138             }
139 
140             /**
141             Set accessor
142 
143             Params:
144                 accessor = accessor used to navigate through document
145             Returns:
146                 typeof(this)
147             **/
148             @autowired
149             typeof(this) accessor(PropertyAccessor!(DocumentType, Object) accessor) @safe nothrow pure {
150                 this.accessor_ = accessor;
151 
152                 return this;
153             }
154 
155             /**
156             Get accessor
157 
158             Returns:
159                 PropertyAccessor!(DocumentType, Object)
160             **/
161             inout(PropertyAccessor!(DocumentType, Object)) accessor() @safe nothrow pure inout {
162                 return this.accessor_;
163             }
164         }
165 
166         /**
167 		Get a component that is associated with key.
168 
169 		Params:
170 			identity = the element id.
171 
172 		Throws:
173 			NotFoundException in case if the element wasn't found.
174 
175 		Returns:
176 			Object element if it is available.
177 		**/
178         Object get(string identity) @trusted {
179             debug(trace) trace("Searching for \"", identity, '"');
180 
181             if (identity in components) {
182                 debug(trace) trace("Found already converted component, supplying it.");
183 
184                 return components[identity];
185             }
186 
187             if (!this.accessor.has(this.document.payload, identity)) {
188                 throw new AediNotFoundException(
189                     text("Could not find ${identity} in document of type ", typeid(DocumentType)), identity
190                 );
191             }
192 
193             Object document = this.accessor.access(this.document.payload, identity);
194 
195             debug(trace) trace("Attempting to guess the desired type.");
196             TypeInfo guess = this.guesser.guess(document);
197 
198             debug(trace) trace("Guessed ", guess, " type, commencing conversion");
199             return convertor.convert(document, guess, this.allocator);
200         }
201 
202         /**
203         Check if an element is present in Locator by key id.
204 
205         Note:
206         	This check should be done for elements that locator actually contains, and
207         	not in chained locator.
208         Params:
209         	identity = identity of element.
210 
211     	Returns:
212     		bool true if an element by key is present in Locator.
213         **/
214         bool has(string identity) inout @trusted {
215             return (identity in components) || this.accessor.has((cast(TypeWrapper) this.document).payload, identity);
216         }
217 
218         /**
219         Sets up the internal state of container.
220 
221         Sets up the internal state of container (Ex, for singleton container it will spawn all objects that locator contains).
222         **/
223         typeof(this) instantiate() {
224             return this;
225         }
226 
227         /**
228         Destruct all managed components.
229 
230         Destruct all managed components. The method denotes the end of container lifetime, and therefore destruction of all managed components
231         by it.
232         **/
233         typeof(this) terminate() @trusted {
234             foreach (id, component; this.components) {
235                 this.convertor.destruct(null, component, allocator);
236             }
237 
238             return this;
239         }
240     }
241 
242     private {
243         /**
244         A dirty hack used to evade tagged algebraic or any other
245         custom implementation of cast while stripping out constness/immutability of the payload in has method
246         **/
247         static struct TypeWrapper {
248             DocumentType payload;
249         }
250     }
251 }