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 module aermicioi.aedi_property_reader.convertor.mapper; 31 32 import aermicioi.aedi.configurer.annotation.annotation; 33 import aermicioi.aedi_property_reader.convertor.exception; 34 import aermicioi.aedi_property_reader.convertor.convertor : 35 CombinedConvertor, 36 ConvertsFromToMixin, 37 Convertor, 38 EqualToHashToStringOpCmpMixin; 39 import aermicioi.aedi_property_reader.convertor.inspector; 40 import aermicioi.aedi_property_reader.convertor.setter; 41 import aermicioi.aedi_property_reader.convertor.accessor; 42 import aermicioi.aedi_property_reader.convertor.placeholder; 43 import std.experimental.allocator; 44 import std.experimental.logger; 45 import std.exception; 46 import std.algorithm; 47 import std.conv; 48 import std.traits : fullyQualifiedName; 49 import std.typecons : Flag; 50 51 /** 52 Interface for components that are able to map from one type to another one. 53 **/ 54 interface Mapper(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 void map(From from, ref To to, RCIAllocator allocator = 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(Convertor convertor) @safe nothrow pure; 79 80 /** 81 Get convertors 82 83 Returns: 84 Convertor[] 85 **/ 86 inout(Convertor) convertor() @safe nothrow pure inout; 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) @safe nothrow pure; 98 99 /** 100 Get force 101 102 Returns: 103 Flag!"force" 104 **/ 105 inout(Flag!"force") force() @safe nothrow pure inout; 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) @safe nothrow pure; 117 118 /** 119 Get conversion 120 121 Returns: 122 Flag!"conversion" 123 **/ 124 inout(Flag!"conversion") conversion() @safe nothrow pure inout; 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) @safe nothrow pure; 136 137 /** 138 Get skip 139 140 Returns: 141 Flag!"skip" 142 **/ 143 inout(Flag!"skip") skip() @safe nothrow pure inout; 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 @component 156 class CompositeMapper(To, From) : Mapper!(To, From) { 157 158 private { 159 Flag!"conversion" conversion_; 160 Flag!"force" force_; 161 Flag!"skip" skip_; 162 163 Convertor convertor_; 164 165 const PropertySetter!(To, Object) setter; 166 const PropertyAccessor!(From, Object) accessor; 167 const Inspector!From fromInspector; 168 const Inspector!To toInspector; 169 } 170 171 public { 172 173 @autowired 174 this( 175 PropertySetter!(To, Object) setter, 176 PropertyAccessor!(From, Object) accessor, 177 Inspector!From fromInspector, 178 Inspector!To toInspector 179 ) { 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 @autowired 197 typeof(this) convertor(Convertor convertor) @safe nothrow pure 198 in (convertor !is null, "Expected convertor, not null value") { 199 this.convertor_ = convertor; 200 201 return this; 202 } 203 204 /** 205 Get convertors 206 207 Returns: 208 Convertor[] 209 **/ 210 inout(Convertor) convertor() @safe nothrow pure inout 211 out(conv; conv !is null, "Expected to have a proper convertor available not null") { 212 return this.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 @autowired 225 typeof(this) conversion(Flag!"conversion" conversion) @safe nothrow pure { 226 this.conversion_ = conversion; 227 228 return this; 229 } 230 231 /** 232 Get conversion 233 234 Returns: 235 Flag!"conversion" 236 **/ 237 inout(Flag!"conversion") conversion() @safe nothrow pure inout { 238 return this.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 @autowired 251 typeof(this) force(Flag!"force" force) @safe nothrow pure { 252 this.force_ = force; 253 254 return this; 255 } 256 257 /** 258 Get force 259 260 Returns: 261 Flag!"force" 262 **/ 263 inout(Flag!"force") force() @safe nothrow pure inout { 264 return this.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 @autowired 277 typeof(this) skip(Flag!"skip" skip) @safe nothrow pure { 278 this.skip_ = skip; 279 280 return this; 281 } 282 283 /** 284 Get skip 285 286 Returns: 287 Flag!"skip" 288 **/ 289 inout(Flag!"skip") skip() @safe nothrow pure inout { 290 return this.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 void map(From from, ref To to, RCIAllocator allocator = theAllocator) const { 306 307 debug(trace) trace( 308 "Mapping ", 309 this.fromInspector.properties(from), 310 " of ", 311 from.identify, 312 " to ", 313 to.identify 314 ); 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 Object value = 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) is typeid(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 throw new ConvertorException(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 throw new InvalidArgumentException(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 property 384 ); 385 386 debug(trace) trace("Migrated \"", property, "\" from ", from.identify, " to ", to.identify); 387 } catch (Exception e) { 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 e 400 ); 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 @component 416 class CompositeConvertor(To, From) : Convertor { 417 import std.algorithm : filter; 418 import std.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 @autowired 436 typeof(this) mapper(Mapper!(To, From) mapper) @safe nothrow pure { 437 this.mapper_ = mapper; 438 439 return this; 440 } 441 442 /** 443 Get mapper 444 445 Returns: 446 Mapper!(To, From) 447 **/ 448 inout(Mapper!(To, From)) mapper() @safe nothrow pure inout { 449 return this.mapper_; 450 } 451 } 452 453 mixin ConvertsFromToMixin!(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 Object convert(in Object from, const TypeInfo to, RCIAllocator allocator = 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 auto placeholder = To.init.pack(from, this, allocator); 487 488 static if (is(To : Object)) { 489 placeholder.value = allocator.make!To; 490 } 491 492 this.mapper.map(from.unwrap!From, placeholder.value, allocator); 493 494 return placeholder; 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 void destruct(const TypeInfo from, ref Object converted, RCIAllocator allocator = theAllocator) const { 511 enforce!ConvertorException(this.destroys(from, converted), text( 512 "Cannot destruct ", converted.identify, " not supported by ", typeid(this), " expected ", this.to 513 )); 514 515 static if (is(To : Object)) { 516 allocator.dispose(converted.unpack!To); 517 } else { 518 converted.unpack!To; 519 } 520 521 converted = Object.init; 522 } 523 524 mixin EqualToHashToStringOpCmpMixin!(); 525 } 526 }