# Source: core/meta/object-descriptor.js

    var Montage = require("../core").Montage,
        DerivedDescriptor = require("./derived-descriptor").DerivedDescriptor,
        EventDescriptor = require("./event-descriptor").EventDescriptor,
        ModelModule = require("./model"),
        Promise = require("../promise").Promise,
        PropertyDescriptor = require("./property-descriptor").PropertyDescriptor,
        PropertyValidationRule = require("./validation-rule").PropertyValidationRule,
        deprecate = require("../deprecate");

    var Defaults = {
        name: "default",
        customPrototype: false
    };

    /**
     * @class ObjectDescriptor
     * @extends Montage
     */
    var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends ObjectDescriptor.prototype # */ {

        FileExtension: {
            value: ".mjson"
        },

        constructor: {
            value: function ObjectDescriptor() {
                this._eventDescriptors = [];
                this._propertyDescriptors = [];
                this.addRangeAtPathChangeListener("_propertyDescriptors", this, "_handlePropertyDescriptorsRangeChange");
                this._propertyDescriptorGroups = {};
                Object.defineProperty(this,"_propertyDescriptorsTable",{ value:{}, writable: false});
                Object.defineProperty(this,"_eventPropertyDescriptorsTable",{ value:{}, writable: false});
                this.defineBinding("eventDescriptors", {"<-": "_eventDescriptors.concat(parent.eventDescriptors)"});
            }
        },

        /**
         * @function
         * @param {string} name The name of the object descriptor
         * @returns itself
         */
        initWithName: {
            value: function (name) {
                this._name = (name !== null ? name : "default");
                this.customPrototype = false;

                return this;
            }
        },

        serializeSelf: {
            value:function (serializer) {
                serializer.setProperty("name", this.name);
                if ((this._model) && (!this.model.isDefault)) {
                    serializer.setProperty("model", this._model, "reference");
                }

                if (this.objectDescriptorInstanceModule) {
                    serializer.setProperty("objectDescriptorModule", this.objectDescriptorInstanceModule);
                }
                if (this._parent) {
                    serializer.setProperty("parent", this._parent);
                }

                this._setPropertyWithDefaults(serializer, "customPrototype", this.customPrototype);
                //
                if (this._propertyDescriptors.length > 0) {
                    serializer.setProperty("propertyDescriptors", this._propertyDescriptors);
                }
                if (Object.getOwnPropertyNames(this._propertyDescriptorGroups).length > 0) {
                    serializer.setProperty("propertyDescriptorGroups", this._propertyDescriptorGroups);
                }
                if (this._eventDescriptors.length > 0) {
                    serializer.setProperty("eventDescriptors", this._eventDescriptors);
                }
                if (this._propertyValidationRules.length > 0) {
                    serializer.setProperty("propertyValidationRules", this._propertyValidationRules);
                }
                if (typeof this.maxAge === "number") {
                    serializer.setProperty("maxAge", this.maxAge);
                }
            }
        },

        deserializeSelf: {
            value:function (deserializer) {
                var value, model, parentReference;
                value = deserializer.getProperty("name");
                if (value !== void 0) {
                    this._name = value;
                }
                value = deserializer.getProperty("model") || deserializer.getProperty("binder");
                if (value) {
                    this._model = value;
                }
                value = deserializer.getProperty("objectDescriptorModule") || deserializer.getProperty("blueprintModule");
                if (value !== void 0) {
                    this.objectDescriptorInstanceModule = value;
                }
                parentReference = deserializer.getProperty("parent");
                if (parentReference) {
                    if (parentReference.promise && parentReference.valueFromReference) {
                        deprecate.deprecationWarningOnce("parent reference via ObjectDescriptorReference", "direct reference with object syntax");
                        this._parentReference = parentReference;
                    } else {
                        this._parent = parentReference;
                    }
                }

                this.customPrototype = this._getPropertyWithDefaults(deserializer, "customPrototype");
                //
                value = deserializer.getProperty("propertyDescriptors") || deserializer.getProperty("propertyBlueprints");
                if (value) {
                    this._propertyDescriptors = value;
                }

                value = deserializer.getProperty("propertyDescriptorGroups") || deserializer.getProperty("propertyBlueprintGroups");
                if (value) {
                    this._propertyDescriptorGroups = value;
                }
                value = deserializer.getProperty("eventDescriptors") || deserializer.getProperty("eventBlueprints");
                if (value) {
                    this._eventDescriptors = value;
                }
                value = deserializer.getProperty("propertyValidationRules");
                if (value) {
                    this._propertyValidationRules = value;
                }
                value = deserializer.getProperty("maxAge");
                if (value) {
                    this.maxAge = value;
                }
            }
        },

        _setPropertyWithDefaults: {
            value:function (serializer, propertyName, value) {
                if (value !== Defaults[propertyName]) {
                    serializer.setProperty(propertyName, value);
                }
            }
        },

        _getPropertyWithDefaults: {
            value:function (deserializer, propertyName) {
                var value = deserializer.getProperty(propertyName);
                return value || this[propertyName] || Defaults[propertyName];
            }
        },

        _name: {
            value: null
        },

        /**
         * Name of the object. The name is used to define the property on the object.
         *
         * This is an accessor. It is not writable or observable.
         *
         * @returns {string} this._name
         */
        name: {
            get: function () {
                return this._name;
            }
        },

        /**
         * This is the canonical way of creating managed objects prototypes.
         *
         * Newly created prototype will be blessed with all the required properties
         * to be well behaved.
         *
         * @function
         * @param {Object} prototype
         * @param {Object} propertyDescriptor
         * @returns newPrototype
         */
        create: {
            value: function (aPrototype, propertyDescriptor, constructorDescriptor) {
                var newConstructor;

                if ((typeof aPrototype === "undefined") || (ObjectDescriptor.prototype.isPrototypeOf(aPrototype))) {
                    var parentCreate = Object.getPrototypeOf(ObjectDescriptor).create;
                    return parentCreate.call(this, (typeof aPrototype === "undefined" ? this : aPrototype), propertyDescriptor, constructorDescriptor);
                }

                if (typeof aPrototype.specialize !== "function" && !propertyDescriptor) {
                    newConstructor = new aPrototype();
                } else {
                    newConstructor = aPrototype.specialize(propertyDescriptor, constructorDescriptor);
                }
                this.ObjectProperty.applyWithObjectDescriptor(newConstructor.prototype, this);
                // We have just created a custom prototype lets use it.
                this.customPrototype = true;

                return newConstructor;
            }
        },

        /**
         * Create a new instance of the target prototype for the object descriptor.
         * @function
         * @returns new instance
         */
        newInstance: {
            value: function () {
                var prototype = this.newInstancePrototype();
                return (prototype ? new prototype() : null);
            }
        },

        /**
         * Returns the target prototype for this object descriptor.
         *
         * **Note:** This method uses the `customPrototype` property to determine
         * if it needs to require a custom prototype or create a default prototype.
         *
         * @function
         * @returns new prototype
         */
        newInstancePrototype: {
            value: function () {
                // FIXME this function is no missing all the data it needs
                var self = this;
                if (this.customPrototype) {
                    throw new Error("Already has customPrototype");
                    /*
                    var resultsPromise = new Promise(function(resolve, reject) {
                        require.async(self.moduleId,
                            function(exports) {
                                resolve(exports);
                            });
                    });
                    return resultsPromise.then(function(exports) {
                            var prototype = exports[self.prototypeName];
                            return (prototype ? prototype : null);
                        }
                    );
                    */
                } else {
                    var parentInstancePrototype = (this.parent ? this.parent.newInstancePrototype() : Montage );
                    var newConstructor = parentInstancePrototype.specialize({
                        // Token class
                        init: {
                            value: function () {
                                return this;
                            }
                        }
                    });
                    this.ObjectProperty.applyWithObjectDescriptor(newConstructor.prototype, this);

                    return (newConstructor ? newConstructor : null);
                }
            }
        },

        /**
         * Return the object descriptor object property for this object descriptor.
         *
         * This will return the default if none is declared.
         *
         * @returns {ObjectProperty}
         */
        ObjectProperty: {
            serializable: false,
            enumerable: true,
            get: function () {
                if (this.model) {
                    return this.model.ObjectProperty;
                }
                return ModelModule.Model.group.defaultObjectDescriptorObjectProperty;
            }
        },

        /**
         * This is used for references only so that we can reload referenced
         * object descriptors.
         */
        objectDescriptorInstanceModule: {
            serializable: false,
            value: null
        },

        /**
         * The identifier is the same as the name and is used to make the
         * serialization of a object descriptor humane.
         * @returns {string}
         * @default `this.name`
         */
        identifier: {
            get: function () {
                // TODO convert UpperCase to lower-case instead of lowercase
                return [
                    "objectDescriptor",
                    (this.name || "unnamed").toLowerCase()
                ].join("_");
            }
        },

        _model: {
            value: null
        },

        /**
         * @returns {Property}
         * @default null
         */
        model: {
            serializable: false,
            get: function () {
                if (! this._model) {
                    this._model = ModelModule.Model.group.defaultModel;
                    this._model.addObjectDescriptor(this);
                }
                return this._model;
            },
            set: function (value) {
                this._model = value;
            }
        },

        _parentReference: {
            value: null
        },

        _parent: {
            value: null
        },

        /**
         * Blueprint parent
         * @type {?ObjectDescriptor}
         */
        parent: {
            serializable: false,
            get: function () {
                return this._parent;
            },
            set: function (objectDescriptor) {
                this._parent = objectDescriptor;
            }
        },

        /**
         * Defines whether the object descriptor should use custom prototype for new
         * instances.
         *
         * Is `true` if the object descriptor needs to require a custom prototype for
         * creating new instances, `false` if new instance are generic prototypes.
         *
         * @property {boolean} value
         * @default false
         */
        customPrototype: {
            value: false
        },

        _propertyDescriptors: {
            value: null
        },

        _handlePropertyDescriptorsRangeChange: {
            value: function (plus, minus, index) {
                var i, n;
                for (i = 0, n = minus.length; i < n; ++i) {
                    minus[i]._owner = null;
                }

                for (i = 0, n = plus.length; i < n; ++i) {
                    plus[i]._owner = plus[i]._owner || this;
                }

            }
        },

        /**
         * @returns {Array.<PropertyDescriptor>}
         */
        propertyDescriptors: {
            get: function () {
                var propertyDescriptors = [];
                propertyDescriptors = propertyDescriptors.concat(this._propertyDescriptors);
                if (this.parent) {
                    propertyDescriptors = propertyDescriptors.concat(this.parent.propertyDescriptors);
                }
                return propertyDescriptors;
            }
        },

        /**
         * Returns all property descriptors that have their serializable property true
         *
         * @method
         * @returns {Array.<PropertyDescriptor>} Arrat of relevant propertyDescriptors
         */

        serializablePropertyDescriptors: {
            get: function () {
                // TODO Add some caching and invalidation when this._propertyDescriptors or this.parent.propertyDescriptors
                // changes, using bindings might be best.
                return this.propertyDescriptors.filter(function(aPropertyDescriptor) {
                    return aPropertyDescriptor.serializable !== false;
                });
            }
        },

        _propertyDescriptorsTable: {
            value: null
        },

        /**
         * Adds a new property descriptor to this object descriptor.
         *
         * If that property descriptor was associated with another object descriptor it will
         * be removed first.
         *
         * @function
         * @param {PropertyDescriptor} property descriptor to be added.
         * @returns the property descriptor
         */
        addPropertyDescriptor: {
            value: function (propertyDescriptor) {
                if (propertyDescriptor !== null && propertyDescriptor.name !== null) {
                    var index = this._propertyDescriptors.indexOf(propertyDescriptor);
                    if (index < 0) {
                        if ((propertyDescriptor.owner !== null) && (propertyDescriptor.owner !== this)) {
                            propertyDescriptor.owner.removePropertyDescriptor(propertyDescriptor);
                        }
                        this._propertyDescriptors.push(propertyDescriptor);
                        this._propertyDescriptorsTable[propertyDescriptor.name] = propertyDescriptor;
                        propertyDescriptor._owner = this;
                    }
                }
                return propertyDescriptor;
            }
        },

        /**
         * Removes a property descriptor from the property descriptor list of this
         * object descriptor.
         *
         * @function
         * @param {PropertyDescriptor} The property descriptor to be removed.
         * @returns the same property descriptor
         */
        removePropertyDescriptor: {
            value: function (propertyDescriptor) {
                if (propertyDescriptor !== null && propertyDescriptor.name !== null) {
                    var index = this._propertyDescriptors.indexOf(propertyDescriptor);
                    if (index >= 0) {
                        this._propertyDescriptors.splice(index, 1);
                        delete this._propertyDescriptorsTable[propertyDescriptor.name];
                        propertyDescriptor._owner = null;
                    }
                }
                return propertyDescriptor;
            }
        },

        /**
         * Return a new property descriptor.
         *
         * **Note:** This is the canonical way of creating new property descriptor
         * in order to enable subclassing.
         * @param {string} name name of the property descriptor to create
         * @param {number} cardinality name of the property descriptor to create
         * @returns {PropertyDescriptor}
         */
        newPropertyDescriptor: {
            value: function (name, cardinality) {
                return new PropertyDescriptor().initWithNameObjectDescriptorAndCardinality(name, this, cardinality);
            }
        },

        /**
         * Convenience to add one property descriptor.
         * @function
         * @param {string} name Add to one property descriptor
         * @returns {PropertyDescriptor}
         */
        addToOnePropertyDescriptorNamed: {
            value: function (name) {
                return this.addPropertyDescriptor(this.newPropertyDescriptor(name, 1));
            }
        },

        /**
         * Convenience to add many property descriptor.
         * @function
         * @param {string} name Add to many property descriptor
         * @returns {PropertyDescriptor}
         */
        addToManyPropertyDescriptorNamed: {
            value: function (name) {
                return this.addPropertyDescriptor(this.newPropertyDescriptor(name, Infinity));
            }
        },

        /**
         * Convenience to add an property descriptor to one relationship.
         * @deprecated
         * @function
         * @param {string} name
         * @param {string} inverse
         * @returns {PropertyDescriptor}
         */
        addToOneAssociationBlueprintNamed: {
            value: function (name, inverse) {
                var relationship = this.addPropertyDescriptor(this.addToOnePropertyDescriptorNamed(name));
                if (inverse) {
                    relationship.valueDescriptor = inverse.owner;
                    inverse.valueDescriptor = this;
                }
                return relationship;
            }
        },

        /**
         * Convenience to add an property descriptor to many relationships.
         * @deprecated
         * @function
         * @param {string} name TODO
         * @param {string} inverse TODO
         * @returns {PropertyDescriptor}
         */
        addToManyAssociationBlueprintNamed: {
            value: function (name, inverse) {
                var relationship = this.addPropertyDescriptor(this.addToManyPropertyDescriptorNamed(name));
                if (inverse) {
                    relationship.valueDescriptor = inverse.owner;
                    inverse.valueDescriptor = this;
                }
                return relationship;
            }
        },

        /**
         * @function
         * @param {string} name
         * @returns {PropertyDescriptor}
         */
        propertyDescriptorForName: {
            value: function (name) {
                var propertyDescriptor = this._propertyDescriptorsTable[name];
                if (propertyDescriptor === undefined) {
                    propertyDescriptor = exports.UnknownPropertyDescriptor;
                    var aPropertyDescriptor, index;
                    for (index = 0; typeof (aPropertyDescriptor = this._propertyDescriptors[index]) !== "undefined"; index++) {
                        if (aPropertyDescriptor.name === name) {
                            propertyDescriptor = aPropertyDescriptor;
                            break;
                        }
                    }
                    this._propertyDescriptorsTable[name] = propertyDescriptor;
                }
                if (propertyDescriptor === exports.UnknownPropertyDescriptor) {
                    propertyDescriptor = null;
                }
                if (!propertyDescriptor && this.parent) {
                    propertyDescriptor = this.parent.propertyDescriptorForName(name);
                }
                return propertyDescriptor;
            }

        },

        _propertyDescriptorGroups: {
            value: null
        },

        /**
         * List of properties descriptor groups names
         * @returns {Array.<PropertyBlueprint>}
         */
        propertyDescriptorGroups: {
            get: function () {
                var groups = [],
                    name;
                for (name in this._propertyDescriptorGroups) {
                    if (this._propertyDescriptorGroups.hasOwnProperty(name)) {
                        groups.push(name);
                    }
                }
                if (this.parent) {
                    groups = groups.concat(this.parent.propertyDescriptorGroups);
                }
                return groups;
            }
        },

        /**
         * Returns the group associated with that name
         * @param {string} name of the group
         * @returns {Array.<PropertyDescriptor>} property descriptor group
         */
        propertyDescriptorGroupForName: {
            value: function (groupName) {
                var group = this._propertyDescriptorGroups[groupName];
                if (!group && this.parent) {
                    group = this.parent.propertyDescriptorGroupForName(groupName);
                }
                return group;
            }
        },

        /**
         * Add a new property descriptor group.
         * @function
         * @param {string} name of the group
         * @returns {Array.<PropertyDescriptor>} new property descriptor group
         */
        addPropertyDescriptorGroupNamed: {
            value: function (groupName) {
                var group = this._propertyDescriptorGroups[groupName];
                if (!group) {
                    group = [];
                    this._propertyDescriptorGroups[groupName] = group;
                }
                return group;
            }
        },

        /**
         * Remove the property descriptor group.
         * @function
         * @param {string} name of the group to remove
         * @returns {Array.<PropertyDescriptor>} removed property descriptor group
         */
        removePropertyDescriptorGroupNamed: {
            value: function (groupName) {
                var group = this._propertyDescriptorGroups[groupName];
                if (!group) {
                    delete this._propertyDescriptorGroups[groupName];
                }
                return group;
            }
        },

        /**
         * Adds a property descriptor to the group name.
         * if the group does not exist creates it.
         * @function
         * @param {string} property to add
         * @param {string} name of the group
         * @returns {Array.<PropertyBlueprint>} property descriptor group
         */
        addPropertyDescriptorToGroupNamed: {
            value: function (propertyDescriptor, groupName) {
                var group = this._propertyDescriptorGroups[groupName],
                    index;
                if (!group) {
                    group = this.addPropertyDescriptorGroupNamed(groupName);
                }
                index = group.indexOf(propertyDescriptor);
                if (index < 0) {
                    group.push(propertyDescriptor);
                }
                return group;
            }
        },

        /**
         * Removes a property descriptor from the group name.
         * @function
         * @param {string} name of the property
         * @param {string} name of the group
         * @returns {Array.<PropertyDescriptor>} property descriptor group
         */
        removePropertyDescriptorFromGroupNamed: {
            value: function (propertyDescriptor, groupName) {
                var group = this._propertyDescriptorGroups[groupName];
                if (group && propertyDescriptor) {
                    var index = group.indexOf(propertyDescriptor);
                    if (index >= 0) {
                        group.splice(index, 1);
                    }
                }
                return group || [];
            }
        },

        _eventDescriptors: {
            value: null
        },

        /**
         * @property {Array.<EventDescriptor>} value
         */
        eventDescriptors: {
            value: null
        },

        _eventPropertyDescriptorsTable: {
            value: null
        },

        /**
         * Adds a new event property descriptor to this object descriptor.
         *
         * If that event property descriptor was associated with another object descriptor it will
         * be removed first.
         *
         * @function
         * @param {string} event property descriptor to be added.
         * @returns {EventPropertyDescriptor}
         */
        addEventDescriptor: {
            value: function (eventDescriptor) {
                if (eventDescriptor && eventDescriptor.name) {
                    var index = this._eventDescriptors.indexOf(eventDescriptor);
                    if (index < 0) {
                        if (eventDescriptor.owner && eventDescriptor.owner !== this) {
                            eventDescriptor.owner.removeEventDescriptor(eventDescriptor);
                        }
                        this._eventDescriptors.push(eventDescriptor);
                        this._eventPropertyDescriptorsTable[eventDescriptor.name] = eventDescriptor;
                        eventDescriptor._owner = this;
                    }
                }
                return eventDescriptor;
            }
        },

        /**
         * Removes an property descriptor from the property descriptor list of this
         * object descriptor.
         * @function
         * @param {Object} property descriptor The property descriptor to be removed.
         * @returns {PropertyDescriptor}
         */
        removeEventDescriptor: {
            value: function (eventDescriptor) {
                if (eventDescriptor && eventDescriptor.name) {
                    var index = this._eventDescriptors.indexOf(eventDescriptor);
                    if (index >= 0) {
                        this._eventDescriptors.splice(index, 1);
                        delete this._eventPropertyDescriptorsTable[eventDescriptor.name];
                        eventDescriptor._owner = null;
                    }
                }
                return eventDescriptor;
            }
        },

        /**
         * Return a new event property descriptor.
         * **Note:** This is the canonical way of creating new event property descriptors in
         * order to enable subclassing.
         * @param {string} name name of the event property descriptor to create
         */
        newEventDescriptor: {
            value: function (name) {
                return new EventDescriptor().initWithNameAndObjectDescriptor(name, this);
            }
        },

        /**
         * Convenience to add an event descriptor.
         * @function
         * @param {string} name
         * @returns {EventDescriptor}
         */
        addEventDescriptorNamed: {
            value: function (name) {
                return this.addEventDescriptor(this.newEventDescriptor(name));
            }
        },

        /**
         * @function
         * @param {string} name
         * @returns {EventDescriptor}
         */
        eventDescriptorForName: {
            value: function (name) {
                var eventDescriptor = this._eventPropertyDescriptorsTable[name];
                if (typeof eventDescriptor === "undefined") {
                    eventDescriptor = exports.UnknownEventDescriptor;
                    var anEventPropertyDescriptor, index;
                    for (index = 0; typeof (anEventPropertyDescriptor = this._eventDescriptors[index]) !== "undefined"; index++) {
                        if (anEventPropertyDescriptor.name === name) {
                            eventDescriptor = anEventPropertyDescriptor;
                            break;
                        }
                    }
                    this._eventPropertyDescriptorsTable[name] = eventDescriptor;
                }

                // TODO: Come back after creating event property descriptor
                if (eventDescriptor === exports.UnknownEventDescriptor) {
                    eventDescriptor = null;
                }

                if (!eventDescriptor && this.parent) {
                    eventDescriptor = this.parent.eventDescriptorForName(name);
                }
                return eventDescriptor;
            }

        },

        _propertyValidationRules: {
            value: {}
        },

        /**
         * Gets the list of properties validation rules.
         * @returns {Array.<PropertyValidationRule>} copy of the list of properties
         * validation rules.
         */
        propertyValidationRules: {
            get: function () {
                var propertyName,
                    propertyValidationRules = [];
                for (propertyName in this._propertyValidationRules) {
                    if (this._propertyValidationRules.hasOwnProperty(propertyName)) {
                        propertyValidationRules.push(this._propertyValidationRules[propertyName]);
                    }
                }
                if (this.parent) {
                    propertyValidationRules = propertyValidationRules.concat(this.parent.propertyValidationRules);
                }
                return propertyValidationRules;
            }
        },

        /**
         * Returns the properties validation rule for that name
         * @param {string} name of the rule
         * @returns {PropertyDescription} property description
         */
        propertyValidationRuleForName: {
            value: function (name) {
                var propertyValidationRule = this._propertyValidationRules[name];
                if (!propertyValidationRule && this.parent) {
                    propertyValidationRule = this.parent.propertyValidationRuleForName(name);
                }
                return propertyValidationRule;
            }
        },

        /**
         * Add a new properties validation rule .
         * @function
         * @param {string} name of the rule
         * @returns {PropertyDescription} new properties validation rule
         */
        addPropertyValidationRule: {
            value: function (name) {
                var propertyValidationRule = this._propertyValidationRules[name];
                if (!propertyValidationRule) {
                    propertyValidationRule = new PropertyValidationRule().initWithNameAndObjectDescriptor(name, this);
                    this._propertyValidationRules[name] = propertyValidationRule;
                }
                return propertyValidationRule;
            }
        },

        /**
         * Remove the properties validation rule  for the name.
         * @function
         * @param {string} name of the rule
         * @returns {PropertyDescription} removed properties validation rule
         */
        removePropertyValidationRule: {
            value: function (name) {
                var propertyValidationRule = this._propertyValidationRules[name];
                if (propertyValidationRule) {
                    delete this._propertyValidationRules[name];
                }
                return propertyValidationRule;
            }
        },

        /**
         * Evaluates the rules based on the object and the properties.
         * @param {Object} object instance to evaluate the rule for
         * @returns {Array.<string>} list of message key for rule that fired. Empty
         * array otherwise.
         */
        evaluateRules: {
            value: function (objectInstance) {
                var name, rule,
                    messages = [];

                for (name in this._propertyValidationRules) {
                    if (this._propertyValidationRules.hasOwnProperty(name)) {
                        rule = this._propertyValidationRules[name];
                        if (rule.evaluateRule(objectInstance)) {
                            messages.push(rule.messageKey);
                        }
                    }
                }
                return messages;
            }
        },

        objectDescriptorModuleId: require("../core")._objectDescriptorModuleIdDescriptor,
        objectDescriptor: require("../core")._objectDescriptorDescriptor,

        /**********************************************************************************
         * Deprecated methods.
         */

        /**
         * Add a new event descriptor to this object descriptor.
         *
         * If that event descriptor was associated with another object descriptor it will
         * be removed first.
         *
         * @function
         * @param {string} property descriptor The property descriptor to be added.
         * @returns {EventDescriptor}
         */
        addEventBlueprint: {
            value: deprecate.deprecateMethod(void 0, function (eventBlueprint) {
                return this.addEventDescriptor(eventBlueprint);
            }, "addEventBlueprint", "addEventDescriptor")
        },

        /**
         * Convenience to add an event descriptor.
         * @function
         * @param {string} name
         * @returns {EventDescriptor}
         */
        addEventBlueprintNamed: {
            value: deprecate.deprecateMethod(void 0, function (name) {
                return this.addEventDescriptorNamed(name);
            }, "addEventBlueprintNamed", "addEventDescriptorNamed")
        },

        /**
         * Add a new property descriptor to this object descriptor.
         *
         * If that property descriptor was associated with another object descriptor it will
         * be removed first.
         * @deprecated
         * @function
         * @param {PropertyDescriptor} property descriptor The property descriptor to
         * be added.
         * @returns the property descriptor
         */
        addPropertyBlueprint: {
            value: deprecate.deprecateMethod(void 0, function (propertyBlueprint) {
                this.addPropertyDescriptor(propertyBlueprint);
            }, "addPropertyBlueprint", "addPropertyDescriptor")
        },

        /**
         * Add a new property descriptor group.
         * @function
         * @param {string} name of the group
         * @returns {Array.<PropertyBlueprint>} new property descriptor group
         */
        addPropertyBlueprintGroupNamed: {
            value: deprecate.deprecateMethod(void 0, function (groupName) {
                this.addPropertyDescriptorGroupNamed(groupName);
            }, "addPropertyBlueprintGroupNamed", "addPropertyDescriptorGroupNamed")
        },

        /**
         * Adds a property descriptor to the group name.
         * if the group does not exist creates it.
         * @function
         * @param {string} property to add
         * @param {string} name of the group
         * @returns {Array.<PropertyBlueprint>} property descriptor group
         */
        addPropertyBlueprintToGroupNamed: {
            value: deprecate.deprecateMethod(void 0, function (propertyBlueprint, groupName) {
                this.addPropertyDescriptorToGroupNamed(propertyBlueprint, groupName);
            }, "addPropertyBlueprintToGroupNamed", "addPropertyDescriptorToGroupNamed")
        },

        /**
         * @deprecated
         */
        addToOnePropertyBlueprintNamed: {
            value: deprecate.deprecateMethod(void 0, function (name) {
                return this.addToOnePropertyDescriptorNamed(name);
            }, "addToOnePropertyBlueprintNamed", "addToOnePropertyDescriptorNamed")
        },

        /**
         * @deprecated
         */
        addToManyPropertyBlueprintNamed: {
            value: deprecate.deprecateMethod(void 0, function (name) {
                return this.addToManyPropertyDescriptorNamed(name);
            }, "addToManyPropertyBlueprintNamed", "addToManyPropertyDescriptorNamed")
        },

        /**
         * @deprecated
         * This is used for references only so that we can reload referenced
         * object descriptors.
         */
        blueprintInstanceModule: {
            serializable: false,
            get: deprecate.deprecateMethod(void 0, function () {
                return this.objectDescriptorInstanceModule;
            }, "blueprintInstanceModule.get", "objectDescriptorInstanceModule.get"),
            set: deprecate.deprecateMethod(void 0, function (value) {
                this.objectDescriptorInstanceModule = value;
            }, "blueprintInstanceModule.set", "objectDescriptorInstanceModule.set")
        },

        /**
         * @deprecated
         * @returns {Model}
         * @default null
         */
        binder: {
            serializable: false,
            get: deprecate.deprecateMethod(void 0, function () {
                return this.model;
            }, "binder.get", "model.get"),
            set: deprecate.deprecateMethod(void 0, function (value) {
                this.model = value;
            }, "binder.set", "model.set")
        },

        /**
         * @deprecated
         * @function
         * @param {string} name
         * @returns {EventDescriptor}
         */
        eventBlueprintForName: {
            value: deprecate.deprecateMethod(void 0, function (name) {
                return this.eventDescriptorForName(name);
            }, "eventBlueprintForName", "eventDescriptorForName")
        },

        /**
         * @property {Array.<EventDescriptor>} value
         */
        eventBlueprints: {
            // value: null
            get: deprecate.deprecateMethod(void 0, function () {
                return this.eventDescriptors;
            }, "eventBlueprints.get", "eventDescriptors.get"),
            set: deprecate.deprecateMethod(void 0, function (value) {
                this.eventDescriptors = value;
            }, "eventBlueprints.set", "eventDescriptors.set")
        },

        /**
         * Return a new property descriptor.
         * **Note:** This method is deprecated use addToOnePropertyDescriptor
         * and addToManyPropertyDescriptor to model relationships.
         * @deprecated
         * @param {string} name of the property descriptor to create
         * @param {number} cardinality name of the property descriptor to create
         * @returns {PropertyDescriptor}
         */
        // TODO: Deprecate -- discuss with Benoit.
        newAssociationBlueprint: {
            value: function (name, cardinality) {
                // TODO: Implement.
                return cardinality === 1 ?  this.addToOnePropertyDescriptorNamed(name) :
                                            this.addToManyPropertyDescriptorNamed(name);
            }
        },

        /**
         * Return a new derived descriptor.
         * **Note:** This is the canonical way of creating new derived descriptors.
         * in order to enable subclassing.
         * @deprecated
         * @param {string} name of the derived descriptor to create
         * @param {number} the cardinality to use for the descriptor.
         * @returns {DerivedDescriptor}
         */
        newDerivedPropertyBlueprint: {
            value: deprecate.deprecateMethod(void 0, function (name, cardinality) {
                return this.newDerivedDescriptor(name, cardinality);
            }, "newDerivedPropertyBlueprint", "newDerivedDescriptor")
        },

        /**
         * Return a new derived descriptor.
         * **Note:** This is the canonical way of creating new derived descriptors.
         * in order to enable subclassing.
         * @deprecated
         * @param {string} name of the derived descriptor to create
         * @param {number} the cardinality to use for the descriptor.
         * @returns {DerivedDescriptor}
         */
        newDerivedDescriptor: {
            value: function (name, cardinality) {
                return new DerivedDescriptor().initWithNameObjectDescriptorAndCardinality(name, this, cardinality);
            }
        },

        /**
         * Return a new event descriptor.
         * **Note:** This is the canonical way of creating new event descriptor in
         * order to enable subclassing.
         * @deprecated
         * @param {string} name name of the event descriptor to create
         */
        newEventBlueprint: {
            value: deprecate.deprecateMethod(void 0, function (name) {
                return this.newEventDescriptor(name);
            }, "newEventBlueprint", "newEventDescriptor")
        },

        /**
         * Return a new property descriptor.
         *
         * **Note:** This is the canonical way of creating new property descriptors
         * in order to enable subclassing.
         * @deprecated
         * @param {string} name name of the property descriptor to create
         * @param {number} cardinality name of the property descriptor to create
         * @returns {PropertyDescriptor}
         */
        newPropertyBlueprint: {
            value: deprecate.deprecateMethod(void 0, function (name, cardinality) {
                return this.newPropertyDescriptor(name, cardinality);
            }, "newPropertyBlueprint", "newPropertyDescriptor")
        },

        /**
         * @deprecated
         * @function
         * @param {string} name
         * @returns {PropertyDescriptor}
         */
        propertyBlueprintForName: {
            value: deprecate.deprecateMethod(void 0, function (name) {
                return this.propertyDescriptorForName(name);
            }, "propertyBlueprintForName", "propertyDescriptorForName")
        },

        /**
         * List of properties descriptor groups names
         * @deprecated
         * @returns {Array.<PropertyDescriptor>}
         */
        propertyBlueprintGroups: {
            get: deprecate.deprecateMethod(void 0, function () {
                return this.propertyDescriptorGroups;
            }, "propertyBlueprintGroups", "propertyDescriptorGroups")
        },

        /**
         * Returns the group associated with that name
         * @deprecated
         * @param {string} name of the group
         * @returns {Array.<PropertyDescriptor>} property descriptor group
         */
        propertyBlueprintGroupForName: {
            value: deprecate.deprecateMethod(void 0, function (groupName) {
                return this.propertyDescriptorGroupForName(groupName);
            }, "propertyBlueprintGroupForName", "propertyDescriptorForName")
        },

        /**
         * Returns the group associated with that name
         * @deprecated
         */
        propertyBlueprints: {
            get: deprecate.deprecateMethod(void 0, function () {
                return this.propertyDescriptors;
            }, "propertyBlueprints", "propertyDescriptors")
        },

        /**
         * Removes a property descriptor from the property descriptor list of this
         * object descriptor.
         * @deprecated
         * @function
         * @param {Object} event descriptor The event descriptor to be removed.
         * @returns {PropertyDescriptor}
         */
        removeEventBlueprint: {
            value: deprecate.deprecateMethod(void 0, function (eventBlueprint) {
                this.removeEventDescriptor(eventBlueprint);
            }, "removeEventBlueprint", "removeEventDescriptor")
        },

        /**
         * Removes a property descriptor from the property descriptor list of this
         * object descriptor.
         *
         * @deprecated
         * @function
         * @param {PropertyDescriptor} property descriptor The property descriptor to
         * be removed.
         * @returns the same property descriptor
         */
        removePropertyBlueprint: {
            value: deprecate.deprecateMethod(void 0, function (propertyBlueprint) {
                this.removePropertyDescriptor(propertyBlueprint);
            }, "removePropertyBlueprint", "removePropertyDescriptor")
        },

        /**
         * Removes a property descriptor from the group name.
         * @deprecated
         * @function
         * @param {string} name of the property
         * @param {string} name of the group
         * @returns {Array.<PropertyDescriptor>} property descriptor group
         */
        removePropertyBlueprintFromGroupNamed: {
            value: deprecate.deprecateMethod(void 0, function (propertyBlueprint, groupName) {
                this.removePropertyDescriptorFromGroupNamed(propertyBlueprint, groupName);
            }, "removePropertyBlueprintFromGroupNamed", "removePropertyDescriptorGroupNamed")
        },

        /**
         * Remove the property descriptor group.
         * @deprecated
         * @function
         * @param {string} name of the group to remove
         * @returns {Array.<PropertyDescriptor>} removed property descriptor group
         */
        removePropertyBlueprintGroupNamed: {
            value: deprecate.deprecateMethod(void 0, function (groupName) {
                this.removePropertyDescriptorGroupNamed(groupName);
            }, "removePropertyBlueprintGroupNamed", "removePropertyDescriptorGroupNamed")
        },

         /**
         *  Specifies the maximum amount of time that the values of on object
         * described by an ObjectDescriptor will be considered fresh.
         *
         * Value is in seconds, default to 4 minutes
         *
         * @returns {number} this.maxAge
         */
        maxAge: {
            value: 240
        },

        blueprintModuleId:require("../core")._objectDescriptorModuleIdDescriptor,
        blueprint: require("../core")._objectDescriptorDescriptor

    }, {

        /**
         * @deprecated
         * Creates a default object descriptor with all enumerable properties.
         *
         * **Note:** Value type are set to the string default.
         */
        createDefaultBlueprintForObject: {
            value: deprecate.deprecateMethod(void 0, function (object) {
                return ObjectDescriptor.createDefaultObjectDescriptorForObject(object);
            }, "Blueprint.createDefaultBlueprintForObject", "ObjectDescriptor.createDefaultObjectDescriptorForObject")
        },

        /**
         * Creates a default object descriptor with all enumerable properties.
         *
         * **Note:** Value type are set to the string default.
         */
        createDefaultObjectDescriptorForObject: {
            value:function (object) {
                if (object) {
                    var target = Montage.getInfoForObject(object).isInstance ? Object.getPrototypeOf(object) : object.prototype;
                    var info = Montage.getInfoForObject(target);

                    // Create `new this()` so that subclassing works
                    var newObjectDescriptor = new this();

                    for (var name in target) {
                        if (name[0] !== "_" && target.hasOwnProperty(name)) {
                            // We don't want to list private properties
                            var value = target[name];
                            var propertyDescriptor;
                            if (Array.isArray(value)) {
                                propertyDescriptor = newObjectDescriptor.addToManyPropertyDescriptorNamed(name);
                            } else {
                                propertyDescriptor = newObjectDescriptor.addToOnePropertyDescriptorNamed(name);
                            }
                            newObjectDescriptor.addPropertyDescriptorGroupNamed(propertyDescriptor, info.objectName);
                        }
                    }
                    var parentObject = Object.getPrototypeOf(target);
                    if (parentObject && "objectDescriptor" in parentObject) {
                        return parentObject.objectDescriptor.then(function (objectDescriptor) {
                            newObjectDescriptor.parent = objectDescriptor;
                            return newObjectDescriptor;
                        });
                    } else {
                        return Promise.resolve(newObjectDescriptor);
                    }
                } else {
                    return Promise.resolve(exports.UnknownObjectDescriptor);
                }
            }
        }
    });

    exports.UnknownObjectDescriptor = Object.freeze(new ObjectDescriptor().initWithName("Unknown"));
    exports.UnknownPropertyDescriptor = Object.freeze(new PropertyDescriptor().initWithNameObjectDescriptorAndCardinality("Unknown", null, 1));
    exports.UnknownEventDescriptor = Object.freeze(new EventDescriptor().initWithNameAndObjectDescriptor("Unknown", null));