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 }