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 }