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);
        }
    }

});