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 aermicioi 29 **/ 30 module aermicioi.aedi_property_reader.core.core; 31 32 import aermicioi.aedi; 33 import aermicioi.aedi : load = configure; 34 import aermicioi.aedi.storage.storage; 35 import aermicioi.aedi.storage.locator; 36 import aermicioi.aedi_property_reader.convertor.convertor; 37 import aermicioi.aedi_property_reader.convertor.chaining_convertor; 38 import aermicioi.aedi_property_reader.convertor.type_guesser; 39 import aermicioi.aedi_property_reader.convertor.mapper; 40 import aermicioi.aedi_property_reader.core.document : DocumentLocator; 41 import std.meta; 42 import std.traits; 43 import std.experimental.logger; 44 import std.experimental.allocator; 45 import std.exception; 46 47 /** 48 Configuration context for a property 49 **/ 50 struct PropertyContext(Type, Context) { 51 string path; 52 Context context; 53 } 54 55 /** 56 Configuration context that is providing a nice api to add convertors into a container for a document. 57 **/ 58 struct DocumentContainerBuilder(PolicyDocument : Marker!(DocumentTypeArg, FieldTypeArg, PoliciesArg), alias Marker, DocumentTypeArg, FieldTypeArg, PoliciesArg...) { 59 60 alias Policies = PoliciesArg; 61 alias FieldType = FieldTypeArg; 62 alias DocumentType = DocumentTypeArg; 63 64 Convertor[] convertors; 65 66 /** 67 Storage for common document components 68 **/ 69 Storage!(ObjectFactory, string) storage; 70 71 /** 72 Locator useable in registering properties. 73 **/ 74 AggregateContainer container; 75 76 /** 77 Root document from which components will be extracted 78 **/ 79 DocumentType document; 80 81 /** 82 Allocator used to construct and destruct components. 83 **/ 84 RCIAllocator allocator; 85 86 /** 87 Constructor for DocumentContainerBuilder 88 89 Params: 90 container = to configure 91 **/ 92 this(PolicyDocument policyDocument, RCIAllocator allocator = theAllocator) { 93 auto storage = singleton.typed.deffered.gcRegistered; 94 this.allocator = allocator; 95 96 this.container = aggregate( 97 storage, "singleton" 98 ); 99 100 this.storage = storage; 101 this.document = policyDocument.document; 102 103 with(storage.load(container)) { 104 register(this.document); 105 } 106 107 static foreach (Policy; Policies) { 108 Policy.initialize(this); 109 } 110 } 111 112 /** 113 ditto 114 **/ 115 alias document this; 116 117 /** 118 Associate a convertor to a field in document that has property path, to be used for conversion. 119 120 Params: 121 To = the type of destination component that convertor should yield 122 path = property path for a field in document. 123 **/ 124 PropertyContext!(To, typeof(this)) register(To, From)(string path) { 125 126 static foreach (Policy; Policies) { 127 Policy.register!(To, From)(this, path); 128 } 129 130 return PropertyContext!(To, typeof(this))(path, this); 131 } 132 133 /** 134 ditto 135 **/ 136 PropertyContext!(To, typeof(this)) register(To)(string path) { 137 138 return register!(To, FieldType)(path); 139 } 140 141 /** 142 Associate a convertor to a type to be used by document when no specific convertor is provided for document field. 143 144 Params: 145 To = the type of destination component that convertor should yield 146 **/ 147 PropertyContext!(To, typeof(this)) register(To)() { 148 149 return this.register!(To, To); 150 } 151 152 /** 153 Associate a convertor to a type for an interface to be used by document when no specific convertor is provided for document field. 154 155 Params: 156 Iface = interface for which the convertor is bound to. 157 To = the type of destination component that convertor should yield 158 **/ 159 PropertyContext!(To, typeof(this)) register(Iface, To : Iface)() { 160 import std.traits : fullyQualifiedName; 161 162 return this.register!To(fullyQualifiedName!Iface); 163 } 164 165 /** 166 Register convertor for a property on path. 167 168 Params: 169 convertor = convertor used to convert value found on path 170 path = property path for which convertor could be used 171 172 Returns: 173 typeof(this) 174 **/ 175 PropertyContext!(void, typeof(this)) register(Convertor convertor, string path, const TypeInfo to) { 176 import aermicioi.aedi_property_reader.convertor.accessor; 177 import aermicioi.aedi_property_reader.convertor.placeholder : pack; 178 179 static foreach (Policy; Policies) { 180 Policy.register(this, convertor, path, to); 181 } 182 183 with (storage.load(container)) { 184 register!Object(path) 185 .factoryMethod( 186 function Object ( 187 RCIAllocator allocator, 188 DocumentType root, 189 PropertyAccessor!(DocumentType, Object) accessor, 190 Convertor convertor, 191 const TypeInfo to, 192 string path 193 ) { 194 import aermicioi.aedi_property_reader.convertor.placeholder : stored; 195 return convertor.convert(accessor.access(root, path), to, allocator); 196 }, 197 this.allocator, 198 lref!DocumentType, 199 lref!(PropertyAccessor!(DocumentType, Object)), 200 convertor, 201 to, 202 path 203 ) 204 .destructor((RCIAllocator allocator, ref Object component, Convertor convertor) => convertor.destruct(null, component, allocator), convertor); 205 } 206 207 convertors ~= convertor; 208 209 return PropertyContext!(void, typeof(this))(path, this); 210 } 211 212 alias property = register; 213 } 214 215 /** 216 Take a document container and create a configuration context for it. 217 218 Params: 219 container = container for which configuration context is created. 220 locator = locator used in registration and construction of properties. 221 storage = storage for document elements. 222 223 Returns: 224 DocumentContainerBuilder(DocumentContainer, AdvisedConvertor, DocumentType, FieldType) a configuration context 225 **/ 226 auto configure(PolicyDocument)(PolicyDocument document, RCIAllocator allocator = theAllocator) { 227 228 return DocumentContainerBuilder!PolicyDocument(document, allocator); 229 } 230 231 /** 232 Register a description for property. 233 234 See: 235 Aedi framework $(D_INLINECODE .describe) component configuration method. 236 **/ 237 Context describe(Context : PropertyContext!(To, RegistrationContext), RegistrationContext, To)(Context context, string title, string description) 238 in(context.context.container !is null, "Component locator is missing. It is required for proper functioning") { 239 import aermicioi.aedi : IdentityDescriber, locate; 240 context.container.locate!(IdentityDescriber!())().register(context.path, title, description); 241 242 return context; 243 } 244 245 /** 246 Register a default value for property. 247 248 Params: 249 context = property context 250 value = default value for property 251 252 Returns: 253 Configuration context 254 **/ 255 Context optional(Context : PropertyContext!(To, RegistrationContext), RegistrationContext, To)(Context context, auto ref To value) 256 in(context.context.container !is null, "Component locator is missing. It is required for fetching default values storage.") { 257 import aermicioi.aedi : ValueContainer, locate, configure; 258 259 with (context.container.locate!ValueContainer.configure) { 260 register(value, context.path); 261 } 262 263 return context; 264 } 265 266 /** 267 Policy that adds information of types to which document container should already have Convertors. 268 **/ 269 struct WithConvertorsFor(ConvertibleTypes...) { 270 271 void initialize(Context)(ref Context context) { 272 static foreach (Type; ConvertibleTypes) { 273 context.register!(Type, Type); 274 } 275 } 276 277 void register(To, From, Context)(ref Context context, string path) {} 278 279 void register(Context)(ref Context context, Convertor convertor, string path, const TypeInfo to) {} 280 } 281 282 /** 283 Policy that adds convertors for additional source/origin types for registered one. 284 **/ 285 struct WithConvertorSources(SourceTypes...) { 286 287 void initialize(Context)(ref Context context) {} 288 289 void register(To, From, Context)(ref Context context, string path) { 290 static foreach (OtherOriginType; SourceTypes) { 291 static if (is(From == OtherOriginType)) { 292 enum checked = true; 293 } 294 } 295 296 static if (!is(typeof(checked))) { 297 298 static foreach (OtherOriginType; SourceTypes) { 299 300 context.register!(To, OtherOriginType)(path); 301 } 302 } 303 } 304 305 void register(Context)(ref Context context, Convertor convertor, string path, const TypeInfo to) {} 306 } 307 308 /** 309 Marker interface that runs initializer of configuration context. 310 311 Params: 312 Initializer = an initializer of context. 313 **/ 314 struct WithInitializer(alias Initializer) { 315 316 void initialize(Context)(ref Context context) { 317 import aermicioi.aedi.configurer.annotation.component_scan : prepare; 318 319 Parameters!Initializer params; 320 prepare!Initializer(context.container, params); 321 322 Initializer(params); 323 } 324 325 void register(To, From, Context)(ref Context context, string path) {} 326 327 void register(Context)(ref Context context, Convertor convertor, string path, const TypeInfo to) {} 328 } 329 330 /** 331 Marker interface that provides a list of components or modules to scan for components to populate convertor container. 332 **/ 333 struct WithContainerScanning(Locations...) { 334 335 void initialize(Context)(ref Context context) { 336 static foreach (Location; Locations) { 337 context.storage.scan!Location(context.container); 338 } 339 } 340 341 void register(To, From, Context)(ref Context context, string path) {} 342 343 void register(Context)(ref Context context, Convertor convertor, string path, const TypeInfo to) {} 344 } 345 346 /** 347 Policy that will populate first available combined convertor in container with all registered types. 348 **/ 349 struct WithConvertorAggregation(ConvertorType) { 350 void initialize(Context)(ref Context context) { 351 context.convertors ~= context.container.locate!ConvertorType; 352 } 353 354 void register(To, From, Context)(ref Context context, string path) {} 355 356 void register(Context)(ref Context context, Convertor convertor, string path, const TypeInfo to) { 357 ConvertorType combined = context.container.locate!ConvertorType; 358 359 if (combined != convertor) { 360 combined.add(convertor); 361 } 362 } 363 } 364 365 /** 366 Policy that adds a locator of unregistered components yet available in root document. 367 368 Since type of unregistered components is not available, document locator will try to infer 369 it using type guesser. 370 **/ 371 struct WithLocatorForUnregisteredComponents { 372 string identity; 373 374 void initialize(Context)(ref Context context) { 375 auto locator = new DocumentLocator!(Context.DocumentType)(context.document, context.allocator); 376 locator.guesser = context.container.locate!(typeof(locator.guesser)); 377 locator.accessor = context.container.locate!(typeof(locator.accessor)); 378 locator.convertor = context.container.locate!(typeof(locator.convertor)); 379 380 context.container.set(locator, identity); 381 } 382 383 void register(To, From, Context)(ref Context context, string path) {} 384 385 void register(Context)(ref Context context, Convertor convertor, string path, const TypeInfo to) {} 386 } 387 388 struct WithConversionToCompositeConvertor { 389 390 void initialize(Context)(ref Context context) {} 391 392 void register(To, From, Context)(ref Context context, string path) { 393 import std.traits : isAggregateType; 394 static if (isAggregateType!To) { 395 if (context.container.locate!(Locator!())("singleton").has(path)) { 396 debug(trace) trace("Container has already registered convertor for ", path, " skipping registration"); 397 return; 398 } 399 400 if (context.container.locate!(Locator!())("singleton").has(typeid(CompositeConvertor!(To, From)).toString)) { 401 debug(trace) trace("Composite convertor for type ", typeid(To), " is already registered."); 402 403 } else { 404 debug(trace) trace("Registering convertor for composite type ", typeid(To)); 405 406 with (context.container.load("singleton")) { 407 register!(CompositeMapper!(To, From)).scan; 408 register!(CompositeConvertor!(To, From)).scan; 409 } 410 } 411 412 context.register(context.container.locate!(CompositeConvertor!(To, From)), path, typeid(To)); 413 } 414 } 415 416 void register(Context)(ref Context context, Convertor convertor, string path, const TypeInfo to) {} 417 } 418 419 struct WithCompositeAccessor { 420 421 void initialize(Context)(ref Context context) {} 422 423 void register(To, From, Context)(ref Context context, string path) { 424 import std.traits : isAggregateType; 425 import aermicioi.aedi_property_reader.convertor.accessor : CompositeAccessor; 426 static if (isAggregateType!To) { 427 debug(trace) trace("Registering composite accessor for type ", typeid(To)); 428 429 with (context.container.load("singleton")) { 430 register!(CompositeAccessor!To).scan; 431 } 432 } 433 } 434 435 void register(Context)(ref Context context, Convertor convertor, string path, const TypeInfo to) {} 436 } 437 438 struct WithCompositeInspector { 439 440 void initialize(Context)(ref Context context) {} 441 442 void register(To, From, Context)(ref Context context, string path) { 443 import std.traits : isAggregateType; 444 import aermicioi.aedi_property_reader.convertor.inspector : CompositeInspector; 445 static if (isAggregateType!To) { 446 debug(trace) trace("Registering composite inspector for type ", typeid(To)); 447 448 with (context.container.load("singleton")) { 449 register!(CompositeInspector!To).scan; 450 } 451 } 452 } 453 454 void register(Context)(ref Context context, Convertor convertor, string path, const TypeInfo to) {} 455 } 456 457 struct WithCompositeSetter { 458 459 void initialize(Context)(ref Context context) {} 460 461 void register(To, From, Context)(ref Context context, string path) { 462 import std.traits : isAggregateType; 463 import aermicioi.aedi_property_reader.convertor.setter : CompositeSetter; 464 static if (isAggregateType!To) { 465 debug(trace) trace("Registering composite setter for type ", typeid(To)); 466 467 with (context.container.load("singleton")) { 468 register!(CompositeSetter!To).scan; 469 } 470 } 471 } 472 473 void register(Context)(ref Context context, Convertor convertor, string path, const TypeInfo to) {} 474 } 475 476 struct WithRangeToRangeConvertor { 477 478 void initialize(Context)(ref Context context) {} 479 480 void register(To, From, Context)(ref Context context, string path) { 481 static if (is(RangeConvertor!(To, From))) { 482 if (context.container.locate!(Locator!())("singleton").has(path)) { 483 debug(trace) trace("Container has already registered convertor for ", path, " skipping registration"); 484 return; 485 } 486 487 if (context.container.locate!(Locator!())("singleton").has(typeid(RangeConvertor!(To, From)).toString)) { 488 debug(trace) trace("Range convertor for type ", typeid(To), " is already registered."); 489 490 } else { 491 debug(trace) trace("Registering range convertor for type ", typeid(To)); 492 493 with (context.container.load("singleton")) { 494 register!(RangeConvertor!(To, From)).scan; 495 } 496 } 497 498 context.register(context.container.locate!(RangeConvertor!(To, From)), path, typeid(To)); 499 } 500 } 501 502 void register(Context)(ref Context context, Convertor convertor, string path, const TypeInfo to) {} 503 } 504 505 struct WithRangeToArrayConvertor { 506 507 void initialize(Context)(ref Context context) {} 508 509 void register(To, From, Context)(ref Context context, string path) { 510 static if (is(RangeToArrayConvertor!(To, From))) { 511 if (context.container.locate!(Locator!())("singleton").has(path)) { 512 debug(trace) trace("Container has already registered convertor for ", path, " skipping registration"); 513 return; 514 } 515 516 if (context.container.locate!(Locator!())("singleton").has(typeid(RangeToArrayConvertor!(To, From)).toString)) { 517 debug(trace) trace("Array convertor for type ", typeid(To), " is already registered."); 518 519 } else { 520 debug(trace) trace("Registering array convertor for type ", typeid(To)); 521 522 with (context.container.load("singleton")) { 523 register!(RangeToArrayConvertor!(To, From)).scan; 524 } 525 } 526 527 context.register(context.container.locate!(RangeToArrayConvertor!(To, From)), path, typeid(To)); 528 } 529 } 530 531 void register(Context)(ref Context context, Convertor convertor, string path, const TypeInfo to) {} 532 } 533 534 struct WithMapToMapConvertor { 535 536 void initialize(Context)(ref Context context) {} 537 538 void register(To, From, Context)(ref Context context, string path) { 539 static if (is(MapConvertor!(To, From))) { 540 if (context.container.locate!(Locator!())("singleton").has(path)) { 541 debug(trace) trace("Container has already registered convertor for ", path, " skipping registration"); 542 return; 543 } 544 545 if (context.container.locate!(Locator!())("singleton").has(typeid(MapConvertor!(To, From)).toString)) { 546 debug(trace) trace("Array convertor for type ", typeid(To), " is already registered."); 547 548 } else { 549 debug(trace) trace("Registering array convertor for type ", typeid(To)); 550 551 with (context.container.load("singleton")) { 552 register!(MapConvertor!(To, From)).scan; 553 } 554 } 555 556 context.register(context.container.locate!(MapConvertor!(To, From)), path, typeid(To)); 557 } 558 } 559 560 void register(Context)(ref Context context, Convertor convertor, string path, const TypeInfo to) {} 561 } 562 563 struct WithVariantConvertor { 564 565 void initialize(Context)(ref Context context) {} 566 567 void register(To, From, Context)(ref Context context, string path) { 568 static if (is(VariantConvertor!To)) { 569 if (context.container.locate!(Locator!())("singleton").has(path)) { 570 debug(trace) trace("Container has already registered convertor for ", path, " skipping registration"); 571 return; 572 } 573 574 if (context.container.locate!(Locator!())("singleton").has(typeid(VariantConvertor!To).toString)) { 575 debug(trace) trace("Array convertor for type ", typeid(To), " is already registered."); 576 577 } else { 578 debug(trace) trace("Registering array convertor for type ", typeid(To)); 579 580 with (context.container.load("singleton")) { 581 register!(VariantConvertor!To).scan; 582 } 583 } 584 585 context.register(context.container.locate!(VariantConvertor!To), path, typeid(To)); 586 } 587 } 588 589 void register(Context)(ref Context context, Convertor convertor, string path, const TypeInfo to) {} 590 } 591 592 struct UsingCombinedConvertor(ConvertorType) { 593 594 void initialize(Context)(ref Context context) { 595 } 596 597 void register(To, From, Context)(ref Context context, string path) { 598 if (context.container.locate!(Locator!())("singleton").has(path)) { 599 return; 600 } 601 602 CombinedConvertor convertor = context.container.locate!ConvertorType; 603 604 if (convertor.converts(typeid(From), typeid(To))) { 605 debug(trace) trace("Combined convertor ", convertor, " is able to convert from ", typeid(From), " to ", typeid(To), " setting as convertor for property ", path); 606 607 context.register(convertor, path, typeid(To)); 608 } 609 } 610 611 void register(Context)(ref Context context, Convertor convertor, string path, const TypeInfo to) {} 612 } 613 614 alias WithDefaultRegisterers = AliasSeq!( 615 UsingCombinedConvertor!CombinedConvertor(), 616 WithCompositeAccessor(), 617 WithCompositeInspector(), 618 WithCompositeSetter(), 619 WithConversionToCompositeConvertor(), 620 WithRangeToArrayConvertor(), 621 WithRangeToRangeConvertor(), 622 WithMapToMapConvertor(), 623 WithVariantConvertor() 624 ); 625 626 /** 627 Wrapper over a document enhancing it with various configuration policies. 628 **/ 629 struct PolicyDocument(DocumentType, FieldType, Policies...) { 630 DocumentType document; 631 }