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.accessor;
31 
32 import aermicioi.aedi.storage.allocator_aware;
33 import aermicioi.aedi.exception.not_found_exception;
34 import aermicioi.aedi.exception.invalid_cast_exception;
35 import aermicioi.aedi_property_reader.core.exception;
36 import aermicioi.util.traits;
37 import aermicioi.aedi_property_reader.core.traits : isD, n;
38 import aermicioi.aedi_property_reader.core.placeholder;
39 import taggedalgebraic;
40 import std.array;
41 import std.conv;
42 import std.algorithm;
43 import std.range;
44 import std.exception : enforce;
45 import std.variant;
46 import std.traits;
47 import std.meta;
48 import std.experimental.logger;
49 
50 /**
51 Interface for objects that are able to get child component out of parent one.
52 
53 The intent of property accessor is to provide runtime means of accessing various properties
54 out of a component which could be a D object, or struct. The implementation is free in allocation
55 policies in order to satisfy the property access, though the burden of freeing allocated memory
56 is left on code using the accessor.
57 **/
58 interface PropertyAccessor(ComponentType, FieldType = ComponentType, KeyType = string) {
59 
60 
61     /**
62      Get a property out of component
63 
64      Params:
65          component = a component which has some properties identified by property.
66      Throws:
67          NotFoundException in case when no requested property is available.
68          InvalidArgumentException in case when passed arguments are somehow invalid for use.
69      Returns:
70          FieldType accessed property.
71      **/
72     FieldType access(ComponentType component, in KeyType property) const;
73 
74     /**
75      Check if requested property is present in component.
76 
77      Check if requested property is present in component.
78      The method could have allocation side effects due to the fact that
79      it is not restricted in calling access method of the accessor.
80 
81      Params:
82          component = component which is supposed to have property
83          property = speculated property that is to be tested if it is present in component
84      Returns:
85          true if property is in component
86      **/
87     bool has(in ComponentType component, in KeyType property) const nothrow;
88 
89     /**
90      Identify the type of supported component.
91 
92      Identify the type of supported component. It returns type info of component
93      if it is supported by accessor, otherwise it will return typeid(void) denoting that
94      the type isn't supported by accessor. The accessor is not limited to returning the type
95      info of passed component, it can actually return type info of super type or any type
96      given the returned type is implicitly convertible or castable to ComponentType.
97 
98      Params:
99          component = the component for which accessor should identify the underlying type
100 
101      Returns:
102          TypeInfo type information about passed component, or typeid(void) if component is not supported.
103      **/
104     TypeInfo componentType(ComponentType component) const nothrow;
105 }
106 
107 /**
108 Interface for accessor objects that are aware of an allocator and supposedly will use it
109 to allocate memory for returned component.
110 
111 Interface for accessor objects that are aware of an allocator and supposedly will use it
112 to allocate memory for returned component.
113 ImplSpec:
114     The allocator in accessor is considered mutable even if the object is constant, therefore
115     it should be safe to access it from const methods. In case of immutable implementor the
116     behavior is undefined, and therefore it is advised to avoid such cases.
117 **/
118 interface AllocatingPropertyAccessor(ComponentType, FieldType = ComponentType, KeyType = string) :
119     PropertyAccessor!(ComponentType, FieldType, KeyType),
120     AllocatorAware!() {
121 
122 }
123 
124 /**
125 An accessor that queries stored accessors for component.
126 **/
127 class AggregatePropertyAccessor(ComponentType, FieldType = ComponentType, KeyType = string) :
128     PropertyAccessor!(ComponentType, FieldType, KeyType) {
129 
130     private {
131 
132         PropertyAccessor!(ComponentType, FieldType)[] accessors_;
133     }
134 
135     public {
136 
137         /**
138         Constructor for aggregate property accessor.
139 
140         Params:
141             accessors = list of accessors used to extract fields from component.
142         **/
143         this(PropertyAccessor!(ComponentType, FieldType)[] accessors...) {
144             this.accessors = accessors.dup;
145         }
146 
147         @property {
148             /**
149             Set accessors
150 
151             Params:
152                 accessors = accessors that implement various logic of accessing a property out of component
153 
154             Returns:
155                 typeof(this)
156             **/
157             typeof(this) accessors(PropertyAccessor!(ComponentType, FieldType, KeyType)[] accessors) @safe nothrow pure {
158                 this.accessors_ = accessors;
159 
160                 return this;
161             }
162 
163             /**
164             Get accessors
165 
166             Returns:
167                 PropertyAccessor!(ComponentType, FieldType, KeyType)
168             **/
169             inout(PropertyAccessor!(ComponentType, FieldType, KeyType)[]) accessors() @safe nothrow pure inout {
170                 return this.accessors_;
171             }
172         }
173 
174     /**
175      Get a property out of component
176 
177      Params:
178          component = a component which has some properties identified by property.
179      Throws:
180          NotFoundException in case when no requested property is available.
181          InvalidArgumentException in case when passed arguments are somehow invalid for use.
182      Returns:
183          FieldType accessed property.
184      **/
185         FieldType access(ComponentType component, in KeyType property) const {
186 
187             foreach (accessor; this.accessors) {
188 
189                 if (accessor.has(component, property)) {
190 
191                     return accessor.access(component, property);
192                 }
193             }
194 
195             import aermicioi.aedi.exception.not_found_exception : NotFoundException;
196             throw new NotFoundException("Could not find element");
197         }
198 
199     /**
200      Check if requested property is present in component.
201 
202      Check if requested property is present in component.
203      The method could have allocation side effects due to the fact that
204      it is not restricted in calling access method of the accessor.
205 
206      Params:
207          component = component which is supposed to have property
208          property = speculated property that is to be tested if it is present in component
209      Returns:
210          true if property is in component
211      **/
212         bool has(in ComponentType component, in KeyType property) const nothrow {
213 
214             foreach (accessor; this.accessors) {
215 
216                 if (accessor.has(component, property)) {
217                     return true;
218                 }
219             }
220 
221             return false;
222         }
223 
224         /**
225         Identify the type of supported component.
226 
227         Identify the type of supported component. It returns type info of component
228         if it is supported by accessor, otherwise it will return typeid(void) denoting that
229         the type isn't supported by accessor. The accessor is not limited to returning the type
230         info of passed component, it can actually return type info of super type or any type
231         given the returned type is implicitly convertible or castable to ComponentType.
232 
233         Params:
234             component = the component for which accessor should identify the underlying type
235 
236         Returns:
237             TypeInfo type information about passed component, or typeid(void) if component is not supported.
238         **/
239         TypeInfo componentType(ComponentType component) const nothrow {
240             return typeid(ComponentType);
241         }
242     }
243 }
244 
245 /**
246 An accessor that splits property into chunks using a separator, and recursively queries an accessor for next property from
247 returned child property.
248 
249 An accessor that splits property into chunks using a separator,and recursively queries an accessor for next property from
250 returned child property.
251 ImplSpec:
252     Since a single accessor is used to fetch subsequent properties, a constraint on property accessors are placed, such as
253     field of a component should be implicitly convertible to component type in order for it to be used to get next child
254     in property chain.
255 **/
256 class PropertyPathAccessor(ComponentType, FieldType = ComponentType, KeyType = string) :
257     PropertyAccessor!(ComponentType, FieldType, KeyType)
258     if (isImplicitlyConvertible!(FieldType, ComponentType) && isInputRange!KeyType) {
259 
260     private {
261         PropertyAccessor!(ComponentType, FieldType) accessor_;
262 
263         ElementType!KeyType separator_;
264     }
265 
266     public {
267 
268         /**
269         Constructor for property path accessor
270 
271         Params:
272             separator = separator used to separate field identities in a field chain.
273             accessor = accessor used to access consecutively new child field.
274         **/
275         this(
276             ElementType!KeyType separator,
277             PropertyAccessor!(ComponentType, FieldType) accessor
278         ) {
279             this.separator = separator;
280             this.accessor = accessor;
281         }
282 
283         @property {
284             /**
285             Set accessor
286 
287             Params:
288                 accessor = accessor instance responsible for getting a property out of component
289 
290             Returns:
291                 typeof(this)
292             **/
293             typeof(this) accessor(PropertyAccessor!(ComponentType, FieldType, KeyType) accessor) @safe nothrow pure {
294                 this.accessor_ = accessor;
295 
296                 return this;
297             }
298 
299             /**
300             Get accessor
301 
302             Returns:
303                 PropertyAccessor!(ComponentType, FieldType, KeyType)
304             **/
305             inout(PropertyAccessor!(ComponentType, FieldType, KeyType)) accessor() @safe nothrow pure inout {
306                 return this.accessor_;
307             }
308 
309             /**
310             Set propertyAccessor
311 
312             Params:
313                 propertyAccessor = property splitter used to cut up the property path into multiple points
314 
315             Returns:
316                 typeof(this)
317             **/
318             typeof(this) separator(ElementType!KeyType separator) @safe nothrow pure {
319                 this.separator_ = separator;
320 
321                 return this;
322             }
323 
324             /**
325             Get propertyAccessor
326 
327             Returns:
328                 ElementType!KeyType
329             **/
330             inout(ElementType!KeyType) separator() @safe nothrow pure inout {
331                 return this.separator_;
332             }
333         }
334 
335         /**
336         Get a property out of component
337 
338         Params:
339             component = a component which has some properties identified by property.
340         Throws:
341             NotFoundException in case when no requested property is available.
342             InvalidArgumentException in case when passed arguments are somehow invalid for use.
343         Returns:
344             FieldType accessed property.
345         **/
346         FieldType access(ComponentType component, in KeyType path) const {
347             import std.algorithm;
348             import std.range;
349 
350             auto identities = path.splitter(this.separator);
351 
352             ComponentType current = component;
353 
354             foreach (identity; identities) {
355 
356                 if (this.accessor.has(current, identity)) {
357 
358                     current = this.accessor.access(current, identity);
359                 } else {
360 
361                     throw new NotFoundException(text(
362                         "Could not find ",
363                         identity,
364                         " in ",
365                         typeid(component),
366                         " for property path of ",
367                         path
368                     ));
369                 }
370             }
371 
372             return current;
373         }
374 
375         /**
376         Check if requested property is present in component.
377 
378         Check if requested property is present in component.
379         The method could have allocation side effects due to the fact that
380         it is not restricted in calling access method of the accessor.
381 
382         Params:
383             component = component which is supposed to have property
384             property = speculated property that is to be tested if it is present in component
385         Returns:
386             true if property is in component
387         **/
388         bool has(in ComponentType component, in KeyType path) const nothrow {
389 
390             try {
391 
392                 auto identities = path.splitter(this.separator);
393 
394                 ComponentType current = cast() component;
395 
396                 foreach (identity; identities) {
397                     if (!this.accessor.has(current, identity)) {
398                         return false;
399                     }
400 
401                     if (this.accessor.has(current, identity)) {
402                         current = this.accessor.access(current, identity);
403                     }
404                 }
405 
406                 return true;
407             } catch (Exception e) {
408                 debug(trace) error("Failed to access a property path ", path, " due to ", e).n;
409             }
410 
411             return false;
412         }
413 
414         /**
415         Identify the type of supported component.
416 
417         Identify the type of supported component. It returns type info of component
418         if it is supported by accessor, otherwise it will return typeid(void) denoting that
419         the type isn't supported by accessor. The accessor is not limited to returning the type
420         info of passed component, it can actually return type info of super type or any type
421         given the returned type is implicitly convertible or castable to ComponentType.
422 
423         Params:
424             component = the component for which accessor should identify the underlying type
425 
426         Returns:
427             TypeInfo type information about passed component, or typeid(void) if component is not supported.
428         **/
429         TypeInfo componentType(ComponentType component) const nothrow {
430             return this.accessor.componentType(component);
431         }
432     }
433 }
434 
435 /**
436 ditto
437 **/
438 auto propertyPathAccessor(T : PropertyAccessor!(ComponentType, FieldType, KeyType), ComponentType, FieldType, KeyType)
439     (ElementType!KeyType separator, T accessor) {
440     return new PropertyPathAccessor!(ComponentType, FieldType, KeyType)(separator, accessor);
441 }
442 
443 /**
444 An accessor that allows accessing of a property and it's childs with array syntax.
445 
446 An accessor that allows accessing of a property and it's childs with array syntax.
447 ImplSpec:
448     Two accessors are used for getting an indexed property's child. The first one is used
449     for accessing an indexed property of a component, and the second one is used for subsequent
450     access of child components recursively.
451 **/
452 class ArrayIndexedPropertyAccessor(ComponentType, FieldType = ComponentType, KeyType = string) : PropertyAccessor!(ComponentType, FieldType, KeyType)
453     if (isBidirectionalRange!KeyType) {
454 
455     private {
456         alias EType = ElementType!KeyType;
457         EType beggining_;
458         EType ending_;
459 
460         PropertyAccessor!(ComponentType, FieldType, KeyType) accessor_;
461         PropertyAccessor!(ComponentType, FieldType, KeyType) indexer_;
462     }
463 
464     public {
465 
466         /**
467         Constructor for array indexed accessor
468 
469         Params:
470             beggining = beggining token denoting the start of an array indexing
471             ending = ending token denoting the end of an array indexing
472             accessor = accessor used to access property part of indexing sequence
473             indexer = accessor used to access indexed part of indexing sequence
474         **/
475         this(
476             EType beggining,
477             EType ending,
478             PropertyAccessor!(ComponentType, FieldType, KeyType) accessor,
479             PropertyAccessor!(ComponentType, FieldType, KeyType) indexer
480         ) {
481             this.beggining = beggining;
482             this.ending = ending;
483             this.accessor = accessor;
484             this.indexer = indexer;
485         }
486 
487         /**
488         Set beggining
489 
490         Params:
491             beggining = element denoting beggining of array syntax in identity, ex. [
492 
493         Returns:
494             typeof(this)
495         **/
496         typeof(this) beggining(EType beggining) @safe nothrow pure {
497             this.beggining_ = beggining;
498 
499             return this;
500         }
501 
502         /**
503         Get beggining
504 
505         Returns:
506             EType
507         **/
508         inout(EType) beggining() @safe nothrow pure inout {
509             return this.beggining_;
510         }
511 
512         /**
513         Set ending
514 
515         Params:
516             ending = element denoting the end of array indexing, ex. ]
517 
518         Returns:
519             typeof(this)
520         **/
521         typeof(this) ending(EType ending) @safe nothrow pure {
522             this.ending_ = ending;
523 
524             return this;
525         }
526 
527         /**
528         Get ending
529 
530         Returns:
531             EType
532         **/
533         inout(EType) ending() @safe nothrow pure inout {
534             return this.ending_;
535         }
536 
537         /**
538         Set accessor
539 
540         Params:
541             accessor = accessor used to access property part from indexed property
542 
543         Returns:
544             typeof(this)
545         **/
546         typeof(this) accessor(PropertyAccessor!(ComponentType, FieldType, KeyType) accessor) @safe nothrow pure {
547             this.accessor_ = accessor;
548 
549             return this;
550         }
551 
552         /**
553         Get accessor
554 
555         Returns:
556             PropertyAccessor!(ComponentType, FieldType, KeyType)
557         **/
558         inout(PropertyAccessor!(ComponentType, FieldType, KeyType)) accessor() @safe nothrow pure inout {
559             return this.accessor_;
560         }
561 
562         /**
563         Set indexer
564 
565         Params:
566             indexer = property accessor used to access element based on contents in index part of property
567 
568         Returns:
569             typeof(this)
570         **/
571         typeof(this) indexer(PropertyAccessor!(ComponentType, FieldType, KeyType) indexer) @safe nothrow pure {
572             this.indexer_ = indexer;
573 
574             return this;
575         }
576 
577         /**
578         Get indexer
579 
580         Returns:
581             PropertyAccessor!(ComponentType, FieldType, KeyType)
582         **/
583         inout(PropertyAccessor!(ComponentType, FieldType, KeyType)) indexer() @safe nothrow pure inout {
584             return this.indexer_;
585         }
586 
587     /**
588      Get a property out of component
589 
590      Params:
591          component = a component which has some properties identified by property.
592      Throws:
593          NotFoundException in case when no requested property is available.
594          InvalidArgumentException in case when passed arguments are somehow invalid for use.
595      Returns:
596          FieldType accessed property.
597      **/
598         FieldType access(ComponentType component, in KeyType path) const {
599             enforce!NotFoundException(this.has(component, path), text("Property ", path, " not found in ", component));
600 
601             auto splitted = path.splitter(this.beggining);
602             enforce!InvalidArgumentException(
603                 !splitted.empty && !splitted.front.empty,
604                 text("Malformed indexed property ", path, ", no property part found")
605             );
606 
607             FieldType property = this.accessor.access(component, splitted.front);
608             splitted.popFront;
609             enforce!InvalidArgumentException(
610                 !splitted.empty,
611                 text("Malformed indexed property ", path, ", no index part found")
612             );
613 
614             foreach (identity; splitted) {
615                 enforce!InvalidArgumentException(
616                     identity.endsWith(this.ending),
617                     text("Malformed indexed property ", path, ", no closing ] found")
618                 );
619 
620                 property = this.indexer.access(property, identity.dropBack(1));
621             }
622 
623             return property;
624         }
625 
626     /**
627      Check if requested property is present in component.
628 
629      Check if requested property is present in component.
630      The method could have allocation side effects due to the fact that
631      it is not restricted in calling access method of the accessor.
632 
633      Params:
634          component = component which is supposed to have property
635          property = speculated property that is to be tested if it is present in component
636      Returns:
637          true if property is in component
638      **/
639         bool has(in ComponentType component, in KeyType path) const nothrow {
640 
641             try {
642 
643                 auto splitted = path.splitter(this.beggining);
644 
645                 if (splitted.empty) {
646                     return false;
647                 }
648 
649                 if (splitted.front.empty) {
650                     return false;
651                 }
652 
653                 if (!this.accessor.has(component, splitted.front)) {
654                     return false;
655                 }
656 
657                 FieldType property = this.accessor.access(cast(ComponentType) component, splitted.front);
658                 splitted.popFront;
659                 if (splitted.empty) {
660                     return false;
661                 }
662 
663                 foreach (identity; splitted) {
664                     if (!identity.endsWith(this.ending)) {
665                         return false;
666                     }
667 
668                     if (!this.indexer.has(property, identity.dropBack(1))) {
669                         return false;
670                     }
671 
672                     property = this.indexer.access(property, identity.dropBack(1));
673                 }
674 
675                 return true;
676             } catch (Exception e) {
677                 debug(trace) error("Failed to check existance of indexed path ", path, " due to ", e).n;
678             }
679 
680             return false;
681         }
682 
683         /**
684         Identify the type of supported component.
685 
686         Identify the type of supported component. It returns type info of component
687         if it is supported by accessor, otherwise it will return typeid(void) denoting that
688         the type isn't supported by accessor. The accessor is not limited to returning the type
689         info of passed component, it can actually return type info of super type or any type
690         given the returned type is implicitly convertible or castable to ComponentType.
691 
692         Params:
693             component = the component for which accessor should identify the underlying type
694 
695         Returns:
696             TypeInfo type information about passed component, or typeid(void) if component is not supported.
697         **/
698         TypeInfo componentType(ComponentType component) const nothrow {
699             return typeid(ComponentType);
700         }
701     }
702 }
703 
704 /**
705 ditto
706 **/
707 auto arrayIndexedPropertyAccessor(T : PropertyAccessor!(ComponentType, FieldType, KeyType), ComponentType, FieldType, KeyType)
708     (ElementType!KeyType beggining, ElementType!KeyType ending, T accessor, T indexer) {
709     return new ArrayIndexedPropertyAccessor!(ComponentType, FieldType, KeyType)(beggining, ending, accessor, indexer);
710 }
711 
712 /**
713 Class that allows accessing properties that are wrapped in ticks.
714 **/
715 class TickedPropertyAccessor(ComponentType, FieldType = ComponentType, KeyType = string) :
716     PropertyAccessor!(ComponentType, FieldType, KeyType)
717     if (isBidirectionalRange!KeyType) {
718 
719     private {
720         alias EType = ElementType!KeyType;
721         EType tick_;
722 
723         PropertyAccessor!(ComponentType, FieldType, KeyType) accessor_;
724     }
725 
726     public {
727 
728         /**
729         Constructor for ticked accessor
730 
731         Params:
732             tick = token used to tick the property
733             accessor = accessor used to access property wrapped in ticks
734         **/
735         this(EType tick, PropertyAccessor!(ComponentType, FieldType, KeyType) accessor) {
736             this.tick = tick;
737             this.accessor = accessor;
738         }
739 
740         /**
741         Set tick
742 
743         Params:
744             tick = ticking element used to encapsulate property
745 
746         Returns:
747             typeof(this)
748         **/
749         typeof(this) tick(EType tick) @safe nothrow pure {
750             this.tick_ = tick;
751 
752             return this;
753         }
754 
755         /**
756         Get tick
757 
758         Returns:
759             EType
760         **/
761         inout(EType) tick() @safe nothrow pure inout {
762             return this.tick_;
763         }
764 
765         /**
766         Set accessor
767 
768         Params:
769             accessor = accessor used to access property enclosed in ticks
770 
771         Returns:
772             typeof(this)
773         **/
774         typeof(this) accessor(PropertyAccessor!(ComponentType, FieldType, KeyType) accessor) @safe nothrow pure {
775             this.accessor_ = accessor;
776 
777             return this;
778         }
779 
780         /**
781         Get accessor
782 
783         Returns:
784             PropertyAccessor!(ComponentType, FieldType, KeyType)
785         **/
786         inout(PropertyAccessor!(ComponentType, FieldType, KeyType)) accessor() @safe nothrow pure inout {
787             return this.accessor_;
788         }
789 
790     /**
791      Get a property out of component
792 
793      Params:
794          component = a component which has some properties identified by property.
795      Throws:
796          NotFoundException in case when no requested property is available.
797          InvalidArgumentException in case when passed arguments are somehow invalid for use.
798      Returns:
799          FieldType accessed property.
800      **/
801         FieldType access(ComponentType component, in KeyType path) const {
802             enforce!InvalidArgumentException(this.valid(path), text("Not found or malformed ticked property ", path));
803 
804             return this.accessor.access(component, path.drop(1).dropBack(1));
805         }
806 
807     /**
808      Check if requested property is present in component.
809 
810      Check if requested property is present in component.
811      The method could have allocation side effects due to the fact that
812      it is not restricted in calling access method of the accessor.
813 
814      Params:
815          component = component which is supposed to have property
816          property = speculated property that is to be tested if it is present in component
817      Returns:
818          true if property is in component
819      **/
820         bool has(in ComponentType component, in KeyType path) const {
821 
822             try {
823 
824                 return this.valid(path) && this.accessor.has(component, path.strip(this.tick));
825             } catch (Exception e) {
826 
827                 debug(trace) error("Failed to check if ticked property exists due to: ", e).n;
828             }
829 
830             return false;
831         }
832 
833         /**
834         Identify the type of supported component.
835 
836         Identify the type of supported component. It returns type info of component
837         if it is supported by accessor, otherwise it will return typeid(void) denoting that
838         the type isn't supported by accessor. The accessor is not limited to returning the type
839         info of passed component, it can actually return type info of super type or any type
840         given the returned type is implicitly convertible or castable to ComponentType.
841 
842         Params:
843             component = the component for which accessor should identify the underlying type
844 
845         Returns:
846             TypeInfo type information about passed component, or typeid(void) if component is not supported.
847         **/
848         TypeInfo componentType(ComponentType component) const nothrow {
849             return this.accessor.componentType(component);
850         }
851 
852         private bool valid(in KeyType path) const nothrow {
853             try {
854 
855                 return (path.front == this.tick) && (path.back == this.tick);
856             } catch (Exception e) {
857 
858                 debug(trace) error("Failed to check if path is ticked due to: ", e).n;
859             }
860 
861             return false;
862         }
863     }
864 }
865 
866 /**
867 ditto
868 **/
869 auto tickedAccessor(T : PropertyAccessor!(ComponentType, FieldType, KeyType), ComponentType, FieldType, KeyType)(ElementType!KeyType tick, T accessor) {
870     return new TickedPropertyAccessor!(ComponentType, FieldType, KeyType)(tick, accessor);
871 }
872 
873 /**
874 Property accessor that is decaying tagged algebraic to a type accepted by decorated accessor.
875 
876 ImplSpec:
877     Any tagged algebraic will be decayed to type X which is accepted by decorated property or fail to do so.
878     Any field returned from accessor are wrapped in same tagged algebraic, therefore there is a constraint that
879     tagged algebraic should be able to hold both the component and it's field.
880 **/
881 class TaggedElementPropertyAccessorWrapper(
882     Tagged : TaggedAlgebraic!Y,
883     PropertyAccessorType : PropertyAccessor!(X, Z, KeyType),
884     X,
885     Z,
886     KeyType = string,
887     Y
888 ) : PropertyAccessor!(Tagged, Tagged, KeyType) {
889 
890     private {
891         PropertyAccessorType accessor_;
892     }
893 
894     public {
895 
896         /**
897         Constructor for tagged element property accessor.
898 
899         Params:
900             accessor = underlying accessor used to access fields out of a particular component type in a tagged algebraic type.
901         **/
902         this(PropertyAccessorType accessor) {
903             this.accessor = accessor;
904         }
905 
906         @property {
907             /**
908             Set accessor
909 
910             Params:
911                 accessor = the property accessor that is wrapped
912 
913             Returns:
914                 typeof(this)
915             **/
916             typeof(this) accessor(PropertyAccessorType accessor) @safe nothrow pure {
917                 this.accessor_ = accessor;
918 
919                 return this;
920             }
921 
922             /**
923             Get accessor
924 
925             Returns:
926                 PropertyAccessorType
927             **/
928             inout(PropertyAccessorType) accessor() @safe nothrow pure inout {
929                 return this.accessor_;
930             }
931         }
932 
933     /**
934      Get a property out of component
935 
936      Params:
937          component = a component which has some properties identified by property.
938      Throws:
939          NotFoundException in case when no requested property is available.
940          InvalidArgumentException in case when passed arguments are somehow invalid for use.
941      Returns:
942          FieldType accessed property.
943      **/
944         Tagged access(Tagged component, in KeyType property) const
945         {
946             if (this.has(component, property)) {
947                 return Tagged(this.accessor.access(cast(X) component, property));
948             }
949 
950             import aermicioi.aedi.exception.not_found_exception : NotFoundException;
951             import std.conv : text;
952             throw new NotFoundException(text(component.kind, " does not have ", property));
953         }
954 
955     /**
956      Check if requested property is present in component.
957 
958      Check if requested property is present in component.
959      The method could have allocation side effects due to the fact that
960      it is not restricted in calling access method of the accessor.
961 
962      Params:
963          component = component which is supposed to have property
964          property = speculated property that is to be tested if it is present in component
965      Returns:
966          true if property is in component
967      **/
968         bool has(in Tagged component, in KeyType property) const nothrow {
969             try {
970 
971                 import std.meta;
972                 import aermicioi.util.traits;
973 
974                 static foreach (e; staticMap!(identifier, EnumMembers!(Tagged.Kind))) {
975                     static if (mixin("is(typeof(Y." ~ e ~ ") : X)")) {
976                         if (mixin("component.Kind." ~ e ~ " == component.kind")) {
977 
978                             return this.accessor.has(cast(const(X)) component, property);
979                         }
980 
981                         return false;
982                     }
983                 }
984 
985                 assert(false, "TaggedAlgebraic does not have " ~ fullyQualifiedName!X ~ " as member");
986 
987             } catch (Exception e) {
988                 debug(trace) error("Failed to unwrap tagged component ", component.kind, " due to ", e).n;
989             }
990 
991             return false;
992         }
993 
994         /**
995         Identify the type of supported component.
996 
997         Identify the type of supported component. It returns type info of component
998         if it is supported by accessor, otherwise it will return typeid(void) denoting that
999         the type isn't supported by accessor. The accessor is not limited to returning the type
1000         info of passed component, it can actually return type info of super type or any type
1001         given the returned type is implicitly convertible or castable to ComponentType.
1002 
1003         Params:
1004             component = the component for which accessor should identify the underlying type
1005 
1006         Returns:
1007             TypeInfo type information about passed component, or typeid(void) if component is not supported.
1008         **/
1009         TypeInfo componentType(Tagged component) const nothrow {
1010             try {
1011 
1012                 import std.meta;
1013                 import aermicioi.util.traits;
1014 
1015                 static foreach (e; staticMap!(identifier, EnumMembers!(Tagged.Kind))) {
1016                     static if (mixin("is(typeof(Y." ~ e ~ ") : X)")) {
1017                         if (mixin("component.Kind." ~ e ~ " == component.kind")) {
1018 
1019                             return this.accessor.componentType(cast(X) component);
1020                         }
1021                     }
1022                 }
1023 
1024             } catch (Exception e) {
1025                 debug(trace) error("Failed to unwrap tagged component ", component.kind, " due to ", e).n;
1026             }
1027 
1028             assert(false, "Got stored a value that is not in tagged algebraic");
1029         }
1030     }
1031 }
1032 
1033 /**
1034 ditto
1035 **/
1036 auto taggedAccessor(Tagged, T : PropertyAccessor!(Composite, Field, Key), Composite, Field, Key)(T accessor) {
1037     return new TaggedElementPropertyAccessorWrapper!(Tagged, T)(accessor);
1038 }
1039 
1040 /**
1041 Implements logic for accessing associative arrays.
1042 **/
1043 class AssociativeArrayAccessor(Type, Key = Type) : PropertyAccessor!(Type[Key], Type, Key) {
1044 
1045     public {
1046 
1047     /**
1048      Get a property out of component
1049 
1050      Params:
1051          component = a component which has some properties identified by property.
1052      Throws:
1053          NotFoundException in case when no requested property is available.
1054          InvalidArgumentException in case when passed arguments are somehow invalid for use.
1055      Returns:
1056          FieldType accessed property.
1057      **/
1058         Type access(Type[Key] component, in Key property) const {
1059             auto peek = property in component;
1060             enforce!NotFoundException(peek, text("Could not find ", property, " in associative array ", component));
1061 
1062             return *peek;
1063         }
1064 
1065     /**
1066      Check if requested property is present in component.
1067 
1068      Check if requested property is present in component.
1069      The method could have allocation side effects due to the fact that
1070      it is not restricted in calling access method of the accessor.
1071 
1072      Params:
1073          component = component which is supposed to have property
1074          property = speculated property that is to be tested if it is present in component
1075      Returns:
1076          true if property is in component
1077      **/
1078         bool has(in Type[Key] component, in Key property) const nothrow {
1079             return (property in component) !is null;
1080         }
1081 
1082         /**
1083         Identify the type of supported component.
1084 
1085         Identify the type of supported component. It returns type info of component
1086         if it is supported by accessor, otherwise it will return typeid(void) denoting that
1087         the type isn't supported by accessor. The accessor is not limited to returning the type
1088         info of passed component, it can actually return type info of super type or any type
1089         given the returned type is implicitly convertible or castable to ComponentType.
1090 
1091         Params:
1092             component = the component for which accessor should identify the underlying type
1093 
1094         Returns:
1095             TypeInfo type information about passed component, or typeid(void) if component is not supported.
1096         **/
1097         TypeInfo componentType(Type[Key] component) const nothrow {
1098             return typeid(Type[Key]);
1099         }
1100     }
1101 }
1102 
1103 /**
1104 Accessor implementing array access.
1105 **/
1106 class ArrayAccessor(Type) : PropertyAccessor!(Type[], Type, size_t) {
1107 
1108     public {
1109 
1110     /**
1111      Get a property out of component
1112 
1113      Params:
1114          component = a component which has some properties identified by property.
1115      Throws:
1116          NotFoundException in case when no requested property is available.
1117          InvalidArgumentException in case when passed arguments are somehow invalid for use.
1118      Returns:
1119          FieldType accessed property.
1120      **/
1121         Type access(Type[] component, in size_t property) const {
1122             enforce!NotFoundException(this.has(component, property), text("Could not find property ", property, " in array ", component));
1123 
1124             return component[property];
1125         }
1126 
1127     /**
1128      Check if requested property is present in component.
1129 
1130      Check if requested property is present in component.
1131      The method could have allocation side effects due to the fact that
1132      it is not restricted in calling access method of the accessor.
1133 
1134      Params:
1135          component = component which is supposed to have property
1136          property = speculated property that is to be tested if it is present in component
1137      Returns:
1138          true if property is in component
1139      **/
1140         bool has(in Type[] component, in size_t property) const nothrow {
1141             return property < component.length;
1142         }
1143 
1144         /**
1145         Identify the type of supported component.
1146 
1147         Identify the type of supported component. It returns type info of component
1148         if it is supported by accessor, otherwise it will return typeid(void) denoting that
1149         the type isn't supported by accessor. The accessor is not limited to returning the type
1150         info of passed component, it can actually return type info of super type or any type
1151         given the returned type is implicitly convertible or castable to ComponentType.
1152 
1153         Params:
1154             component = the component for which accessor should identify the underlying type
1155 
1156         Returns:
1157             TypeInfo type information about passed component, or typeid(void) if component is not supported.
1158         **/
1159         TypeInfo componentType(Type[] component) const nothrow {
1160             return typeid(Type[]);
1161         }
1162     }
1163 }
1164 
1165 /**
1166 Accessor implementing logic for accessingstd.variant.
1167 
1168 ImplSpec:
1169     The accessor can accept any variant of ComponentType. The restriction is that the underlying stored data should be
1170     of either associative array or dynamic array in order to access the contents of them. The return type is as well a
1171     variant that must be able to store associative's array field, or arrays elements.
1172 **/
1173 class VariantAccessor(
1174     ComponentType,
1175     FieldType = ComponentType,
1176     KeyType = ComponentType
1177 ) : PropertyAccessor!(ComponentType, FieldType, KeyType)
1178     if (
1179         is(ComponentType : VariantN!(ComponentSize, ComponentTypes), size_t ComponentSize, ComponentTypes...) &&
1180         is(FieldType : VariantN!(FieldSize, FieldTypes), size_t FieldSize, FieldTypes...) &&
1181         is(KeyType : VariantN!(KeySize, KeyTypes), size_t KeySize, KeyTypes...)
1182     ) {
1183 
1184     private {
1185         static if (is(ComponentType : VariantN!(ComponentSize, CTypes), size_t ComponentSize, CTypes...)) {
1186             alias ComponentTypes = CTypes;
1187         }
1188 
1189         static if (is(FieldType : VariantN!(FieldSize, FTypes), size_t FieldSize, FTypes...)) {
1190             alias FieldTypes = FTypes;
1191         }
1192 
1193         static if (is(KeyType : VariantN!(KeySize, KTypes), size_t KeySize, KTypes...)) {
1194             alias KeyTypes = KTypes;
1195         }
1196     }
1197 
1198     public {
1199         static foreach (KType; KeyTypes) {
1200             /**
1201             Get a property out of component
1202 
1203             Params:
1204                 component = a component which has some properties identified by property.
1205             Throws:
1206                 NotFoundException in case when no requested property is available.
1207                 InvalidArgumentException in case when passed arguments are somehow invalid for use.
1208             Returns:
1209                 FieldType accessed property.
1210             **/
1211             FieldType access(ComponentType component, KType key) const {
1212                 return this.access(component, KeyType(key));
1213             }
1214 
1215             /**
1216             Check if requested property is present in component.
1217 
1218             Check if requested property is present in component.
1219             The method could have allocation side effects due to the fact that
1220             it is not restricted in calling access method of the accessor.
1221 
1222             Params:
1223                 component = component which is supposed to have property
1224                 property = speculated property that is to be tested if it is present in component
1225             Returns:
1226                 true if property is in component
1227             **/
1228             bool has(ComponentType component, KType key) const nothrow {
1229                 return this.has(component, KeyType(key));
1230             }
1231         }
1232 
1233         /**
1234         Get a property out of component
1235 
1236         Params:
1237             component = a component which has some properties identified by property.
1238         Throws:
1239             NotFoundException in case when no requested property is available.
1240             InvalidArgumentException in case when passed arguments are somehow invalid for use.
1241         Returns:
1242             FieldType accessed property.
1243         **/
1244         FieldType access(ComponentType component, in KeyType key) const {
1245             static foreach (Component; ComponentTypes) {{
1246                 static if (
1247                     is(Component : X[W], X, W) &&
1248                     anySatisfy!(ApplyLeft!(isD, X), FieldTypes) &&
1249                     anySatisfy!(ApplyLeft!(isD, W), KeyTypes)
1250                 ) {
1251                     if (
1252                         (component.type is typeid(Component)) &&
1253                         (key.type is typeid(W)) &&
1254                         this.has(component, key)
1255                     ) {
1256                         return FieldType(component.get!Component[key.get!W]);
1257                     }
1258                 }
1259 
1260                 static if (
1261                     is(Component : Y[], Y) &&
1262                     anySatisfy!(ApplyLeft!(isD, Y), FieldTypes) &&
1263                     anySatisfy!(ApplyLeft!(isD, size_t), KeyTypes)
1264                 ) {
1265                     if (
1266                         (component.type is typeid(Component)) &&
1267                         (key.type is typeid(size_t)) &&
1268                         this.has(component, key)
1269                     ) {
1270                         return FieldType(component.get!Component[key.get!size_t]);
1271                     }
1272                 }
1273             }}
1274 
1275             throw new NotFoundException(text("Could not find ", key, " in ", component));
1276         }
1277 
1278         /**
1279         Check if requested property is present in component.
1280 
1281         Check if requested property is present in component.
1282         The method could have allocation side effects due to the fact that
1283         it is not restricted in calling access method of the accessor.
1284 
1285         Params:
1286             component = component which is supposed to have property
1287             property = speculated property that is to be tested if it is present in component
1288         Returns:
1289             true if property is in component
1290         **/
1291         bool has(in ComponentType component, in KeyType key) const nothrow {
1292             try {
1293                 static foreach (Component; ComponentTypes) {{
1294                     static if (
1295                         is(Component : X[W], X, W) &&
1296                         anySatisfy!(ApplyLeft!(isD, X), FieldTypes) &&
1297                         anySatisfy!(ApplyLeft!(isD, W), KeyTypes)
1298                     ) {
1299                         if (
1300                             (component.type is typeid(Component)) &&
1301                             (key.type is typeid(W)) &&
1302                             ((key.get!W in component.get!Component) !is null)
1303                         ) {
1304                             return true;
1305                         }
1306                     }
1307 
1308                     static if (
1309                         is(Component : Y[], Y) &&
1310                         anySatisfy!(ApplyLeft!(isD, Y), FieldTypes) &&
1311                         anySatisfy!(ApplyLeft!(isD, size_t), KeyTypes)
1312                     ) {
1313                         if (
1314                             (component.type is typeid(Component)) &&
1315                             (key.type is typeid(size_t)) &&
1316                             (key.get!size_t < component.get!Component.length)
1317                         ) {
1318                             return true;
1319                         }
1320                     }
1321                 }}
1322             } catch (Exception e) {
1323                 debug(trace) error("Accessing contents of a variant failed with following exception: ", e).n;
1324             }
1325 
1326             return false;
1327         }
1328 
1329         /**
1330         Identify the type of supported component.
1331 
1332         Identify the type of supported component. It returns type info of component
1333         if it is supported by accessor, otherwise it will return typeid(void) denoting that
1334         the type isn't supported by accessor. The accessor is not limited to returning the type
1335         info of passed component, it can actually return type info of super type or any type
1336         given the returned type is implicitly convertible or castable to ComponentType.
1337 
1338         Params:
1339             component = the component for which accessor should identify the underlying type
1340 
1341         Returns:
1342             TypeInfo type information about passed component, or typeid(void) if component is not supported.
1343         **/
1344         TypeInfo componentType(ComponentType component) const nothrow {
1345             return typeid(ComponentType);
1346         }
1347     }
1348 }
1349 
1350 /**
1351 Accessor that accepts ComponentType with it's type erased up to Object or wrapped in a Placeholder!ComponentType if it is
1352 not rooted into Object class (i.e. any value type, and extern c++ objects)
1353 
1354 ImplSpec:
1355     The accessor will accept any object, after which it will attempt to downcast it's original type ComponentType if it is
1356     rooted in Object, otherwise the object will be downcasted to Placeholder!ComponentType. Failing to do so will result in
1357     an exception
1358 **/
1359 class RuntimeCompositeAccessor(ComponentType, FieldType = ComponentType, KeyType = string) : PropertyAccessor!(Object, FieldType, KeyType) {
1360     import aermicioi.aedi_property_reader.core.placeholder : identify, unwrap;
1361     private {
1362         PropertyAccessor!(ComponentType, FieldType, KeyType) accessor_;
1363     }
1364 
1365     public {
1366         /**
1367         Constructor for runtime composite accessor.
1368 
1369         Params:
1370             accessor = underlying accessor used to access downcasted component.
1371         **/
1372         this(PropertyAccessor!(ComponentType, FieldType, KeyType) accessor) {
1373             this.accessor = accessor;
1374         }
1375 
1376         /**
1377         Set accessor
1378 
1379         Params:
1380             accessor = underlying accessor working on unwrapped element
1381 
1382         Returns:
1383             typeof(this)
1384         **/
1385         typeof(this) accessor(PropertyAccessor!(ComponentType, FieldType, KeyType) accessor) @safe nothrow pure {
1386             this.accessor_ = accessor;
1387 
1388             return this;
1389         }
1390 
1391         /**
1392         Get accessor
1393 
1394         Returns:
1395             PropertyAccessor!(ComponentType, FieldType, KeyType)
1396         **/
1397         inout(PropertyAccessor!(ComponentType, FieldType, KeyType)) accessor() @safe nothrow pure inout {
1398             return this.accessor_;
1399         }
1400 
1401         /**
1402         Get a property out of component
1403 
1404         Params:
1405             component = a component which has some properties identified by property.
1406         Throws:
1407             NotFoundException in case when no requested property is available.
1408             InvalidArgumentException in case when passed arguments are somehow invalid for use.
1409         Returns:
1410             FieldType accessed property.
1411         **/
1412         FieldType access(Object component, in KeyType path) const {
1413             enforce!InvalidArgumentException(
1414                 component.identify is typeid(ComponentType),
1415                 text("Invalid component passed ", component.identify, " when expected ", typeid(ComponentType))
1416             );
1417 
1418             enforce!NotFoundException(this.has(component, path), text("Could not find property ", path));
1419 
1420             return this.accessor.access(component.unwrap!ComponentType, path);
1421         }
1422 
1423     /**
1424      Check if requested property is present in component.
1425 
1426      Check if requested property is present in component.
1427      The method could have allocation side effects due to the fact that
1428      it is not restricted in calling access method of the accessor.
1429 
1430      Params:
1431          component = component which is supposed to have property
1432          property = speculated property that is to be tested if it is present in component
1433      Returns:
1434          true if property is in component
1435      **/
1436         bool has(in Object component, in KeyType path) const nothrow {
1437             return (component !is null) &&
1438                 (component.identify is typeid(ComponentType)) &&
1439                 component.unwrap!ComponentType &&
1440                 this.accessor.has(component.unwrap!ComponentType, path);
1441         }
1442 
1443         /**
1444         Identify the type of supported component.
1445 
1446         Identify the type of supported component. It returns type info of component
1447         if it is supported by accessor, otherwise it will return typeid(void) denoting that
1448         the type isn't supported by accessor. The accessor is not limited to returning the type
1449         info of passed component, it can actually return type info of super type or any type
1450         given the returned type is implicitly convertible or castable to ComponentType.
1451 
1452         Params:
1453             component = the component for which accessor should identify the underlying type
1454 
1455         Returns:
1456             TypeInfo type information about passed component, or typeid(void) if component is not supported.
1457         **/
1458         TypeInfo componentType(Object component) const nothrow {
1459             if (component.identify is typeid(ComponentType)) {
1460 
1461                 return this.accessor.componentType(component.unwrap!ComponentType);
1462             }
1463 
1464             return typeid(void);
1465         }
1466     }
1467 }
1468 
1469 /**
1470 An accessor that erases returned property's type.
1471 
1472 ImplSpec:
1473     The accessor will simply upcast any property field to Object if they are rooted in Object class, otherwise
1474     it will allocate a Placeholder!FieldType for returned value and return it erased up to Object class. As such
1475     it is advised to use an allocator that will dispose automatically it's contents once they are not required.
1476     The accessor itself is not responsible for deallocation of placeholders that it returned. As a consequence
1477     no components should point to placeholders allocated by this accessor.
1478 **/
1479 class RuntimeFieldAccessor(ComponentType, FieldType = ComponentType, KeyType = string) :
1480     AllocatingPropertyAccessor!(ComponentType, Object, KeyType) {
1481     import aermicioi.aedi_property_reader.core.placeholder : identify, unwrap;
1482     import std.experimental.allocator;
1483 
1484     mixin AllocatorAwareMixin!(typeof(this));
1485 
1486     private {
1487         PropertyAccessor!(ComponentType, FieldType, KeyType) accessor_;
1488     }
1489 
1490     public {
1491 
1492         /**
1493         Constructor for runtime field accessor.
1494 
1495         Params:
1496             accessor = accessor used to access fields out of component that are to be type erased on return.
1497         **/
1498         this(PropertyAccessor!(ComponentType, FieldType, KeyType) accessor) {
1499             this.allocator = theAllocator;
1500             this.accessor = accessor;
1501         }
1502 
1503         /**
1504         Set accessor
1505 
1506         Params:
1507             accessor = underlying accessor working on unwrapped element
1508 
1509         Returns:
1510             typeof(this)
1511         **/
1512         typeof(this) accessor(PropertyAccessor!(ComponentType, FieldType, KeyType) accessor) @safe nothrow pure {
1513             this.accessor_ = accessor;
1514 
1515             return this;
1516         }
1517 
1518         /**
1519         Get accessor
1520 
1521         Returns:
1522             PropertyAccessor!(ComponentType, FieldType, KeyType)
1523         **/
1524         inout(PropertyAccessor!(ComponentType, FieldType, KeyType)) accessor() @safe nothrow pure inout {
1525             return this.accessor_;
1526         }
1527 
1528     /**
1529      Get a property out of component
1530 
1531      Params:
1532          component = a component which has some properties identified by property.
1533      Throws:
1534          NotFoundException in case when no requested property is available.
1535          InvalidArgumentException in case when passed arguments are somehow invalid for use.
1536      Returns:
1537          FieldType accessed property.
1538      **/
1539         Object access(ComponentType component, in KeyType path) const {
1540             import aermicioi.aedi_property_reader.core.placeholder : placeholder;
1541             enforce!NotFoundException(this.has(component, path), text("Could not find property ", path));
1542 
1543             return this.accessor.access(component, path).placeholder(cast() this.allocator);
1544         }
1545 
1546     /**
1547      Check if requested property is present in component.
1548 
1549      Check if requested property is present in component.
1550      The method could have allocation side effects due to the fact that
1551      it is not restricted in calling access method of the accessor.
1552 
1553      Params:
1554          component = component which is supposed to have property
1555          property = speculated property that is to be tested if it is present in component
1556      Returns:
1557          true if property is in component
1558      **/
1559         bool has(in ComponentType component, in KeyType path) const nothrow {
1560 
1561             return this.accessor.has(component, path);
1562         }
1563 
1564         /**
1565         Identify the type of supported component.
1566 
1567         Identify the type of supported component. It returns type info of component
1568         if it is supported by accessor, otherwise it will return typeid(void) denoting that
1569         the type isn't supported by accessor. The accessor is not limited to returning the type
1570         info of passed component, it can actually return type info of super type or any type
1571         given the returned type is implicitly convertible or castable to ComponentType.
1572 
1573         Params:
1574             component = the component for which accessor should identify the underlying type
1575 
1576         Returns:
1577             TypeInfo type information about passed component, or typeid(void) if component is not supported.
1578         **/
1579         TypeInfo componentType(ComponentType component) const nothrow {
1580             return this.accessor.componentType(component);
1581         }
1582 
1583     }
1584 }
1585 
1586 /**
1587 Accessor that allows access to fields and properties of a component of Type.
1588 
1589 ImplSpec:
1590     The returned field will be erased up to Object class if it is rooted in Object class, or will
1591     be placed into a Placeholder!(field type) that is allocated using an allocator. As such it is advised
1592     that allocator used in the accessor to be disposable after accessing of components was done. As a consequence
1593     no components should point to placeholders allocated by this accessor.
1594 **/
1595 class CompositeAccessor(Type) : AllocatingPropertyAccessor!(Type, Object, string) {
1596     import std.traits;
1597     import std.meta;
1598     import aermicioi.util.traits;
1599     import aermicioi.aedi_property_reader.core.convertor;
1600 
1601     mixin AllocatorAwareMixin!(typeof(this));
1602 
1603     public {
1604         /**
1605         Default constructor for composite accessor.
1606         **/
1607         this() {
1608             import std.experimental.allocator : theAllocator;
1609 
1610             this.allocator = theAllocator;
1611         }
1612 
1613         /**
1614         Get a property out of component
1615 
1616         Params:
1617             component = a component which has some properties identified by property.
1618         Throws:
1619             NotFoundException in case when no requested property is available.
1620             InvalidArgumentException in case when passed arguments are somehow invalid for use.
1621         Returns:
1622             FieldType accessed property.
1623         **/
1624         Object access(Type component, in string property) const {
1625             foreach (string member; __traits(allMembers, Type)) {
1626 
1627                 static if (isPublic!(component, member)) {
1628                     if (member == property) {
1629                         static if (
1630                             isField!(Type, member) ||
1631                             (
1632                                 isSomeFunction!(__traits(getMember, component, member)) &&
1633                                 anySatisfy!(isPropertyGetter, __traits(getOverloads, component, member)))
1634                             ) {
1635 
1636                             return (__traits(getMember, component, member)).placeholder(cast() this.allocator);
1637                         }
1638                     }
1639                 }
1640             }
1641 
1642             throw new NotFoundException(text(typeid(Type), " does not have ", property, " property"));
1643         }
1644 
1645         /**
1646         Check if requested property is present in component.
1647 
1648         Check if requested property is present in component.
1649         The method could have allocation side effects due to the fact that
1650         it is not restricted in calling access method of the accessor.
1651 
1652         Params:
1653             component = component which is supposed to have property
1654             property = speculated property that is to be tested if it is present in component
1655         Returns:
1656             true if property is in component
1657         **/
1658         bool has(in Type component, in string property) const nothrow {
1659 
1660             foreach (string member; __traits(allMembers, Type)) {
1661 
1662                 static if (isPublic!(component, member)) {
1663                     if (member == property) {
1664                         static if (
1665                             isField!(Type, member) ||
1666                             (
1667                                 isSomeFunction!(__traits(getMember, component, member)) &&
1668                                 anySatisfy!(isPropertyGetter, __traits(getOverloads, component, member)))
1669                             ) {
1670 
1671                             return true;
1672                         }
1673                     }
1674                 }
1675             }
1676 
1677             return false;
1678         }
1679 
1680         /**
1681         Identify the type of supported component.
1682 
1683         Identify the type of supported component. It returns type info of component
1684         if it is supported by accessor, otherwise it will return typeid(void) denoting that
1685         the type isn't supported by accessor. The accessor is not limited to returning the type
1686         info of passed component, it can actually return type info of super type or any type
1687         given the returned type is implicitly convertible or castable to ComponentType.
1688 
1689         Params:
1690             component = the component for which accessor should identify the underlying type
1691 
1692         Returns:
1693             TypeInfo type information about passed component, or typeid(void) if component is not supported.
1694         **/
1695         TypeInfo componentType(Type component) const nothrow {
1696             return typeid(Type);
1697         }
1698 
1699     }
1700 }
1701 
1702 /**
1703 Creates a dlang dsl language for accessing properties out of a ComponentType.
1704 
1705 Params:
1706     accessor = accessor used to access property like values
1707     indexer = accessor used to access indexed like values
1708 
1709 Returns:
1710     PropertyPathAccessor!(ComponentType, FieldType, KeyType) with dsl like configuration.
1711 **/
1712 auto dsl(ComponentType, FieldType, KeyType)(PropertyAccessor!(ComponentType, FieldType, KeyType) accessor, PropertyAccessor!(ComponentType, FieldType, KeyType) indexer) {
1713     return new PropertyPathAccessor!(ComponentType, FieldType, KeyType)(
1714         '.',
1715         new AggregatePropertyAccessor!(ComponentType, FieldType, KeyType)(
1716             accessor,
1717             new ArrayIndexedPropertyAccessor!(ComponentType, FieldType, KeyType)(
1718                 '[', ']',
1719                 accessor,
1720                 new AggregatePropertyAccessor!(ComponentType, FieldType, KeyType)(
1721                     new TickedPropertyAccessor!(ComponentType, FieldType, KeyType)(
1722                         '\'',
1723                         accessor,
1724                     ),
1725                     new TickedPropertyAccessor!(ComponentType, FieldType, KeyType)(
1726                         '"',
1727                         accessor,
1728                     ),
1729                     indexer
1730                 )
1731             )
1732         )
1733     );
1734 }