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.core.document; 31 32 import aermicioi.aedi; 33 import aermicioi.aedi_property_reader.core.exception : ConvertorException; 34 import aermicioi.aedi_property_reader.core.convertor; 35 import aermicioi.aedi_property_reader.core.placeholder; 36 import aermicioi.aedi.storage.wrapper; 37 import std.meta; 38 import std.conv; 39 import std.experimental.allocator; 40 import std.exception : enforce; 41 import aermicioi.aedi_property_reader.core.accessor; 42 import aermicioi.aedi_property_reader.core.type_guesser; 43 import std.algorithm; 44 import std.array; 45 import std.experimental.logger; 46 47 /** 48 An implementation of Container interface from aedi IoC providing access to 49 components that are stored into a document. 50 51 An implementation of Container interface from aedi IoC providing access to 52 components that are stored into a document where a document could be anything 53 that is accessable by a PropertyAccessor implementation. Components stored in 54 document will be converted according to convertor associated to property 55 path from document. 56 **/ 57 class DocumentContainer(DocumentType, FieldType = DocumentType) : 58 Container, Storage!(Convertor, string), 59 AllocatorAware!(), 60 Convertor 61 { 62 63 mixin AllocatorAwareMixin!(typeof(this)); 64 65 private { 66 Convertor[string] convertors; 67 PropertyAccessor!(DocumentType, FieldType) accessor_; 68 TypeGuesser!FieldType guesser_; 69 70 DocumentType document_; 71 Object[string] components; 72 } 73 74 public { 75 76 /** 77 Constructor for a container for document. 78 79 Params: 80 document = document stored in container. 81 **/ 82 this(DocumentType document) { 83 this.document = document; 84 } 85 86 @property { 87 88 /** 89 Set guesser 90 91 Params: 92 guesser = guesser uset to guess the D type of document. 93 94 Returns: 95 typeof(this) 96 **/ 97 typeof(this) guesser(TypeGuesser!FieldType guesser) @safe nothrow pure { 98 this.guesser_ = guesser; 99 100 return this; 101 } 102 103 /** 104 Get guesser 105 106 Returns: 107 TypeGuesser!FieldType 108 **/ 109 inout(TypeGuesser!FieldType) guesser() @safe nothrow pure inout { 110 return this.guesser_; 111 } 112 /** 113 Set document 114 115 Params: 116 document = document containing valuable components 117 118 Returns: 119 typeof(this) 120 **/ 121 typeof(this) document(DocumentType document) @safe nothrow { 122 this.document_ = document; 123 124 return this; 125 } 126 127 /** 128 Get document 129 130 Returns: 131 DocumentType 132 **/ 133 inout(DocumentType) document() @safe nothrow pure inout { 134 return this.document_; 135 } 136 137 /** 138 Set accessor 139 140 Params: 141 accessor = accessor used to navigate through document 142 Returns: 143 typeof(this) 144 **/ 145 typeof(this) accessor(PropertyAccessor!(DocumentType, FieldType) accessor) @safe nothrow pure { 146 this.accessor_ = accessor; 147 148 return this; 149 } 150 151 /** 152 Get accessor 153 154 Returns: 155 PropertyAccessor!(DocumentType, FieldType) 156 **/ 157 inout(PropertyAccessor!(DocumentType, FieldType)) accessor() @safe nothrow pure inout { 158 return this.accessor_; 159 } 160 } 161 162 /** 163 Save an element in Storage by key identity. 164 165 Params: 166 identity = identity of element in Storage. 167 element = element which is to be saved in Storage. 168 169 Return: 170 Storage 171 **/ 172 typeof(this) set(Convertor element, string identity) { 173 this.convertors[identity] = element; 174 175 return this; 176 } 177 178 /** 179 Remove an element from Storage with identity. 180 181 Remove an element from Storage with identity. If there is no element by provided identity, then no action is performed. 182 183 Params: 184 identity = the identity of element to be removed. 185 186 Return: 187 Storage 188 **/ 189 typeof(this) remove(string identity) { 190 this.convertors.remove(identity); 191 192 return this; 193 } 194 195 /** 196 Sets up the internal state of container. 197 198 Sets up the internal state of container (Ex, for singleton container it will spawn all objects that locator contains). 199 **/ 200 Container instantiate() { 201 foreach (identity, convertor; this.convertors) { 202 if (this.accessor.has(this.document, identity)) { 203 this.get(identity); 204 } 205 } 206 207 return this; 208 } 209 210 /** 211 Destruct all managed components. 212 213 Destruct all managed components. The method denotes the end of container lifetime, and therefore destruction of all managed components 214 by it. 215 **/ 216 Container terminate() { 217 foreach (identity, convertor; this.convertors) { 218 if (auto component = identity in this.components) { 219 convertor.destruct(*component, this.allocator); 220 } 221 } 222 223 return this; 224 } 225 226 /** 227 Get a component that is associated with key. 228 229 Params: 230 identity = the element id. 231 232 Throws: 233 NotFoundException in case if the element wasn't found. 234 235 Returns: 236 Object element if it is available. 237 **/ 238 Object get(string identity) { 239 debug(trace) trace("Searching for \"", identity, '"'); 240 241 Object converted; 242 243 if (auto peeked = identity in this.components) { 244 debug(trace) trace("Found already converted \"", identity, '"'); 245 246 return *peeked; 247 } 248 249 if (!this.accessor.has(this.document, identity)) { 250 throw new NotFoundException(text("Could not find \"", identity, "\" in document of type ", typeid(DocumentType))); 251 } 252 253 static if (is(FieldType : Object)) { 254 255 FieldType document = this.accessor.access(this.document, identity); 256 } else { 257 import std.typecons : scoped; 258 auto document = scoped!(PlaceholderImpl!FieldType)(this.accessor.access(this.document, identity)); 259 } 260 261 debug(trace) trace("Searching for suitable convertor for \"", identity, "\" of ", typeid(FieldType)); 262 263 if (auto convertor = identity in this.convertors) { 264 if (convertor.to !is typeid(void)) { 265 debug(trace) trace("Found convertor for \"", identity, "\" commencing conversion to ", convertor.to); 266 267 converted = (*convertor).convert(document, convertor.to, this.allocator); 268 this.components[identity] = converted; 269 return converted; 270 } 271 } 272 273 debug(trace) trace("No suitable convertor found, attempting to guess the desired type."); 274 static if (is(FieldType : Object)) { 275 276 TypeInfo guess = this.guesser.guess(document); 277 } else { 278 279 TypeInfo guess = this.guesser.guess(document.value); 280 } 281 debug(trace) trace("Guessed ", guess, " type, commencing conversion"); 282 return this.convert(document, guess, this.allocator); 283 } 284 285 /** 286 Check if an element is present in Locator by key id. 287 288 Note: 289 This check should be done for elements that locator actually contains, and 290 not in chained locator. 291 Params: 292 identity = identity of element. 293 294 Returns: 295 bool true if an element by key is present in Locator. 296 **/ 297 bool has(string identity) inout { 298 if (identity in this.components) { 299 300 return true; 301 } 302 303 return this.accessor.has(this.document, identity); 304 } 305 306 @property { 307 308 /** 309 Get the type info of component that convertor can convert from. 310 311 Get the type info of component that convertor can convert from. 312 The method is returning the default type that it is able to convert, 313 though it is not necessarily limited to this type only. More generalistic 314 checks should be done by convertsFrom method. 315 316 Returns: 317 type info of component that convertor is able to convert. 318 **/ 319 TypeInfo from() const nothrow { 320 return typeid(FieldType); 321 } 322 323 /** 324 Get the type info of component that convertor is able to convert to. 325 326 Get the type info of component that convertor is able to convert to. 327 The method is returning the default type that is able to convert, 328 though it is not necessarily limited to this type only. More generalistic 329 checks should be done by convertsTo method. 330 331 Returns: 332 type info of component that can be converted to. 333 **/ 334 TypeInfo to() const nothrow { 335 return typeid(void); 336 } 337 } 338 339 /** 340 Check whether convertor is able to convert from. 341 342 Check whether convertor is able to convert from. 343 The intent of method is to implement customized type checking 344 is not limited immediatly to supported default from component. 345 346 Params: 347 from = the type info of component that could potentially be converted by convertor. 348 Returns: 349 true if it is able to convert from, or false otherwise. 350 **/ 351 bool convertsFrom(TypeInfo from) const nothrow { 352 return this.convertors.byValue.canFind!(convertor => convertor.convertsFrom(from)); 353 } 354 355 /** 356 Check whether convertor is able to convert from. 357 358 Check whether convertor is able to convert from. 359 The method will try to extract type info out of from 360 object and use for subsequent type checking. 361 The intent of method is to implement customized type checking 362 is not limited immediatly to supported default from component. 363 364 Params: 365 from = the type info of component that could potentially be converted by convertor. 366 Returns: 367 true if it is able to convert from, or false otherwise. 368 **/ 369 bool convertsFrom(in Object from) const nothrow { 370 return this.convertors.byValue.canFind!(convertor => convertor.convertsFrom(from)); 371 } 372 373 /** 374 Check whether convertor is able to convert to. 375 376 Check whether convertor is able to convert to. 377 The intent of the method is to implement customized type checking 378 that is not limited immediatly to supported default to component. 379 380 Params: 381 to = type info of component that convertor could potentially convert to. 382 383 Returns: 384 true if it is able to convert to, false otherwise. 385 **/ 386 bool convertsTo(TypeInfo to) const nothrow { 387 return this.convertors.byValue.canFind!(convertor => convertor.convertsTo(to)); 388 } 389 390 /** 391 Check whether convertor is able to convert to. 392 393 Check whether convertor is able to convert to. 394 The method will try to extract type info out of to object and use 395 for subsequent type checking. 396 The intent of the method is to implement customized type checking 397 that is not limited immediatly to supported default to component. 398 399 Params: 400 to = type info of component that convertor could potentially convert to. 401 402 Returns: 403 true if it is able to convert to, false otherwise. 404 **/ 405 bool convertsTo(in Object to) const nothrow { 406 return this.convertors.byValue.canFind!(convertor => convertor.convertsTo(to)); 407 } 408 409 /** 410 Convert from component to component. 411 412 Params: 413 from = original component that is to be converted. 414 to = destination object that will be constructed out for original one. 415 allocator = optional allocator that could be used to construct to component. 416 Throws: 417 ConvertorException when there is a converting error 418 InvalidArgumentException when arguments passed are not of right type or state 419 Returns: 420 Resulting converted component. 421 **/ 422 Object convert(in Object from, TypeInfo to, RCIAllocator allocator = theAllocator) { 423 debug(trace) trace("Searching for convertor for ", from.identify, " to ", to); 424 auto convertors = this.convertors.byValue.filter!( 425 c => c.convertsTo(to) && c.convertsFrom(from) 426 ); 427 428 if (!convertors.empty) { 429 debug(trace) trace("Found convertor ", convertors.front.classinfo, " for ", from.identify, " to ", to); 430 431 return convertors.front.convert(from, to, allocator); 432 } 433 434 debug(trace) trace("No suitable convertor found for ", from.identify, " to ", to); 435 throw new ConvertorException(text("Could not convert ", from.identify, " to ", to)); 436 } 437 438 /** 439 Destroy component created using this convertor. 440 441 Destroy component created using this convertor. 442 Since convertor could potentially allocate memory for 443 converted component, only itself is containing history of allocation, 444 and therefore it is responsible as well to destroy and free allocated 445 memory with allocator. 446 447 Params: 448 converted = component that should be destroyed. 449 allocator = allocator used to allocate converted component. 450 **/ 451 void destruct(ref Object converted, RCIAllocator allocator = theAllocator) { 452 auto destructors = this.convertors.byValue.filter!(convertor => convertor.convertsTo(converted)); 453 454 enforce!ConvertorException(!destructors.empty, text("Could not destroy ", converted.identify, ", no suitable convertor found.")); 455 456 destructors.front.destruct(converted, allocator); 457 } 458 } 459 } 460 461 /** 462 An implementation of document container that holds an advised convertor along the document for usage as default constructor for convertors 463 in configuration api. 464 **/ 465 class AdvisedDocumentContainer(DocumentType, FieldType, alias AdvisedConvertor) : DocumentContainer!(DocumentType, FieldType) { 466 467 public { 468 469 this(DocumentType document) { 470 super(document); 471 } 472 } 473 }