1 // 2 // Copyright 2010, Tridium, Inc. All Rights Reserved. 3 // 4 5 /** 6 * BOX System Object Notation. 7 * <p> 8 * BSON is BOG notation in a JSON format. JSON is used instead of XML because 9 * tests show that browsers can parse JSON significantly faster. 10 * 11 * @author Gareth Johnson 12 * @version 1.0.0.0 13 */ 14 15 //JsLint options (see http://www.jslint.com ) 16 /*jslint rhino: true, onevar: false, plusplus: true, white: true, undef: false, nomen: false, eqeqeq: true, 17 bitwise: true, regexp: true, newcap: true, immed: true, strict: false, indent: 2, vars: true, continue: true */ 18 19 // Globals for JsLint to ignore 20 /*global baja, syncVal, encodeVal, BaseBajaObj*/ 21 22 (function bson(baja) { 23 24 // Use ECMAScript 5 Strict Mode 25 "use strict"; 26 27 // Create local for improved minification 28 var bajaDef = baja.def, 29 objectify = baja.objectify, 30 serverDecodeContext = baja.$serverDecodeContext = { serverDecode: true }, 31 bsonDecodeValue; 32 33 /** 34 * @namespace BOX System Object Notation. 35 */ 36 baja.bson = new BaseBajaObj(); 37 38 //////////////////////////////////////////////////////////////// 39 // Decode Actions/Topics 40 //////////////////////////////////////////////////////////////// 41 42 /** 43 * Decode the BSON for an Action Type. 44 * 45 * @private 46 * 47 * @param bson the BSON to decode. 48 * @returns type the parameter for the Action Type (or null if none). 49 */ 50 function decodeActionParamType(bson) { 51 var paramType = null; 52 53 // Decode Action Parameter Type 54 if (typeof bson.apt === "string") { 55 paramType = baja.lt(bson.apt); 56 } 57 58 return paramType; 59 } 60 61 /** 62 * Decode the BSON for an Action Default. 63 * 64 * @private 65 * 66 * @param bson the BSON to decode. 67 * @returns type the parameter for the Action Default (or null if none). 68 */ 69 function decodeActionParamDefault(bson) { 70 var paramDef = null; 71 72 // Decode default Action Parameter Type 73 if (typeof bson.apd === "object") { 74 paramDef = bsonDecodeValue(bson.apd, serverDecodeContext); 75 } 76 77 return paramDef; 78 } 79 80 /** 81 * Decode the BSON for an Action Return Type. 82 * 83 * @private 84 * 85 * @param bson the BSON to decode. 86 * @returns type the parameter for the Action Return Type (or null if none). 87 */ 88 function decodeActionReturnType(bson) { 89 var returnType = null; 90 91 // Decode default Action Parameter Type 92 if (typeof bson.art === "string") { 93 returnType = baja.lt(bson.art); 94 } 95 96 return returnType; 97 } 98 99 /** 100 * Decode the BSON for the Topic and return the event type. 101 * 102 * @private 103 * 104 * @param bson the BSON to decode. 105 * @returns {Type} the event type for a Topic. 106 */ 107 function decodeTopicEventType(bson) { 108 var eventType = null; 109 110 if (typeof bson.tet === "string") { 111 eventType = baja.lt(bson.tet); 112 } 113 114 return eventType; 115 } 116 117 //////////////////////////////////////////////////////////////// 118 // BSON Frozen Slots 119 //////////////////////////////////////////////////////////////// 120 121 /** 122 * @class Frozen Property Slot. 123 * <p> 124 * Property defines a Slot which is a storage location 125 * for a variable in a Complex. 126 * <p> 127 * A new object should never be directly created with this Constructor. All Slots are 128 * created internally by BajaScript. 129 * 130 * @name FrozenProperty 131 * @extends baja.Property 132 */ 133 var FrozenProperty = function (bson, complex) { 134 FrozenProperty.$super.call(this, bson.n, bson.dn); 135 this.$bson = bson; 136 this.$complex = complex; 137 }.$extend(baja.Property); 138 139 FrozenProperty.prototype.isFrozen = function () { 140 return true; 141 }; 142 143 /** 144 * Return the Property value. 145 * <p> 146 * Please note, this method is intended for INTERNAL use by Tridium only. An 147 * external developer should never call this method. 148 * 149 * @private 150 * 151 * @returns value 152 */ 153 FrozenProperty.prototype.$getValue = function () { 154 if (this.$val === undefined) { 155 var val = this.$val = bsonDecodeValue(this.$bson.v, serverDecodeContext); 156 // Set up any parenting if needed 157 if (val.getType().isComplex() && this.$complex) { 158 val.$parent = this.$complex; 159 val.$propInParent = this; 160 } 161 } 162 return this.$val; 163 }; 164 165 /** 166 * Return true if the value has been lazily decoded. 167 * 168 * @private 169 * 170 * @returns {Boolean} 171 */ 172 FrozenProperty.prototype.$isValueDecoded = function () { 173 return this.$val !== undefined; 174 }; 175 176 /** 177 * Set the Property value. 178 * <p> 179 * Please note, this method is intended for INTERNAL use by Tridium only. An 180 * external developer should never call this method. 181 * 182 * @private 183 * 184 * @param val value to be set. 185 */ 186 FrozenProperty.prototype.$setValue = function (val) { 187 this.$val = val; 188 }; 189 190 /** 191 * Return the Flags for the Property. 192 * 193 * @see baja.Flags 194 * 195 * @returns {Number} 196 */ 197 FrozenProperty.prototype.getFlags = function () { 198 if (this.$flags === undefined) { 199 this.$flags = this.$bson.f === undefined ? 0 : baja.Flags.decodeFromString(this.$bson.f); 200 } 201 return this.$flags; 202 }; 203 204 /** 205 * Set the Flags for the Property. 206 * <p> 207 * Please note, this method is intended for INTERNAL use by Tridium only. An 208 * external developer should never call this method. 209 * 210 * @private 211 * @see baja.Flags 212 * 213 * @param {Number} flags 214 */ 215 FrozenProperty.prototype.$setFlags = function (flags) { 216 this.$flags = flags; 217 }; 218 219 /** 220 * Return the Facets for the Property. 221 * 222 * @see baja.Facets 223 * 224 * @returns the Slot Facets 225 */ 226 FrozenProperty.prototype.getFacets = function () { 227 if (this.$facets === undefined) { 228 this.$facets = this.$bson.x === undefined ? baja.Facets.DEFAULT : baja.Facets.DEFAULT.decodeFromString(this.$bson.x); 229 } 230 return this.$facets; 231 }; 232 233 /** 234 * Set the Facets for the Property. 235 * <p> 236 * Please note, this method is intended for INTERNAL use by Tridium only. An 237 * external developer should never call this method. 238 * 239 * @private 240 * @see baja.Facets 241 * 242 * @param {baja.Facets} facets 243 */ 244 FrozenProperty.prototype.$setFacets = function (facets) { 245 this.$facets = facets; 246 }; 247 248 /** 249 * Return the default flags for the Property. 250 * 251 * @returns {Number} 252 */ 253 FrozenProperty.prototype.getDefaultFlags = function () { 254 if (this.$defFlags === undefined) { 255 this.$defFlags = this.$bson.f === undefined ? 0 : baja.Flags.decodeFromString(this.$bson.f); 256 } 257 return this.$defFlags; 258 }; 259 260 /** 261 * Return the default value for the Property. 262 * 263 * @returns the default value for the Property. 264 */ 265 FrozenProperty.prototype.getDefaultValue = function () { 266 if (this.$defVal === undefined) { 267 this.$defVal = bsonDecodeValue(this.$bson.v, serverDecodeContext); 268 } 269 return this.$defVal; 270 }; 271 272 /** 273 * Return the Type for this Property. 274 * 275 * @returns the Type for the Property. 276 */ 277 FrozenProperty.prototype.getType = function () { 278 if (this.$val === undefined) { 279 if (this.$initType === undefined) { 280 this.$initType = baja.lt(this.$bson.v.t); 281 } 282 return this.$initType; 283 } 284 else { 285 return this.$getValue().getType(); 286 } 287 }; 288 289 /** 290 * Return the display String for this Property. 291 * <p> 292 * Please note, this method is intended for INTERNAL use by Tridium only. An 293 * external developer should never call this method. 294 * 295 * @private 296 * 297 * @returns {String} 298 */ 299 FrozenProperty.prototype.$getDisplay = function () { 300 if (this.$display === undefined) { 301 this.$display = this.$bson.v.d || ""; 302 } 303 return this.$display; 304 }; 305 306 /** 307 * Set the display for this Property. 308 * <p> 309 * Please note, this method is intended for INTERNAL use by Tridium only. An 310 * external developer should never call this method. 311 * 312 * @private 313 * 314 * @param {String} display the display String 315 */ 316 FrozenProperty.prototype.$setDisplay = function (display) { 317 this.$display = display; 318 }; 319 320 /** 321 * @class Frozen Action Slot. 322 * <p> 323 * Action is a Slot that defines a behavior which can 324 * be invoked on a Component. 325 * <p> 326 * A new object should never be directly created with this Constructor. All Slots are 327 * created internally by BajaScript 328 * 329 * @name FrozenAction 330 * @extends baja.Action 331 */ 332 var FrozenAction = function (bson) { 333 FrozenAction.$super.call(this, bson.n, bson.dn); 334 this.$bson = bson; 335 }.$extend(baja.Action); 336 337 FrozenAction.prototype.isFrozen = FrozenProperty.prototype.isFrozen; 338 339 /** 340 * Return the Flags for the Action. 341 * 342 * @function 343 * @see baja.Flags 344 * 345 * @returns {Number} 346 */ 347 FrozenAction.prototype.getFlags = FrozenProperty.prototype.getFlags; 348 349 /** 350 * Set the Flags for the Action. 351 * <p> 352 * Please note, this method is intended for INTERNAL use by Tridium only. An 353 * external developer should never call this method. 354 * 355 * @function 356 * @private 357 * @see baja.Flags 358 * 359 * @param {Number} flags 360 */ 361 FrozenAction.prototype.$setFlags = FrozenProperty.prototype.$setFlags; 362 363 /** 364 * Return the Facets for the Action. 365 * 366 * @function 367 * @see baja.Facets 368 * 369 * @returns the Slot Facets 370 */ 371 FrozenAction.prototype.getFacets = FrozenProperty.prototype.getFacets; 372 373 /** 374 * Set the Facets for the Action. 375 * <p> 376 * Please note, this method is intended for INTERNAL use by Tridium only. An 377 * external developer should never call this method. 378 * 379 * @function 380 * @private 381 * @see baja.Facets 382 * 383 * @param {baja.Facets} facets 384 */ 385 FrozenAction.prototype.$setFacets = FrozenProperty.prototype.$setFacets; 386 387 /** 388 * Return the default flags for the Action. 389 * 390 * @returns {Number} 391 */ 392 FrozenAction.prototype.getDefaultFlags = FrozenProperty.prototype.getDefaultFlags; 393 394 /** 395 * Return the Action's Parameter Type. 396 * 397 * @returns {Type} the Parameter's Type (or null if the Action has no argument). 398 */ 399 FrozenAction.prototype.getParamType = function () { 400 if (this.$paramType === undefined) { 401 this.$paramType = decodeActionParamType(this.$bson); 402 } 403 return this.$paramType; 404 }; 405 406 /** 407 * Return the Action's Default Value. 408 * 409 * @returns the parameter default value (or null if the Action has no argument). 410 */ 411 FrozenAction.prototype.getParamDefault = function () { 412 if (this.$paramDef === undefined) { 413 this.$paramDef = decodeActionParamDefault(this.$bson); 414 } 415 return this.$paramDef; 416 }; 417 418 /** 419 * Return the return Type for the Action. 420 * 421 * @returns {Type} the return Type (or null if the Action has no return Type). 422 */ 423 FrozenAction.prototype.getReturnType = function () { 424 if (this.$returnType === undefined) { 425 this.$returnType = decodeActionReturnType(this.$bson); 426 } 427 return this.$returnType; 428 }; 429 430 /** 431 * @class Frozen Topic Slot. 432 * <p> 433 * Topic defines a Slot which indicates an event that 434 * is fired on a Component. 435 * <p> 436 * A new object should never be directly created with this Constructor. All Slots are 437 * created internally by BajaScript. 438 * 439 * @name FrozenTopic 440 * @extends baja.Topic 441 */ 442 var FrozenTopic = function (bson) { 443 FrozenTopic.$super.call(this, bson.n, bson.dn); 444 this.$bson = bson; 445 }.$extend(baja.Topic); 446 447 FrozenTopic.prototype.isFrozen = FrozenProperty.prototype.isFrozen; 448 449 /** 450 * Return the Flags for the Topic. 451 * 452 * @function 453 * @see baja.Flags 454 * 455 * @returns {Number} 456 */ 457 FrozenTopic.prototype.getFlags = FrozenProperty.prototype.getFlags; 458 459 /** 460 * Set the Flags for the Topic. 461 * <p> 462 * Please note, this method is intended for INTERNAL use by Tridium only. An 463 * external developer should never call this method. 464 * 465 * @function 466 * @private 467 * @see baja.Flags 468 * 469 * @param {Number} flags 470 */ 471 FrozenTopic.prototype.$setFlags = FrozenProperty.prototype.$setFlags; 472 473 /** 474 * Return the Facets for the Topic. 475 * 476 * @function 477 * @see baja.Facets 478 * 479 * @returns the Slot Facets 480 */ 481 FrozenTopic.prototype.getFacets = FrozenProperty.prototype.getFacets; 482 483 /** 484 * Set the Facets for the Topic. 485 * <p> 486 * Please note, this method is intended for INTERNAL use by Tridium only. An 487 * external developer should never call this method. 488 * 489 * @function 490 * @private 491 * @see baja.Facets 492 * 493 * @param {baja.Facets} facets 494 */ 495 FrozenTopic.prototype.$setFacets = FrozenProperty.prototype.$setFacets; 496 497 /** 498 * Return the default flags for the Topic. 499 * 500 * @returns {Number} 501 */ 502 FrozenTopic.prototype.getDefaultFlags = FrozenProperty.prototype.getDefaultFlags; 503 504 /** 505 * Return the event type. 506 * 507 * @returns {Type} the event type (or null if the Topic has not event). 508 */ 509 FrozenTopic.prototype.getEventType = function () { 510 if (this.$eventType === undefined) { 511 this.$eventType = decodeTopicEventType(this.$bson); 512 } 513 return this.$eventType; 514 }; 515 516 //////////////////////////////////////////////////////////////// 517 // Auto-generate Slot Methods 518 //////////////////////////////////////////////////////////////// 519 520 function generateSlotMethods(complexType, complex, slot) { 521 // Cache auto-generated methods onto Type 522 var autoGen = complexType.$autoGen = complexType.$autoGen || {}, 523 slotName = slot.getName(), 524 methods = autoGen.hasOwnProperty(slotName) ? autoGen[slotName] : null; 525 526 // If the methods already exist then simply copy them over and return 527 if (methods) { 528 var methodName; 529 for (methodName in methods) { 530 if (methods.hasOwnProperty(methodName)) { 531 complex[methodName] = methods[methodName]; 532 } 533 } 534 return; 535 } 536 537 autoGen[slotName] = methods = {}; 538 539 // Please note: these auto-generated methods should always respect the fact that a Slot can be overridden by 540 // sub-Types. Therefore, be aware of using too much closure in these auto-generated methods. 541 542 // Form appropriate name for getters, setters and firers 543 var capSlotName = slot.getName().capitalizeFirstLetter(), 544 i = 0; 545 546 if (slot.isProperty()) { 547 548 var origGetterName = "get" + capSlotName, 549 getterName = origGetterName, 550 origGetterDisplayName = origGetterName + "Display", 551 getterDisplayName = origGetterDisplayName, 552 origSetterName = "set" + capSlotName, 553 setterName = origSetterName; 554 555 // Find some unique names for the getter and setter (providing this isn't the icon Slot which we DO want to override)... 556 i = 0; 557 if (capSlotName !== "Icon") { 558 while (complex[getterName] !== undefined || complex[getterDisplayName] !== undefined || complex[setterName] !== undefined) { 559 getterName = origGetterName + (++i); 560 getterDisplayName = origGetterDisplayName + i; 561 setterName = origSetterName + i; 562 } 563 } 564 565 // Add Getter 566 complex[getterName] = methods[getterName] = function () { 567 var v = this.get(slotName); 568 569 // If a number then return its inner boxed value 570 return v.getType().isNumber() ? v.valueOf() : v; 571 }; 572 573 // Add Display String Getter 574 complex[getterDisplayName] = methods[getterDisplayName] = function () { 575 return this.getDisplay(slotName); 576 }; 577 578 // Add Setter 579 complex[setterName] = methods[setterName] = function (obj) { 580 obj = objectify(obj, "value"); 581 obj.slot = slotName; 582 583 // TODO: Need to check incoming value to ensure it's the same Type!!! 584 this.set(obj); 585 }; 586 } 587 588 var invokeActionName = slotName; 589 if (slot.isAction()) { 590 591 // Find a unique name for the Action invocation method 592 i = 0; 593 while (complex[invokeActionName] !== undefined) { 594 invokeActionName = slotName + (++i); 595 } 596 597 complex[invokeActionName] = methods[invokeActionName] = function (obj) { 598 obj = objectify(obj, "value"); 599 obj.slot = slotName; 600 601 return this.invoke(obj); 602 }; 603 } 604 605 if (slot.isTopic()) { 606 607 // Find a unique name for the topic invocation method 608 var origFireTopicName = "fire" + capSlotName, 609 fireTopicName = origFireTopicName; 610 i = 0; 611 while (complex[fireTopicName] !== undefined) { 612 fireTopicName = origFireTopicName + (++i); 613 } 614 615 complex[fireTopicName] = methods[fireTopicName] = function (obj) { 616 obj = objectify(obj, "value"); 617 obj.slot = slotName; 618 619 this.fire(obj); 620 }; 621 } 622 } 623 624 //////////////////////////////////////////////////////////////// 625 // Contracts 626 //////////////////////////////////////////////////////////////// 627 628 /** 629 * Return an instance of a frozen Slot. 630 * 631 * @param bson 632 * @param {baja.Complex} [complex] 633 * 634 * @returns {baja.Slot} 635 */ 636 function createContractSlot(bson, complex) { 637 var slot, 638 slotType = bson.st; 639 640 // Create frozen Slot 641 if (slotType === "p") { 642 slot = new FrozenProperty(bson, complex); 643 } 644 else if (slotType === "a") { 645 slot = new FrozenAction(bson); 646 } 647 else if (slotType === "t") { 648 slot = new FrozenTopic(bson); 649 } 650 else { 651 throw new Error("Invalid BSON: Cannot decode: " + JSON.stringify(bson)); 652 } 653 654 return slot; 655 } 656 657 /** 658 * Return return a decoded Contract Slot. 659 * 660 * @private 661 * 662 * @param complex the Complex to decode the Slots onto 663 * @param bson the BSON to decode 664 */ 665 function decodeContractSlot(complexType, complex, bson) { 666 var slot = createContractSlot(bson, complex), 667 slotName = bson.n; 668 669 // Only auto-generate the Slot methods if Slot doesn't already exist. 670 // This caters for Slots that are overridden by sub-Types. 671 if (!complex.$map.contains(slotName)) { 672 // Auto-generate the methods and copy them over to the complex 673 generateSlotMethods(complexType, complex, slot); 674 } 675 676 // Add to Slot Map 677 complex.$map.put(slotName, slot); 678 } 679 680 /** 681 * Return a decoded array of Slots from a BSON Contract Definition. 682 * 683 * @private 684 * 685 * @see baja.Slot 686 * 687 * @param type the Type. 688 * @param {baja.Complex} complex the complex instance the Slots are being loaded for. 689 */ 690 baja.bson.decodeComplexContract = function (type, complex) { 691 var clxTypes = [], 692 t = type, 693 i, 694 j, 695 bson; 696 697 // Get a list of all the Super types 698 while (t && t.isComplex()) { 699 clxTypes.push(t); 700 t = t.getSuperType(); 701 } 702 703 // Iterate down through the Super Types and build up the Contract list 704 for (i = clxTypes.length - 1; i >= 0; --i) { 705 if (!clxTypes[i].hasContract()) { 706 clxTypes[i].loadContract(); 707 } 708 709 bson = clxTypes[i].getContract(); 710 711 if (bson) { 712 for (j = 0; j < bson.length; ++j) { 713 // Add newly created Slot to array 714 decodeContractSlot(type, complex, bson[j]); 715 } 716 } 717 } 718 }; 719 720 //////////////////////////////////////////////////////////////// 721 // BSON Type Scanning 722 //////////////////////////////////////////////////////////////// 723 724 function scanUnknown(bson, typeSpecs) { 725 if (!bson) { 726 return; 727 } 728 729 var type, 730 prop, 731 i; 732 733 // Ensure we're dealing with a Slot 734 if ((bson.nm === "p" || bson.nm === "a" || bson.nm === "t") && bson.t) { 735 // Check to see if the Type is loaded and has a Contract associated with it 736 if (baja.registry.hasType(bson.t)) { 737 738 type = baja.lt(bson.t); 739 740 // Only request type information if we need too 741 if ((type.isComplex() || type.isFrozenEnum()) && !type.hasContract()) { 742 typeSpecs[bson.t] = bson.t; 743 } 744 } 745 else { 746 typeSpecs[bson.t] = bson.t; 747 } 748 } 749 750 // Scan for sub-properties 751 if (bson instanceof Array) { 752 for (i = 0; i < bson.length; ++i) { 753 scanUnknown(bson[i], typeSpecs); 754 } 755 } 756 else if (bson instanceof Object) { 757 for (prop in bson) { 758 if (bson.hasOwnProperty(prop)) { 759 scanUnknown(bson[prop], typeSpecs); 760 } 761 } 762 } 763 } 764 765 /** 766 * Scan for Types and Contracts that aren't yet loaded into the BajaScript Registry. 767 * 768 * @private 769 * 770 * @param bson the BSON to scan Types for. 771 * @returns {Array} An array of typeSpec information we need to request for (String). 772 */ 773 baja.bson.scanForUnknownTypes = function (bson) { 774 775 // Store results in an object as we only want type information added once 776 var typeSpecs = {}, 777 typeSpecArray = [], // Convert from object map to array 778 prop; 779 780 // Scan the data structure for Slot Type information 781 scanUnknown(bson, typeSpecs); 782 783 for (prop in typeSpecs) { 784 if (typeSpecs.hasOwnProperty(prop)) { 785 typeSpecArray.push(prop); 786 } 787 } 788 789 return typeSpecArray; 790 }; 791 792 //////////////////////////////////////////////////////////////// 793 // BOG BSON Decoding 794 //////////////////////////////////////////////////////////////// 795 796 /** 797 * Decode and return a Knob. 798 * 799 * @private 800 * 801 * @param bson the BSON that contains knob information to decode 802 * @returns a decoded value (null if unable to decode). 803 */ 804 baja.bson.decodeKnob = function (bson) { 805 var targetOrd = baja.Ord.make(bson.to); 806 807 // TODO: Document these methods 808 return { 809 getId: function getId() { 810 return bson.id; 811 }, 812 813 getSourceSlotName: function getSourceSlotName() { 814 return bson.ss; 815 }, 816 817 getTargetOrd: function getTargetOrd() { 818 return targetOrd; 819 }, 820 821 getTargetSlotName: function getTargetSlotName() { 822 return bson.ts; 823 } 824 }; 825 }; 826 827 /** 828 * Return a decoded value. 829 * 830 * @private 831 * 832 * @param bson the BSON to decode. 833 * @param [Object] cx the context used when decoding. 834 * @returns a decoded value (null if unable to decode). 835 */ 836 baja.bson.decodeValue = function (bson, cx, parent) { 837 cx = cx || {}; 838 839 // Please note the parent object is designed only to be used internally! 840 841 // TODO: Skip this from LoadOp - needed for loading security permissions at some point! 842 if (!(bson.nm === "p" || bson.nm === "a" || bson.nm === "t")) { 843 return null; 844 } 845 846 // Decode 847 var slot = null, 848 slotType = bajaDef(bson.nm, "p"), 849 stub = bajaDef(bson.stub, false), 850 flags, 851 i, x; 852 853 if (parent) { 854 slot = parent.getSlot(bson.n); 855 } 856 857 // Slot Flags 858 if (slot !== null) { 859 if (bson.f !== undefined) { 860 flags = baja.Flags.decodeFromString(bson.f); 861 if (flags !== slot.getFlags()) { 862 parent.setFlags({ 863 "slot": slot, 864 "flags": flags, 865 "cx": cx 866 }); 867 } 868 } 869 870 if (!slot.isProperty()) { 871 return null; 872 } 873 } 874 else { 875 if (slotType !== "p") { 876 throw new Error("Error decoding Slot from BSON: Missing frozen Slot: " + slotType); 877 } 878 } 879 880 // Create object used for decoding 881 var obj; 882 if (bson.t === undefined) { 883 // TODO: Should be getDefaultValue()? 884 obj = slot.$getValue(); 885 } 886 else { 887 // Get an instance of the Type 888 obj = baja.$(bson.t); 889 } 890 891 // Decode if a Simple 892 if (obj.getType().isSimple() && bson.v !== undefined) { 893 obj = obj.decodeFromString(bson.v); 894 } 895 896 // Decode BSON specifically for baja:Action and baja:Topic 897 if (obj.getType().isAction()) { 898 obj.$paramType = decodeActionParamType(bson); 899 obj.$paramDef = decodeActionParamDefault(bson); 900 obj.$returnType = decodeActionReturnType(bson); 901 } 902 else if (obj.getType().isTopic()) { 903 obj.$eventType = decodeTopicEventType(bson); 904 } 905 906 // Decode Component 907 if (obj.getType().isComponent()) { 908 // Decode handle 909 if (bson.h !== undefined) { 910 obj.$handle = bson.h; 911 } 912 913 // Decode knobs 914 if (bson.nk) { 915 for (x = 0; x < bson.nk.length; ++x) { 916 obj.$fw("installKnob", baja.bson.decodeKnob(bson.nk[x]), cx); 917 } 918 } 919 920 // Decode permissions 921 if (bson.l && typeof bson.l.p === "string") { 922 obj.$fw("setPermissions", bson.l.p); 923 } 924 925 // TODO: Handle Component Stub decoding here 926 if (!stub) { 927 obj.$bPropsLoaded = true; 928 } 929 } 930 931 var facets; 932 if (parent) { 933 try { 934 cx.displayName = bajaDef(bson.dn, ""); 935 cx.display = bajaDef(bson.d, ""); 936 937 if (slot !== null) { 938 parent.set({ 939 "slot": slot, 940 "value": obj, 941 "cx": cx 942 }); 943 } 944 else if (parent.getType().isComponent()) { 945 facets = baja.Facets.DEFAULT.decodeFromString(bajaDef(bson.x, "")); 946 flags = baja.Flags.decodeFromString(bajaDef(bson.f, "0")); 947 parent.add({ 948 "slot": bson.n, 949 "value": obj, 950 "flags": flags, 951 "facets": facets, 952 "cx": cx 953 }); 954 } 955 } 956 finally { 957 cx.displayName = undefined; 958 cx.display = undefined; 959 } 960 } 961 962 // Decode kids 963 if (bson.s) { 964 for (i = 0; i < bson.s.length; ++i) { 965 bsonDecodeValue(bson.s[i], cx, obj); 966 } 967 } 968 969 return obj; 970 }; 971 972 bsonDecodeValue = baja.bson.decodeValue; 973 974 //////////////////////////////////////////////////////////////// 975 // BSON Encoding 976 //////////////////////////////////////////////////////////////// 977 978 function encodeSlot(parObj, par, slot) { 979 if (slot === null) { 980 return; 981 } 982 983 // Encode Slot Flags (if they differ from the default 984 var value = null, // Property Value 985 skipv = false; // Skip value 986 987 if (slot.isProperty()) { 988 value = par.get(slot); 989 if (slot.isFrozen()) { 990 if (value.equivalent(slot.getDefaultValue())) { 991 skipv = true; 992 } 993 } 994 } 995 else { 996 skipv = true; 997 } 998 999 var flags = par.getFlags(slot); 1000 1001 // Skip frozen Slots that have default flags and value 1002 if (flags === slot.getDefaultFlags() && skipv) { 1003 return; 1004 } 1005 1006 // Encode Slot Type 1007 var o = {}; 1008 if (slot.isProperty()) { 1009 o.nm = "p"; 1010 } 1011 else if (slot.isAction()) { 1012 o.nm = "a"; 1013 } 1014 else if (slot.isTopic()) { 1015 o.nm = "t"; 1016 } 1017 1018 // Slot name 1019 o.n = slot.getName(); 1020 1021 // Slot Flags if necessary 1022 if (((!slot.isFrozen() && flags !== 0) || (flags !== slot.getDefaultFlags())) && par.getType().isComponent()) { 1023 o.flags = flags; 1024 } 1025 1026 // Slot facets if necessary 1027 var fc = slot.getFacets(); 1028 if (!slot.isFrozen() && fc.getKeys().length > 0) { 1029 o.x = fc.encodeToString(); 1030 } 1031 1032 if (value !== null && value.getType().isComponent()) { 1033 // Encode handle 1034 if (value.isMounted()) { 1035 o.h = value.getHandle(); 1036 } 1037 1038 // TODO: Categories and stub? 1039 } 1040 // TODO: Need to re-evalulate this method by going through BogEncoder.encodeSlot again 1041 1042 if (!skipv && slot.isProperty()) { 1043 o.t = value.getType().getTypeSpec(); 1044 encodeVal(o, value); 1045 } 1046 1047 // Now we've encoded the Slot, add it to the Slots array 1048 if (!parObj.s) { 1049 parObj.s = []; 1050 } 1051 1052 parObj.s.push(o); 1053 } 1054 1055 function encodeVal(obj, val) { 1056 1057 var cursor; 1058 1059 if (val.getType().isSimple()) { 1060 // Encode Simple 1061 obj.v = val.encodeToString(); 1062 } 1063 else { 1064 // Encode Complex 1065 cursor = val.getSlots(); 1066 1067 // Encode all of the Slots on the Complex 1068 while (cursor.next()) { 1069 encodeSlot(obj, val, cursor.get()); 1070 } 1071 } 1072 } 1073 1074 function encode(name, val) { 1075 var o = { nm: "p" }; 1076 1077 // Encode name 1078 if (name !== null) { 1079 o.n = name; 1080 } 1081 1082 if (val.getType().isComponent()) { 1083 1084 // Encode handle 1085 if (val.isMounted()) { 1086 o.h = val.getHandle(); 1087 } 1088 1089 // TODO: Encode categories 1090 1091 // TODO: Encode whether this Component is fully loaded or not??? 1092 } 1093 1094 o.t = val.getType().getTypeSpec(); 1095 encodeVal(o, val); 1096 return o; 1097 } 1098 1099 /** 1100 * Return an encoded BSON value. 1101 * 1102 * @private 1103 * 1104 * @param val the value to encode to BSON. 1105 * @returns encoded BSON value. 1106 */ 1107 baja.bson.encodeValue = function (val) { 1108 return encode(null, val); 1109 }; 1110 1111 }(baja));