/**
* @module montage/data/object-property
* @requires montage/data/pledge
* @requires montage/core/core
* @requires montage/core/exception
* @requires montage/core/logger
*/
var Montage = require("../core").Montage,
Exception = require("../exception").Exception,
Model = require("./model").Model,
deprecate = require("../deprecate"),
logger = require("../logger").logger("object-property");
/**
* @class ObjectProperty
* @extends Montage
*/
exports.ObjectProperty = Montage.specialize( /** @lends ObjectProperty# */ {
/**
* @function
* @returns itself
*/
init: {
serializable: false,
enumerable: false,
value: function () {
return this;
}
},
/**
* Add all the properties defined in the object descriptor to the target prototype.
*
* If the object descriptor is null, this method will make a best attempt to locate
* it.
*
* @function
* @param {Object} prototype
* @param {Blueprint} blueprint
*/
apply: {
value: function (prototype, objectDescriptor) {
var info;
if (!prototype.hasOwnProperty("objectDescriptor")) {
info = Montage.getInfoForObject(prototype);
if (info !== null && info.isInstance === false) {
if (objectDescriptor === undefined) {
objectDescriptor = Model.group.objectDescriptorForPrototype(info.objectName, info.moduleId);
} else if (objectDescriptor.prototypeName !== info.objectName || objectDescriptor.moduleId !== info.moduleId) {
// Something is wrong, the hierarchies are out of whack
objectDescriptor = null;
}
this.applyWithObjectDescriptor(prototype, objectDescriptor);
}
}
}
},
/**
* Add all the properties defined in the blueprint to the target prototype.
*
* **Note:** This method will explore the blueprint hierarchy recursively.
*
* @function
* @param {Object} prototype
* @param {Blueprint} blueprint
*/
applyWithBlueprint: {
value: deprecate.deprecateMethod(void 0, function (prototype, objectDescriptor) {
return this.applyWithObjectDescriptor(prototype, objectDescriptor);
}, "applyWithBlueprint", "applyWithObjectDescriptor")
},
applyWithObjectDescriptor: {
value: function (prototype, objectDescriptor) {
if (objectDescriptor !== null) {
this.addProperties(prototype, objectDescriptor);
if (objectDescriptor.parent !== null) {
this.apply(Object.getPrototypeOf(prototype), objectDescriptor);
}
}
}
},
/**
* Add all the properties defined in the blueprint to the target prototype.
*
* @function
* @param {Object} prototype
* @param {Blueprint} blueprint
*/
addProperties: {
value: function (prototype, objectDescriptor) {
//for loop over attributes
var i = 0, attribute;
while ((attribute = objectDescriptor.propertyDescriptors[i++])) {
if (attribute.isDerived) {
this.addDerivedProperty(prototype, attribute);
} else if (attribute.isAssociation) {
// TODO: How to handle this?
this.addAssociation(prototype, attribute);
} else {
this.addProperty(prototype, attribute);
}
}
// For backwards compatibility.
Montage.defineProperty(prototype, "blueprint", { enumerable: false, serializable: false, get: function () {
return this._objectDescriptor;
}});
Montage.defineProperty(prototype, "_objectDescriptor", { serializable: false, enumerable: false, value: objectDescriptor });
Montage.defineProperty(prototype, "objectDescriptor", { enumerable: false, serializable: false, get: function () {
return this._objectDescriptor;
}});
// TODO: Determine if it is safe to remove blueprintGet && blueprintSet?
// Enable access to the 'inherited' get method for easy override.
Montage.defineProperty(prototype, "blueprintGet", { serializable: false, enumerable: false, value: this.objectDescriptorGet});
// Enable access to the 'inherited' set method for easy override.
Montage.defineProperty(prototype, "blueprintSet", { serializable: false, enumerable: false, value: this.objectDescriptorSet});
// Enable access to the 'inherited' get method for easy override.
Montage.defineProperty(prototype, "objectDescriptorGet", { serializable: false, enumerable: false, value: this.objectDescriptorGet});
// Enable access to the 'inherited' set method for easy override.
Montage.defineProperty(prototype, "objectDescriptorSet", { serializable: false, enumerable: false, value: this.objectDescriptorSet});
}
},
/**
* Add one property defined in the attribute to the target prototype.
*
* @function
* @param {Prototype} prototype
* @param {Attribute} attribute
*/
addProperty: {
value: function (prototype, attribute) {
this.addPropertyStorage(prototype, attribute);
this.addPropertyDefinition(prototype, attribute);
this.addPropertyStoredValue(prototype, attribute);
}
},
/**
* @function
* @param {Object} prototype
* @param {Attribute} attribute
*/
addPropertyStorage: {
value: function (prototype, attribute) {
var storageKey = "_" + attribute.name,
lazyStorageKey = "_" + storageKey,
storageDefinition = null;
if (!prototype.hasOwnProperty(storageKey)) {
if (attribute.isToMany) {
Montage.defineProperty(prototype, lazyStorageKey, {
value: null,
enumerable: false,
serializable: false
});
storageDefinition = {
get: function() {
return this[lazyStorageKey] || (this[lazyStorageKey] = []);
},
enumerable: false,
serializable: true
};
} else {
storageDefinition = {
value: null,
enumerable: false,
serializable: true
};
}
Montage.defineProperty(prototype, storageKey, storageDefinition);
} else {
if (logger.isError) {
logger.error("We have an issue here. The developer should not override the storage value for " + storageKey + ".");
}
}
}
},
/**
* @function
* @param {Property} prototype TODO
* @param {Object} attribute TODO
*/
addPropertyDefinition: {
value: function (prototype, attribute) {
var propertyKey = attribute.name,
propertyDefinition = null;
if (!prototype.hasOwnProperty(propertyKey)) {
propertyDefinition = {
get: function () {
return this.objectDescriptorGet(propertyKey);
},
enumerable: true,
serializable: false
};
if (!attribute.readOnly) {
propertyDefinition.set = function (value) {
return this.objectDescriptorSet(propertyKey, value);
};
}
Montage.defineProperty(prototype, propertyKey, propertyDefinition);
} else {
if (logger.isDebug) {
logger.debug("The developer has already created the property " + propertyKey + " method do nothing.");
}
}
}
},
/**
* This is the get function called on the target object to access
* properties.
*
* @function
* @param {string} propertyName
* @returns {PropertyDescriptor}
*/
blueprintGet: {
value: deprecate.deprecateMethod(void 0, function (propertyName) {
return this.objectDescriptorGet(propertyName);
}, "blueprintGet", "objectDescriptorGet"),
enumerable: false,
serializable: false
},
/**
* This is the get function called on the target object to access
* properties.
*
* @function
* @param {string} propertyName
* @returns {PropertyDescriptor}
*/
objectDescriptorGet: {
value: function (propertyName) {
var propertyDescriptor = this.objectDescriptor.propertyDescriptorForName(propertyName),
storageKey = "_" + propertyDescriptor.name;
return this[storageKey];
},
enumerable: false,
serializable: false
},
/**
* This is the get function called on the target object to set
* properties.
*
* @function
* @param {string} propertyName
* @param {PropertyBlueprint} value
*/
blueprintSet: {
value: deprecate.deprecateMethod(void 0, function (propertyName, value) {
return this.objectDescriptorSet(propertyName, value);
}, "blueprintSet", "objectDescriptorSet"),
enumerable: false,
serializable: false
},
/**
* This is the get function called on the target object to set
* properties.
*
* @function
* @param {string} propertyName
* @param {PropertyDescriptor} value
*/
objectDescriptorSet: {
value: function (propertyName, value) {
var propertyDescriptor = this.objectDescriptor.propertyDescriptorForName(propertyName),
storageKey = "_" + propertyDescriptor.name;
if (value === null && propertyDescriptor.denyDelete) {
throw new Exception().initWithMessageTargetAndMethod("Deny Delete", this, propertyDescriptor.name);
} else {
this[storageKey] = value;
}
},
enumerable: false,
serializable: false
},
/**
* @function
* @param {Object} prototype
* @param {Attribute} attribute
*/
addPropertyStoredValue: {
value: function (prototype, attribute) {
var storedValueKey = attribute.name + "$Storage",
privateStoredValueKey = "_" + storedValueKey,
storedValueDefinition = null;
if (!prototype.hasOwnProperty(storedValueKey)) {
if (attribute.isToMany) {
Montage.defineProperty(prototype, privateStoredValueKey, {
value: null,
enumerable: false,
serializable: false
});
storedValueDefinition = {
get: function() {
return this[privateStoredValueKey] || (this[privateStoredValueKey] = []);
},
enumerable: false,
serializable: false
};
} else {
storedValueDefinition = {
value: null,
enumerable: false,
serializable: false
};
}
Montage.defineProperty(prototype, storedValueKey, storedValueDefinition);
} else {
if (logger.isError) {
logger.error("We have an issue here. The developer should not override the stored value for " + storedValueKey + ".");
}
}
}
},
/**
* Adds a relationship management methods to the managed object.
* @function
* @param {Object} prototype
* @param {Attribute} attribute relationship to add
*/
// TODO: Part of deprecating associations in preference to property descriptor with a value descriptor.
addAssociation: {
value: function (prototype, attribute) {
this.addPropertyStorage(prototype, attribute);
this.addAssociationDefinition(prototype, attribute);
this.addPropertyStoredValue(prototype, attribute);
}
},
/**
* @function
* @param {Object} prototype
* @param {Attribute} attribute
*/
// TODO: Part of deprecating associations in preference to property descriptor with a value descriptor.
addAssociationDefinition: {
value: function (prototype, attribute) {
if (attribute.isToMany) {
this.addToManyAssociationDefinition(prototype, attribute);
} else {
this.addToOneAssociationDefinition(prototype, attribute);
}
}
},
/**
* @function
* @param {Object} prototype
* @param {Attribute} attribute
*/
// TODO: Part of deprecating associations in preference to property descriptor with a value descriptor.
addToOneAssociationDefinition: {
value: function (prototype, attribute) {
var relationshipKey = attribute.name.toCapitalized();
var key = "addTo" + relationshipKey;
if (!prototype.hasOwnProperty(key)) {
Montage.defineProperty(prototype, key, { serializable: false, enumerable: false, value: function () {
return null;
}});
} else {
if (logger.isError) {
logger.error("We have an issue here. The developer should not override the method " + key + ".");
}
}
key = "removeFrom" + relationshipKey;
if (!prototype.hasOwnProperty(key)) {
Montage.defineProperty(prototype, key, { serializable: false, enumerable: false, value: function () {
return null;
}});
} else {
if (logger.isError) {
logger.error("We have an issue here. The developer should not override the method " + key + ".");
}
}
key = "clear" + relationshipKey;
if (!prototype.hasOwnProperty(key)) {
Montage.defineProperty(prototype, key, { serializable: false, enumerable: false, value: function () {
return null;
}});
} else {
if (logger.isError) {
logger.error("We have an issue here. The developer should not override the method " + key + ".");
}
}
}
},
/**
* @function
* @param {Object} prototype
* @param {Association} attribute
*/
// TODO: Part of deprecating associations in preference to property descriptor with a value descriptor.
addToManyAssociationDefinition: {
value: function (prototype, attribute) {
var relationshipKey = attribute.name.toCapitalized();
var key = "addTo" + relationshipKey;
if (!prototype.hasOwnProperty(key)) {
Montage.defineProperty(prototype, key, { serializable: false, enumerable: false, value: function () {
return null;
}});
} else {
if (logger.isError) {
logger.error("We have an issue here. The developer should not override the method " + key + ".");
}
}
key = "removeFrom" + relationshipKey;
if (!prototype.hasOwnProperty(key)) {
Montage.defineProperty(prototype, key, { serializable: false, enumerable: false, value: function () {
return null;
}});
} else {
if (logger.isError) {
logger.error("We have an issue here. The developer should not override the method " + key + ".");
}
}
key = "clear" + relationshipKey;
if (!prototype.hasOwnProperty(key)) {
Montage.defineProperty(prototype, key, { serializable: false, enumerable: false, value: function () {
return null;
}});
} else {
if (logger.isError) {
logger.error("We have an issue here. The developer should not override the method " + key + ".");
}
}
}
},
/**
* Adds a derived attribute to the managed object.
* @function
* @param {Object} prototype
* @param {Attribute} attribute
*/
addDerivedProperty: {
value: function (prototype, attribute) {
}
}
});