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 }