linx-simulator2/node_modules/protobufjs/src/ProtoBuf/Builder.js
2019-09-18 11:11:16 +03:00

635 lines
26 KiB
JavaScript

/**
* @alias ProtoBuf.Builder
* @expose
*/
ProtoBuf.Builder = (function(ProtoBuf, Lang, Reflect) {
"use strict";
/**
* Constructs a new Builder.
* @exports ProtoBuf.Builder
* @class Provides the functionality to build protocol messages.
* @param {Object.<string,*>=} options Options
* @constructor
*/
var Builder = function(options) {
/**
* Namespace.
* @type {ProtoBuf.Reflect.Namespace}
* @expose
*/
this.ns = new Reflect.Namespace(this, null, ""); // Global namespace
/**
* Namespace pointer.
* @type {ProtoBuf.Reflect.T}
* @expose
*/
this.ptr = this.ns;
/**
* Resolved flag.
* @type {boolean}
* @expose
*/
this.resolved = false;
/**
* The current building result.
* @type {Object.<string,ProtoBuf.Builder.Message|Object>|null}
* @expose
*/
this.result = null;
/**
* Imported files.
* @type {Array.<string>}
* @expose
*/
this.files = {};
/**
* Import root override.
* @type {?string}
* @expose
*/
this.importRoot = null;
/**
* Options.
* @type {!Object.<string, *>}
* @expose
*/
this.options = options || {};
};
/**
* @alias ProtoBuf.Builder.prototype
* @inner
*/
var BuilderPrototype = Builder.prototype;
// ----- Definition tests -----
/**
* Tests if a definition most likely describes a message.
* @param {!Object} def
* @returns {boolean}
* @expose
*/
Builder.isMessage = function(def) {
// Messages require a string name
if (typeof def["name"] !== 'string')
return false;
// Messages do not contain values (enum) or rpc methods (service)
if (typeof def["values"] !== 'undefined' || typeof def["rpc"] !== 'undefined')
return false;
return true;
};
/**
* Tests if a definition most likely describes a message field.
* @param {!Object} def
* @returns {boolean}
* @expose
*/
Builder.isMessageField = function(def) {
// Message fields require a string rule, name and type and an id
if (typeof def["rule"] !== 'string' || typeof def["name"] !== 'string' || typeof def["type"] !== 'string' || typeof def["id"] === 'undefined')
return false;
return true;
};
/**
* Tests if a definition most likely describes an enum.
* @param {!Object} def
* @returns {boolean}
* @expose
*/
Builder.isEnum = function(def) {
// Enums require a string name
if (typeof def["name"] !== 'string')
return false;
// Enums require at least one value
if (typeof def["values"] === 'undefined' || !Array.isArray(def["values"]) || def["values"].length === 0)
return false;
return true;
};
/**
* Tests if a definition most likely describes a service.
* @param {!Object} def
* @returns {boolean}
* @expose
*/
Builder.isService = function(def) {
// Services require a string name and an rpc object
if (typeof def["name"] !== 'string' || typeof def["rpc"] !== 'object' || !def["rpc"])
return false;
return true;
};
/**
* Tests if a definition most likely describes an extended message
* @param {!Object} def
* @returns {boolean}
* @expose
*/
Builder.isExtend = function(def) {
// Extends rquire a string ref
if (typeof def["ref"] !== 'string')
return false;
return true;
};
// ----- Building -----
/**
* Resets the pointer to the root namespace.
* @returns {!ProtoBuf.Builder} this
* @expose
*/
BuilderPrototype.reset = function() {
this.ptr = this.ns;
return this;
};
/**
* Defines a namespace on top of the current pointer position and places the pointer on it.
* @param {string} namespace
* @return {!ProtoBuf.Builder} this
* @expose
*/
BuilderPrototype.define = function(namespace) {
if (typeof namespace !== 'string' || !Lang.TYPEREF.test(namespace))
throw Error("illegal namespace: "+namespace);
namespace.split(".").forEach(function(part) {
var ns = this.ptr.getChild(part);
if (ns === null) // Keep existing
this.ptr.addChild(ns = new Reflect.Namespace(this, this.ptr, part));
this.ptr = ns;
}, this);
return this;
};
/**
* Creates the specified definitions at the current pointer position.
* @param {!Array.<!Object>} defs Messages, enums or services to create
* @returns {!ProtoBuf.Builder} this
* @throws {Error} If a message definition is invalid
* @expose
*/
BuilderPrototype.create = function(defs) {
if (!defs)
return this; // Nothing to create
if (!Array.isArray(defs))
defs = [defs];
else {
if (defs.length === 0)
return this;
defs = defs.slice();
}
// It's quite hard to keep track of scopes and memory here, so let's do this iteratively.
var stack = [defs];
while (stack.length > 0) {
defs = stack.pop();
if (!Array.isArray(defs)) // Stack always contains entire namespaces
throw Error("not a valid namespace: "+JSON.stringify(defs));
while (defs.length > 0) {
var def = defs.shift(); // Namespaces always contain an array of messages, enums and services
if (Builder.isMessage(def)) {
var obj = new Reflect.Message(this, this.ptr, def["name"], def["options"], def["isGroup"], def["syntax"]);
// Create OneOfs
var oneofs = {};
if (def["oneofs"])
Object.keys(def["oneofs"]).forEach(function(name) {
obj.addChild(oneofs[name] = new Reflect.Message.OneOf(this, obj, name));
}, this);
// Create fields
if (def["fields"])
def["fields"].forEach(function(fld) {
if (obj.getChild(fld["id"]|0) !== null)
throw Error("duplicate or invalid field id in "+obj.name+": "+fld['id']);
if (fld["options"] && typeof fld["options"] !== 'object')
throw Error("illegal field options in "+obj.name+"#"+fld["name"]);
var oneof = null;
if (typeof fld["oneof"] === 'string' && !(oneof = oneofs[fld["oneof"]]))
throw Error("illegal oneof in "+obj.name+"#"+fld["name"]+": "+fld["oneof"]);
fld = new Reflect.Message.Field(this, obj, fld["rule"], fld["keytype"], fld["type"], fld["name"], fld["id"], fld["options"], oneof, def["syntax"]);
if (oneof)
oneof.fields.push(fld);
obj.addChild(fld);
}, this);
// Push children to stack
var subObj = [];
if (def["enums"])
def["enums"].forEach(function(enm) {
subObj.push(enm);
});
if (def["messages"])
def["messages"].forEach(function(msg) {
subObj.push(msg);
});
if (def["services"])
def["services"].forEach(function(svc) {
subObj.push(svc);
});
// Set extension ranges
if (def["extensions"]) {
if (typeof def["extensions"][0] === 'number') // pre 5.0.1
obj.extensions = [ def["extensions"] ];
else
obj.extensions = def["extensions"];
}
// Create on top of current namespace
this.ptr.addChild(obj);
if (subObj.length > 0) {
stack.push(defs); // Push the current level back
defs = subObj; // Continue processing sub level
subObj = null;
this.ptr = obj; // And move the pointer to this namespace
obj = null;
continue;
}
subObj = null;
} else if (Builder.isEnum(def)) {
obj = new Reflect.Enum(this, this.ptr, def["name"], def["options"], def["syntax"]);
def["values"].forEach(function(val) {
obj.addChild(new Reflect.Enum.Value(this, obj, val["name"], val["id"]));
}, this);
this.ptr.addChild(obj);
} else if (Builder.isService(def)) {
obj = new Reflect.Service(this, this.ptr, def["name"], def["options"]);
Object.keys(def["rpc"]).forEach(function(name) {
var mtd = def["rpc"][name];
obj.addChild(new Reflect.Service.RPCMethod(this, obj, name, mtd["request"], mtd["response"], !!mtd["request_stream"], !!mtd["response_stream"], mtd["options"]));
}, this);
this.ptr.addChild(obj);
} else if (Builder.isExtend(def)) {
obj = this.ptr.resolve(def["ref"], true);
if (obj) {
def["fields"].forEach(function(fld) {
if (obj.getChild(fld['id']|0) !== null)
throw Error("duplicate extended field id in "+obj.name+": "+fld['id']);
// Check if field id is allowed to be extended
if (obj.extensions) {
var valid = false;
obj.extensions.forEach(function(range) {
if (fld["id"] >= range[0] && fld["id"] <= range[1])
valid = true;
});
if (!valid)
throw Error("illegal extended field id in "+obj.name+": "+fld['id']+" (not within valid ranges)");
}
// Convert extension field names to camel case notation if the override is set
var name = fld["name"];
if (this.options['convertFieldsToCamelCase'])
name = ProtoBuf.Util.toCamelCase(name);
// see #161: Extensions use their fully qualified name as their runtime key and...
var field = new Reflect.Message.ExtensionField(this, obj, fld["rule"], fld["type"], this.ptr.fqn()+'.'+name, fld["id"], fld["options"]);
// ...are added on top of the current namespace as an extension which is used for
// resolving their type later on (the extension always keeps the original name to
// prevent naming collisions)
var ext = new Reflect.Extension(this, this.ptr, fld["name"], field);
field.extension = ext;
this.ptr.addChild(ext);
obj.addChild(field);
}, this);
} else if (!/\.?google\.protobuf\./.test(def["ref"])) // Silently skip internal extensions
throw Error("extended message "+def["ref"]+" is not defined");
} else
throw Error("not a valid definition: "+JSON.stringify(def));
def = null;
obj = null;
}
// Break goes here
defs = null;
this.ptr = this.ptr.parent; // Namespace done, continue at parent
}
this.resolved = false; // Require re-resolve
this.result = null; // Require re-build
return this;
};
/**
* Propagates syntax to all children.
* @param {!Object} parent
* @inner
*/
function propagateSyntax(parent) {
if (parent['messages']) {
parent['messages'].forEach(function(child) {
child["syntax"] = parent["syntax"];
propagateSyntax(child);
});
}
if (parent['enums']) {
parent['enums'].forEach(function(child) {
child["syntax"] = parent["syntax"];
});
}
}
/**
* Imports another definition into this builder.
* @param {Object.<string,*>} json Parsed import
* @param {(string|{root: string, file: string})=} filename Imported file name
* @returns {!ProtoBuf.Builder} this
* @throws {Error} If the definition or file cannot be imported
* @expose
*/
BuilderPrototype["import"] = function(json, filename) {
var delim = '/';
// Make sure to skip duplicate imports
if (typeof filename === 'string') {
if (ProtoBuf.Util.IS_NODE)
filename = require("path")['resolve'](filename);
if (this.files[filename] === true)
return this.reset();
this.files[filename] = true;
} else if (typeof filename === 'object') { // Object with root, file.
var root = filename.root;
if (ProtoBuf.Util.IS_NODE)
root = require("path")['resolve'](root);
if (root.indexOf("\\") >= 0 || filename.file.indexOf("\\") >= 0)
delim = '\\';
var fname;
if (ProtoBuf.Util.IS_NODE)
fname = require("path")['join'](root, filename.file);
else
fname = root + delim + filename.file;
if (this.files[fname] === true)
return this.reset();
this.files[fname] = true;
}
// Import imports
if (json['imports'] && json['imports'].length > 0) {
var importRoot,
resetRoot = false;
if (typeof filename === 'object') { // If an import root is specified, override
this.importRoot = filename["root"]; resetRoot = true; // ... and reset afterwards
importRoot = this.importRoot;
filename = filename["file"];
if (importRoot.indexOf("\\") >= 0 || filename.indexOf("\\") >= 0)
delim = '\\';
} else if (typeof filename === 'string') {
if (this.importRoot) // If import root is overridden, use it
importRoot = this.importRoot;
else { // Otherwise compute from filename
if (filename.indexOf("/") >= 0) { // Unix
importRoot = filename.replace(/\/[^\/]*$/, "");
if (/* /file.proto */ importRoot === "")
importRoot = "/";
} else if (filename.indexOf("\\") >= 0) { // Windows
importRoot = filename.replace(/\\[^\\]*$/, "");
delim = '\\';
} else
importRoot = ".";
}
} else
importRoot = null;
for (var i=0; i<json['imports'].length; i++) {
if (typeof json['imports'][i] === 'string') { // Import file
if (!importRoot)
throw Error("cannot determine import root");
var importFilename = json['imports'][i];
if (importFilename === "google/protobuf/descriptor.proto")
continue; // Not needed and therefore not used
if (ProtoBuf.Util.IS_NODE)
importFilename = require("path")['join'](importRoot, importFilename);
else
importFilename = importRoot + delim + importFilename;
if (this.files[importFilename] === true)
continue; // Already imported
if (/\.proto$/i.test(importFilename) && !ProtoBuf.DotProto) // If this is a light build
importFilename = importFilename.replace(/\.proto$/, ".json"); // always load the JSON file
var contents = ProtoBuf.Util.fetch(importFilename);
if (contents === null)
throw Error("failed to import '"+importFilename+"' in '"+filename+"': file not found");
if (/\.json$/i.test(importFilename)) // Always possible
this["import"](JSON.parse(contents+""), importFilename); // May throw
else
this["import"](ProtoBuf.DotProto.Parser.parse(contents), importFilename); // May throw
} else // Import structure
if (!filename)
this["import"](json['imports'][i]);
else if (/\.(\w+)$/.test(filename)) // With extension: Append _importN to the name portion to make it unique
this["import"](json['imports'][i], filename.replace(/^(.+)\.(\w+)$/, function($0, $1, $2) { return $1+"_import"+i+"."+$2; }));
else // Without extension: Append _importN to make it unique
this["import"](json['imports'][i], filename+"_import"+i);
}
if (resetRoot) // Reset import root override when all imports are done
this.importRoot = null;
}
// Import structures
if (json['package'])
this.define(json['package']);
if (json['syntax'])
propagateSyntax(json);
var base = this.ptr;
if (json['options'])
Object.keys(json['options']).forEach(function(key) {
base.options[key] = json['options'][key];
});
if (json['messages'])
this.create(json['messages']),
this.ptr = base;
if (json['enums'])
this.create(json['enums']),
this.ptr = base;
if (json['services'])
this.create(json['services']),
this.ptr = base;
if (json['extends'])
this.create(json['extends']);
return this.reset();
};
/**
* Resolves all namespace objects.
* @throws {Error} If a type cannot be resolved
* @returns {!ProtoBuf.Builder} this
* @expose
*/
BuilderPrototype.resolveAll = function() {
// Resolve all reflected objects
var res;
if (this.ptr == null || typeof this.ptr.type === 'object')
return this; // Done (already resolved)
if (this.ptr instanceof Reflect.Namespace) { // Resolve children
this.ptr.children.forEach(function(child) {
this.ptr = child;
this.resolveAll();
}, this);
} else if (this.ptr instanceof Reflect.Message.Field) { // Resolve type
if (!Lang.TYPE.test(this.ptr.type)) {
if (!Lang.TYPEREF.test(this.ptr.type))
throw Error("illegal type reference in "+this.ptr.toString(true)+": "+this.ptr.type);
res = (this.ptr instanceof Reflect.Message.ExtensionField ? this.ptr.extension.parent : this.ptr.parent).resolve(this.ptr.type, true);
if (!res)
throw Error("unresolvable type reference in "+this.ptr.toString(true)+": "+this.ptr.type);
this.ptr.resolvedType = res;
if (res instanceof Reflect.Enum) {
this.ptr.type = ProtoBuf.TYPES["enum"];
if (this.ptr.syntax === 'proto3' && res.syntax !== 'proto3')
throw Error("proto3 message cannot reference proto2 enum");
}
else if (res instanceof Reflect.Message)
this.ptr.type = res.isGroup ? ProtoBuf.TYPES["group"] : ProtoBuf.TYPES["message"];
else
throw Error("illegal type reference in "+this.ptr.toString(true)+": "+this.ptr.type);
} else
this.ptr.type = ProtoBuf.TYPES[this.ptr.type];
// If it's a map field, also resolve the key type. The key type can be only a numeric, string, or bool type
// (i.e., no enums or messages), so we don't need to resolve against the current namespace.
if (this.ptr.map) {
if (!Lang.TYPE.test(this.ptr.keyType))
throw Error("illegal key type for map field in "+this.ptr.toString(true)+": "+this.ptr.keyType);
this.ptr.keyType = ProtoBuf.TYPES[this.ptr.keyType];
}
// If it's a repeated and packable field then proto3 mandates it should be packed by
// default
if (
this.ptr.syntax === 'proto3' &&
this.ptr.repeated && this.ptr.options.packed === undefined &&
ProtoBuf.PACKABLE_WIRE_TYPES.indexOf(this.ptr.type.wireType) !== -1
) {
this.ptr.options.packed = true;
}
} else if (this.ptr instanceof ProtoBuf.Reflect.Service.Method) {
if (this.ptr instanceof ProtoBuf.Reflect.Service.RPCMethod) {
res = this.ptr.parent.resolve(this.ptr.requestName, true);
if (!res || !(res instanceof ProtoBuf.Reflect.Message))
throw Error("Illegal type reference in "+this.ptr.toString(true)+": "+this.ptr.requestName);
this.ptr.resolvedRequestType = res;
res = this.ptr.parent.resolve(this.ptr.responseName, true);
if (!res || !(res instanceof ProtoBuf.Reflect.Message))
throw Error("Illegal type reference in "+this.ptr.toString(true)+": "+this.ptr.responseName);
this.ptr.resolvedResponseType = res;
} else // Should not happen as nothing else is implemented
throw Error("illegal service type in "+this.ptr.toString(true));
} else if (
!(this.ptr instanceof ProtoBuf.Reflect.Message.OneOf) && // Not built
!(this.ptr instanceof ProtoBuf.Reflect.Extension) && // Not built
!(this.ptr instanceof ProtoBuf.Reflect.Enum.Value) // Built in enum
)
throw Error("illegal object in namespace: "+typeof(this.ptr)+": "+this.ptr);
return this.reset();
};
/**
* Builds the protocol. This will first try to resolve all definitions and, if this has been successful,
* return the built package.
* @param {(string|Array.<string>)=} path Specifies what to return. If omitted, the entire namespace will be returned.
* @returns {!ProtoBuf.Builder.Message|!Object.<string,*>}
* @throws {Error} If a type could not be resolved
* @expose
*/
BuilderPrototype.build = function(path) {
this.reset();
if (!this.resolved)
this.resolveAll(),
this.resolved = true,
this.result = null; // Require re-build
if (this.result === null) // (Re-)Build
this.result = this.ns.build();
if (!path)
return this.result;
var part = typeof path === 'string' ? path.split(".") : path,
ptr = this.result; // Build namespace pointer (no hasChild etc.)
for (var i=0; i<part.length; i++)
if (ptr[part[i]])
ptr = ptr[part[i]];
else {
ptr = null;
break;
}
return ptr;
};
/**
* Similar to {@link ProtoBuf.Builder#build}, but looks up the internal reflection descriptor.
* @param {string=} path Specifies what to return. If omitted, the entire namespace wiil be returned.
* @param {boolean=} excludeNonNamespace Excludes non-namespace types like fields, defaults to `false`
* @returns {?ProtoBuf.Reflect.T} Reflection descriptor or `null` if not found
*/
BuilderPrototype.lookup = function(path, excludeNonNamespace) {
return path ? this.ns.resolve(path, excludeNonNamespace) : this.ns;
};
/**
* Returns a string representation of this object.
* @return {string} String representation as of "Builder"
* @expose
*/
BuilderPrototype.toString = function() {
return "Builder";
};
// ----- Base classes -----
// Exist for the sole purpose of being able to "... instanceof ProtoBuf.Builder.Message" etc.
/**
* @alias ProtoBuf.Builder.Message
*/
Builder.Message = function() {};
/**
* @alias ProtoBuf.Builder.Enum
*/
Builder.Enum = function() {};
/**
* @alias ProtoBuf.Builder.Message
*/
Builder.Service = function() {};
return Builder;
})(ProtoBuf, ProtoBuf.Lang, ProtoBuf.Reflect);