mirror of
https://github.com/titanscouting/tra-analysis.git
synced 2025-01-19 03:15:55 +00:00
434 lines
15 KiB
JavaScript
434 lines
15 KiB
JavaScript
"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");
|
|
|
|
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}
|
|
*/
|
|
// ^ BEWARE: VSCode hangs forever when using more than 5 types (that's why AnyExtensionField exists in the first place)
|
|
|
|
/**
|
|
* 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 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_;
|
|
};
|