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.inspector;
31 
32 import std.traits;
33 import std.meta;
34 import std.exception;
35 import std.conv;
36 import aermicioi.aedi.util.traits : isPropertyGetter, isPropertySetter, isPublic, isField;
37 import aermicioi.aedi : NotFoundException;
38 import aermicioi.aedi_property_reader.convertor.placeholder : unwrap, identify;
39 import aermicioi.aedi_property_reader.convertor.traits : isD;
40 import taggedalgebraic;
41 
42 /**
43 Provides type and field information of a composite component at runtime.
44 **/
45 interface Inspector(ComponentType, KeyType = string) {
46 
47     /**
48     Identify the type of child field of component.
49 
50     Params:
51         component = a composite component (class, struct, assoc array etc.) containing some fields
52 
53     Returns:
54         Type of field, or typeid(void) if field is not present in component
55     **/
56     TypeInfo typeOf(ComponentType component, in KeyType property) const nothrow;
57 
58     /**
59     Identify the type of component itself.
60 
61     Identify the type of component itself. It will inspect the component and will return accurate
62     type info that the component represents.
63 
64     Params:
65         component = component which should be identified.
66 
67     Returns:
68         Type info of component, or typeid(void) if component cannot be identified by inspector
69     **/
70     TypeInfo typeOf(ComponentType component) const nothrow;
71 
72     /**
73     Check if component has a field or a property.
74 
75     Params:
76         component = component with fields
77         property = component property that is tested for existence
78 
79     Returns:
80         true if field is present either in readonly, or writeonly form (has getters and setters).
81     **/
82     bool has(ComponentType component, in KeyType property) const nothrow;
83 
84     /**
85     Return a list of properties that component holds.
86 
87     Params:
88         component = the component with fields
89 
90     Returns:
91         an arary of property identities.
92     **/
93     KeyType[] properties(ComponentType component) const nothrow;
94 }
95 
96 /**
97 Associative array inspector.
98 **/
99 class AssociativeArrayInspector(ValueType, KeyType = ValueType) : Inspector!(ValueType[KeyType], KeyType) {
100 
101     /**
102     Identify the type of child field of component.
103 
104     Params:
105         component = a composite component (class, struct, assoc array etc.) containing some fields
106 
107     Returns:
108         Type of field, or typeid(void) if field is not present in component
109     **/
110     TypeInfo typeOf(ValueType[KeyType] component, in KeyType property) const nothrow {
111 
112         return typeid(ValueType);
113     }
114 
115     /**
116     Identify the type of component itself.
117 
118     Identify the type of component itself. It will inspect the component and will return accurate
119     type info that the component represents.
120 
121     Params:
122         component = component which should be identified.
123 
124     Returns:
125         Type info of component, or typeid(void) if component cannot be identified by inspector
126     **/
127     TypeInfo typeOf(ValueType[KeyType] component) const nothrow {
128         return typeid(ValueType[KeyType]);
129     }
130 
131     /**
132     Check if component has a field or a property.
133 
134     Params:
135         component = component with fields
136         property = component property that is tested for existence
137 
138     Returns:
139         true if field is present either in readonly, or writeonly form (has getters and setters).
140     **/
141     bool has(ValueType[KeyType] component, in KeyType property) const nothrow {
142         return (property in component) !is null;
143     }
144 
145     /**
146     Return a list of properties that component holds.
147 
148     Params:
149         component = the component with fields
150 
151     Returns:
152         an arary of property identities.
153     **/
154     KeyType[] properties(ValueType[KeyType] component) const nothrow {
155         import std.array : array;
156 
157         return component.byKey.array;
158     }
159 }
160 
161 /**
162 Array inspector
163 **/
164 class ArrayInspector(ComponentType) : Inspector!(ComponentType[], size_t) {
165 
166     /**
167     Identify the type of child field of component.
168 
169     Params:
170         component = a composite component (class, struct, assoc array etc.) containing some fields
171 
172     Returns:
173         Type of field, or typeid(void) if field is not present in component
174     **/
175     TypeInfo typeOf(ComponentType[] component, in size_t property) const nothrow {
176         if (property < component.length) {
177 
178             return typeid(ComponentType);
179         }
180 
181         return typeid(void);
182     }
183 
184     /**
185     Identify the type of component itself.
186 
187     Identify the type of component itself. It will inspect the component and will return accurate
188     type info that the component represents.
189 
190     Params:
191         component = component which should be identified.
192 
193     Returns:
194         Type info of component, or typeid(void) if component cannot be identified by inspector
195     **/
196     TypeInfo typeOf(ComponentType[] component) const nothrow {
197         return typeid(ComponentType[]);
198     }
199 
200     /**
201     Check if component has a field or a property.
202 
203     Params:
204         component = component with fields
205         property = component property that is tested for existence
206 
207     Returns:
208         true if field is present either in readonly, or writeonly form (has getters and setters).
209     **/
210     bool has(ComponentType[] component, in size_t property) const nothrow {
211         return component.length > property;
212     }
213 
214     /**
215     Return a list of properties that component holds.
216 
217     Params:
218         component = the component with fields
219 
220     Returns:
221         an arary of property identities.
222     **/
223     size_t[] properties(ComponentType[] component) const nothrow {
224         import std.array : array;
225         import std.range : iota;
226 
227         return iota(component.length).array;
228     }
229 }
230 
231 /**
232 An inspector that is able to inspect tagged types, and provide insights for them.
233 **/
234 class TaggedInspector(Tagged : TaggedAlgebraic!(Union), Type, Union) : Inspector!Tagged
235     if (anySatisfy!(ApplyRight!(isD, Type), Fields!Union)) {
236     import std.experimental.logger : error;
237     import aermicioi.aedi_property_reader.convertor.traits : n;
238 
239     private {
240         Inspector!Type inspector_;
241     }
242 
243     /**
244     Constructs TaggedInspector
245 
246     Params:
247         inspector = underlying inspector that is able to inspect just one subtype of tagged type
248     **/
249     this(Inspector!Type inspector) {
250         this.inspector = inspector;
251     }
252 
253     @property {
254         /**
255         Set inspector
256 
257         Params:
258             inspector = inspector used to inspect underlying tagged value
259 
260         Returns:
261             typeof(this)
262         **/
263         typeof(this) inspector(Inspector!Type inspector) @safe nothrow pure {
264             this.inspector_ = inspector;
265 
266             return this;
267         }
268 
269         /**
270         Get inspector
271 
272         Returns:
273             Inspector!Type
274         **/
275         inout(Inspector!Type) inspector() @safe nothrow pure inout {
276             return this.inspector_;
277         }
278     }
279 
280     /**
281     Identify the type of child field of component.
282 
283     Params:
284         component = a composite component (class, struct, assoc array etc.) containing some fields
285 
286     Returns:
287         Type of field, or typeid(void) if field is not present in component
288     **/
289     TypeInfo typeOf(Tagged component, in string property) const nothrow {
290         try {
291 
292             import std.meta : staticMap;
293             import aermicioi.aedi.util.traits.traits : identifier;
294 
295             static foreach (e; staticMap!(identifier, EnumMembers!(Tagged.Kind))) {
296                 static if (mixin("is(typeof(Union." ~ e ~ ") : Type)")) {
297                     if (mixin("component.Kind." ~ e ~ " == component.kind")) {
298 
299                         return typeid(Tagged);
300                     }
301 
302                     debug(trace) error(
303                         "Could not identify tagged algebraic's ",
304                         typeid(component),
305                         " inner type, returning void."
306                     ).n;
307 
308                     return typeid(void);
309                 }
310             }
311 
312         } catch (Exception e) {
313             debug(trace) error("Failed to unwrap tagged component ", component, " due to ", e, " returning void").n;
314         }
315 
316         // return typeid(void);
317     }
318 
319     /**
320     Identify the type of component itself.
321 
322     Identify the type of component itself. It will inspect the component and will return accurate
323     type info that the component represents.
324 
325     Params:
326         component = component which should be identified.
327 
328     Returns:
329         Type info of component, or typeid(void) if component cannot be identified by inspector
330     **/
331     TypeInfo typeOf(Tagged component) const nothrow {
332         try {
333 
334             import std.meta : staticMap;
335             import aermicioi.aedi.util.traits.traits : identifier;
336 
337             static foreach (e; staticMap!(identifier, EnumMembers!(Tagged.Kind))) {
338                 static if (mixin("is(typeof(Union." ~ e ~ ") : Type)")) {
339                     if (mixin("component.Kind." ~ e ~ " == component.kind")) {
340 
341                         return typeid(Tagged);
342                     }
343 
344                     return typeid(void);
345                 }
346             }
347 
348         } catch (Exception e) {
349             debug(trace) error("Failed to unwrap tagged component ", component, " due to ", e).n;
350         }
351 
352         // return typeid(void);
353     }
354 
355     /**
356     Check if component has a field or a property.
357 
358     Params:
359         component = component with fields
360         property = component property that is tested for existence
361 
362     Returns:
363         true if field is present either in readonly, or writeonly form (has getters and setters).
364     **/
365     bool has(Tagged component, in string property) const nothrow {
366         try {
367 
368             import std.meta : staticMap;
369             import aermicioi.aedi.util.traits.traits : identifier;
370 
371             static foreach (e; staticMap!(identifier, EnumMembers!(Tagged.Kind))) {
372                 static if (mixin("is(typeof(Union." ~ e ~ ") : Type)")) {
373                     if (mixin("component.Kind." ~ e ~ " == component.kind")) {
374 
375                         return this.inspector.has(cast(Type) component, property);
376                     }
377 
378                     return false;
379                 }
380             }
381 
382         } catch (Exception e) {
383             debug(trace) error("Failed to unwrap tagged component ", component, " due to ", e).n;
384         }
385 
386         return false;
387     }
388 
389     /**
390     Return a list of properties that component holds.
391 
392     Params:
393         component = the component with fields
394 
395     Returns:
396         an arary of property identities.
397     **/
398     string[] properties(Tagged component) const nothrow {
399         try {
400 
401             import std.meta : staticMap;
402             import aermicioi.aedi.util.traits.traits : identifier;
403 
404             static foreach (e; staticMap!(identifier, EnumMembers!(Tagged.Kind))) {
405                 static if (mixin("is(typeof(Union." ~ e ~ ") : Type)")) {
406                     if (mixin("component.Kind." ~ e ~ " == component.kind")) {
407 
408                         return this.inspector.properties(cast(Type) component);
409                     }
410                 }
411             }
412 
413         } catch (Exception e) {
414             debug(trace) error("Failed to unwrap tagged component ", component, " due to ", e).n;
415         }
416 
417         return [];
418     }
419 }
420 
421 /**
422 Inspector for composite components (structs, objects, unions, etc.).
423 **/
424 class CompositeInspector(ComponentType) : Inspector!(ComponentType, string)
425     if (isAggregateType!ComponentType) {
426 
427     /**
428     Identify the type of child field of component.
429 
430     Params:
431         component = a composite component (class, struct, assoc array etc.) containing some fields
432 
433     Returns:
434         Type of field, or typeid(void) if field is not present in component
435     **/
436     TypeInfo typeOf(ComponentType component, in string property) const nothrow {
437         if (this.has(component, property)) {
438             static foreach (member; __traits(allMembers, ComponentType)) {
439                 static if (isPublic!(ComponentType, member)) {{
440                     alias m = Alias!(__traits(getMember, component, member));
441                     if (member == property) {
442                         static if (
443                             isField!(ComponentType, member) ||
444                             (
445                                 isSomeFunction!m &&
446                                 anySatisfy!(isPropertyGetter, __traits(getOverloads, component, member))
447                             )
448                         ) {
449 
450                             return typeid(typeof(__traits(getMember, component, member)));
451                         }
452                     }
453                 }}
454             }
455         }
456 
457 
458         return typeid(void);
459     }
460 
461     /**
462     Identify the type of component itself.
463 
464     Identify the type of component itself. It will inspect the component and will return accurate
465     type info that the component represents.
466 
467     Params:
468         component = component which should be identified.
469 
470     Returns:
471         Type info of component, or typeid(void) if component cannot be identified by inspector
472     **/
473     TypeInfo typeOf(ComponentType component) const nothrow {
474         return typeid(ComponentType);
475     }
476 
477     /**
478     Check if component has a field or a property.
479 
480     Params:
481         component = component with fields
482         property = component property that is tested for existence
483 
484     Returns:
485         true if field is present either in readonly, or writeonly form (has getters and setters).
486     **/
487     bool has(ComponentType component, in string property) const nothrow {
488         import std.algorithm : canFind;
489         return this.properties(component).canFind(property);
490     }
491 
492     /**
493     Return a list of properties that component holds.
494 
495     Params:
496         component = the component with fields
497 
498     Returns:
499         an arary of property identities.
500     **/
501     string[] properties(ComponentType component) const nothrow {
502         string[] props;
503 
504         static foreach (member; __traits(allMembers, ComponentType)) {
505             static if (isPublic!(ComponentType, member)) {{
506                 static if (
507                     isField!(ComponentType, member) ||
508                     (isSomeFunction!(__traits(getMember, component, member)) && (
509                         anySatisfy!(isPropertyGetter, __traits(getOverloads, component, member)) ||
510                         anySatisfy!(isPropertySetter, __traits(getOverloads, component, member))
511 
512                     ))) {
513                     props ~= member;
514                 }
515             }}
516         }
517 
518         return props;
519     }
520 }
521 
522 /**
523 An inspector that accepts a component with erased type for inspecting.
524 
525 An inspector that accepts a component with erased type for inspecting. It will
526 attempt to downcast the component to rightful type, if Type is rooted into Object
527 or it will attempt to downcast to object implementing Placeholder!Type interface
528 that effectively is storage for inspected component.
529 **/
530 class RuntimeInspector(Type, KeyType = string) : Inspector!(Object, KeyType) {
531     private {
532         Inspector!(Type, KeyType) inspector_;
533     }
534 
535     public {
536 
537         /**
538         Constructor for RuntimeInspector
539 
540         Params:
541             inspector = underlying inspector that works with unwrapped type
542         **/
543         this(Inspector!(Type, KeyType) inspector) {
544             this.inspector = inspector;
545         }
546 
547         @property {
548             /**
549             Set inspector
550 
551             Params:
552                 inspector = underlying inspector used to extract data
553 
554             Returns:
555                 typeof(this)
556             **/
557             typeof(this) inspector(Inspector!(Type, KeyType) inspector) @safe nothrow pure {
558                 this.inspector_ = inspector;
559 
560                 return this;
561             }
562 
563             /**
564             Get inspector
565 
566             Returns:
567                 Inspector!Type
568             **/
569             inout(Inspector!(Type, KeyType)) inspector() @safe nothrow pure inout {
570                 return this.inspector_;
571             }
572         }
573 
574         /**
575         Identify the type of child field of component.
576 
577         Params:
578             component = a composite component (class, struct, assoc array etc.) containing some fields
579 
580         Returns:
581             Type of field, or typeid(void) if field is not present in component
582         **/
583         TypeInfo typeOf(Object wrapped, in KeyType property) const nothrow {
584             if (wrapped.identify is typeid(Type)) {
585 
586                 return this.inspector.typeOf(wrapped.unwrap!Type, property);
587             }
588 
589             return typeid(void);
590         }
591 
592         /**
593         Identify the type of component itself.
594 
595         Identify the type of component itself. It will inspect the component and will return accurate
596         type info that the component represents.
597 
598         Params:
599             component = component which should be identified.
600 
601         Returns:
602             Type info of component, or typeid(void) if component cannot be identified by inspector
603         **/
604         TypeInfo typeOf(Object component) const nothrow {
605             if (component.identify is typeid(Type)) {
606 
607                 return this.inspector.typeOf(component.unwrap!Type);
608             }
609 
610             return typeid(void);
611         }
612 
613         /**
614         Guess the D type of serialized based on information available in it.
615 
616         Params:
617             serialized = the component for which guesser will attempt to guess the type.
618 
619         Returns:
620             TypeInfo guessed type
621         **/
622         TypeInfo guess(Object serialized) const {
623             TypeInfo type = this.typeOf(serialized);
624 
625             if (type is typeid(void)) {
626                 return serialized.classinfo;
627             }
628 
629             return type;
630         }
631 
632         /**
633         Check if component has a field or a property.
634 
635         Params:
636             component = component with fields
637             property = component property that is tested for existence
638 
639         Returns:
640             true if field is present either in readonly, or writeonly form (has getters and setters).
641         **/
642         bool has(Object wrapped, in KeyType property) const nothrow {
643             return this.inspector.has(wrapped.unwrap!Type, property);
644         }
645 
646         /**
647         Return a list of properties that component holds.
648 
649         Params:
650             component = the component with fields
651 
652         Returns:
653             an arary of property identities.
654         **/
655         KeyType[] properties(Object wrapped) const nothrow {
656             return this.inspector.properties(wrapped.unwrap!Type);
657         }
658     }
659 }
660 
661 /**
662 Provides type and field information of a composite component at runtime.
663 **/
664 class CombinedInspector(ComponentType, KeyType = string) : Inspector!(ComponentType, KeyType) {
665 
666     private {
667         Inspector!(ComponentType, KeyType) inspectors_;
668     }
669 
670     /**
671     Set inspectors
672 
673     Params:
674         inspectors = list of inspectors used to inspect the type
675 
676     Returns:
677         typeof(this)
678     **/
679     typeof(this) inspectors(Inspector!(ComponentType, KeyType)[] inspectors) @safe nothrow pure {
680         this.inspectors_ = inspectors;
681 
682         return this;
683     }
684 
685     /**
686     Set inspectors
687 
688     Params:
689         inspectors = list of inspectors used to inspect the type
690 
691     Returns:
692         typeof(this)
693     **/
694     typeof(this) inspectors(Inspector!(ComponentType, KeyType) inspectors...) @safe nothrow pure {
695         this.inspectors_ = inspectors.dup;
696 
697         return this;
698     }
699 
700     /**
701     Get inspectors
702 
703     Returns:
704         Inspector!(ComponentType, KeyType)[]
705     **/
706     inout(Inspector!(ComponentType, KeyType)) inspectors() @safe nothrow pure inout {
707         return this.inspectors_;
708     }
709 
710     /**
711     Identify the type of child field of component.
712 
713     Params:
714         component = a composite component (class, struct, assoc array etc.) containing some fields
715 
716     Returns:
717         Type of field, or typeid(void) if field is not present in component
718     **/
719     TypeInfo typeOf(ComponentType component, in KeyType property) const nothrow {
720         foreach (inspector; this.inspectors) {
721             TypeInfo type = inspector.typeOf(component, property);
722 
723             if (type !is typeid(void)) {
724                 return type;
725             }
726         }
727 
728         return typeid(void);
729     }
730 
731     /**
732     Identify the type of component itself.
733 
734     Identify the type of component itself. It will inspect the component and will return accurate
735     type info that the component represents.
736 
737     Params:
738         component = component which should be identified.
739 
740     Returns:
741         Type info of component, or typeid(void) if component cannot be identified by inspector
742     **/
743     TypeInfo typeOf(ComponentType component) const nothrow {
744         foreach (inspector; this.inspectors) {
745             TypeInfo type = inspector.typeOf(component);
746 
747             if (type !is typeid(void)) {
748                 return type;
749             }
750         }
751 
752         return typeid(void);
753     }
754 
755     /**
756     Check if component has a field or a property.
757 
758     Params:
759         component = component with fields
760         property = component property that is tested for existence
761 
762     Returns:
763         true if field is present either in readonly, or writeonly form (has getters and setters).
764     **/
765     bool has(ComponentType component, in KeyType property) const nothrow {
766         foreach (inspector; this.inspectors) {
767             if (inspector.has(component, property)) {
768                 return true;
769             }
770         }
771 
772         return false;
773     }
774 
775     /**
776     Return a list of properties that component holds.
777 
778     Params:
779         component = the component with fields
780 
781     Returns:
782         an arary of property identities.
783     **/
784     KeyType[] properties(ComponentType component) const nothrow {
785         return this.inspectors.map!(i => i.properties).joiner.array;
786     }
787 }