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 }