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