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