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.convertor.setter;
31 
32 import aermicioi.aedi : NotFoundException;
33 import aermicioi.aedi_property_reader.convertor.exception;
34 import aermicioi.aedi_property_reader.convertor.convertor;
35 import aermicioi.aedi_property_reader.convertor.placeholder;
36 import aermicioi.aedi.util.traits : isPublic, isField;
37 import aermicioi.aedi_property_reader.convertor.traits;
38 import std.conv;
39 import std.traits;
40 import std.meta;
41 import std.exception;
42 import std.experimental.allocator;
43 
44 /**
45 Provides ability to set a property of FieldType into CompositeType.
46 **/
47 interface PropertySetter(CompositeType, FieldType = CompositeType, KeyType = string) {
48 
49     /**
50     Set a field or property of CompositeType.
51 
52     Params:
53         composite = composite that will store value
54         value = actual value that is assigned to a field in composite
55         property = the identity of field in composite
56     Throws:
57         InvalidArgumentException when value or composite is not what was expected
58     **/
59     void set(ref CompositeType composite, FieldType value, KeyType property, RCIAllocator allocator = theAllocator) const;
60 
61     /**
62      Identify the type of supported component.
63 
64      Identify the type of supported component. It returns type info of component
65      if it is supported by accessor, otherwise it will return typeid(void) denoting that
66      the type isn't supported by accessor. The accessor is not limited to returning the type
67      info of passed component, it can actually return type info of super type or any type
68      given the returned type is implicitly convertible or castable to ComponentType.
69 
70      Params:
71          component = the component for which accessor should identify the underlying type
72 
73      Returns:
74          TypeInfo type information about passed component, or typeid(void) if component is not supported.
75      **/
76     TypeInfo componentType(ref CompositeType composite) const nothrow;
77 
78     /**
79      Identify the type of supported component.
80 
81      Identify the type of supported component. It returns type info of component
82      if it is supported by accessor, otherwise it will return typeid(void) denoting that
83      the type isn't supported by accessor. The accessor is not limited to returning the type
84      info of passed component, it can actually return type info of super type or any type
85      given the returned type is implicitly convertible or castable to ComponentType.
86 
87      Params:
88          component = the component for which accessor should identify the underlying type
89 
90      Returns:
91          TypeInfo type information about passed component, or typeid(void) if component is not supported.
92      **/
93     final TypeInfo componentType(CompositeType composite) const nothrow {
94         return this.componentType(composite);
95     }
96 }
97 
98 /**
99 Associative array setter.
100 **/
101 class AssociativeArraySetter(Type, KeyType = Type) : PropertySetter!(Type[KeyType], Type, KeyType) {
102 
103     public {
104         /**
105         Set a field or property of CompositeType.
106 
107         Params:
108             composite = composite that will store value
109             value = actual value that is assigned to a field in composite
110             property = the identity of field in composite
111         Throws:
112             InvalidArgumentException when value or composite is not what was expected
113         **/
114         void set(ref Type[KeyType] composite, Type field, KeyType key, RCIAllocator allocator = theAllocator) const {
115             composite[key] = field;
116         }
117 
118         /**
119          Identify the type of supported component.
120 
121          Identify the type of supported component. It returns type info of component
122          if it is supported by accessor, otherwise it will return typeid(void) denoting that
123          the type isn't supported by accessor. The accessor is not limited to returning the type
124          info of passed component, it can actually return type info of super type or any type
125          given the returned type is implicitly convertible or castable to ComponentType.
126 
127          Params:
128              component = the component for which accessor should identify the underlying type
129 
130          Returns:
131              TypeInfo type information about passed component, or typeid(void) if component is not supported.
132          **/
133         TypeInfo componentType(ref Type[KeyType] composite) const nothrow {
134             return typeid(Type[KeyType]);
135         }
136     }
137 }
138 
139 /**
140 Array setter.
141 **/
142 class ArraySetter(Type) : PropertySetter!(Type[], Type, size_t) {
143 
144     /**
145     Set a field or property of CompositeType.
146 
147     Params:
148         composite = composite that will store value
149         value = actual value that is assigned to a field in composite
150         property = the identity of field in composite
151     Throws:
152         InvalidArgumentException when value or composite is not what was expected
153     **/
154     void set(ref Type[] composite, Type field, size_t key, RCIAllocator allocator = theAllocator) const {
155         enforce!InvalidArgumentException(key < composite.length, text(
156             "Cannot assign ",
157             field,
158             " to ",
159             composite,
160             " index ",
161             key,
162             " out of bounds ",
163             composite.length
164         ));
165 
166         composite[key] = field;
167     }
168 
169     /**
170      Identify the type of supported component.
171 
172      Identify the type of supported component. It returns type info of component
173      if it is supported by accessor, otherwise it will return typeid(void) denoting that
174      the type isn't supported by accessor. The accessor is not limited to returning the type
175      info of passed component, it can actually return type info of super type or any type
176      given the returned type is implicitly convertible or castable to ComponentType.
177 
178      Params:
179          component = the component for which accessor should identify the underlying type
180 
181      Returns:
182          TypeInfo type information about passed component, or typeid(void) if component is not supported.
183      **/
184     TypeInfo componentType(ref Type[] composite) const nothrow {
185         return typeid(Type[]);
186     }
187 }
188 
189 /**
190 Composite (object, struct, union) setter.
191 **/
192 class CompositeSetter(ComponentType) : PropertySetter!(ComponentType, Object, string)
193     if (isAggregateType!ComponentType) {
194 
195     /**
196     Set a field or property of CompositeType.
197 
198     Params:
199         composite = composite that will store value
200         value = actual value that is assigned to a field in composite
201         property = the identity of field in composite
202     Throws:
203         InvalidArgumentException when value or composite is not what was expected
204     **/
205     void set(T)(ref ComponentType component, T field, string property, RCIAllocator allocator = theAllocator) const {
206         import std.experimental.allocator : theAllocator, dispose;
207         auto wrapped = field.stored;
208         this.set(component, wrapped, property);
209     }
210 
211     /**
212     ditto
213     **/
214     void set(ref ComponentType component, Object field, string property, RCIAllocator allocator = theAllocator) const {
215         static foreach (member; __traits(allMembers, ComponentType)) {{
216             static if (isPublic!(ComponentType, member)) {
217                 alias m = Alias!(__traits(getMember, component, member));
218 
219                 if (member == property) {
220                     static if (isField!(ComponentType, member)) {
221                         alias FieldType = typeof(__traits(getMember, component, member));
222 
223                         enforce!InvalidArgumentException(field.identify is typeid(FieldType), text(
224                             "Cannot set value of type ", field.identify,
225                             " to property ", member,
226                             " of type ", typeid(FieldType)
227                         ));
228 
229                         __traits(getMember, component, member) = field.unwrap!FieldType;
230                         return;
231                     } else static if (
232                         isSomeFunction!m &&
233                         anySatisfy!(isPropertyPropertySetter, __traits(getOverloads, ComponentType, member))
234                     ) {
235                         alias FieldType = Parameters!(
236                             match!(
237                                 isPropertyPropertySetter,
238                                 __traits(getOverloads, ComponentType, member)
239                                 )
240                             )[0];
241 
242                         enforce!InvalidArgumentException(field.identify is typeid(FieldType), text(
243                             "Cannot set value of type ", field.identify,
244                             " to property ", member,
245                             " of type ", typeid(FieldType)
246                         ));
247 
248                         __traits(getMember, component, member) = field.unwrap!FieldType;
249                         return;
250                     }
251                 }
252             }
253         }}
254 
255         throw new NotFoundException(text(
256             "Component of type ",
257             typeid(ComponentType),
258             " does not have ${identity} property"
259         ), property);
260     }
261 
262     /**
263      Identify the type of supported component.
264 
265      Identify the type of supported component. It returns type info of component
266      if it is supported by accessor, otherwise it will return typeid(void) denoting that
267      the type isn't supported by accessor. The accessor is not limited to returning the type
268      info of passed component, it can actually return type info of super type or any type
269      given the returned type is implicitly convertible or castable to ComponentType.
270 
271      Params:
272          component = the component for which accessor should identify the underlying type
273 
274      Returns:
275          TypeInfo type information about passed component, or typeid(void) if component is not supported.
276      **/
277     TypeInfo componentType(ref ComponentType composite) const nothrow {
278         return typeid(ComponentType);
279     }
280 }
281 
282 /**
283 Runtime composite setter that accepts erased Type, restores it's type and sets data in it.
284 **/
285 class RuntimeCompositeSetter(Type, FieldType = Type, KeyType = string) : PropertySetter!(Object, FieldType, KeyType) {
286     private {
287         PropertySetter!(Type, FieldType, KeyType) setter_;
288     }
289 
290     public {
291         /**
292         Constructor for runtime composite setter.
293 
294         Params:
295             setter = underlying setter used for assigning fields to component
296         **/
297         this(PropertySetter!(Type, FieldType, KeyType) setter) {
298             this.setter = setter;
299         }
300 
301         @property {
302             /**
303             Set setter
304 
305             Params:
306                 setter = underlying setter used to set data
307 
308             Returns:
309                 typeof(this)
310             **/
311             typeof(this) setter(PropertySetter!(Type, FieldType, KeyType) setter) @safe nothrow pure {
312                 this.setter_ = setter;
313 
314                 return this;
315             }
316 
317             /**
318             Get setter
319 
320             Returns:
321                 PropertySetter!(Type, FieldType, KeyType)
322             **/
323             inout(PropertySetter!(Type, FieldType, KeyType)) setter() @safe nothrow pure inout {
324                 return this.setter_;
325             }
326         }
327 
328         /**
329         Set a field or property of CompositeType.
330 
331         Params:
332             composite = composite that will store value
333             value = actual value that is assigned to a field in composite
334             property = the identity of field in composite
335         Throws:
336             InvalidArgumentException when value or composite is not what was expected
337         **/
338         void set(ref Object composite, FieldType field, KeyType key, RCIAllocator allocator = theAllocator) const {
339             this.setter.set(composite.unwrap!Type, field, key);
340         }
341 
342         /**
343         Identify the type of supported component.
344 
345         Identify the type of supported component. It returns type info of component
346         if it is supported by accessor, otherwise it will return typeid(void) denoting that
347         the type isn't supported by accessor. The accessor is not limited to returning the type
348         info of passed component, it can actually return type info of super type or any type
349         given the returned type is implicitly convertible or castable to ComponentType.
350 
351         Params:
352             component = the component for which accessor should identify the underlying type
353 
354         Returns:
355             TypeInfo type information about passed component, or typeid(void) if component is not supported.
356         **/
357         TypeInfo componentType(ref Object composite) const nothrow {
358             if (composite.identify is typeid(Type)) {
359                 return this.setter.componentType(composite.unwrap!Type);
360             }
361 
362             return typeid(void);
363         }
364     }
365 }
366 
367 /**
368 Runtime field setter that accepts type erased fields, that are restored and then assigned to component.
369 **/
370 class RuntimeFieldSetter(Type, FieldType = Type, KeyType = string) : PropertySetter!(Type, Object, KeyType) {
371     private {
372         PropertySetter!(Type, FieldType, KeyType) setter_;
373     }
374 
375     public {
376         /**
377         Constructor for runtime field setter.
378 
379         Params:
380             setter = underlying setter that works directly with FieldType values
381         **/
382         this(PropertySetter!(Type, FieldType, KeyType) setter) {
383             this.setter = setter;
384         }
385 
386         @property {
387             /**
388             Set setter
389 
390             Params:
391                 setter = underlying setter used to set data
392 
393             Returns:
394                 typeof(this)
395             **/
396             typeof(this) setter(PropertySetter!(Type, FieldType, KeyType) setter) @safe nothrow pure {
397                 this.setter_ = setter;
398 
399                 return this;
400             }
401 
402             /**
403             Get setter
404 
405             Returns:
406                 PropertySetter!(Type, FieldType, KeyType)
407             **/
408             inout(PropertySetter!(Type, FieldType, KeyType)) setter() @safe nothrow pure inout {
409                 return this.setter_;
410             }
411         }
412 
413         /**
414         Set a field or property of CompositeType.
415 
416         Params:
417             composite = composite that will store value
418             value = actual value that is assigned to a field in composite
419             property = the identity of field in composite
420         Throws:
421             InvalidArgumentException when value or composite is not what was expected
422         **/
423         void set(ref Type composite, Object field, KeyType key, RCIAllocator allocator = theAllocator) const {
424             this.setter.set(composite, field.unwrap!FieldType, key);
425         }
426 
427         /**
428         Identify the type of supported component.
429 
430         Identify the type of supported component. It returns type info of component
431         if it is supported by accessor, otherwise it will return typeid(void) denoting that
432         the type isn't supported by accessor. The accessor is not limited to returning the type
433         info of passed component, it can actually return type info of super type or any type
434         given the returned type is implicitly convertible or castable to ComponentType.
435 
436         Params:
437             component = the component for which accessor should identify the underlying type
438 
439         Returns:
440             TypeInfo type information about passed component, or typeid(void) if component is not supported.
441         **/
442         TypeInfo componentType(ref Type composite) const nothrow {
443             return typeid(Type);
444         }
445     }
446 }