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 moduleaermicioi.aedi_property_reader.convertor.mapper;
31 32 importaermicioi.aedi.configurer.annotation.annotation;
33 importaermicioi.aedi_property_reader.convertor.exception;
34 importaermicioi.aedi_property_reader.convertor.convertor :
35 CombinedConvertor,
36 ConvertsFromToMixin,
37 Convertor,
38 EqualToHashToStringOpCmpMixin;
39 importaermicioi.aedi_property_reader.convertor.inspector;
40 importaermicioi.aedi_property_reader.convertor.setter;
41 importaermicioi.aedi_property_reader.convertor.accessor;
42 importaermicioi.aedi_property_reader.convertor.placeholder;
43 importstd.experimental.allocator;
44 importstd.experimental.logger;
45 importstd.exception;
46 importstd.algorithm;
47 importstd.conv;
48 importstd.traits : fullyQualifiedName;
49 importstd.typecons : Flag;
50 51 /**
52 Interface for components that are able to map from one type to another one.
53 **/54 interfaceMapper(To, From = To) {
55 56 /**
57 Map from component to component.
58 59 Map from component to component, or transfer data from component to component
60 with optional conversion of data along the way.
61 62 Params:
63 from = original component that has it's data transferred
64 to = destination component that receives transferred data
65 allocator = optional allocator that could be used by convertors when doing field conversion
66 **/67 voidmap(Fromfrom, refToto, RCIAllocatorallocator = theAllocator) const;
68 69 @property {
70 /**
71 Set convertors
72 73 Params:
74 convertors = set a list of convertors to be used by mapper to automatically convert a mapped field to designated type
75 Returns:
76 typeof(this)
77 **/78 typeof(this) convertor(Convertorconvertor) @safenothrowpure;
79 80 /**
81 Get convertors
82 83 Returns:
84 Convertor[]
85 **/86 inout(Convertor) convertor() @safenothrowpureinout;
87 88 /**
89 Set force
90 91 Params:
92 force = forces mapper to try and set a field even if it is not existent
93 94 Returns:
95 typeof(this)
96 **/97 typeof(this) force(Flag!"force"force) @safenothrowpure;
98 99 /**
100 Get force
101 102 Returns:
103 Flag!"force"
104 **/105 inout(Flag!"force") force() @safenothrowpureinout;
106 107 /**
108 Set conversion
109 110 Params:
111 conversion = whether to enable or not automatic conversion of fields using convertors.
112 113 Returns:
114 typeof(this)
115 **/116 typeof(this) conversion(Flag!"conversion"conversion) @safenothrowpure;
117 118 /**
119 Get conversion
120 121 Returns:
122 Flag!"conversion"
123 **/124 inout(Flag!"conversion") conversion() @safenothrowpureinout;
125 126 /**
127 Set skip
128 129 Params:
130 skip = whether to skip mapping of fields that have their type not identifiable in destination component
131 132 Returns:
133 typeof(this)
134 **/135 typeof(this) skip(Flag!"skip"skip) @safenothrowpure;
136 137 /**
138 Get skip
139 140 Returns:
141 Flag!"skip"
142 **/143 inout(Flag!"skip") skip() @safenothrowpureinout;
144 }
145 }
146 147 /**
148 An implementation of a mapper that specifically converts From component To component.
149 150 An implementation of a mapper that specifically converts From component To component.
151 It will use inspectors for From and To component to get information about component fields
152 at runtime, and then use accessor and setter implementations to transfer data from one
153 component to another, with optional conversion of data using passed convertors.
154 **/155 @component156 classCompositeMapper(To, From) : Mapper!(To, From) {
157 158 private {
159 Flag!"conversion"conversion_;
160 Flag!"force"force_;
161 Flag!"skip"skip_;
162 163 Convertorconvertor_;
164 165 constPropertySetter!(To, Object) setter;
166 constPropertyAccessor!(From, Object) accessor;
167 constInspector!FromfromInspector;
168 constInspector!TotoInspector;
169 }
170 171 public {
172 173 @autowired174 this(
175 PropertySetter!(To, Object) setter,
176 PropertyAccessor!(From, Object) accessor,
177 Inspector!FromfromInspector,
178 Inspector!TotoInspector179 ) {
180 this.setter = setter;
181 this.accessor = accessor;
182 this.fromInspector = fromInspector;
183 this.toInspector = toInspector;
184 }
185 186 @property {
187 /**
188 Set convertors
189 190 Params:
191 convertors = a list of optional convertors used to convert from one format to another one
192 193 Returns:
194 typeof(this)
195 **/196 @autowired197 typeof(this) convertor(Convertorconvertor) @safenothrowpure198 in (convertor !isnull, "Expected convertor, not null value") {
199 this.convertor_ = convertor;
200 201 returnthis;
202 }
203 204 /**
205 Get convertors
206 207 Returns:
208 Convertor[]
209 **/210 inout(Convertor) convertor() @safenothrowpureinout211 out(conv; conv !isnull, "Expected to have a proper convertor available not null") {
212 returnthis.convertor_;
213 }
214 215 /**
216 Set conversion
217 218 Params:
219 conversion = wether to convert or not values using convertors.
220 221 Returns:
222 typeof(this)
223 **/224 @autowired225 typeof(this) conversion(Flag!"conversion"conversion) @safenothrowpure {
226 this.conversion_ = conversion;
227 228 returnthis;
229 }
230 231 /**
232 Get conversion
233 234 Returns:
235 Flag!"conversion"
236 **/237 inout(Flag!"conversion") conversion() @safenothrowpureinout {
238 returnthis.conversion_;
239 }
240 241 /**
242 Set force
243 244 Params:
245 force = whether to force attempt in setting a property in a mapped component
246 247 Returns:
248 typeof(this)
249 **/250 @autowired251 typeof(this) force(Flag!"force"force) @safenothrowpure {
252 this.force_ = force;
253 254 returnthis;
255 }
256 257 /**
258 Get force
259 260 Returns:
261 Flag!"force"
262 **/263 inout(Flag!"force") force() @safenothrowpureinout {
264 returnthis.force_;
265 }
266 267 /**
268 Set skip
269 270 Params:
271 skip = whether to skip mapping of fields that have their type not identifiable in destination component
272 273 Returns:
274 typeof(this)
275 **/276 @autowired277 typeof(this) skip(Flag!"skip"skip) @safenothrowpure {
278 this.skip_ = skip;
279 280 returnthis;
281 }
282 283 /**
284 Get skip
285 286 Returns:
287 Flag!"skip"
288 **/289 inout(Flag!"skip") skip() @safenothrowpureinout {
290 returnthis.skip_;
291 }
292 }
293 294 /**
295 Map from component to component.
296 297 Map from component to component, or transfer data from component to component
298 with optional conversion of data along the way.
299 300 Params:
301 from = original component that has it's data transferred
302 to = destination component that receives transferred data
303 allocator = optional allocator that could be used by convertors when doing field conversion
304 **/305 voidmap(Fromfrom, refToto, RCIAllocatorallocator = theAllocator) const {
306 307 debug(trace) trace(
308 "Mapping ",
309 this.fromInspector.properties(from),
310 " of ",
311 from.identify,
312 " to ",
313 to.identify314 );
315 316 foreach (property; this.fromInspector.properties(from)) {
317 318 debug(trace) trace("Migrating \"", property, "\" property ");
319 if (this.toInspector.has(to, property) || this.force) {
320 321 Objectvalue = this.accessor.access(from, property, allocator);
322 323 if (
324 this.fromInspector.typeOf(from, property) != this.toInspector.typeOf(to, property)
325 ) {
326 if (this.conversion) {
327 if (this.toInspector.typeOf(to, property) istypeid(void)) {
328 if (this.skip) {
329 debug(trace) trace(
330 "Skipping migration of \"",
331 property,
332 "\" due to missing type information in destination component"333 );
334 335 continue;
336 }
337 338 thrownewConvertorException(text(
339 "Cannot identify type of \"", property,
340 "\" in destination component, probably missing"341 ));
342 }
343 344 debug(trace) trace("\"",
345 property,
346 "\"'s type differs in original component and destination component, ",
347 this.fromInspector.typeOf(from, property), " and ",
348 this.toInspector.typeOf(to, property)
349 );
350 351 enforce!ConvertorException(
352 this.convertor.converts(this.fromInspector.typeOf(from, property), this.toInspector.typeOf(to, property)),
353 text(
354 "Could not convert \"", property, "\" from ",
355 this.fromInspector.typeOf(from, property), " to ", this.toInspector.typeOf(to, property), " ",
356 this.convertor, " does not support it"357 )
358 );
359 360 debug(trace) trace(
361 "Converting \"", property, "\" from ",
362 this.fromInspector.typeOf(from, property), " to ",
363 this.toInspector.typeOf(to, property)
364 );
365 366 value = this.convertor.convert(value, this.toInspector.typeOf(to, property), allocator);
367 } else {
368 369 thrownewInvalidArgumentException(text(
370 "Invalid assignment \"", property, "\" has type of ",
371 this.fromInspector.typeOf(from, property),
372 " in from component while in to component it is ",
373 this.toInspector.typeOf(to, property)
374 ));
375 }
376 }
377 378 try {
379 380 this.setter.set(
381 to,
382 value,
383 property384 );
385 386 debug(trace) trace("Migrated \"", property, "\" from ", from.identify, " to ", to.identify);
387 } catch (Exceptione) {
388 389 debug(trace) trace(
390 "Couldn't ",
391 this.force ? "forcefully " : "",
392 "set property \"",
393 property,
394 "\" to ",
395 to.identify,
396 " from ",
397 from.identify,
398 " due to ",
399 e400 );
401 }
402 } else {
403 404 debug(trace) error(to.identify, " element does not have: ", property);
405 }
406 }
407 }
408 409 }
410 }
411 412 /**
413 An implementation of convertor that is using a mapper to map from component to component.
414 **/415 @component416 classCompositeConvertor(To, From) : Convertor {
417 importstd.algorithm : filter;
418 importstd.array: array;
419 420 private {
421 Mapper!(To, From) mapper_;
422 }
423 424 public {
425 @property {
426 /**
427 Set mapper
428 429 Params:
430 mapper = mapper used to map from component to component
431 432 Returns:
433 typeof(this)
434 **/435 @autowired436 typeof(this) mapper(Mapper!(To, From) mapper) @safenothrowpure {
437 this.mapper_ = mapper;
438 439 returnthis;
440 }
441 442 /**
443 Get mapper
444 445 Returns:
446 Mapper!(To, From)
447 **/448 inout(Mapper!(To, From)) mapper() @safenothrowpureinout {
449 returnthis.mapper_;
450 }
451 }
452 453 mixinConvertsFromToMixin!(From, To);
454 455 /**
456 Convert from component to component.
457 458 Params:
459 from = original component that is to be converted.
460 to = destination object that will be constructed out for original one.
461 allocator = optional allocator that could be used to construct to component.
462 Throws:
463 ConvertorException when there is a converting error
464 InvalidArgumentException when arguments passed are not of right type or state
465 Returns:
466 Resulting converted component.
467 **/468 Objectconvert(inObjectfrom, constTypeInfoto, RCIAllocatorallocator = theAllocator) const {
469 enforce!InvalidArgumentException(
470 this.convertsFrom(from),
471 text(
472 "Cannot convert ", from.identify, " to ", typeid(To),
473 ", ", from.identify, " is not supported by ", typeid(this)
474 )
475 );
476 477 enforce!InvalidArgumentException(
478 this.convertsTo(to),
479 text(
480 "Cannot convert ", from.identify, " to ", typeid(To),
481 ", ", to, " is not supported by ", typeid(this)
482 )
483 );
484 485 486 autoplaceholder = To.init.pack(from, this, allocator);
487 488 staticif (is(To : Object)) {
489 placeholder.value = allocator.make!To;
490 }
491 492 this.mapper.map(from.unwrap!From, placeholder.value, allocator);
493 494 returnplaceholder;
495 }
496 497 /**
498 Destroy component created using this convertor.
499 500 Destroy component created using this convertor.
501 Since convertor could potentially allocate memory for
502 converted component, only itself is containing history of allocation,
503 and therefore it is responsible as well to destroy and free allocated
504 memory with allocator.
505 506 Params:
507 converted = component that should be destroyed.
508 allocator = allocator used to allocate converted component.
509 **/510 voiddestruct(constTypeInfofrom, refObjectconverted, RCIAllocatorallocator = theAllocator) const {
511 enforce!ConvertorException(this.destroys(from, converted), text(
512 "Cannot destruct ", converted.identify, " not supported by ", typeid(this), " expected ", this.to513 ));
514 515 staticif (is(To : Object)) {
516 allocator.dispose(converted.unpack!To);
517 } else {
518 converted.unpack!To;
519 }
520 521 converted = Object.init;
522 }
523 524 mixinEqualToHashToStringOpCmpMixin!();
525 }
526 }