| "use strict"; |
| module.exports = Namespace; |
| |
| // extends ReflectionObject |
| var ReflectionObject = require("./object"); |
| ((Namespace.prototype = Object.create(ReflectionObject.prototype)).constructor = Namespace).className = "Namespace"; |
| |
| var Field = require("./field"), |
| util = require("./util"), |
| OneOf = require("./oneof"); |
| |
| var Type, // cyclic |
| Service, |
| Enum; |
| |
| /** |
| * Constructs a new namespace instance. |
| * @name Namespace |
| * @classdesc Reflected namespace. |
| * @extends NamespaceBase |
| * @constructor |
| * @param {string} name Namespace name |
| * @param {Object.<string,*>} [options] Declared options |
| */ |
| |
| /** |
| * Constructs a namespace from JSON. |
| * @memberof Namespace |
| * @function |
| * @param {string} name Namespace name |
| * @param {Object.<string,*>} json JSON object |
| * @returns {Namespace} Created namespace |
| * @throws {TypeError} If arguments are invalid |
| */ |
| Namespace.fromJSON = function fromJSON(name, json) { |
| return new Namespace(name, json.options).addJSON(json.nested); |
| }; |
| |
| /** |
| * Converts an array of reflection objects to JSON. |
| * @memberof Namespace |
| * @param {ReflectionObject[]} array Object array |
| * @param {IToJSONOptions} [toJSONOptions] JSON conversion options |
| * @returns {Object.<string,*>|undefined} JSON object or `undefined` when array is empty |
| */ |
| function arrayToJSON(array, toJSONOptions) { |
| if (!(array && array.length)) |
| return undefined; |
| var obj = {}; |
| for (var i = 0; i < array.length; ++i) |
| obj[array[i].name] = array[i].toJSON(toJSONOptions); |
| return obj; |
| } |
| |
| Namespace.arrayToJSON = arrayToJSON; |
| |
| /** |
| * Tests if the specified id is reserved. |
| * @param {Array.<number[]|string>|undefined} reserved Array of reserved ranges and names |
| * @param {number} id Id to test |
| * @returns {boolean} `true` if reserved, otherwise `false` |
| */ |
| Namespace.isReservedId = function isReservedId(reserved, id) { |
| if (reserved) |
| for (var i = 0; i < reserved.length; ++i) |
| if (typeof reserved[i] !== "string" && reserved[i][0] <= id && reserved[i][1] > id) |
| return true; |
| return false; |
| }; |
| |
| /** |
| * Tests if the specified name is reserved. |
| * @param {Array.<number[]|string>|undefined} reserved Array of reserved ranges and names |
| * @param {string} name Name to test |
| * @returns {boolean} `true` if reserved, otherwise `false` |
| */ |
| Namespace.isReservedName = function isReservedName(reserved, name) { |
| if (reserved) |
| for (var i = 0; i < reserved.length; ++i) |
| if (reserved[i] === name) |
| return true; |
| return false; |
| }; |
| |
| /** |
| * Not an actual constructor. Use {@link Namespace} instead. |
| * @classdesc Base class of all reflection objects containing nested objects. This is not an actual class but here for the sake of having consistent type definitions. |
| * @exports NamespaceBase |
| * @extends ReflectionObject |
| * @abstract |
| * @constructor |
| * @param {string} name Namespace name |
| * @param {Object.<string,*>} [options] Declared options |
| * @see {@link Namespace} |
| */ |
| function Namespace(name, options) { |
| ReflectionObject.call(this, name, options); |
| |
| /** |
| * Nested objects by name. |
| * @type {Object.<string,ReflectionObject>|undefined} |
| */ |
| this.nested = undefined; // toJSON |
| |
| /** |
| * Cached nested objects as an array. |
| * @type {ReflectionObject[]|null} |
| * @private |
| */ |
| this._nestedArray = null; |
| } |
| |
| function clearCache(namespace) { |
| namespace._nestedArray = null; |
| return namespace; |
| } |
| |
| /** |
| * Nested objects of this namespace as an array for iteration. |
| * @name NamespaceBase#nestedArray |
| * @type {ReflectionObject[]} |
| * @readonly |
| */ |
| Object.defineProperty(Namespace.prototype, "nestedArray", { |
| get: function() { |
| return this._nestedArray || (this._nestedArray = util.toArray(this.nested)); |
| } |
| }); |
| |
| /** |
| * Namespace descriptor. |
| * @interface INamespace |
| * @property {Object.<string,*>} [options] Namespace options |
| * @property {Object.<string,AnyNestedObject>} [nested] Nested object descriptors |
| */ |
| |
| /** |
| * Any extension field descriptor. |
| * @typedef AnyExtensionField |
| * @type {IExtensionField|IExtensionMapField} |
| */ |
| |
| /** |
| * Any nested object descriptor. |
| * @typedef AnyNestedObject |
| * @type {IEnum|IType|IService|AnyExtensionField|INamespace|IOneOf} |
| */ |
| |
| /** |
| * Converts this namespace to a namespace descriptor. |
| * @param {IToJSONOptions} [toJSONOptions] JSON conversion options |
| * @returns {INamespace} Namespace descriptor |
| */ |
| Namespace.prototype.toJSON = function toJSON(toJSONOptions) { |
| return util.toObject([ |
| "options" , this.options, |
| "nested" , arrayToJSON(this.nestedArray, toJSONOptions) |
| ]); |
| }; |
| |
| /** |
| * Adds nested objects to this namespace from nested object descriptors. |
| * @param {Object.<string,AnyNestedObject>} nestedJson Any nested object descriptors |
| * @returns {Namespace} `this` |
| */ |
| Namespace.prototype.addJSON = function addJSON(nestedJson) { |
| var ns = this; |
| /* istanbul ignore else */ |
| if (nestedJson) { |
| for (var names = Object.keys(nestedJson), i = 0, nested; i < names.length; ++i) { |
| nested = nestedJson[names[i]]; |
| ns.add( // most to least likely |
| ( nested.fields !== undefined |
| ? Type.fromJSON |
| : nested.values !== undefined |
| ? Enum.fromJSON |
| : nested.methods !== undefined |
| ? Service.fromJSON |
| : nested.id !== undefined |
| ? Field.fromJSON |
| : Namespace.fromJSON )(names[i], nested) |
| ); |
| } |
| } |
| return this; |
| }; |
| |
| /** |
| * Gets the nested object of the specified name. |
| * @param {string} name Nested object name |
| * @returns {ReflectionObject|null} The reflection object or `null` if it doesn't exist |
| */ |
| Namespace.prototype.get = function get(name) { |
| return this.nested && this.nested[name] |
| || null; |
| }; |
| |
| /** |
| * Gets the values of the nested {@link Enum|enum} of the specified name. |
| * This methods differs from {@link Namespace#get|get} in that it returns an enum's values directly and throws instead of returning `null`. |
| * @param {string} name Nested enum name |
| * @returns {Object.<string,number>} Enum values |
| * @throws {Error} If there is no such enum |
| */ |
| Namespace.prototype.getEnum = function getEnum(name) { |
| if (this.nested && this.nested[name] instanceof Enum) |
| return this.nested[name].values; |
| throw Error("no such enum: " + name); |
| }; |
| |
| /** |
| * Adds a nested object to this namespace. |
| * @param {ReflectionObject} object Nested object to add |
| * @returns {Namespace} `this` |
| * @throws {TypeError} If arguments are invalid |
| * @throws {Error} If there is already a nested object with this name |
| */ |
| Namespace.prototype.add = function add(object) { |
| |
| if (!(object instanceof Field && object.extend !== undefined || object instanceof Type || object instanceof OneOf || object instanceof Enum || object instanceof Service || object instanceof Namespace)) |
| throw TypeError("object must be a valid nested object"); |
| |
| if (!this.nested) |
| this.nested = {}; |
| else { |
| var prev = this.get(object.name); |
| if (prev) { |
| if (prev instanceof Namespace && object instanceof Namespace && !(prev instanceof Type || prev instanceof Service)) { |
| // replace plain namespace but keep existing nested elements and options |
| var nested = prev.nestedArray; |
| for (var i = 0; i < nested.length; ++i) |
| object.add(nested[i]); |
| this.remove(prev); |
| if (!this.nested) |
| this.nested = {}; |
| object.setOptions(prev.options, true); |
| |
| } else |
| throw Error("duplicate name '" + object.name + "' in " + this); |
| } |
| } |
| this.nested[object.name] = object; |
| object.onAdd(this); |
| return clearCache(this); |
| }; |
| |
| /** |
| * Removes a nested object from this namespace. |
| * @param {ReflectionObject} object Nested object to remove |
| * @returns {Namespace} `this` |
| * @throws {TypeError} If arguments are invalid |
| * @throws {Error} If `object` is not a member of this namespace |
| */ |
| Namespace.prototype.remove = function remove(object) { |
| |
| if (!(object instanceof ReflectionObject)) |
| throw TypeError("object must be a ReflectionObject"); |
| if (object.parent !== this) |
| throw Error(object + " is not a member of " + this); |
| |
| delete this.nested[object.name]; |
| if (!Object.keys(this.nested).length) |
| this.nested = undefined; |
| |
| object.onRemove(this); |
| return clearCache(this); |
| }; |
| |
| /** |
| * Defines additial namespaces within this one if not yet existing. |
| * @param {string|string[]} path Path to create |
| * @param {*} [json] Nested types to create from JSON |
| * @returns {Namespace} Pointer to the last namespace created or `this` if path is empty |
| */ |
| Namespace.prototype.define = function define(path, json) { |
| |
| if (util.isString(path)) |
| path = path.split("."); |
| else if (!Array.isArray(path)) |
| throw TypeError("illegal path"); |
| if (path && path.length && path[0] === "") |
| throw Error("path must be relative"); |
| |
| var ptr = this; |
| while (path.length > 0) { |
| var part = path.shift(); |
| if (ptr.nested && ptr.nested[part]) { |
| ptr = ptr.nested[part]; |
| if (!(ptr instanceof Namespace)) |
| throw Error("path conflicts with non-namespace objects"); |
| } else |
| ptr.add(ptr = new Namespace(part)); |
| } |
| if (json) |
| ptr.addJSON(json); |
| return ptr; |
| }; |
| |
| /** |
| * Resolves this namespace's and all its nested objects' type references. Useful to validate a reflection tree, but comes at a cost. |
| * @returns {Namespace} `this` |
| */ |
| Namespace.prototype.resolveAll = function resolveAll() { |
| var nested = this.nestedArray, i = 0; |
| while (i < nested.length) |
| if (nested[i] instanceof Namespace) |
| nested[i++].resolveAll(); |
| else |
| nested[i++].resolve(); |
| return this.resolve(); |
| }; |
| |
| /** |
| * Recursively looks up the reflection object matching the specified path in the scope of this namespace. |
| * @param {string|string[]} path Path to look up |
| * @param {*|Array.<*>} filterTypes Filter types, any combination of the constructors of `protobuf.Type`, `protobuf.Enum`, `protobuf.Service` etc. |
| * @param {boolean} [parentAlreadyChecked=false] If known, whether the parent has already been checked |
| * @returns {ReflectionObject|null} Looked up object or `null` if none could be found |
| */ |
| Namespace.prototype.lookup = function lookup(path, filterTypes, parentAlreadyChecked) { |
| |
| /* istanbul ignore next */ |
| if (typeof filterTypes === "boolean") { |
| parentAlreadyChecked = filterTypes; |
| filterTypes = undefined; |
| } else if (filterTypes && !Array.isArray(filterTypes)) |
| filterTypes = [ filterTypes ]; |
| |
| if (util.isString(path) && path.length) { |
| if (path === ".") |
| return this.root; |
| path = path.split("."); |
| } else if (!path.length) |
| return this; |
| |
| // Start at root if path is absolute |
| if (path[0] === "") |
| return this.root.lookup(path.slice(1), filterTypes); |
| |
| // Test if the first part matches any nested object, and if so, traverse if path contains more |
| var found = this.get(path[0]); |
| if (found) { |
| if (path.length === 1) { |
| if (!filterTypes || filterTypes.indexOf(found.constructor) > -1) |
| return found; |
| } else if (found instanceof Namespace && (found = found.lookup(path.slice(1), filterTypes, true))) |
| return found; |
| |
| // Otherwise try each nested namespace |
| } else |
| for (var i = 0; i < this.nestedArray.length; ++i) |
| if (this._nestedArray[i] instanceof Namespace && (found = this._nestedArray[i].lookup(path, filterTypes, true))) |
| return found; |
| |
| // If there hasn't been a match, try again at the parent |
| if (this.parent === null || parentAlreadyChecked) |
| return null; |
| return this.parent.lookup(path, filterTypes); |
| }; |
| |
| /** |
| * Looks up the reflection object at the specified path, relative to this namespace. |
| * @name NamespaceBase#lookup |
| * @function |
| * @param {string|string[]} path Path to look up |
| * @param {boolean} [parentAlreadyChecked=false] Whether the parent has already been checked |
| * @returns {ReflectionObject|null} Looked up object or `null` if none could be found |
| * @variation 2 |
| */ |
| // lookup(path: string, [parentAlreadyChecked: boolean]) |
| |
| /** |
| * Looks up the {@link Type|type} at the specified path, relative to this namespace. |
| * Besides its signature, this methods differs from {@link Namespace#lookup|lookup} in that it throws instead of returning `null`. |
| * @param {string|string[]} path Path to look up |
| * @returns {Type} Looked up type |
| * @throws {Error} If `path` does not point to a type |
| */ |
| Namespace.prototype.lookupType = function lookupType(path) { |
| var found = this.lookup(path, [ Type ]); |
| if (!found) |
| throw Error("no such type: " + path); |
| return found; |
| }; |
| |
| /** |
| * Looks up the values of the {@link Enum|enum} at the specified path, relative to this namespace. |
| * Besides its signature, this methods differs from {@link Namespace#lookup|lookup} in that it throws instead of returning `null`. |
| * @param {string|string[]} path Path to look up |
| * @returns {Enum} Looked up enum |
| * @throws {Error} If `path` does not point to an enum |
| */ |
| Namespace.prototype.lookupEnum = function lookupEnum(path) { |
| var found = this.lookup(path, [ Enum ]); |
| if (!found) |
| throw Error("no such Enum '" + path + "' in " + this); |
| return found; |
| }; |
| |
| /** |
| * Looks up the {@link Type|type} or {@link Enum|enum} at the specified path, relative to this namespace. |
| * Besides its signature, this methods differs from {@link Namespace#lookup|lookup} in that it throws instead of returning `null`. |
| * @param {string|string[]} path Path to look up |
| * @returns {Type} Looked up type or enum |
| * @throws {Error} If `path` does not point to a type or enum |
| */ |
| Namespace.prototype.lookupTypeOrEnum = function lookupTypeOrEnum(path) { |
| var found = this.lookup(path, [ Type, Enum ]); |
| if (!found) |
| throw Error("no such Type or Enum '" + path + "' in " + this); |
| return found; |
| }; |
| |
| /** |
| * Looks up the {@link Service|service} at the specified path, relative to this namespace. |
| * Besides its signature, this methods differs from {@link Namespace#lookup|lookup} in that it throws instead of returning `null`. |
| * @param {string|string[]} path Path to look up |
| * @returns {Service} Looked up service |
| * @throws {Error} If `path` does not point to a service |
| */ |
| Namespace.prototype.lookupService = function lookupService(path) { |
| var found = this.lookup(path, [ Service ]); |
| if (!found) |
| throw Error("no such Service '" + path + "' in " + this); |
| return found; |
| }; |
| |
| // Sets up cyclic dependencies (called in index-light) |
| Namespace._configure = function(Type_, Service_, Enum_) { |
| Type = Type_; |
| Service = Service_; |
| Enum = Enum_; |
| }; |