/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Gareth Johnson
*/
/**
* BOX System Object Notation.
*
* BSON is BOG notation in a JSON format. JSON is used instead of XML because
* tests show that browsers can parse JSON significantly faster.
*/
define([ "bajaScript/comp" ], function defineBson(baja) {
// Use ECMAScript 5 Strict Mode
"use strict";
// Create local for improved minification
var bajaDef = baja.def,
objectify = baja.objectify,
serverDecodeContext = baja.$serverDecodeContext = { serverDecode: true },
subclass = baja.subclass,
callSuper = baja.callSuper,
bsonDecodeValue,
BaseBajaObj = baja.BaseBajaObj;
/**
* BOX System Object Notation.
* @namespace baja.bson
*/
baja.bson = new BaseBajaObj();
////////////////////////////////////////////////////////////////
// Decode Actions/Topics
////////////////////////////////////////////////////////////////
/**
* Decode the BSON for an Action Type.
*
* @private
*
* @param bson the BSON to decode.
* @returns type the parameter for the Action Type (or null if none).
*/
function decodeActionParamType(bson) {
var paramType = null;
// Decode Action Parameter Type
if (typeof bson.apt === "string") {
paramType = baja.lt(bson.apt);
}
return paramType;
}
/**
* Decode the BSON for an Action Default.
*
* @private
*
* @param bson the BSON to decode.
* @returns type the parameter for the Action Default (or null if none).
*/
function decodeActionParamDefault(bson) {
var paramDef = null;
// Decode default Action Parameter Type
if (typeof bson.apd === "object") {
paramDef = bsonDecodeValue(bson.apd, serverDecodeContext);
}
return paramDef;
}
/**
* Decode the BSON for an Action Return Type.
*
* @private
*
* @param bson the BSON to decode.
* @returns type the parameter for the Action Return Type (or null if none).
*/
function decodeActionReturnType(bson) {
var returnType = null;
// Decode default Action Parameter Type
if (typeof bson.art === "string") {
returnType = baja.lt(bson.art);
}
return returnType;
}
/**
* Decode the BSON for the Topic and return the event type.
*
* @private
*
* @param bson the BSON to decode.
* @returns {Type} the event type for a Topic.
*/
function decodeTopicEventType(bson) {
var eventType = null;
if (typeof bson.tet === "string") {
eventType = baja.lt(bson.tet);
}
return eventType;
}
////////////////////////////////////////////////////////////////
// BSON Frozen Slots
////////////////////////////////////////////////////////////////
/**
* Frozen Property Slot.
*
* Property defines a Slot which is a storage location
* for a variable in a Complex.
*
* A new object should never be directly created with this Constructor. All Slots are
* created internally by BajaScript.
*
* @class
* @alias FrozenProperty
* @extends baja.Property
*/
var FrozenProperty = function (bson, complex) {
callSuper(FrozenProperty, this, [ bson.n, bson.dn ]);
this.$bson = bson;
this.$complex = complex;
};
subclass(FrozenProperty, baja.Property);
FrozenProperty.prototype.isFrozen = function () {
return true;
};
/**
* Return the Property value.
*
* Please note, this method is intended for INTERNAL use by Tridium only. An
* external developer should never call this method.
*
* @private
*
* @returns value
*/
FrozenProperty.prototype.$getValue = function () {
if (this.$val === undefined) {
var val = this.$val = bsonDecodeValue(this.$bson.v, serverDecodeContext);
// Set up any parenting if needed
if (val.getType().isComplex() && this.$complex) {
val.$parent = this.$complex;
val.$propInParent = this;
}
}
return this.$val;
};
/**
* Return true if the value has been lazily decoded.
*
* @private
*
* @returns {Boolean}
*/
FrozenProperty.prototype.$isValueDecoded = function () {
return this.$val !== undefined;
};
/**
* Set the Property value.
*
* Please note, this method is intended for INTERNAL use by Tridium only. An
* external developer should never call this method.
*
* @private
*
* @param val value to be set.
*/
FrozenProperty.prototype.$setValue = function (val) {
this.$val = val;
};
/**
* Return the Flags for the Property.
*
* @see baja.Flags
*
* @returns {Number}
*/
FrozenProperty.prototype.getFlags = function () {
if (this.$flags === undefined) {
this.$flags = decodeFlags(this.$bson);
}
return this.$flags;
};
/**
* Set the Flags for the Property.
*
* Please note, this method is intended for INTERNAL use by Tridium only. An
* external developer should never call this method.
*
* @private
* @see baja.Flags
*
* @param {Number} flags
*/
FrozenProperty.prototype.$setFlags = function (flags) {
this.$flags = flags;
};
/**
* Return the Facets for the Property.
*
* @see baja.Facets
*
* @returns {baja.Facets} the Slot Facets
*/
FrozenProperty.prototype.getFacets = function () {
if (this.$facets === undefined) {
this.$facets = this.$bson.x === undefined ? baja.Facets.DEFAULT : baja.Facets.DEFAULT.decodeFromString(this.$bson.x);
}
return this.$facets;
};
/**
* Set the Facets for the Property.
*
* Please note, this method is intended for INTERNAL use by Tridium only. An
* external developer should never call this method.
*
* @private
* @see baja.Facets
*
* @param {baja.Facets} facets
*/
FrozenProperty.prototype.$setFacets = function (facets) {
this.$facets = facets;
};
/**
* Return the default flags for the Property.
*
* @returns {Number}
*/
FrozenProperty.prototype.getDefaultFlags = function () {
if (this.$defFlags === undefined) {
this.$defFlags = decodeFlags(this.$bson);
}
return this.$defFlags;
};
/**
* Return the default value for the Property.
*
* @returns the default value for the Property.
*/
FrozenProperty.prototype.getDefaultValue = function () {
if (this.$defVal === undefined) {
this.$defVal = bsonDecodeValue(this.$bson.v, serverDecodeContext);
}
return this.$defVal;
};
/**
* Return the Type for this Property.
*
* @returns {Type} the Type for the Property.
*/
FrozenProperty.prototype.getType = function () {
if (this.$initType === undefined) {
this.$initType = baja.lt(this.$bson.ts || this.$bson.v.t);
}
return this.$initType || null;
};
/**
* Return the display String for this Property.
*
* Please note, this method is intended for INTERNAL use by Tridium only. An
* external developer should never call this method.
*
* @private
*
* @returns {String}
*/
FrozenProperty.prototype.$getDisplay = function () {
if (this.$display === undefined) {
this.$display = this.$bson.v.d || "";
}
return this.$display;
};
/**
* Set the display for this Property.
*
* Please note, this method is intended for INTERNAL use by Tridium only. An
* external developer should never call this method.
*
* @private
*
* @param {String} display the display String
*/
FrozenProperty.prototype.$setDisplay = function (display) {
this.$display = display;
};
/**
* Frozen Action Slot.
*
* Action is a Slot that defines a behavior which can
* be invoked on a Component.
*
* A new object should never be directly created with this Constructor. All Slots are
* created internally by BajaScript
*
* @class
* @alias FrozenAction
* @extends baja.Action
*/
var FrozenAction = function (bson) {
callSuper(FrozenAction, this, [ bson.n, bson.dn ]);
this.$bson = bson;
};
subclass(FrozenAction, baja.Action);
FrozenAction.prototype.isFrozen = FrozenProperty.prototype.isFrozen;
/**
* Return the Flags for the Action.
*
* @function
* @see baja.Flags
*
* @returns {Number}
*/
FrozenAction.prototype.getFlags = FrozenProperty.prototype.getFlags;
/**
* Set the Flags for the Action.
*
* Please note, this method is intended for INTERNAL use by Tridium only. An
* external developer should never call this method.
*
* @function
* @private
* @see baja.Flags
*
* @param {Number} flags
*/
FrozenAction.prototype.$setFlags = FrozenProperty.prototype.$setFlags;
/**
* Return the Facets for the Action.
*
* @function
* @see baja.Facets
*
* @returns the Slot Facets
*/
FrozenAction.prototype.getFacets = FrozenProperty.prototype.getFacets;
/**
* Set the Facets for the Action.
*
* Please note, this method is intended for INTERNAL use by Tridium only. An
* external developer should never call this method.
*
* @function
* @private
* @see baja.Facets
*
* @param {baja.Facets} facets
*/
FrozenAction.prototype.$setFacets = FrozenProperty.prototype.$setFacets;
/**
* Return the default flags for the Action.
*
* @returns {Number}
*/
FrozenAction.prototype.getDefaultFlags = FrozenProperty.prototype.getDefaultFlags;
/**
* Return the Action's Parameter Type.
*
* @returns {Type} the Parameter's Type (or null if the Action has no argument).
*/
FrozenAction.prototype.getParamType = function () {
if (this.$paramType === undefined) {
this.$paramType = decodeActionParamType(this.$bson);
}
return this.$paramType;
};
/**
* Return the Action's Default Value.
*
* @returns the parameter default value (or null if the Action has no argument).
*/
FrozenAction.prototype.getParamDefault = function () {
if (this.$paramDef === undefined) {
this.$paramDef = decodeActionParamDefault(this.$bson);
}
return this.$paramDef;
};
/**
* Return the return Type for the Action.
*
* @returns {Type} the return Type (or null if the Action has no return Type).
*/
FrozenAction.prototype.getReturnType = function () {
if (this.$returnType === undefined) {
this.$returnType = decodeActionReturnType(this.$bson);
}
return this.$returnType;
};
/**
* Frozen Topic Slot.
*
* Topic defines a Slot which indicates an event that
* is fired on a Component.
*
* A new object should never be directly created with this Constructor. All Slots are
* created internally by BajaScript.
*
* @class
* @alias FrozenTopic
* @extends baja.Topic
*/
var FrozenTopic = function (bson) {
callSuper(FrozenTopic, this, [ bson.n, bson.dn ]);
this.$bson = bson;
};
subclass(FrozenTopic, baja.Topic);
FrozenTopic.prototype.isFrozen = FrozenProperty.prototype.isFrozen;
/**
* Return the Flags for the Topic.
*
* @function
* @see baja.Flags
*
* @returns {Number}
*/
FrozenTopic.prototype.getFlags = FrozenProperty.prototype.getFlags;
/**
* Set the Flags for the Topic.
*
* Please note, this method is intended for INTERNAL use by Tridium only. An
* external developer should never call this method.
*
* @function
* @private
* @see baja.Flags
*
* @param {Number} flags
*/
FrozenTopic.prototype.$setFlags = FrozenProperty.prototype.$setFlags;
/**
* Return the Facets for the Topic.
*
* @function
* @see baja.Facets
*
* @returns the Slot Facets
*/
FrozenTopic.prototype.getFacets = FrozenProperty.prototype.getFacets;
/**
* Set the Facets for the Topic.
*
* Please note, this method is intended for INTERNAL use by Tridium only. An
* external developer should never call this method.
*
* @function
* @private
* @see baja.Facets
*
* @param {baja.Facets} facets
*/
FrozenTopic.prototype.$setFacets = FrozenProperty.prototype.$setFacets;
/**
* Return the default flags for the Topic.
*
* @returns {Number}
*/
FrozenTopic.prototype.getDefaultFlags = FrozenProperty.prototype.getDefaultFlags;
/**
* Return the event type.
*
* @returns {Type} the event type (or null if the Topic has not event).
*/
FrozenTopic.prototype.getEventType = function () {
if (this.$eventType === undefined) {
this.$eventType = decodeTopicEventType(this.$bson);
}
return this.$eventType;
};
////////////////////////////////////////////////////////////////
// Auto-generate Slot Methods
////////////////////////////////////////////////////////////////
function generateSlotMethods(complexType, complex, slot) {
// Cache auto-generated methods onto Type
var autoGen = complexType.$autoGen = complexType.$autoGen || {},
slotName = slot.getName(),
methods = autoGen.hasOwnProperty(slotName) ? autoGen[slotName] : null;
// If the methods already exist then simply copy them over and return
if (methods) {
var methodName;
for (methodName in methods) {
if (methods.hasOwnProperty(methodName)) {
complex[methodName] = methods[methodName];
}
}
return;
}
autoGen[slotName] = methods = {};
// Please note: these auto-generated methods should always respect the fact that a Slot can be overridden by
// sub-Types. Therefore, be aware of using too much closure in these auto-generated methods.
// Form appropriate name for getters, setters and firers
var capSlotName = slot.getName().capitalizeFirstLetter(),
i = 0;
if (slot.isProperty()) {
var origGetterName = "get" + capSlotName,
getterName = origGetterName,
origGetterDisplayName = origGetterName + "Display",
getterDisplayName = origGetterDisplayName,
origSetterName = "set" + capSlotName,
setterName = origSetterName;
// Find some unique names for the getter and setter (providing this isn't the icon Slot which we DO want to override)...
i = 0;
if (capSlotName !== "Icon") {
while (complex[getterName] !== undefined || complex[getterDisplayName] !== undefined || complex[setterName] !== undefined) {
getterName = origGetterName + (++i);
getterDisplayName = origGetterDisplayName + i;
setterName = origSetterName + i;
}
}
// Add Getter
complex[getterName] = methods[getterName] = function () {
var v = this.get(slotName);
// If a number then return its inner boxed value
return v.getType().isNumber() ? v.valueOf() : v;
};
// Add Display String Getter
complex[getterDisplayName] = methods[getterDisplayName] = function (cx) {
return this.getDisplay(slotName, cx);
};
// Add Setter
complex[setterName] = methods[setterName] = function (obj) {
obj = objectify(obj, "value");
obj.slot = slotName;
// TODO: Need to check incoming value to ensure it's the same Type!!!
return this.set(obj);
};
}
var invokeActionName = slotName;
if (slot.isAction()) {
// Find a unique name for the Action invocation method
i = 0;
while (complex[invokeActionName] !== undefined) {
invokeActionName = slotName + (++i);
}
complex[invokeActionName] = methods[invokeActionName] = function (obj) {
obj = objectify(obj, "value");
obj.slot = slotName;
return this.invoke(obj);
};
}
if (slot.isTopic()) {
// Find a unique name for the topic invocation method
var origFireTopicName = "fire" + capSlotName,
fireTopicName = origFireTopicName;
i = 0;
while (complex[fireTopicName] !== undefined) {
fireTopicName = origFireTopicName + (++i);
}
complex[fireTopicName] = methods[fireTopicName] = function (obj) {
obj = objectify(obj, "value");
obj.slot = slotName;
return this.fire(obj);
};
}
}
////////////////////////////////////////////////////////////////
// Contracts
////////////////////////////////////////////////////////////////
/**
* Return an instance of a frozen Slot.
*
* @param {Object} bson
* @param {baja.Complex} [complex]
*
* @returns {baja.Slot}
*/
function createContractSlot(bson, complex) {
var slotType = bson.st;
// Create frozen Slot
switch (slotType) {
case 'p': return new FrozenProperty(bson, complex);
case 'a': return new FrozenAction(bson);
case 't': return new FrozenTopic(bson);
default:
throw new Error("Invalid BSON: Cannot decode: " + JSON.stringify(bson));
}
}
/**
* Return return a decoded Contract Slot.
*
* @private
*
* @param {Type} complexType
* @param {baja.Complex} complex the Complex to decode the Slots onto
* @param {Object} bson the BSON to decode
*/
function decodeContractSlot(complexType, complex, bson) {
var slot = createContractSlot(bson, complex),
slotName = bson.n;
// Only auto-generate the Slot methods if Slot doesn't already exist.
// This caters for Slots that are overridden by sub-Types.
if (!complex.$map.contains(slotName)) {
// Auto-generate the methods and copy them over to the complex
generateSlotMethods(complexType, complex, slot);
}
// Add to Slot Map
complex.$map.put(slotName, slot);
}
/**
* Return a decoded array of Slots from a BSON Contract Definition.
*
* @private
*
* @see baja.Slot
*
* @param type the Type.
* @param {baja.Complex} complex the complex instance the Slots are being loaded for.
*/
baja.bson.decodeComplexContract = function (type, complex) {
var clxTypes = [],
t = type,
i,
j,
bson;
// Get a list of all the Super types
while (t && t.isComplex()) {
clxTypes.push(t);
t = t.getSuperType();
}
// Iterate down through the Super Types and build up the Contract list
for (i = clxTypes.length - 1; i >= 0; --i) {
bson = clxTypes[i].getContract();
if (bson) {
for (j = 0; j < bson.length; ++j) {
// Add newly created Slot to array
decodeContractSlot(type, complex, bson[j]);
}
}
}
};
////////////////////////////////////////////////////////////////
// BSON Type Scanning
////////////////////////////////////////////////////////////////
/**
* Scan a BSON object for type information.
*
* @private
*
* @param {Object} bson A chunk of BSON to scan.
* @param {Object} typeSpecs An object that will have type information added to it.
*/
baja.bson.scan = function (bson, typeSpecs) {
if (!bson) {
return;
}
var prop,
i,
nm = bson.nm;
// Ensure we're dealing with a Slot, an AddOp or a SetFacetsOp.
if (nm === "p" || nm === "a" || nm === "t" || nm === "a" || nm === "x") {
// If we've found a Type then record it
if (bson.t) {
typeSpecs[bson.t] = bson.t;
}
// Scan any type dependencies for facets
if (bson.xtd) {
for (i = 0; i < bson.xtd.length; ++i) {
typeSpecs[bson.xtd] = bson.xtd;
}
}
// If this Property specifies some other Type dependencies then pick them up here
if (nm === "p" && bson.td) {
for (i = 0; i < bson.td.length; ++i) {
typeSpecs[bson.td[i]] = bson.td[i];
}
}
// If an Action then record any parameter or return Type
if (nm === "a") {
// Parameter Type
if (bson.apt) {
typeSpecs[bson.apt] = bson.apt;
}
// Return Type
if (bson.art) {
typeSpecs[bson.art] = bson.art;
}
}
// If a Topic then record any event type
if (nm === "t" && bson.tet) {
typeSpecs[bson.tet] = bson.tet;
}
}
// Scan for other Slots
if (bson instanceof Array) {
for (i = 0; i < bson.length; ++i) {
if (bson[i] && (bson[i] instanceof Array || bson[i] instanceof Object)) {
baja.bson.scan(bson[i], typeSpecs);
}
}
} else if (bson instanceof Object) {
for (prop in bson) {
if (bson.hasOwnProperty(prop)) {
if (bson[prop] && (bson[prop] instanceof Array || bson[prop] instanceof Object)) {
baja.bson.scan(bson[prop], typeSpecs);
}
}
}
}
};
/**
* Scan for Types and Contracts that aren't yet loaded into the BajaScript Registry.
*
* @private
*
* @param bson the BSON to scan Types for.
* @param {Function} ok the ok callback.
* @param {Function} [fail] the fail callback.
* @param {baja.comm.Batch} [batch] the optional batch.
* @returns {Promise}
*/
baja.bson.importUnknownTypes = function (bson, ok, fail, batch) {
// Store results in an object as we only want type information added once
var typeSpecs = {};
// Scan the data structure for Slot Type information
baja.bson.scan(bson, typeSpecs);
return baja.importTypes({
"typeSpecs": Object.keys(typeSpecs),
"ok": ok,
"fail": fail || baja.fail,
"batch": batch
});
};
var bsonImportUnknownTypes = baja.bson.importUnknownTypes;
////////////////////////////////////////////////////////////////
// BOG BSON Decoding
////////////////////////////////////////////////////////////////
/**
* Decode and return a Knob.
*
* @private
*
* @param {Object} bson the BSON that contains knob information to decode
* @returns {Object} a decoded value (null if unable to decode)
*/
baja.bson.decodeKnob = function (bson) {
var targetOrd = baja.Ord.make(bson.to);
// TODO: Document these methods
return {
getId: function getId() {
return bson.id;
},
getSourceComponent: function getSourceComponent() {
return null;
},
getSourceSlotName: function getSourceSlotName() {
return bson.ss;
},
getTargetOrd: function getTargetOrd() {
return targetOrd;
},
getTargetSlotName: function getTargetSlotName() {
return bson.ts;
}
};
};
/**
* Decode and return a Relation Knob.
*
* @private
*
* @param {Object} bson the BSON that contains relation knob information to decode
* @returns {Object} a decoded value (null if unable to decode).
*/
baja.bson.decodeRelationKnob = function (bson) {
// TODO: Document these methods
return {
getId: function getId() {
return bson.id;
},
getRelationId: function getRelationId() {
return bson.ri;
},
getRelationTags: function getRelationTags() {
return baja.Facets.DEFAULT.decodeFromString(bson.rt);
},
getRelationOrd: function getRelationOrd() {
return baja.Ord.make(bson.ro);
}
};
};
/**
* Return a decoded value.
*
* @private
*
* @param bson the BSON to decode.
* @param {Object} [cx] the context used when decoding.
* @param {baja.Complex} [parent]
* @returns {baja.Value|null} a decoded value (null if unable to decode).
*/
baja.bson.decodeValue = function (bson, cx, parent) {
cx = cx || {};
// Please note the parent object is designed only to be used internally!
// TODO: Skip this from LoadOp - needed for loading security permissions at some point!
if (!(bson.nm === "p" || bson.nm === "a" || bson.nm === "t")) {
return null;
}
// Decode
var slot = null,
slotType = bajaDef(bson.nm, "p"),
stub = bajaDef(bson.stub, false),
flags,
i, x;
if (parent) {
slot = parent.getSlot(bson.n);
}
// Slot Flags
if (slot !== null) {
if (bson.f !== undefined) {
flags = decodeFlags(bson);
if (flags !== slot.getFlags()) {
parent.setFlags({
"slot": slot,
"flags": flags,
"cx": cx
});
}
}
if (bson.dn !== undefined) {
slot.$setDisplayName(bson.dn);
}
if (!slot.isProperty()) {
return null;
}
} else {
if (slotType !== "p") {
throw new Error("Error decoding Slot from BSON: Missing frozen Slot: " + slotType);
}
}
// Create object used for decoding
var obj;
if (bson.t === undefined) {
// TODO: Should be getDefaultValue()?
obj = slot.$getValue();
} else {
// Get an instance of the Type
obj = baja.$(bson.t);
}
// Decode if a Simple
if (obj.getType().isSimple() && bson.v !== undefined) {
obj = obj.decodeFromString(bson.v);
}
if (bson.d !== undefined && obj instanceof baja.DefaultSimple) {
obj.$displayValue = bson.d;
}
// Decode BSON specifically for baja:Action and baja:Topic
if (obj.getType().isAction()) {
obj.$paramType = decodeActionParamType(bson);
obj.$paramDef = decodeActionParamDefault(bson);
obj.$returnType = decodeActionReturnType(bson);
} else if (obj.getType().isTopic()) {
obj.$eventType = decodeTopicEventType(bson);
}
// Decode Component
if (obj.getType().isComponent()) {
// Decode handle
if (bson.h !== undefined) {
obj.$handle = bson.h;
}
// Decode whether the Component is a Nav Child
if (bson.nc !== undefined) {
obj.$nc = bson.nc === "true";
}
// Decode knobs
if (bson.nk) {
for (x = 0; x < bson.nk.length; ++x) {
obj.$fw("installKnob", baja.bson.decodeKnob(bson.nk[x]), cx);
}
}
// Decode relation knobs
if (bson.nrk) {
for (x = 0; x < bson.nrk.length; ++x) {
obj.$fw("installRelationKnob", baja.bson.decodeRelationKnob(bson.nrk[x]), cx);
}
}
// Decode permissions
if (bson.l && typeof bson.l.p === "string") {
obj.$fw("setPermissions", bson.l.p);
}
// TODO: Handle Component Stub decoding here
if (!stub) {
obj.$bPropsLoaded = true;
}
}
var facets;
if (parent) {
try {
if (bson.dn !== undefined) {
cx.displayName = bson.dn;
}
if (bson.d !== undefined) {
cx.display = bson.d;
}
if (slot !== null) {
parent.set({
"slot": slot,
"value": obj,
"cx": cx
});
} else if (parent.getType().isComponent()) {
facets = baja.Facets.DEFAULT.decodeFromString(bajaDef(bson.x, ""));
flags = decodeFlags(bson);
parent.add({
"slot": bson.n,
"value": obj,
"flags": flags,
"facets": facets,
"cx": cx
});
}
} finally {
cx.displayName = undefined;
cx.display = undefined;
}
}
// Decode kids
if (bson.s) {
for (i = 0; i < bson.s.length; ++i) {
bsonDecodeValue(bson.s[i], cx, obj);
}
}
return obj;
};
bsonDecodeValue = baja.bson.decodeValue;
/**
* decodedAsync will resolve to the decoded value. Use this method if you are
* not sure whether all types in the bson have already been imported; it will
* detect and import all unknown types before it attempts to decoded the value.
*
* @private
*
* @param bson the BSON to decode.
* @param {Object} [cx] the context used when decoding.
* @param {baja.Complex} [parent]
* @returns {Promise<baja.Value|null>} resolves to a decoded value (null if unable to decode).
*/
baja.bson.decodeAsync = function (bson, cx, parent) {
return bsonImportUnknownTypes(bson)
.then(function () {
return bsonDecodeValue(bson, cx, parent);
});
};
////////////////////////////////////////////////////////////////
// BSON Encoding
////////////////////////////////////////////////////////////////
/**
* From a bson object, derive the slot flags as a number. If the flags were
* incorrectly encoded as a number `bson.flags`, use that - otherwise use the
* correct method of decoding `bson.f` from string. See NCCB-25981.
* @param {object} bson
* @returns {number} slot flags, or 0 if not present
*/
function decodeFlags(bson) {
var flags = bson.flags;
if (typeof flags === 'number') { return flags; }
return baja.Flags.decodeFromString(bajaDef(bson.f, "0"));
}
function encodeSlot(parObj, par, slot) {
if (slot === null) {
return;
}
// Encode Slot Flags (if they differ from the default
var value = null, // Property Value
skipv = false; // Skip value
if (slot.isProperty()) {
value = par.get(slot);
if (slot.isFrozen()) {
if (value.equivalent(slot.getDefaultValue())) {
skipv = true;
}
}
} else {
skipv = true;
}
var flags = par.getFlags(slot);
// Skip frozen Slots that have default flags and value
if (flags === slot.getDefaultFlags() && skipv) {
return;
}
// Encode Slot Type
var o = {};
if (slot.isProperty()) {
o.nm = "p";
} else if (slot.isAction()) {
o.nm = "a";
} else if (slot.isTopic()) {
o.nm = "t";
}
// Slot name
o.n = slot.getName();
// Slot Flags if necessary
if (((!slot.isFrozen() && flags !== 0) || (flags !== slot.getDefaultFlags())) && par.getType().isComponent()) {
o.f = baja.Flags.encodeToString(flags);
}
// Slot facets if necessary
var fc = slot.getFacets();
if (!slot.isFrozen() && fc.getKeys().length > 0) {
o.x = fc.encodeToString();
}
if (value !== null && value.getType().isComponent()) {
// Encode handle
if (value.isMounted()) {
o.h = value.getHandle();
}
// TODO: Categories and stub?
}
// TODO: Need to re-evalulate this method by going through BogEncoder.encodeSlot again
if (!skipv && slot.isProperty()) {
o.t = value.getType().getTypeSpec();
encodeVal(o, value);
}
// Now we've encoded the Slot, add it to the Slots array
if (!parObj.s) {
parObj.s = [];
}
parObj.s.push(o);
}
function encodeVal(obj, val) {
var cursor;
var valueType = val.getType(),
valueTypeSpec = valueType.getTypeSpec();
if (valueType.isSimple()) {
//Throw if blacklisted (see BlacklistedTypes) and trying to set a different value
if (valueType.$isBlackListed && !val.equals(baja.$(valueTypeSpec))) {
throw new Error("Cannot add blacklisted types to a component");
} else {
// Encode Simple
obj.v = val.encodeToString();
}
} else {
// Encode Complex
cursor = val.getSlots();
// Encode all of the Slots on the Complex
while (cursor.next()) {
encodeSlot(obj, val, cursor.get());
}
}
}
function encode(name, val) {
var o = { nm: "p" };
// Encode name
if (name !== null) {
o.n = name;
}
if (val.getType().isComponent()) {
// Encode handle
if (val.isMounted()) {
o.h = val.getHandle();
}
// TODO: Encode categories
// TODO: Encode whether this Component is fully loaded or not???
}
o.t = val.getType().getTypeSpec();
encodeVal(o, val);
return o;
}
/**
* Return an encoded BSON value.
*
* @private
*
* @param val the value to encode to BSON.
* @returns encoded BSON value.
*/
baja.bson.encodeValue = function (val) {
return encode(null, val);
};
return baja;
});