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.container.describing_container; 31 32 import aermicioi.aedi.container.container; 33 import aermicioi.aedi.storage.object_storage; 34 import aermicioi.aedi.storage.decorator; 35 import aermicioi.aedi.storage.alias_aware; 36 import aermicioi.aedi.storage.storage; 37 import aermicioi.aedi.factory.factory; 38 import aermicioi.aedi.exception.not_found_exception; 39 import aermicioi.aedi.util.traits; 40 import std.meta; 41 import std.traits; 42 43 import std.range.interfaces; 44 45 /** 46 Description of a component. 47 **/ 48 @safe struct Description(IdentityType) { 49 50 /** 51 Identity of component 52 **/ 53 const IdentityType identity; 54 55 /** 56 Title of component 57 **/ 58 string title; 59 60 /** 61 Exhaustive description of component 62 **/ 63 string description; 64 65 /** 66 Assign a description to this description. 67 68 Params: 69 description = description to be assigned. 70 **/ 71 void opAssign(ref Description description) 72 in (this.identity == description.identity) { 73 this.title = description.title; 74 this.description = description.description; 75 } 76 77 /** 78 ditto 79 **/ 80 void opAssign(Description description) 81 in (this.identity == description.identity) { 82 this = description; 83 } 84 } 85 86 /** 87 Provides a set of descriptions used for help information. 88 **/ 89 @safe interface DescriptionsProvider(IdentityType) { 90 91 /** 92 Provide a list of descriptions for usage. 93 94 Returns: 95 A list of descriptions. 96 **/ 97 const(Description!IdentityType)[] provide() const @safe; 98 } 99 100 /** 101 Interface for component that is able to provide description for passed components. 102 **/ 103 @safe interface Describer(ComponentType = Object, IdentityType = string) { 104 import aermicioi.aedi.util.typecons : Optional, optional; 105 106 public { 107 108 /** 109 Describe a component based on it's identity and itself. 110 111 Params: 112 identity = identity of component being described 113 component = component that is described 114 Returns: 115 A description or empty if it is not describable by this describer 116 **/ 117 Optional!(const(Description!IdentityType)) describe(const ref IdentityType identity, const ref ComponentType component) @safe const; 118 119 /** 120 ditto 121 **/ 122 final Optional!(const(Description!IdentityType)) describe(const IdentityType identity, const ComponentType component) @safe const { 123 return this.describe(identity, component); 124 } 125 } 126 } 127 128 /** 129 Describer that stores a list of descriptions based on identity, based on which it is describing passed components. 130 **/ 131 @safe class IdentityDescriber(ComponentType = Object, IdentityType = string) : Describer!(ComponentType, IdentityType), DescriptionsProvider!IdentityType { 132 import aermicioi.aedi.util.typecons : Optional, optional; 133 134 private { 135 Description!IdentityType[] descriptions; 136 } 137 138 public { 139 140 /** 141 Register a description in describer. 142 143 Params: 144 description = description registered in describer. 145 Returns: 146 typeof(this) 147 **/ 148 typeof(this) register(Description!IdentityType description) @safe nothrow pure { 149 import std.algorithm.searching : countUntil; 150 151 ptrdiff_t index = descriptions.countUntil!(d => d.identity == description.identity); 152 153 if (index > -1) { 154 descriptions[index] = description; 155 156 return this; 157 } 158 159 descriptions ~= description; 160 161 return this; 162 } 163 164 /** 165 Register a description in describer. 166 167 Params: 168 identity = identity of component for which description is registered 169 title = title of description 170 description = description itself 171 Returns: 172 typeof(this) 173 **/ 174 typeof(this) register(IdentityType identity, string title, string description) @safe nothrow pure { 175 return this.register(Description!IdentityType(identity, title, description)); 176 } 177 178 /** 179 Remove a description from decriber 180 181 Params: 182 identity = identity of component for which to remove description 183 Returns: 184 typeof(this) 185 **/ 186 typeof(this) remove(IdentityType identity) { 187 import std.algorithm.mutation : remove; 188 this.descriptions = this.descriptions.remove!(d => d.identity == identity); 189 190 return this; 191 } 192 193 /** 194 Describe a component based on it's identity and itself. 195 196 Params: 197 identity = identity of component being described 198 component = component that is described 199 Returns: 200 A description or empty if it is not describable by this describer 201 **/ 202 Optional!(const(Description!IdentityType)) describe(const ref IdentityType identity, const ref ComponentType component) @safe const { 203 import std.algorithm.searching : countUntil; 204 205 ptrdiff_t index = this.descriptions.countUntil!(d => d.identity == identity); 206 207 if (index > -1) { 208 return cast(Optional!(const(Description!IdentityType))) this.descriptions[index].optional; 209 } 210 211 return Optional!(const(Description!IdentityType))(); 212 } 213 214 /** 215 ditto 216 **/ 217 alias describe = Describer!(ComponentType, IdentityType).describe; 218 219 /** 220 Provide a list of descriptions for usage. 221 222 Returns: 223 A list of descriptions. 224 **/ 225 const(Description!IdentityType)[] provide() const @safe { 226 return this.descriptions; 227 } 228 } 229 } 230 231 /** 232 Describer that creates descriptions on the fly based on passed identity and component. 233 **/ 234 @safe class TypeDescriber(ComponentType = Object, IdentityType = string) : Describer!(ComponentType, IdentityType) { 235 import aermicioi.aedi.util.typecons : Optional, optional; 236 237 private { 238 string title; 239 string description; 240 string delegate(const ref IdentityType) @safe pure identityFormatter; 241 string delegate(const ref ComponentType) @safe pure componentFormatter; 242 } 243 244 public { 245 246 /** 247 Constructor with custom title and description template 248 249 This describer will replace ${identity} with formatted identity and 250 ${component} with formatted component in title and description using 251 identityFormatter and componentFormatter for formatting. 252 253 Params: 254 title = title template used to generate description title 255 description = description template used to generate description itself 256 identityFormatter = formatter that produces a readable string out of passed component identity 257 componentFormatter = formatter that produces a readable string out of passed component 258 **/ 259 this( 260 string title, 261 string description, 262 string delegate(const ref IdentityType identity) @safe pure identityFormatter, 263 string delegate(const ref ComponentType component) @safe pure componentFormatter 264 ) { 265 this.title = title; 266 this.description = description; 267 this.identityFormatter = identityFormatter; 268 this.componentFormatter = componentFormatter; 269 } 270 271 /** 272 ditto 273 **/ 274 this( 275 string title, 276 string description 277 ) { 278 import std.conv : text; 279 this( 280 title, 281 description, 282 (const ref IdentityType identity) => text(identity), 283 (const ref ComponentType component) => typeid(ComponentType).toString 284 ); 285 } 286 287 /** 288 ditto 289 **/ 290 this() { 291 this( 292 "${identity}", 293 "${identity} typeof ${component}" 294 ); 295 } 296 297 /** 298 Describe a component based on it's identity and itself using generic identity and type formatter in title and description templates. 299 300 Params: 301 identity = identity of component being described 302 component = component that is described 303 Returns: 304 A description or empty if it is not describable by this describer 305 **/ 306 Optional!(const(Description!IdentityType)) describe(const ref IdentityType identity, const ref ComponentType component) const @safe { 307 import std.algorithm.iteration : substitute; 308 import std.array : array; 309 import std.utf : byChar; 310 311 return (cast(const) Description!IdentityType( 312 identity, 313 title.substitute("${identity}", identityFormatter(identity), "${component}", componentFormatter(component)).byChar.array.idup, 314 description.substitute("${identity}", identityFormatter(identity), "${component}", componentFormatter(component)).byChar.array.idup, 315 )).optional; 316 } 317 318 /** 319 ditto 320 **/ 321 alias describe = Describer!(ComponentType, IdentityType).describe; 322 } 323 } 324 325 /** 326 A describer that provides same description for any component 327 **/ 328 @safe class StaticDescriber(ComponentType = Object, IdentityType = string) : Describer!(ComponentType, IdentityType) { 329 import aermicioi.aedi.util.typecons : Optional, optional; 330 private { 331 const Description!IdentityType description; 332 } 333 334 public { 335 /** 336 Constructor accepting a constructed description 337 338 Params: 339 description = description to use. 340 **/ 341 this(const Description!IdentityType description) { 342 this.description = description; 343 } 344 345 /** 346 Constructor accepting contents of a description 347 348 Params: 349 identity = custom identity in case if none is passed in describing function 350 title = title used in description 351 description = description used to provide 352 **/ 353 this(const IdentityType identity, string title, string description) { 354 this(Description!IdentityType(identity, title, description)); 355 } 356 357 /** 358 ditto 359 **/ 360 this(string title, string description) { 361 this(IdentityType.init, title, description); 362 } 363 364 /** 365 Give same description for any passed component. 366 367 Params: 368 identity = identity of component being described 369 component = component that is described 370 Returns: 371 A description or empty if it is not describable by this describer 372 **/ 373 Optional!(const(Description!IdentityType)) describe(const ref IdentityType identity, const ref ComponentType component) const @safe { 374 if (identity != IdentityType.init) { 375 376 return (cast(const) Description!IdentityType(identity, this.description.title, this.description.description)).optional; 377 } 378 379 return this.description.optional; 380 } 381 382 /** 383 ditto 384 **/ 385 alias describe = Describer!(ComponentType, IdentityType).describe; 386 } 387 } 388 389 /** 390 A describer that provides empty descriptions (i.e. no descriptions at all) 391 **/ 392 @safe class NullDescriber(ComponentType = Object, IdentityType = string) : Describer!(ComponentType, IdentityType) { 393 import aermicioi.aedi.util.typecons : Optional, optional; 394 public { 395 /** 396 Give no description at all. 397 398 Params: 399 identity = identity of component being described 400 component = component that is described 401 Returns: 402 A description or empty if it is not describable by this describer 403 **/ 404 Optional!(const(Description!IdentityType)) describe(const ref IdentityType identity, const ref ComponentType component) const @safe { 405 return Optional!(const(Description!IdentityType))(); 406 } 407 408 /** 409 ditto 410 **/ 411 alias describe = Describer!(ComponentType, IdentityType).describe; 412 } 413 } 414 415 /** 416 Decorating container that enhances existing ones with a set 417 of descriptions for itself and underlying components managed by it. 418 This decorated will inherit following interfaces only and only if the 419 T also implements them: 420 $(OL 421 $(LI Storage!(ObjectFactory, string)) 422 $(LI FactoryLocator!ObjectFactory) 423 $(LI AliasAware!string) 424 ) 425 Decorated container must implement following interfaces: 426 $(OL 427 $(LI Container) 428 $(LI MutableDecorator!T) 429 $(LI Describer!(Object, string)) 430 $(LI Decorator!Container) 431 ) 432 433 Params: 434 T = The decorated that switchable decorated will decorate. 435 **/ 436 template DescribingContainer(T) 437 { 438 import std.conv : text; 439 import aermicioi.aedi.util.typecons : Optional, optional; 440 441 /** 442 Set which the switchable container will decorate for T. By default 443 Locator!() and Subscribable!ContainerInstantiationEventType is included. 444 **/ 445 alias InheritanceSet = NoDuplicates!(Filter!( 446 templateOr!( 447 partialSuffixed!( 448 isDerived, 449 Storage!(ObjectFactory, string) 450 ), 451 partialSuffixed!( 452 isDerived, 453 AliasAware!string 454 ), 455 partialSuffixed!( 456 isDerived, 457 FactoryLocator!ObjectFactory 458 ) 459 ), 460 InterfacesTuple!T), 461 Container, 462 Describer!(Object, string), 463 Decorator!Container, 464 ); 465 466 @safe class DescribingContainer : InheritanceSet 467 { 468 private { 469 Describer!() fallback; 470 Describer!() main; 471 Describer!() container; 472 } 473 474 public 475 { 476 this( 477 T decorated, 478 Describer!() main = new IdentityDescriber!(), 479 Describer!() container = new StaticDescriber!()("Component container", "A container of components"), 480 Describer!() fallback = new TypeDescriber!()("${identity}", text("${identity} part of ", typeid(T), " of ${component}")), 481 ) in (main !is null) 482 { 483 this.decorated = decorated; 484 this.fallback = fallback; 485 this.main = main; 486 this.container = container; 487 } 488 489 alias describe = Describer!(Object, string).describe; 490 Optional!(const(Description!string)) describe(const ref string identity, const ref Object component) const @safe { 491 if (component is decorated) { 492 return container.describe(null, this.decorated); 493 } 494 495 foreach (decorator; component.decorators!Container) { 496 if (this is decorator) { 497 return container.describe(null, this.decorated); 498 } 499 } 500 501 auto result = main.describe(identity, component); 502 503 if (result.isNull) { 504 return fallback.describe(identity, component); 505 } 506 507 return result; 508 } 509 510 mixin MutableDecoratorMixin!T; 511 mixin ContainerMixin!T; 512 513 /** 514 Get object created by a factory identified by key 515 516 Params: 517 key = identity of factory 518 Returns: 519 Object 520 **/ 521 Object get(string key) 522 { 523 if (key == typeid(DescriptionsProvider!string).toString) { 524 foreach (candidate; [main, container, fallback]) { 525 DescriptionsProvider!string subject = (() @trusted => cast(DescriptionsProvider!string) candidate)(); 526 527 if (subject !is null) { 528 Object returnable = (() @trusted => cast(Object) subject)(); 529 530 if (returnable is null) { 531 import aermicioi.aedi.storage.wrapper : WrapperImpl; 532 returnable = new WrapperImpl!(DescriptionsProvider!string)(subject); 533 } 534 535 return returnable; 536 } 537 } 538 } 539 540 if (key == typeid(Describer!()).toString) { 541 return this; 542 } 543 544 foreach (candidate; [ main, container, fallback ]) { 545 Object tested = (() @trusted => cast(Object) candidate)(); 546 547 if ((tested !is null) && (tested.classinfo.toString == key)) { 548 return tested; 549 } 550 } 551 552 return this.decorated.get(key); 553 } 554 555 /** 556 Check if an object factory for it exists in container. 557 558 Params: 559 key = identity of factory 560 Returns: 561 bool 562 **/ 563 bool has(in string key) inout 564 { 565 if (key == typeid(DescriptionsProvider!string).toString) { 566 foreach (candidate; [main, container, fallback]) { 567 DescriptionsProvider!string subject = (() @trusted => cast(DescriptionsProvider!string) candidate)(); 568 569 if (subject !is null) { 570 return true; 571 } 572 } 573 } 574 575 if (key == typeid(Describer!()).toString) { 576 return true; 577 } 578 579 580 foreach (candidate; [ main, container, fallback ]) { 581 Object tested = (() @trusted => cast(Object) candidate)(); 582 583 if ((tested !is null) && (tested.classinfo.toString == key)) { 584 return true; 585 } 586 } 587 588 return this.decorated.has(key); 589 } 590 591 static if (is(T : Storage!(ObjectFactory, string))) 592 { 593 mixin StorageMixin!(typeof(this)) StorageScope; 594 } 595 596 static if (is(T : AliasAware!string)) 597 { 598 mixin AliasAwareMixin!(typeof(this)); 599 } 600 601 static if (is(T : FactoryLocator!ObjectFactory)) 602 { 603 mixin FactoryLocatorMixin!(typeof(this)); 604 } 605 } 606 } 607 }