Source: core/meta/object-property.js


/**
 * @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) {
        }
    }

});