# Source: data/model/data-object-descriptor.js

    var ObjectDescriptor = require("./object-descriptor").ObjectDescriptor,
        DataPropertyDescriptor = require("./data-property-descriptor").DataPropertyDescriptor;

    /**
     * Extends an object descriptor with the additional object information needed by
     * Montage Data.
     *
     * @deprecated
     * @class
     * @extends ObjectDescriptor
     */
    exports.DataObjectDescriptor = ObjectDescriptor.specialize(/** @lends DataObjectDescriptor.prototype */ {

        /**
         * The names of the properties containing the identifier for this type of
         * data object.
         *
         * @type {Array.<string>}
         */
        identifierNames: {
            value: []
        },

        /**
         * Descriptors of the properties of data objects of this type, by property
         * name.
         *
         * The returned map should not be modified.
         * [setPropertyDescriptor()]{@link DataObjectDescriptor#setPropertyDescriptor}
         * and
         * [deletePropertyDescriptor()]{@link ObjectDescriptor#deletePropertyDescriptor}
         * should be used instead to modify the property descriptors.
         *
         * @type {Object.<string, DataPropertyDescriptor>}
         */
        propertyDescriptors: {
            get: function () {
                // This returns the same value as the superclass property of the
                // same name, only the JSDoc type is different.
                return Object.getOwnPropertyDescriptor(ObjectDescriptor.prototype, "propertyDescriptors").get.call(this);
            }
        },

        /**
         * Add or replace a property descriptor.
         *
         * @method
         * @argument {string} name                       - The name of the property.
         * @argument {DataPropertyDescriptor} descriptor - Describes the property.
         */
        setPropertyDescriptor: {
            // This does the same thing as the superclass method of the same name,
            // only the JSDoc type is different.
            value: function (name, descriptor) {
                ObjectDescriptor.prototype.setPropertyDescriptor.call(this, name, descriptor);
            }
        },

        /**
         * Create a property descriptor.
         *
         * This overrides the
         * [superclass implementation]{@link PropertyDescriptor#makePropertyDescriptor}
         * to create a {@link DataPropertyDescriptor} instead of a
         * {@link PropertyDescriptor} instance.
         *
         * @method
         * @returns {DataPropertyDescriptor} - The created property descriptor.
         */
        makePropertyDescriptor: {
            value: function () {
                return new DataPropertyDescriptor();
            }
        },

        /**
         * @private
         * @method
         */
        _addRelationships: {
            value: function (relationships) {
                var names, i, n;
                if (relationships) {
                    names = Object.keys(relationships);
                    for (i = 0, n = names.length; i < n; i += 1) {
                        this._addRelationship(names[i], relationships[names[i]]);
                    }
                }
            }
        },

        /**
         * @private
         * @method
         */
        _addRelationship: {
            value: function (name, relationship) {
                // TODO: Add derived properties, relationship criteria,
                // relationship targets, and shared fetch information.
                if (!this.propertyDescriptors[name]) {
                    this.setPropertyDescriptor(name, this.makePropertyDescriptor());
                }
                this.propertyDescriptors[name].isRelationship = true;
                this.propertyDescriptors[name].isGlobal = relationship.isGlobal ? true : false;
            }
        }

    }, /** @lends DataObjectDescriptor */ {

        /**
         * Generates a getter function that will create and cache a data object
         * descriptor.
         *
         * @method
         * @argument {Object} exports             - A
         *                                          [Montage Require]{@link external:Require}
         *                                          exports object defining the
         *                                          constructor for the object to
         *                                          describe. Usually this is
         *                                          `exports`.
         * @argument {string} constructorName     - The name with which that
         *                                          constructor can be looked up in
         *                                          the exports. This will also be
         *                                          used as the created descriptor's
         *                                          type name.
         * @argument {Object<string, string>}
         *           [propertyTypes]              - The types of each of the
         *                                          described objects' properties,
         *                                          by property name. Optional
         *                                          except if identifier names or
         *                                          relationship information are to
         *                                          be specified. Pass in a null
         *                                          or undefined value to specify
         *                                          identifier names or relationship
         *                                          information but no property
         *                                          types. If no types array is
         *                                          specified the property
         *                                          information will be derived from
         *                                          the properties of the objects'
         *                                          prototype. If an empty types
         *                                          array is specified the objects
         *                                          will be assumed to have no
         *                                          properties.
         * @argument {Array.<string>}
         *           [identifierNames=[]]         - The names of the properties of
         *                                          described objects whose values,
         *                                          when taken together, define
         *                                          a unique identifier for each
         *                                          such object. If no names are
         *                                          specified those objects are
         *                                          assumed to not be uniquely
         *                                          identifiable. The names can be
         *                                          specified as an array or as a
         *                                          sequence of strings.
         * @argument {Object<string, Object>}
         *           [relationshipInformation={}] - Information about each of the
         *                                          objects' relationships, by
         *                                          relationship property name. If
         *                                          no information is provided the
         *                                          objects are assumed to have no
         *                                          relationships.
         */
        getterFor: {
            value: function (exports, constructorName, propertyTypes, identifierNames, relationshipInformation) {
                // The returned getter function has to check
                // `this.hasOwnProperty("_TYPE")`, not just `this._TYPE`, because if
                // the class using the getter is a subclass of another class using a
                // similar getter `this._TYPE` will return the value of the the
                // superclass type instead of the desired subclass type.
                var self = this,
                    parsed = this._parseGetterForArguments.apply(this, arguments),
                    getter = ObjectDescriptor.getterFor.call(this, parsed.exports, parsed.name, parsed.types);
                return function () {

                    if (!this.hasOwnProperty("_TYPE")) {
                        this._TYPE = getter.call(this);
                        this._TYPE.identifierNames = parsed.identifiers;
                        this._TYPE._addRelationships(parsed.relationships);
                    }

                    return this._TYPE;
                };
            }
        },

        /**
         * @private
         * @method
         */
        _parseGetterForArguments: {
            value: function (/* exports, constructorName, [propertyTypes,] identifierNames[, relationshipInformation]
                                exports, constructorName, [propertyTypes,] relationshipInformation
                                exports, constructorName */) {
                var parsed, index, i, n;
                // Parse the exports object and constructor name.
                parsed = {exports: arguments[0], name: arguments[1]};
                // Parse the property types if they are provided: They must be a
                // "real" object (non-array, non-string, non-numeric, and
                // non-boolean), and they can't be the last argument (they must
                // be followed by either an identifier name or a relationship
                // information argument). After this parsing "index" will point to
                // the next argument to parse.
                if (arguments.length > 3 && this._isRealObject(arguments[2])) {
                    parsed.types = arguments[2];
                    index = 3;
                } else {
                    index = 2;
                }
                // Parse the identifier names: If provided these will be in an array
                // or in a sequence of string. After this parsing "index" will point
                // to the next argument to parse.
                if (Array.isArray(arguments[index])) {
                    parsed.identifiers = arguments[index];
                    index += 1;
                } else {
                    for (i = index, n = arguments.length; i < n && typeof arguments[i] === "string"; i += 1) {
                        parsed.identifiers = Array.prototype.slice.call(arguments, index, i);
                    }
                    index = i;
                }
                // Parse the relationship information.
                parsed.relationships = arguments[index];
                // Return the parsed arguments.
                return parsed;
            }
        },

        /**
         * @private
         * @method
         */
        _isRealObject: {
            value: function (value) {
                return value &&
                       typeof value === "object" &&
                       !Array.isArray(value) &&
                       !(value instanceof String) &&
                       !(value instanceof Number) &&
                       !(value instanceof Boolean);
            }
        }

    });