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