1 /** 2 * The module contains the objects and the properties of control functions 3 * 4 * Copyright: (c) 2015-2016, Milofon Project. 5 * License: Subject to the terms of the BSD license, as written in the included LICENSE.txt file. 6 * Authors: Maksim Galanin 7 */ 8 module proped.properties; 9 10 private 11 { 12 import std.typecons : NullableRef, Nullable; 13 import std.array : split, empty; 14 import std.meta : staticIndexOf, AliasSeq; 15 import std.conv : to; 16 static import std.traits; 17 18 import proped.uninode; 19 import proped.exception; 20 } 21 22 alias Types = AliasSeq!(bool, int, long, double, string); 23 alias PropNode = UniNode!(Types); 24 25 26 enum DELIMITER_CHAR = '.'; 27 enum DEFAULT_FIELD_NAME = "v"; 28 29 30 template IsValidType(T) 31 { 32 enum IsValidType = staticIndexOf!(T, Types) >= 0 33 || (std.traits.isArray!T && is(std.traits.ForeachType!T == PropNode)) 34 || (std.traits.isAssociativeArray!T && is(std.traits.KeyType!T == string) && is(std.traits.ValueType!T == PropNode)); 35 } 36 37 38 /** 39 * Class contains methods to manage the properties 40 */ 41 struct Properties 42 { 43 private PropNode head; 44 45 46 this(T)(T val) if (IsValidType!T) 47 { 48 head = PropNode(val); 49 } 50 51 52 this(PropNode node) 53 { 54 head = node; 55 } 56 57 58 /** 59 * Getting node in the specified path 60 * 61 * It the node is an object, the we try to find the embedded objects in the specified path 62 * 63 * Params: 64 * 65 * path = The path to the desired site 66 */ 67 private NullableRef!PropNode findNode(string path) 68 { 69 auto names = path.split(DELIMITER_CHAR); 70 71 NullableRef!PropNode findPath(PropNode* node, string[] names) 72 { 73 if(names.length == 0) 74 return NullableRef!PropNode(node); 75 76 string name = names[0]; 77 if ((*node).isObject) 78 if (auto chd = name in (*node).toObject) 79 return findPath(chd, names[1..$]); 80 81 return NullableRef!PropNode.init; 82 } 83 84 if (names.length > 0) 85 return findPath(&head, names); 86 87 return NullableRef!PropNode.init; 88 } 89 90 91 /** 92 * Checking for the presence of the node in the specified path 93 * 94 * It the node is an object, the we try to find the embedded objects in the specified path 95 * 96 * Params: 97 * 98 * path = The path to the desired site 99 */ 100 bool opBinaryRight(string op)(string path) if ("in" == op) 101 { 102 auto node = findNode(path); 103 return !node.isNull; 104 } 105 106 107 /** 108 * Returns the length of an object 109 */ 110 ulong length() @property 111 { 112 if (head.isArray) 113 return head.toArray().length; 114 else if (head.isObject) 115 return head.toObject().length; 116 else if (typeid(string) == head.type) 117 return head.length; 118 return 0; 119 } 120 121 122 alias opDollar = length; 123 124 125 /** 126 * Get the node if the specified path is not a node 127 * 128 * Params: 129 * 130 * path = The path to the desired site 131 * 132 * Example: 133 * --- 134 * node.get!int("foo.bar"); 135 * --- 136 */ 137 Nullable!T get(T)(string path) if (IsValidType!T) 138 { 139 return doGet!T(findNode(path)); 140 } 141 142 143 /** 144 * Get the value of the node 145 * 146 * Example 147 * --- 148 * node.get!int; 149 * --- 150 */ 151 Nullable!T get(T)() if (IsValidType!T) 152 { 153 return doGet!T(NullableRef!PropNode(&head)); 154 } 155 156 157 private Nullable!T doGet(T)(NullableRef!PropNode node) 158 { 159 if (!node.isNull) 160 { 161 if (node.get.convertsTo!T) 162 return Nullable!T(node.get.get!T); 163 else 164 return Nullable!T.init; 165 } 166 else 167 return Nullable!T.init; 168 } 169 170 171 /** 172 * Get the value, otherwise return the default value 173 * 174 * Params: 175 * 176 * alt = Default value 177 * 178 * Example: 179 * --- 180 * getOrElse(1); 181 * --- 182 */ 183 T getOrElse(T)(T alt) if (IsValidType!T) 184 { 185 return doGetOrElse!T(NullableRef!PropNode(&head), alt); 186 } 187 188 189 /** 190 * Get the node at the specified path, or return to the default value 191 * 192 * Params: 193 * 194 * path = The path to the desired site 195 * alt = Default value 196 * 197 * Example: 198 * --- 199 * getOrElse("foo", 1); 200 * --- 201 */ 202 T getOrElse(T)(string path, T alt) if (IsValidType!T) 203 { 204 return doGetOrElse!T(findNode(path), alt); 205 } 206 207 208 private T doGetOrElse(T)(NullableRef!PropNode node, T alt) if (IsValidType!T) 209 { 210 if (!node.isNull) 211 { 212 if (node.get.convertsTo!T) 213 return node.get.get!T; 214 else 215 return alt; 216 } 217 else 218 return alt; 219 } 220 221 222 /** 223 * Check type node 224 */ 225 bool isType(T)() 226 { 227 return head.type == typeid(T); 228 } 229 230 231 /** 232 * Get an array of properties on the specified path 233 * 234 * Params: 235 * 236 * path = The path to the desired site 237 * 238 * Example: 239 * --- 240 * getArray("services"); 241 * --- 242 */ 243 Properties[] getArray(string path) 244 { 245 return doGetArray(findNode(path)); 246 } 247 248 249 /** 250 * Get an array of properties on the node 251 * 252 * Example 253 * --- 254 * node.getArray(); 255 * --- 256 */ 257 Properties[] getArray() 258 { 259 return doGetArray(NullableRef!PropNode(&head)); 260 } 261 262 263 private Properties[] doGetArray(NullableRef!PropNode nodeRef) 264 { 265 Properties[] result; 266 267 if (nodeRef.isNull) 268 return result; 269 270 PropNode node = nodeRef.get; 271 272 if (node.isArray) 273 { 274 foreach(PropNode ch; node.get!(PropNode[])) 275 result ~= Properties(ch); 276 } 277 else 278 result ~= Properties(node); 279 280 return result; 281 } 282 283 284 /** 285 * Get an associative array of object properties in the specified path 286 * 287 * Params: 288 * 289 * path = The path to the desired site 290 * 291 * Example: 292 * --- 293 * getObject("services"); 294 * --- 295 */ 296 Properties[string] getObject(string path) 297 { 298 return doGetObject(findNode(path)); 299 } 300 301 302 /** 303 * Get an associative array of object properties in the node 304 * 305 * Params: 306 * 307 * path = The path to the desired site 308 * 309 * Example: 310 * --- 311 * getObject(); 312 * --- 313 */ 314 Properties[string] getObject() 315 { 316 return doGetObject(NullableRef!PropNode(&head)); 317 } 318 319 320 private Properties[string] doGetObject(NullableRef!PropNode nodeRef) 321 { 322 Properties[string] result; 323 324 if (nodeRef.isNull) 325 return result; 326 327 PropNode node = nodeRef.get; 328 if (!node.isObject) 329 { 330 result[DEFAULT_FIELD_NAME] = Properties(node); 331 } 332 else 333 { 334 foreach(string k, PropNode ch; node.get!(PropNode[string])) 335 result[k] = Properties(ch); 336 } 337 338 return result; 339 } 340 341 342 /** 343 * The string representation 344 */ 345 string toString() 346 { 347 return head.toString(); 348 } 349 350 351 /** 352 * Recursive merge properties 353 * 354 * When the merger is not going to existing nodes 355 * If the parameter is an array, it will their concatenation 356 * 357 * Params: 358 * 359 * src = Source properties 360 */ 361 Properties opOpAssign(string op)(Properties src) if ("~" == op) 362 { 363 if (src.head.hasNull) 364 return this; 365 366 if (head.hasNull) 367 { 368 head = src.head; 369 return this; 370 } 371 372 void mergeNode(ref PropNode dst, ref PropNode src) 373 { 374 if (dst.isObject && src.isObject) 375 { 376 auto dstMap = dst.toObject; 377 foreach(string k, PropNode ch; src.toObject) 378 { 379 if (auto tg = k in dstMap) 380 mergeNode(*tg, ch); 381 else 382 dstMap[k] = ch; 383 } 384 } 385 else if (dst.isArray) 386 { 387 auto dstArr = dst.toArray; 388 dst = dstArr ~ src; 389 } 390 } 391 392 mergeNode(head, src.head); 393 return this; 394 } 395 396 397 /** 398 * Get a subset of properties in the specified path 399 * 400 * Params: 401 * 402 * path = The path to the desired site 403 */ 404 Nullable!Properties sub(string path) 405 { 406 auto node = findNode(path); 407 return node.isNull ? Nullable!Properties.init : Nullable!Properties(Properties(node.get)); 408 } 409 410 411 /** 412 * Finding and installing a new value in the specified path 413 * 414 * If the specified path object will be the object or an array, it will be thrown 415 * Also, when a situation of lack of necessaty way, a new item will be created 416 * 417 * Params: 418 * 419 * path = The path to the desired site 420 * val = New value 421 */ 422 void set(T)(string path, T val) if (IsValidType!T) 423 { 424 set(path, PropNode(val)); 425 } 426 427 428 /** 429 * Finding and installing a new value in the specified path 430 * 431 * If the specified path object will be the object or an array, it will be thrown 432 * Also, when a situation of lack of necessaty way, a new item will be created 433 * 434 * Params: 435 * 436 * path = The path to the desired site 437 * val = New value 438 */ 439 void set(string path, PropNode val) 440 { 441 auto names = path.split(DELIMITER_CHAR); 442 443 PropNode createPath(PropNode terminal, string[] names) 444 { 445 if (names.empty) 446 return terminal; 447 448 PropNode[string] map; 449 map[names[0]] = createPath(terminal, names[1..$]); 450 return PropNode(map); 451 } 452 453 void setNode(ref PropNode node, string[] names) 454 { 455 if (names.empty) 456 { 457 if (node.isObject || node.isArray) 458 throw new PropertiesException("The node '" ~ path ~ "' is not a simple type"); 459 node = val; 460 return; 461 } 462 463 string name = names[0]; 464 if (node.isObject) 465 { 466 if (auto chd = name in node.toObject) 467 setNode(*chd, names[1..$]); 468 else 469 { 470 PropNode newNode = createPath(val, names[1..$]); 471 if (node.length == 0) 472 { 473 PropNode[string] map; 474 map[name] = newNode; 475 node = map; 476 } 477 else 478 node.toObject[name] = newNode; 479 } 480 } 481 else 482 throw new PropertiesException("Failed to set values for the key. The parent node for '" ~ name ~ "' is not object"); 483 } 484 485 if (head.hasNull) 486 { 487 PropNode[string] map; 488 head = PropNode(map); 489 } 490 491 setNode(head, names); 492 } 493 494 495 /** 496 * Installing a new value in the node 497 * 498 * Params: 499 * 500 * val = New value 501 */ 502 void set(T)(T val) if (IsValidType!T) 503 { 504 if (node.isObject || node.isArray) 505 throw new PropertiesException("The node '" ~ path ~ "' is not a simple type"); 506 head = val; 507 } 508 509 510 /** 511 * Check property is object 512 */ 513 bool isObject() @property 514 { 515 return head.isObject; 516 } 517 518 519 /** 520 * Check property is array 521 */ 522 bool isArray() @property 523 { 524 return head.isArray; 525 } 526 527 528 /** 529 * Check property is null 530 */ 531 bool isNull() @property 532 { 533 return head.hasNull; 534 } 535 536 /** 537 * Convert value type to object 538 */ 539 void valueToObject() 540 { 541 if (!head.isObject) 542 head = PropNode([DEFAULT_FIELD_NAME: head]); 543 } 544 } 545