signal-desktop/js-deps/ProtoBuf.min.js
2014-03-09 17:54:44 -07:00

3721 lines
No EOL
168 KiB
JavaScript

/*
Copyright 2013 Daniel Wirtz <dcode@dcode.io>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* @license ProtoBuf.js (c) 2013 Daniel Wirtz <dcode@dcode.io>
* Released under the Apache License, Version 2.0
* see: https://github.com/dcodeIO/ProtoBuf.js for details
*/
(function(global) {
"use strict";
function loadProtoBuf(ByteBuffer) {
/**
* The ProtoBuf namespace.
* @exports ProtoBuf
* @namespace
* @expose
*/
var ProtoBuf = {};
/**
* ProtoBuf.js version.
* @type {string}
* @const
* @expose
*/
ProtoBuf.VERSION = "2.0.5";
/**
* Wire types.
* @type {Object.<string,number>}
* @const
* @expose
*/
ProtoBuf.WIRE_TYPES = {};
/**
* Varint wire type.
* @type {number}
* @expose
*/
ProtoBuf.WIRE_TYPES.VARINT = 0;
/**
* Fixed 64 bits wire type.
* @type {number}
* @const
* @expose
*/
ProtoBuf.WIRE_TYPES.BITS64 = 1;
/**
* Length delimited wire type.
* @type {number}
* @const
* @expose
*/
ProtoBuf.WIRE_TYPES.LDELIM = 2;
/**
* Start group wire type.
* @type {number}
* @const
* @deprecated Not supported.
* @expose
*/
ProtoBuf.WIRE_TYPES.STARTGROUP = 3;
/**
* End group wire type.
* @type {number}
* @const
* @deprecated Not supported.
* @expose
*/
ProtoBuf.WIRE_TYPES.ENDGROUP = 4;
/**
* Fixed 32 bits wire type.
* @type {number}
* @const
* @expose
*/
ProtoBuf.WIRE_TYPES.BITS32 = 5;
/**
* Types.
* @dict
* @type {Object.<string,{name: string, wireType: number}>}
* @const
* @expose
*/
ProtoBuf.TYPES = {
// According to the protobuf spec.
"int32": {
name: "int32",
wireType: ProtoBuf.WIRE_TYPES.VARINT
},
"uint32": {
name: "uint32",
wireType: ProtoBuf.WIRE_TYPES.VARINT
},
"sint32": {
name: "sint32",
wireType: ProtoBuf.WIRE_TYPES.VARINT
},
"int64": {
name: "int64",
wireType: ProtoBuf.WIRE_TYPES.VARINT
},
"uint64": {
name: "uint64",
wireType: ProtoBuf.WIRE_TYPES.VARINT
},
"sint64": {
name: "sint64",
wireType: ProtoBuf.WIRE_TYPES.VARINT
},
"bool": {
name: "bool",
wireType: ProtoBuf.WIRE_TYPES.VARINT
},
"double": {
name: "double",
wireType: ProtoBuf.WIRE_TYPES.BITS64
},
"string": {
name: "string",
wireType: ProtoBuf.WIRE_TYPES.LDELIM
},
"bytes": {
name: "bytes",
wireType: ProtoBuf.WIRE_TYPES.LDELIM
},
"fixed32": {
name: "fixed32",
wireType: ProtoBuf.WIRE_TYPES.BITS32
},
"sfixed32": {
name: "sfixed32",
wireType: ProtoBuf.WIRE_TYPES.BITS32
},
"fixed64": {
name: "fixed64",
wireType: ProtoBuf.WIRE_TYPES.BITS64
},
"sfixed64": {
name: "sfixed64",
wireType: ProtoBuf.WIRE_TYPES.BITS64
},
"float": {
name: "float",
wireType: ProtoBuf.WIRE_TYPES.BITS32
},
"enum": {
name: "enum",
wireType: ProtoBuf.WIRE_TYPES.VARINT
},
"message": {
name: "message",
wireType: ProtoBuf.WIRE_TYPES.LDELIM
}
};
/**
* @type {?Long}
*/
ProtoBuf.Long = ByteBuffer.Long;
/**
* If set to `true`, field names will be converted from underscore notation to camel case. Defaults to `false`.
* Must be set prior to parsing.
* @type {boolean}
* @expose
*/
ProtoBuf.convertFieldsToCamelCase = false;
/**
* @alias ProtoBuf.Util
* @expose
*/
ProtoBuf.Util = (function() {
"use strict";
// Object.create polyfill
// ref: https://developer.mozilla.org/de/docs/JavaScript/Reference/Global_Objects/Object/create
if (!Object.create) {
/** @expose */
Object.create = function (o) {
if (arguments.length > 1) {
throw new Error('Object.create implementation only accepts the first parameter.');
}
function F() {}
F.prototype = o;
return new F();
};
}
/**
* ProtoBuf utilities.
* @exports ProtoBuf.Util
* @namespace
*/
var Util = {};
/**
* Flag if running in node or not.
* @type {boolean}
* @const
* @expose
*/
Util.IS_NODE = (typeof window === 'undefined' || !window.window) && typeof require === 'function' && typeof process !== 'undefined' && typeof process["nextTick"] === 'function';
/**
* Constructs a XMLHttpRequest object.
* @return {XMLHttpRequest}
* @throws {Error} If XMLHttpRequest is not supported
* @expose
*/
Util.XHR = function() {
// No dependencies please, ref: http://www.quirksmode.org/js/xmlhttp.html
var XMLHttpFactories = [
function () {return new XMLHttpRequest()},
function () {return new ActiveXObject("Msxml2.XMLHTTP")},
function () {return new ActiveXObject("Msxml3.XMLHTTP")},
function () {return new ActiveXObject("Microsoft.XMLHTTP")}
];
/** @type {?XMLHttpRequest} */
var xhr = null;
for (var i=0;i<XMLHttpFactories.length;i++) {
try { xhr = XMLHttpFactories[i](); }
catch (e) { continue; }
break;
}
if (!xhr) throw(new Error("XMLHttpRequest is not supported"));
return xhr;
};
/**
* Fetches a resource.
* @param {string} path Resource path
* @param {function(?string)=} callback Callback receiving the resource's contents. If omitted the resource will
* be fetched synchronously. If the request failed, contents will be null.
* @return {?string|undefined} Resource contents if callback is omitted (null if the request failed), else undefined.
* @expose
*/
Util.fetch = function(path, callback) {
if (callback && typeof callback != 'function') callback = null;
if (Util.IS_NODE) {
if (callback) {
require("fs").readFile(path, function(err, data) {
if (err) {
callback(null);
}
else callback(""+data);
});
} else {
try {
return require("fs").readFileSync(path);
} catch (e) {
return null;
}
}
} else {
var xhr = Util.XHR();
xhr.open('GET', path, callback ? true : false);
// xhr.setRequestHeader('User-Agent', 'XMLHTTP/1.0');
xhr.setRequestHeader('Accept', 'text/plain');
if (typeof xhr.overrideMimeType === 'function') xhr.overrideMimeType('text/plain');
if (callback) {
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
if (/* remote */ xhr.status == 200 || /* local */ (xhr.status == 0 && typeof xhr.responseText === 'string')) {
callback(xhr.responseText);
} else {
callback(null);
}
};
if (xhr.readyState == 4) return;
xhr.send(null);
} else {
xhr.send(null);
if (/* remote */ xhr.status == 200 || /* local */ (xhr.status == 0 && typeof xhr.responseText === 'string')) {
return xhr.responseText;
}
return null;
}
}
};
/**
* Tests if an object is an array.
* @param {*} obj Object to test
* @returns {boolean} true if it is an array, else false
* @expose
*/
Util.isArray = function(obj) {
if (!obj) return false;
if (obj instanceof Array) return true;
if (Array.isArray) return Array.isArray(obj);
return Object.prototype.toString.call(obj) === "[object Array]";
};
return Util;
})();
/**
* @alias ProtoBuf.Lang
* @expose
*/
ProtoBuf.Lang = (function() {
"use strict";
/**
* ProtoBuf Language.
* @exports ProtoBuf.Lang
* @type {Object.<string,string|RegExp>}
* @namespace
* @expose
*/
var Lang = { // Look, so cute!
OPEN: "{",
CLOSE: "}",
OPTOPEN: "[",
OPTCLOSE: "]",
OPTEND: ",",
EQUAL: "=",
END: ";",
STRINGOPEN: '"',
STRINGCLOSE: '"',
COPTOPEN: '(',
COPTCLOSE: ')',
DELIM: /[\s\{\}=;\[\],"\(\)]/g,
KEYWORD: /^(?:package|option|import|message|enum|extend|service|syntax|extensions)$/,
RULE: /^(?:required|optional|repeated)$/,
TYPE: /^(?:double|float|int32|uint32|sint32|int64|uint64|sint64|fixed32|sfixed32|fixed64|sfixed64|bool|string|bytes)$/,
NAME: /^[a-zA-Z][a-zA-Z_0-9]*$/,
OPTNAME: /^(?:[a-zA-Z][a-zA-Z_0-9]*|\([a-zA-Z][a-zA-Z_0-9]*\))$/,
TYPEDEF: /^[a-zA-Z][a-zA-Z_0-9]*$/,
TYPEREF: /^(?:\.?[a-zA-Z][a-zA-Z_0-9]*)+$/,
FQTYPEREF: /^(?:\.[a-zA-Z][a-zA-Z_0-9]*)+$/,
NUMBER: /^-?(?:[1-9][0-9]*|0|0x[0-9a-fA-F]+|0[0-7]+|[0-9]*\.[0-9]+)$/,
NUMBER_DEC: /^(?:[1-9][0-9]*|0)$/,
NUMBER_HEX: /^0x[0-9a-fA-F]+$/,
NUMBER_OCT: /^0[0-7]+$/,
NUMBER_FLT: /^[0-9]*\.[0-9]+$/,
ID: /^(?:[1-9][0-9]*|0|0x[0-9a-fA-F]+|0[0-7]+)$/,
NEGID: /^\-?(?:[1-9][0-9]*|0|0x[0-9a-fA-F]+|0[0-7]+)$/,
WHITESPACE: /\s/,
STRING: /"([^"\\]*(\\.[^"\\]*)*)"/g,
BOOL: /^(?:true|false)$/i,
ID_MIN: 1,
ID_MAX: 0x1FFFFFFF
};
return Lang;
})();
/**
* Utilities to parse .proto files.
* @namespace
* @expose
*/
ProtoBuf.DotProto = {}; // Not present in "noparse" builds
/**
* @alias ProtoBuf.DotProto.Tokenizer
* @expose
*/
ProtoBuf.DotProto.Tokenizer = (function(Lang) {
/**
* Constructs a new Tokenizer.
* @exports ProtoBuf.DotProto.Tokenizer
* @class A ProtoBuf .proto Tokenizer.
* @param {string} proto Proto to tokenize
* @constructor
*/
var Tokenizer = function(proto) {
/**
* Source to parse.
* @type {string}
* @expose
*/
this.source = ""+proto;
/**
* Current index.
* @type {number}
* @expose
*/
this.index = 0;
/**
* Current line.
* @type {number}
* @expose
*/
this.line = 1;
/**
* Stacked values.
* @type {Array}
* @expose
*/
this.stack = [];
/**
* Whether currently reading a string or not.
* @type {boolean}
* @expose
*/
this.readingString = false;
};
/**
* Reads a string beginning at the current index.
* @return {string} The string
* @throws {Error} If it's not a valid string
* @private
*/
Tokenizer.prototype._readString = function() {
Lang.STRING.lastIndex = this.index-1; // Include the open quote
var match;
if ((match = Lang.STRING.exec(this.source)) !== null) {
var s = match[1];
this.index = Lang.STRING.lastIndex;
this.stack.push(Lang.STRINGCLOSE);
return s;
}
throw(new Error("Illegal string value at line "+this.line+", index "+this.index));
};
/**
* Gets the next token and advances by one.
* @return {?string} Token or `null` on EOF
* @throws {Error} If it's not a valid proto file
* @expose
*/
Tokenizer.prototype.next = function() {
if (this.stack.length > 0) {
return this.stack.shift();
}
if (this.index >= this.source.length) {
return null; // No more tokens
}
if (this.readingString) {
this.readingString = false;
return this._readString();
}
var repeat, last;
do {
repeat = false;
// Strip white spaces
while (Lang.WHITESPACE.test(last = this.source.charAt(this.index))) {
this.index++;
if (last === "\n") this.line++;
if (this.index === this.source.length) return null;
}
// Strip comments
if (this.source.charAt(this.index) === '/') {
if (this.source.charAt(++this.index) === '/') { // Single line
while (this.source.charAt(this.index) !== "\n") {
this.index++;
if (this.index == this.source.length) return null;
}
this.index++;
this.line++;
repeat = true;
} else if (this.source.charAt(this.index) === '*') { /* Block */
last = '';
while (last+(last=this.source.charAt(this.index)) !== '*/') {
this.index++;
if (last === "\n") this.line++;
if (this.index === this.source.length) return null;
}
this.index++;
repeat = true;
} else {
throw(new Error("Invalid comment at line "+this.line+": /"+this.source.charAt(this.index)+" ('/' or '*' expected)"));
}
}
} while (repeat);
if (this.index === this.source.length) return null;
// Read the next token
var end = this.index;
Lang.DELIM.lastIndex = 0;
var delim = Lang.DELIM.test(this.source.charAt(end));
if (!delim) {
end++;
while(end < this.source.length && !Lang.DELIM.test(this.source.charAt(end))) {
end++;
}
} else {
end++;
}
var token = this.source.substring(this.index, this.index = end);
if (token === Lang.STRINGOPEN) {
this.readingString = true;
}
return token;
};
/**
* Peeks for the next token.
* @return {?string} Token or `null` on EOF
* @throws {Error} If it's not a valid proto file
* @expose
*/
Tokenizer.prototype.peek = function() {
if (this.stack.length == 0) {
var token = this.next();
if (token === null) return null;
this.stack.push(token);
}
return this.stack[0];
};
/**
* Returns a string representation of this object.
* @return {string} String representation as of "Tokenizer(index/length)"
* @expose
*/
Tokenizer.prototype.toString = function() {
return "Tokenizer("+this.index+"/"+this.source.length+" at line "+this.line+")";
};
return Tokenizer;
})(ProtoBuf.Lang);
/**
* @alias ProtoBuf.DotProto.Parser
* @expose
*/
ProtoBuf.DotProto.Parser = (function(ProtoBuf, Lang, Tokenizer) {
"use strict";
/**
* Constructs a new Parser.
* @exports ProtoBuf.DotProto.Parser
* @class A ProtoBuf .proto parser.
* @param {string} proto Protocol source
* @constructor
*/
var Parser = function(proto) {
/**
* Tokenizer.
* @type {ProtoBuf.DotProto.Tokenizer}
* @expose
*/
this.tn = new Tokenizer(proto);
};
/**
* Runs the parser.
* @return {{package: string|null, messages: Array.<object>, enums: Array.<object>, imports: Array.<string>, options: object<string,*>}}
* @throws {Error} If the source cannot be parsed
* @expose
*/
Parser.prototype.parse = function() {
var topLevel = {
"name": "[ROOT]", // temporary
"package": null,
"messages": [],
"enums": [],
"imports": [],
"options": {},
"services": []
};
var token, header = true;
do {
token = this.tn.next();
if (token == null) {
break; // No more messages
}
if (token == 'package') {
if (!header) {
throw(new Error("Illegal package definition at line "+this.tn.line+": Must be declared before the first message or enum"));
}
if (topLevel["package"] !== null) {
throw(new Error("Illegal package definition at line "+this.tn.line+": Package already declared"));
}
topLevel["package"] = this._parsePackage(token);
} else if (token == 'import') {
if (!header) {
throw(new Error("Illegal import definition at line "+this.tn.line+": Must be declared before the first message or enum"));
}
topLevel.imports.push(this._parseImport(token));
} else if (token === 'message') {
this._parseMessage(topLevel, token);
header = false;
} else if (token === 'enum') {
this._parseEnum(topLevel, token);
header = false;
} else if (token === 'option') {
if (!header) {
throw(new Error("Illegal option definition at line "+this.tn.line+": Must be declared before the first message or enum"));
}
this._parseOption(topLevel, token);
} else if (token === 'service') {
this._parseService(topLevel, token);
} else if (token === 'extend') {
this._parseExtend(topLevel, token);
} else if (token === 'syntax') {
this._parseIgnoredStatement(topLevel, token);
} else {
throw(new Error("Illegal top level declaration at line "+this.tn.line+": "+token));
}
} while (true);
delete topLevel["name"];
return topLevel;
};
/**
* Parses a number value.
* @param {string} val Number value to parse
* @return {number} Number
* @throws {Error} If the number value is invalid
* @private
*/
Parser.prototype._parseNumber = function(val) {
var sign = 1;
if (val.charAt(0) == '-') {
sign = -1; val = val.substring(1);
}
if (Lang.NUMBER_DEC.test(val)) {
return sign*parseInt(val, 10);
} else if (Lang.NUMBER_HEX.test(val)) {
return sign*parseInt(val.substring(2), 16);
} else if (Lang.NUMBER_OCT.test(val)) {
return sign*parseInt(val.substring(1), 8);
} else if (Lang.NUMBER_FLT.test(val)) {
return sign*parseFloat(val);
}
throw(new Error("Illegal number value at line "+this.tn.line+": "+(sign < 0 ? '-' : '')+val));
};
/**
* Parses an ID value.
* @param {string} val ID value to parse
* @param {boolean=} neg Whether the ID may be negative, defaults to `false`
* @returns {number} ID
* @throws {Error} If the ID value is invalid
* @private
*/
Parser.prototype._parseId = function(val, neg) {
var id = -1;
var sign = 1;
if (val.charAt(0) == '-') {
sign = -1; val = val.substring(1);
}
if (Lang.NUMBER_DEC.test(val)) {
id = parseInt(val);
} else if (Lang.NUMBER_HEX.test(val)) {
id = parseInt(val.substring(2), 16);
} else if (Lang.NUMBER_OCT.test(val)) {
id = parseInt(val.substring(1), 8);
} else {
throw(new Error("Illegal ID value at line "+this.tn.line+": "+(sign < 0 ? '-' : '')+val));
}
id = (sign*id)|0; // Force to 32bit
if (!neg && id < 0) {
throw(new Error("Illegal ID range at line "+this.tn.line+": "+(sign < 0 ? '-' : '')+val));
}
return id;
};
/**
* Parses the package definition.
* @param {string} token Initial token
* @return {string} Package name
* @throws {Error} If the package definition cannot be parsed
* @private
*/
Parser.prototype._parsePackage = function(token) {
token = this.tn.next();
if (!Lang.TYPEREF.test(token)) {
throw(new Error("Illegal package name at line "+this.tn.line+": "+token));
}
var pkg = token;
token = this.tn.next();
if (token != Lang.END) {
throw(new Error("Illegal end of package definition at line "+this.tn.line+": "+token+" ('"+Lang.END+"' expected)"));
}
return pkg;
};
/**
* Parses an import definition.
* @param {string} token Initial token
* @return {string} Import file name
* @throws {Error} If the import definition cannot be parsed
* @private
*/
Parser.prototype._parseImport = function(token) {
token = this.tn.next();
if (token === "public") {
token = this.tn.next();
}
if (token !== Lang.STRINGOPEN) {
throw(new Error("Illegal begin of import value at line "+this.tn.line+": "+token+" ('"+Lang.STRINGOPEN+"' expected)"));
}
var imported = this.tn.next();
token = this.tn.next();
if (token !== Lang.STRINGCLOSE) {
throw(new Error("Illegal end of import value at line "+this.tn.line+": "+token+" ('"+Lang.STRINGCLOSE+"' expected)"));
}
token = this.tn.next();
if (token !== Lang.END) {
throw(new Error("Illegal end of import definition at line "+this.tn.line+": "+token+" ('"+Lang.END+"' expected)"));
}
return imported;
};
/**
* Parses a namespace option.
* @param {Object} parent Parent definition
* @param {string} token Initial token
* @throws {Error} If the option cannot be parsed
* @private
*/
Parser.prototype._parseOption = function(parent, token) {
token = this.tn.next();
var custom = false;
if (token == Lang.COPTOPEN) {
custom = true;
token = this.tn.next();
}
if (!Lang.NAME.test(token)) {
// we can allow options of the form google.protobuf.* since they will just get ignored anyways
if (!/google\.protobuf\./.test(token)) {
throw(new Error("Illegal option name in message "+parent.name+" at line "+this.tn.line+": "+token));
}
}
var name = token;
token = this.tn.next();
if (custom) { // (my_method_option).foo, (my_method_option), some_method_option
if (token !== Lang.COPTCLOSE) {
throw(new Error("Illegal custom option name delimiter in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token+" ('"+Lang.COPTCLOSE+"' expected)"));
}
name = '('+name+')';
token = this.tn.next();
if (Lang.FQTYPEREF.test(token)) {
name += token;
token = this.tn.next();
}
}
if (token !== Lang.EQUAL) {
throw(new Error("Illegal option operator in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token+" ('"+Lang.EQUAL+"' expected)"));
}
var value;
token = this.tn.next();
if (token === Lang.STRINGOPEN) {
value = this.tn.next();
token = this.tn.next();
if (token !== Lang.STRINGCLOSE) {
throw(new Error("Illegal end of option value in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token+" ('"+Lang.STRINGCLOSE+"' expected)"));
}
} else {
if (Lang.NUMBER.test(token)) {
value = this._parseNumber(token, true);
} else if (Lang.TYPEREF.test(token)) {
value = token;
} else {
throw(new Error("Illegal option value in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token));
}
}
token = this.tn.next();
if (token !== Lang.END) {
throw(new Error("Illegal end of option in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token+" ('"+Lang.END+"' expected)"));
}
parent["options"][name] = value;
};
/**
* Parses an ignored block of the form ['keyword', 'typeref', '{' ... '}'].
* @param {Object} parent Parent definition
* @param {string} keyword Initial token
* @throws {Error} If the directive cannot be parsed
* @private
*/
Parser.prototype._parseIgnoredBlock = function(parent, keyword) {
var token = this.tn.next();
if (!Lang.TYPEREF.test(token)) {
throw(new Error("Illegal "+keyword+" type in "+parent.name+": "+token));
}
var name = token;
token = this.tn.next();
if (token !== Lang.OPEN) {
throw(new Error("Illegal OPEN in "+parent.name+" after "+keyword+" "+name+" at line "+this.tn.line+": "+token));
}
var depth = 1;
do {
token = this.tn.next();
if (token === null) {
throw(new Error("Unexpected EOF in "+parent.name+", "+keyword+" (ignored) at line "+this.tn.line+": "+name));
}
if (token === Lang.OPEN) {
depth++;
} else if (token === Lang.CLOSE) {
token = this.tn.peek();
if (token === Lang.END) this.tn.next();
depth--;
if (depth === 0) {
break;
}
}
} while(true);
};
/**
* Parses an ignored statement of the form ['keyword', ..., ';'].
* @param {Object} parent Parent definition
* @param {string} keyword Initial token
* @throws {Error} If the directive cannot be parsed
* @private
*/
Parser.prototype._parseIgnoredStatement = function(parent, keyword) {
var token;
do {
token = this.tn.next();
if (token === null) {
throw(new Error("Unexpected EOF in "+parent.name+", "+keyword+" (ignored) at line "+this.tn.line));
}
if (token === Lang.END) break;
} while (true);
};
/**
* Parses a service definition.
* @param {Object} parent Parent definition
* @param {string} keyword Initial token
* @throws {Error} If the service cannot be parsed
* @private
*/
Parser.prototype._parseService = function(parent, keyword) {
var token = this.tn.next();
if (!Lang.NAME.test(token)) {
throw(new Error("Illegal service name at line "+this.tn.line+": "+token));
}
var name = token;
var svc = {
"name": name,
"rpc": {},
"options": {}
};
token = this.tn.next();
if (token !== Lang.OPEN) {
throw(new Error("Illegal OPEN after service "+name+" at line "+this.tn.line+": "+token+" ('"+Lang.OPEN+"' expected)"));
}
do {
token = this.tn.next();
if (token === "option") {
this._parseOption(svc, token);
} else if (token === 'rpc') {
this._parseServiceRPC(svc, token);
} else if (token !== Lang.CLOSE) {
throw(new Error("Illegal type for service "+name+" at line "+this.tn.line+": "+token));
}
} while (token !== Lang.CLOSE);
parent["services"].push(svc);
};
/**
* Parses a RPC service definition of the form ['rpc', name, (request), 'returns', (response)].
* @param {Object} svc Parent definition
* @param {string} token Initial token
* @private
*/
Parser.prototype._parseServiceRPC = function(svc, token) {
var type = token;
token = this.tn.next();
if (!Lang.NAME.test(token)) {
throw(new Error("Illegal RPC method name in service "+svc["name"]+" at line "+this.tn.line+": "+token));
}
var name = token;
var method = {
"request": null,
"response": null,
"options": {}
};
token = this.tn.next();
if (token !== Lang.COPTOPEN) {
throw(new Error("Illegal start of request type in RPC service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token+" ('"+Lang.COPTOPEN+"' expected)"));
}
token = this.tn.next();
if (!Lang.TYPEREF.test(token)) {
throw(new Error("Illegal request type in RPC service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token));
}
method["request"] = token;
token = this.tn.next();
if (token != Lang.COPTCLOSE) {
throw(new Error("Illegal end of request type in RPC service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token+" ('"+Lang.COPTCLOSE+"' expected)"))
}
token = this.tn.next();
if (token.toLowerCase() !== "returns") {
throw(new Error("Illegal request/response delimiter in RPC service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token+" ('returns' expected)"));
}
token = this.tn.next();
if (token != Lang.COPTOPEN) {
throw(new Error("Illegal start of response type in RPC service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token+" ('"+Lang.COPTOPEN+"' expected)"));
}
token = this.tn.next();
method["response"] = token;
token = this.tn.next();
if (token !== Lang.COPTCLOSE) {
throw(new Error("Illegal end of response type in RPC service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token+" ('"+Lang.COPTCLOSE+"' expected)"))
}
token = this.tn.next();
if (token === Lang.OPEN) {
do {
token = this.tn.next();
if (token === 'option') {
this._parseOption(method, token); // <- will fail for the custom-options example
} else if (token !== Lang.CLOSE) {
throw(new Error("Illegal start of option in RPC service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token+" ('option' expected)"));
}
} while (token !== Lang.CLOSE);
} else if (token !== Lang.END) {
throw(new Error("Illegal method delimiter in RPC service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token+" ('"+Lang.END+"' or '"+Lang.OPEN+"' expected)"));
}
if (typeof svc[type] === 'undefined') svc[type] = {};
svc[type][name] = method;
};
/**
* Parses a message definition.
* @param {Object} parent Parent definition
* @param {string} token First token
* @return {Object}
* @throws {Error} If the message cannot be parsed
* @private
*/
Parser.prototype._parseMessage = function(parent, token) {
/** @dict */
var msg = {}; // Note: At some point we might want to exclude the parser, so we need a dict.
token = this.tn.next();
if (!Lang.NAME.test(token)) {
throw(new Error("Illegal message name"+(parent ? " in message "+parent["name"] : "")+" at line "+this.tn.line+": "+token));
}
msg["name"] = token;
token = this.tn.next();
if (token != Lang.OPEN) {
throw(new Error("Illegal OPEN after message "+msg.name+" at line "+this.tn.line+": "+token+" ('"+Lang.OPEN+"' expected)"));
}
msg["fields"] = []; // Note: Using arrays to support also browser that cannot preserve order of object keys.
msg["enums"] = [];
msg["messages"] = [];
msg["options"] = {};
// msg["extensions"] = undefined
do {
token = this.tn.next();
if (token === Lang.CLOSE) {
token = this.tn.peek();
if (token === Lang.END) this.tn.next();
break;
} else if (Lang.RULE.test(token)) {
this._parseMessageField(msg, token);
} else if (token === "enum") {
this._parseEnum(msg, token);
} else if (token === "message") {
this._parseMessage(msg, token);
} else if (token === "option") {
this._parseOption(msg, token);
} else if (token === "extensions") {
msg["extensions"] = this._parseExtensions(msg, token);
} else if (token === "extend") {
this._parseExtend(msg, token);
} else {
throw(new Error("Illegal token in message "+msg.name+" at line "+this.tn.line+": "+token+" (type or '"+Lang.CLOSE+"' expected)"));
}
} while (true);
parent["messages"].push(msg);
return msg;
};
/**
* Parses a message field.
* @param {Object} msg Message definition
* @param {string} token Initial token
* @throws {Error} If the message field cannot be parsed
* @private
*/
Parser.prototype._parseMessageField = function(msg, token) {
/** @dict */
var fld = {};
fld["rule"] = token;
token = this.tn.next();
if (!Lang.TYPE.test(token) && !Lang.TYPEREF.test(token)) {
throw(new Error("Illegal field type in message "+msg.name+" at line "+this.tn.line+": "+token));
}
fld["type"] = token;
token = this.tn.next();
if (!Lang.NAME.test(token)) {
throw(new Error("Illegal field name in message "+msg.name+" at line "+this.tn.line+": "+token));
}
fld["name"] = token;
token = this.tn.next();
if (token !== Lang.EQUAL) {
throw(new Error("Illegal field number operator in message "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token+" ('"+Lang.EQUAL+"' expected)"));
}
token = this.tn.next();
try {
fld["id"] = this._parseId(token);
} catch (e) {
throw(new Error("Illegal field id in message "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token));
}
/** @dict */
fld["options"] = {};
token = this.tn.next();
if (token === Lang.OPTOPEN) {
this._parseFieldOptions(msg, fld, token);
token = this.tn.next();
}
if (token !== Lang.END) {
throw(new Error("Illegal field delimiter in message "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token+" ('"+Lang.END+"' expected)"));
}
msg["fields"].push(fld);
};
/**
* Parses a set of field option definitions.
* @param {Object} msg Message definition
* @param {Object} fld Field definition
* @param {string} token Initial token
* @throws {Error} If the message field options cannot be parsed
* @private
*/
Parser.prototype._parseFieldOptions = function(msg, fld, token) {
var first = true;
do {
token = this.tn.next();
if (token === Lang.OPTCLOSE) {
break;
} else if (token === Lang.OPTEND) {
if (first) {
throw(new Error("Illegal start of message field options in message "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token));
}
token = this.tn.next();
}
this._parseFieldOption(msg, fld, token);
first = false;
} while (true);
};
/**
* Parses a single field option.
* @param {Object} msg Message definition
* @param {Object} fld Field definition
* @param {string} token Initial token
* @throws {Error} If the mesage field option cannot be parsed
* @private
*/
Parser.prototype._parseFieldOption = function(msg, fld, token) {
var custom = false;
if (token === Lang.COPTOPEN) {
token = this.tn.next();
custom = true;
}
if (!Lang.NAME.test(token)) {
throw(new Error("Illegal field option in message "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token));
}
var name = token;
token = this.tn.next();
if (custom) {
if (token !== Lang.COPTCLOSE) {
throw(new Error("Illegal custom field option name delimiter in message "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token+" (')' expected)"));
}
name = '('+name+')';
token = this.tn.next();
if (Lang.FQTYPEREF.test(token)) {
name += token;
token = this.tn.next();
}
}
if (token !== Lang.EQUAL) {
throw(new Error("Illegal field option operation in message "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token+" ('=' expected)"));
}
var value;
token = this.tn.next();
if (token === Lang.STRINGOPEN) {
value = this.tn.next();
token = this.tn.next();
if (token != Lang.STRINGCLOSE) {
throw(new Error("Illegal end of field value in message "+msg.name+"#"+fld.name+", option "+name+" at line "+this.tn.line+": "+token+" ('"+Lang.STRINGCLOSE+"' expected)"));
}
} else if (Lang.NUMBER.test(token, true)) {
value = this._parseNumber(token, true);
} else if (Lang.BOOL.test(token)) {
value = token.toLowerCase() === 'true';
} else if (Lang.TYPEREF.test(token)) {
value = token; // TODO: Resolve?
} else {
throw(new Error("Illegal field option value in message "+msg.name+"#"+fld.name+", option "+name+" at line "+this.tn.line+": "+token));
}
fld["options"][name] = value;
};
/**
* Parses an enum.
* @param {Object} msg Message definition
* @param {string} token Initial token
* @throws {Error} If the enum cannot be parsed
* @private
*/
Parser.prototype._parseEnum = function(msg, token) {
/** @dict */
var enm = {};
token = this.tn.next();
if (!Lang.NAME.test(token)) {
throw(new Error("Illegal enum name in message "+msg.name+" at line "+this.tn.line+": "+token));
}
enm["name"] = token;
token = this.tn.next();
if (token !== Lang.OPEN) {
throw(new Error("Illegal OPEN after enum "+enm.name+" at line "+this.tn.line+": "+token));
}
enm["values"] = [];
enm["options"] = {};
do {
token = this.tn.next();
if (token === Lang.CLOSE) {
token = this.tn.peek();
if (token === Lang.END) this.tn.next();
break;
}
if (token == 'option') {
this._parseOption(enm, token);
} else {
if (!Lang.NAME.test(token)) {
throw(new Error("Illegal enum value name in enum "+enm.name+" at line "+this.tn.line+": "+token));
}
this._parseEnumValue(enm, token);
}
} while (true);
msg["enums"].push(enm);
};
/**
* Parses an enum value.
* @param {Object} enm Enum definition
* @param {string} token Initial token
* @throws {Error} If the enum value cannot be parsed
* @private
*/
Parser.prototype._parseEnumValue = function(enm, token) {
/** @dict */
var val = {};
val["name"] = token;
token = this.tn.next();
if (token !== Lang.EQUAL) {
throw(new Error("Illegal enum value operator in enum "+enm.name+" at line "+this.tn.line+": "+token+" ('"+Lang.EQUAL+"' expected)"));
}
token = this.tn.next();
try {
val["id"] = this._parseId(token, true);
} catch (e) {
throw(new Error("Illegal enum value id in enum "+enm.name+" at line "+this.tn.line+": "+token));
}
enm["values"].push(val);
token = this.tn.next();
if (token === Lang.OPTOPEN) {
var opt = { 'options' : {} }; // TODO: Actually expose them somehow.
this._parseFieldOptions(enm, opt, token);
token = this.tn.next();
}
if (token !== Lang.END) {
throw(new Error("Illegal enum value delimiter in enum "+enm.name+" at line "+this.tn.line+": "+token+" ('"+Lang.END+"' expected)"));
}
};
/**
* Parses an extensions statement.
* @param {Object} msg Message object
* @param {string} token Initial token
* @throws {Error} If the extensions statement cannot be parsed
* @private
*/
Parser.prototype._parseExtensions = function(msg, token) {
/** @type {Array.<number>} */
var range = [];
token = this.tn.next();
if (token === "min") { // FIXME: Does the official implementation support this?
range.push(Lang.ID_MIN);
} else if (token === "max") {
range.push(Lang.ID_MAX);
} else {
range.push(this._parseNumber(token));
}
token = this.tn.next();
if (token !== 'to') {
throw("Illegal extensions delimiter in message "+msg.name+" at line "+this.tn.line+" ('to' expected)");
}
token = this.tn.next();
if (token === "min") {
range.push(Lang.ID_MIN);
} else if (token === "max") {
range.push(Lang.ID_MAX);
} else {
range.push(this._parseNumber(token));
}
token = this.tn.next();
if (token !== Lang.END) {
throw(new Error("Illegal extension delimiter in message "+msg.name+" at line "+this.tn.line+": "+token+" ('"+Lang.END+"' expected)"));
}
return range;
};
/**
* Parses an extend block.
* @param {Object} parent Parent object
* @param {string} token Initial token
* @throws {Error} If the extend block cannot be parsed
* @private
*/
Parser.prototype._parseExtend = function(parent, token) {
token = this.tn.next();
if (!Lang.TYPEREF.test(token)) {
throw(new Error("Illegal extended message name at line "+this.tn.line+": "+token));
}
/** @dict */
var ext = {};
ext["ref"] = token;
ext["fields"] = [];
token = this.tn.next();
if (token !== Lang.OPEN) {
throw(new Error("Illegal OPEN in extend "+ext.name+" at line "+this.tn.line+": "+token+" ('"+Lang.OPEN+"' expected)"));
}
do {
token = this.tn.next();
if (token === Lang.CLOSE) {
token = this.tn.peek();
if (token == Lang.END) this.tn.next();
break;
} else if (Lang.RULE.test(token)) {
this._parseMessageField(ext, token);
} else {
throw(new Error("Illegal token in extend "+ext.name+" at line "+this.tn.line+": "+token+" (rule or '"+Lang.CLOSE+"' expected)"));
}
} while (true);
parent["messages"].push(ext);
return ext;
};
/**
* Returns a string representation of this object.
* @returns {string} String representation as of "Parser"
*/
Parser.prototype.toString = function() {
return "Parser";
};
return Parser;
})(ProtoBuf, ProtoBuf.Lang, ProtoBuf.DotProto.Tokenizer);
/**
* @alias ProtoBuf.Reflect
* @expose
*/
ProtoBuf.Reflect = (function(ProtoBuf) {
"use strict";
/**
* @exports ProtoBuf.Reflect
* @namespace
*/
var Reflect = {};
/**
* Constructs a Reflect base class.
* @exports ProtoBuf.Reflect.T
* @constructor
* @param {ProtoBuf.Reflect.T} parent Parent object
* @param {string} name Object name
*/
var T = function(parent, name) {
/**
* Parent object.
* @type {ProtoBuf.Reflect.T|null}
* @expose
*/
this.parent = parent;
/**
* Object name in namespace.
* @type {string}
* @expose
*/
this.name = name;
};
/**
* Returns the fully qualified name of this object.
* @returns {string} Fully qualified name as of ".PATH.TO.THIS"
* @expose
*/
T.prototype.fqn = function() {
var name = this.name,
ptr = this;
do {
ptr = ptr.parent;
if (ptr == null) break;
name = ptr.name+"."+name;
} while (true);
return name;
};
/**
* Returns a string representation of this Reflect object (its fully qualified name).
* @param {boolean=} includeClass Set to true to include the class name. Defaults to false.
* @return String representation
* @expose
*/
T.prototype.toString = function(includeClass) {
var name = this.fqn();
if (includeClass) {
if (this instanceof Message) {
name = "Message "+name;
} else if (this instanceof Message.Field) {
name = "Message.Field "+name;
} else if (this instanceof Enum) {
name = "Enum "+name;
} else if (this instanceof Enum.Value) {
name = "Enum.Value "+name;
} else if (this instanceof Service) {
name = "Service "+name;
} else if (this instanceof Service.Method) {
if (this instanceof Service.RPCMethod) {
name = "Service.RPCMethod "+name;
} else {
name = "Service.Method "+name; // Should not happen as it is abstract
}
} else if (this instanceof Namespace) {
name = "Namespace "+name;
}
}
return name;
};
/**
* Builds this type.
* @throws {Error} If this type cannot be built directly
* @expose
*/
T.prototype.build = function() {
throw(new Error(this.toString(true)+" cannot be built directly"));
};
/**
* @alias ProtoBuf.Reflect.T
* @expose
*/
Reflect.T = T;
/**
* Constructs a new Namespace.
* @exports ProtoBuf.Reflect.Namespace
* @param {ProtoBuf.Reflect.Namespace|null} parent Namespace parent
* @param {string} name Namespace name
* @param {Object.<string,*>} options Namespace options
* @constructor
* @extends ProtoBuf.Reflect.T
*/
var Namespace = function(parent, name, options) {
T.call(this, parent, name);
/**
* Children inside the namespace.
* @type {Array.<ProtoBuf.Reflect.T>}
*/
this.children = [];
/**
* Options.
* @type {Object.<string, *>}
*/
this.options = options || {};
};
// Extends T
Namespace.prototype = Object.create(T.prototype);
/**
* Returns an array of the namespace's children.
* @param {ProtoBuf.Reflect.T=} type Filter type (returns instances of this type only). Defaults to null (all children).
* @return {Array.<ProtoBuf.Reflect.T>}
* @expose
*/
Namespace.prototype.getChildren = function(type) {
type = type || null;
if (type == null) {
return this.children.slice();
}
var children = [];
for (var i=0; i<this.children.length; i++) {
if (this.children[i] instanceof type) {
children.push(this.children[i]);
}
}
return children;
};
/**
* Adds a child to the namespace.
* @param {ProtoBuf.Reflect.T} child Child
* @throws {Error} If the child cannot be added (duplicate)
* @expose
*/
Namespace.prototype.addChild = function(child) {
var other;
if (other = this.getChild(child.name)) {
// Try to revert camelcase transformation on collision
if (other instanceof Message.Field && other.name !== other.originalName && !this.hasChild(other.originalName)) {
other.name = other.originalName; // Revert previous first (effectively keeps both originals)
} else if (child instanceof Message.Field && child.name !== child.originalName && !this.hasChild(child.originalName)) {
child.name = child.originalName;
} else {
throw(new Error("Duplicate name in namespace "+this.toString(true)+": "+child.name));
}
}
this.children.push(child);
};
/**
* Tests if this namespace has a child with the specified name.
* @param {string|number} nameOrId Child name or id
* @returns {boolean} true if there is one, else false
* @expose
*/
Namespace.prototype.hasChild = function(nameOrId) {
var i;
if (typeof nameOrId == 'number') {
for (i=0; i<this.children.length; i++) if (typeof this.children[i].id !== 'undefined' && this.children[i].id == nameOrId) return true;
} else {
for (i=0; i<this.children.length; i++) if (typeof this.children[i].name !== 'undefined' && this.children[i].name == nameOrId) return true;
}
return false;
};
/**
* Gets a child by its name.
* @param {string|number} nameOrId Child name or id
* @return {?ProtoBuf.Reflect.T} The child or null if not found
* @expose
*/
Namespace.prototype.getChild = function(nameOrId) {
var i;
if (typeof nameOrId == 'number') {
for (i=0; i<this.children.length; i++) if (typeof this.children[i].id !== 'undefined' && this.children[i].id == nameOrId) return this.children[i];
} else {
for (i=0; i<this.children.length; i++) if (typeof this.children[i].name !== 'undefined' && this.children[i].name == nameOrId) return this.children[i];
}
return null;
};
/**
* Resolves a reflect object inside of this namespace.
* @param {string} qn Qualified name to resolve
* @param {boolean=} excludeFields Excludes fields, defaults to `false`
* @return {ProtoBuf.Reflect.Namespace|null} The resolved type or null if not found
* @expose
*/
Namespace.prototype.resolve = function(qn, excludeFields) {
var part = qn.split(".");
var ptr = this, i=0;
if (part[i] == "") { // Fully qualified name, e.g. ".My.Message'
while (ptr.parent != null) {
ptr = ptr.parent;
}
i++;
}
var child;
do {
do {
child = ptr.getChild(part[i]);
if (!child || !(child instanceof Reflect.T) || (excludeFields && child instanceof Reflect.Message.Field)) {
ptr = null;
break;
}
ptr = child; i++;
} while (i < part.length);
if (ptr != null) break; // Found
// Else search the parent
if (this.parent !== null) {
return this.parent.resolve(qn, excludeFields);
}
} while (ptr != null);
return ptr;
};
/**
* Builds the namespace and returns the runtime counterpart.
* @return {Object.<string,Function|Object>} Runtime namespace
* @expose
*/
Namespace.prototype.build = function() {
/** @dict */
var ns = {};
var children = this.getChildren(), child;
for (var i=0; i<children.length; i++) {
child = children[i];
if (child instanceof Namespace) {
ns[child.name] = child.build();
}
}
if (Object.defineProperty) {
Object.defineProperty(ns, "$options", {
"value": this.buildOpt(),
"enumerable": false,
"configurable": false,
"writable": false
});
}
return ns;
};
/**
* Builds the namespace's '$options' property.
* @return {Object.<string,*>}
*/
Namespace.prototype.buildOpt = function() {
var opt = {};
var keys = Object.keys(this.options);
for (var i=0; i<keys.length; i++) {
var key = keys[i];
var val = this.options[keys[i]];
// TODO: Options are not resolved, yet.
// if (val instanceof Namespace) {
// opt[key] = val.build();
// } else {
opt[key] = val;
// }
}
return opt;
};
/**
* Gets the value assigned to the option with the specified name.
* @param {string=} name Returns the option value if specified, otherwise all options are returned.
* @return {*|Object.<string,*>}null} Option value or NULL if there is no such option
*/
Namespace.prototype.getOption = function(name) {
if (typeof name == 'undefined') {
return this.options;
}
return typeof this.options[name] != 'undefined' ? this.options[name] : null;
};
/**
* @alias ProtoBuf.Reflect.Namespace
* @expose
*/
Reflect.Namespace = Namespace;
/**
* Constructs a new Message.
* @exports ProtoBuf.Reflect.Message
* @param {ProtoBuf.Reflect.Namespace} parent Parent message or namespace
* @param {string} name Message name
* @param {Object.<string,*>} options Message options
* @constructor
* @extends ProtoBuf.Reflect.Namespace
*/
var Message = function(parent, name, options) {
Namespace.call(this, parent, name, options);
/**
* Extensions range.
* @type {!Array.<number>}
* @expose
*/
this.extensions = [ProtoBuf.Lang.ID_MIN, ProtoBuf.Lang.ID_MAX];
/**
* Runtime message class.
* @type {?function(new:ProtoBuf.Builder.Message)}
* @expose
*/
this.clazz = null;
};
// Extends Namespace
Message.prototype = Object.create(Namespace.prototype);
/**
* Builds the message and returns the runtime counterpart, which is a fully functional class.
* @see ProtoBuf.Builder.Message
* @param {boolean=} rebuild Whether to rebuild or not, defaults to false
* @return {ProtoBuf.Reflect.Message} Message class
* @throws {Error} If the message cannot be built
* @expose
*/
Message.prototype.build = function(rebuild) {
if (this.clazz && !rebuild) return this.clazz;
// We need to create a prototyped Message class in an isolated scope
var clazz = (function(ProtoBuf, T) {
var fields = T.getChildren(Reflect.Message.Field);
/**
* Constructs a new runtime Message.
* @name ProtoBuf.Builder.Message
* @class Barebone of all runtime messages.
* @param {Object.<string,*>|...[string]} values Preset values
* @constructor
* @throws {Error} If the message cannot be created
*/
var Message = function(values) {
ProtoBuf.Builder.Message.call(this);
var i, field;
// Create fields on the object itself to allow setting and getting through Message#fieldname
for (i=0; i<fields.length; i++) {
field = fields[i];
this[field.name] = (field.repeated) ? [] : null;
}
// Set the default values
for (i=0; i<fields.length; i++) {
field = fields[i];
if (typeof field.options['default'] != 'undefined') {
try {
this.set(field.name, field.options['default']); // Should not throw
} catch (e) {
throw(new Error("[INTERNAL] "+e));
}
}
}
// Set field values from a values object
if (arguments.length == 1 && typeof values == 'object' &&
/* not another Message */ typeof values.encode != 'function' &&
/* not a repeated field */ !ProtoBuf.Util.isArray(values) &&
/* not a ByteBuffer */ !(values instanceof ByteBuffer) &&
/* not an ArrayBuffer */ !(values instanceof ArrayBuffer) &&
/* not a Long */ !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) {
var keys = Object.keys(values);
for (i=0; i<keys.length; i++) {
this.set(keys[i], values[keys[i]]); // May throw
}
// Else set field values from arguments, in correct order
} else {
for (i=0; i<arguments.length; i++) {
if (i<fields.length) {
this.set(fields[i].name, arguments[i]); // May throw
}
}
}
};
// Extends ProtoBuf.Builder.Message
Message.prototype = Object.create(ProtoBuf.Builder.Message.prototype);
/**
* Adds a value to a repeated field.
* @name ProtoBuf.Builder.Message#add
* @function
* @param {string} key Field name
* @param {*} value Value to add
* @throws {Error} If the value cannot be added
* @expose
*/
Message.prototype.add = function(key, value) {
var field = T.getChild(key);
if (!field) {
throw(new Error(this+"#"+key+" is undefined"));
}
if (!(field instanceof ProtoBuf.Reflect.Message.Field)) {
throw(new Error(this+"#"+key+" is not a field: "+field.toString(true))); // May throw if it's an enum or embedded message
}
if (!field.repeated) {
throw(new Error(this+"#"+key+" is not a repeated field"));
}
if (this[field.name] === null) this[field.name] = [];
this[field.name].push(field.verifyValue(value, true));
};
/**
* Sets a field value.
* @name ProtoBuf.Builder.Message#set
* @function
* @param {string} key Key
* @param {*} value Value to set
* @throws {Error} If the value cannot be set
* @expose
*/
Message.prototype.set = function(key, value) {
var field = T.getChild(key);
if (!field) {
throw(new Error(this+"#"+key+" is not a field: undefined"));
}
if (!(field instanceof ProtoBuf.Reflect.Message.Field)) {
throw(new Error(this+"#"+key+" is not a field: "+field.toString(true)));
}
this[field.name] = field.verifyValue(value); // May throw
};
/**
* Gets a value.
* @name ProtoBuf.Builder.Message#get
* @function
* @param {string} key Key
* @return {*} Value
* @throws {Error} If there is no such field
* @expose
*/
Message.prototype.get = function(key) {
var field = T.getChild(key);
if (!field || !(field instanceof ProtoBuf.Reflect.Message.Field)) {
throw(new Error(this+"#"+key+" is not a field: undefined"));
}
if (!(field instanceof ProtoBuf.Reflect.Message.Field)) {
throw(new Error(this+"#"+key+" is not a field: "+field.toString(true)));
}
return this[field.name];
};
// Getters and setters
for (var i=0; i<fields.length; i++) {
var field = fields[i];
(function(field) {
// set/get[SomeValue]
var Name = field.originalName.replace(/(_[a-zA-Z])/g,
function(match) {
return match.toUpperCase().replace('_','');
}
);
Name = Name.substring(0,1).toUpperCase()+Name.substring(1);
// set/get_[some_value]
var name = field.originalName.replace(/([A-Z])/g,
function(match) {
return "_"+match;
}
);
/**
* Sets a value. This method is present for each field, but only if there is no name conflict with
* another field.
* @name ProtoBuf.Builder.Message#set[SomeField]
* @function
* @param {*} value Value to set
* @abstract
* @throws {Error} If the value cannot be set
*/
if (!T.hasChild("set"+Name)) {
Message.prototype["set"+Name] = function(value) {
this.set(field.name, value);
}
}
/**
* Sets a value. This method is present for each field, but only if there is no name conflict with
* another field.
* @name ProtoBuf.Builder.Message#set_[some_field]
* @function
* @param {*} value Value to set
* @abstract
* @throws {Error} If the value cannot be set
*/
if (!T.hasChild("set_"+name)) {
Message.prototype["set_"+name] = function(value) {
this.set(field.name, value);
};
}
/**
* Gets a value. This method is present for each field, but only if there is no name conflict with
* another field.
* @name ProtoBuf.Builder.Message#get[SomeField]
* @function
* @abstract
* @return {*} The value
*/
if (!T.hasChild("get"+Name)) {
Message.prototype["get"+Name] = function() {
return this.get(field.name); // Does not throw, field exists
}
}
/**
* Gets a value. This method is present for each field, but only if there is no name conflict with
* another field.
* @name ProtoBuf.Builder.Message#get_[some_field]
* @function
* @return {*} The value
* @abstract
*/
if (!T.hasChild("get_"+name)) {
Message.prototype["get_"+name] = function() {
return this.get(field.name); // Does not throw, field exists
};
}
})(field);
}
// En-/decoding
/**
* Encodes the message.
* @name ProtoBuf.Builder.Message#encode
* @function
* @param {(!ByteBuffer|boolean)=} buffer ByteBuffer to encode to. Will create a new one if omitted.
* @return {!ByteBuffer} Encoded message as a ByteBuffer
* @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
* returns the encoded ByteBuffer in the `encoded` property on the error.
* @expose
* @see ProtoBuf.Builder.Message#encode64
* @see ProtoBuf.Builder.Message#encodeHex
* @see ProtoBuf.Builder.Message#encodeAB
*/
Message.prototype.encode = function(buffer) {
buffer = buffer || new ByteBuffer();
var le = buffer.littleEndian;
try {
return T.encode(this, buffer.LE()).flip().LE(le);
} catch (e) {
buffer.LE(le);
throw(e);
}
};
/**
* Directly encodes the message to an ArrayBuffer.
* @name ProtoBuf.Builder.Message#encodeAB
* @function
* @return {ArrayBuffer} Encoded message as ArrayBuffer
* @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
* returns the encoded ArrayBuffer in the `encoded` property on the error.
* @expose
*/
Message.prototype.encodeAB = function() {
var enc;
try {
return this.encode().toArrayBuffer();
} catch (err) {
if (err["encoded"]) err["encoded"] = err["encoded"].toArrayBuffer();
throw(err);
}
};
/**
* Returns the message as an ArrayBuffer. This is an alias for {@link ProtoBuf.Builder.Message#encodeAB}.
* @name ProtoBuf.Builder.Message#toArrayBuffer
* @function
* @return {ArrayBuffer} Encoded message as ArrayBuffer
* @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
* returns the encoded ArrayBuffer in the `encoded` property on the error.
* @expose
*/
Message.prototype.toArrayBuffer = Message.prototype.encodeAB;
/**
* Directly encodes the message to a node Buffer.
* @name ProtoBuf.Builder.Message#encodeNB
* @function
* @return {!Buffer}
* @throws {Error} If the message cannot be encoded, not running under node.js or if required fields are
* missing. The later still returns the encoded node Buffer in the `encoded` property on the error.
* @expose
*/
Message.prototype.encodeNB = function() {
try {
return this.encode().toBuffer();
} catch (err) {
if (err["encoded"]) err["encoded"] = err["encoded"].toBuffer();
throw(err);
}
};
/**
* Returns the message as a node Buffer. This is an alias for {@link ProtoBuf.Builder.Message#encodeNB}.
* @name ProtoBuf.Builder.Message#encodeNB
* @function
* @return {!Buffer}
* @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
* returns the encoded node Buffer in the `encoded` property on the error.
* @expose
*/
Message.prototype.toBuffer = Message.prototype.encodeNB;
/**
* Directly encodes the message to a base64 encoded string.
* @name ProtoBuf.Builder.Message#encode64
* @function
* @return {string} Base64 encoded string
* @throws {Error} If the underlying buffer cannot be encoded or if required fields are missing. The later
* still returns the encoded base64 string in the `encoded` property on the error.
* @expose
*/
Message.prototype.encode64 = function() {
try {
return this.encode().toBase64();
} catch (err) {
if (err["encoded"]) err["encoded"] = err["encoded"].toBase64();
throw(err);
}
};
/**
* Returns the message as a base64 encoded string. This is an alias for {@link ProtoBuf.Builder.Message#encode64}.
* @name ProtoBuf.Builder.Message#toBase64
* @function
* @return {string} Base64 encoded string
* @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
* returns the encoded base64 string in the `encoded` property on the error.
* @expose
*/
Message.prototype.toBase64 = Message.prototype.encode64;
/**
* Directly encodes the message to a hex encoded string.
* @name ProtoBuf.Builder.Message#encodeHex
* @function
* @return {string} Hex encoded string
* @throws {Error} If the underlying buffer cannot be encoded or if required fields are missing. The later
* still returns the encoded hex string in the `encoded` property on the error.
* @expose
*/
Message.prototype.encodeHex = function() {
try {
return this.encode().toHex();
} catch (err) {
if (err["encoded"]) err["encoded"] = err["encoded"].toHex();
throw(err);
}
};
/**
* Returns the message as a hex encoded string. This is an alias for {@link ProtoBuf.Builder.Message#encodeHex}.
* @name ProtoBuf.Builder.Message#toHex
* @function
* @return {string} Hex encoded string
* @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
* returns the encoded hex string in the `encoded` property on the error.
* @expose
*/
Message.prototype.toHex = Message.prototype.encodeHex;
/**
* Decodes the message from the specified buffer or string.
* @name ProtoBuf.Builder.Message.decode
* @function
* @param {!ByteBuffer|!ArrayBuffer|!Buffer|string} buffer Buffer to decode from
* @param {string=} enc Encoding if buffer is a string: hex, utf8 (not recommended), defaults to base64
* @return {!ProtoBuf.Builder.Message} Decoded message
* @throws {Error} If the message cannot be decoded or if required fields are missing. The later still
* returns the decoded message with missing fields in the `decoded` property on the error.
* @expose
* @see ProtoBuf.Builder.Message.decode64
* @see ProtoBuf.Builder.Message.decodeHex
*/
Message.decode = function(buffer, enc) {
if (buffer === null) throw(new Error("buffer must not be null"));
if (typeof buffer === 'string') {
buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64");
}
buffer = buffer instanceof ByteBuffer ? buffer : ByteBuffer.wrap(buffer); // May throw
var le = buffer.littleEndian;
try {
var msg = T.decode(buffer.LE());
buffer.LE(le);
return msg;
} catch (e) {
buffer.LE(le);
throw(e);
}
};
/**
* Decodes the message from the specified base64 encoded string.
* @name ProtoBuf.Builder.Message.decode64
* @function
* @param {string} str String to decode from
* @return {!ProtoBuf.Builder.Message} Decoded message
* @throws {Error} If the message cannot be decoded or if required fields are missing. The later still
* returns the decoded message with missing fields in the `decoded` property on the error.
* @expose
*/
Message.decode64 = function(str) {
return Message.decode(str, "base64");
};
/**
* Decodes the message from the specified hex encoded string.
* @name ProtoBuf.Builder.Message.decodeHex
* @function
* @param {string} str String to decode from
* @return {!ProtoBuf.Builder.Message} Decoded message
* @throws {Error} If the message cannot be decoded or if required fields are missing. The later still
* returns the decoded message with missing fields in the `decoded` property on the error.
* @expose
*/
Message.decodeHex = function(str) {
return Message.decode(str, "hex");
};
// Utility
/**
* Returns a string representation of this Message.
* @name ProtoBuf.Builder.Message#toString
* @function
* @return {string} String representation as of ".Fully.Qualified.MessageName"
* @expose
*/
Message.prototype.toString = function() {
return T.toString();
};
// Static
/**
* Options.
* @name ProtoBuf.Builder.Message.$options
* @type {Object.<string,*>}
* @expose
*/
var O_o; // for cc
if (Object.defineProperty) {
Object.defineProperty(Message, '$options', {
'value': T.buildOpt(),
'enumerable': false,
'configurable': false,
'writable': false
});
}
return Message;
})(ProtoBuf, this);
// Static enums and prototyped sub-messages
var children = this.getChildren();
for (var i=0; i<children.length; i++) {
if (children[i] instanceof Enum) {
clazz[children[i]['name']] = children[i].build();
} else if (children[i] instanceof Message) {
clazz[children[i]['name']] = children[i].build();
} else if (children[i] instanceof Message.Field) {
// Ignore
} else {
throw(new Error("Illegal reflect child of "+this.toString(true)+": "+children[i].toString(true)));
}
}
return this.clazz = clazz;
};
/**
* Encodes a runtime message's contents to the specified buffer.
* @param {ProtoBuf.Builder.Message} message Runtime message to encode
* @param {ByteBuffer} buffer ByteBuffer to write to
* @return {ByteBuffer} The ByteBuffer for chaining
* @throws {string} If requried fields are missing or the message cannot be encoded for another reason
* @expose
*/
Message.prototype.encode = function(message, buffer) {
var fields = this.getChildren(Message.Field),
fieldMissing = null;
for (var i=0; i<fields.length; i++) {
var val = message.get(fields[i].name);
if (fields[i].required && val === null) {
if (fieldMissing === null) fieldMissing = fields[i];
} else {
fields[i].encode(val, buffer);
}
}
if (fieldMissing !== null) {
var err = new Error("Missing at least one required field for "+this.toString(true)+": "+fieldMissing);
err["encoded"] = buffer; // Still expose what we got
throw(err);
}
return buffer;
};
/**
* Decodes an encoded message and returns the decoded message.
* @param {ByteBuffer} buffer ByteBuffer to decode from
* @param {number=} length Message length. Defaults to decode all the available data.
* @return {ProtoBuf.Builder.Message} Decoded message
* @throws {Error} If the message cannot be decoded
* @expose
*/
Message.prototype.decode = function(buffer, length) {
length = typeof length === 'number' ? length : -1;
var start = buffer.offset;
var msg = new (this.clazz)();
while (buffer.offset < start+length || (length == -1 && buffer.remaining() > 0)) {
var tag = buffer.readVarint32();
var wireType = tag & 0x07,
id = tag >> 3;
var field = this.getChild(id); // Message.Field only
if (!field) {
// "messages created by your new code can be parsed by your old code: old binaries simply ignore the new field when parsing."
switch (wireType) {
case ProtoBuf.WIRE_TYPES.VARINT:
buffer.readVarint32();
break;
case ProtoBuf.WIRE_TYPES.BITS32:
buffer.offset += 4;
break;
case ProtoBuf.WIRE_TYPES.BITS64:
buffer.offset += 8;
break;
case ProtoBuf.WIRE_TYPES.LDELIM:
var len = buffer.readVarint32();
buffer.offset += len;
break;
default:
throw(new Error("Illegal wire type of unknown field "+id+" in "+this.toString(true)+"#decode: "+wireType));
}
continue;
}
if (field.repeated && !field.options["packed"]) {
msg.add(field.name, field.decode(wireType, buffer));
} else {
msg.set(field.name, field.decode(wireType, buffer));
}
}
// Check if all required fields are present
var fields = this.getChildren(ProtoBuf.Reflect.Field);
for (var i=0; i<fields.length; i++) {
if (fields[i].required && msg[fields[i].name] === null) {
var err = new Error("Missing at least one required field for "+this.toString(true)+": "+fields[i].name);
err["decoded"] = msg; // Still expose what we got
throw(err);
}
}
return msg;
};
/**
* @alias ProtoBuf.Reflect.Message
* @expose
*/
Reflect.Message = Message;
/**
* Constructs a new Message Field.
* @exports ProtoBuf.Reflect.Message.Field
* @param {ProtoBuf.Reflect.Message} message Message reference
* @param {string} rule Rule, one of requried, optional, repeated
* @param {string} type Data type, e.g. int32
* @param {string} name Field name
* @param {number} id Unique field id
* @param {Object.<string.*>=} options Options
* @constructor
* @extends ProtoBuf.Reflect.T
*/
var Field = function(message, rule, type, name, id, options) {
T.call(this, message, name);
/**
* Message field required flag.
* @type {boolean}
* @expose
*/
this.required = rule == "required";
/**
* Message field repeated flag.
* @type {boolean}
* @expose
*/
this.repeated = rule == "repeated";
/**
* Message field type. Type reference string if unresolved, protobuf type if resolved.
* @type {string|{name: string, wireType: number}
* @expose
*/
this.type = type;
/**
* Resolved type reference inside the global namespace.
* @type {ProtoBuf.Reflect.T|null}
* @expose
*/
this.resolvedType = null;
/**
* Unique message field id.
* @type {number}
* @expose
*/
this.id = id;
/**
* Message field options.
* @type {!Object.<string,*>}
* @dict
* @expose
*/
this.options = options || {};
/**
* Original field name.
* @type {string}
* @expose
*/
this.originalName = this.name; // Used to revert camelcase transformation on naming collisions
// Convert field names to camel case notation if the override is set
if (ProtoBuf.convertFieldsToCamelCase) {
this.name = this.name.replace(/_([a-zA-Z])/g, function($0, $1) {
return $1.toUpperCase();
});
}
};
// Extends T
Field.prototype = Object.create(T.prototype);
/**
* Checks if the given value can be set for this field.
* @param {*} value Value to check
* @param {boolean=} skipRepeated Whether to skip the repeated value check or not. Defaults to false.
* @return {*} Verified, maybe adjusted, value
* @throws {Error} If the value cannot be set for this field
* @expose
*/
Field.prototype.verifyValue = function(value, skipRepeated) {
skipRepeated = skipRepeated || false;
if (value === null) { // NULL values for optional fields
if (this.required) {
throw(new Error("Illegal value for "+this.toString(true)+": "+value+" (required)"));
}
return null;
}
var i;
if (this.repeated && !skipRepeated) { // Repeated values as arrays
if (!ProtoBuf.Util.isArray(value)) {
value = [value];
}
var res = [];
for (i=0; i<value.length; i++) {
res.push(this.verifyValue(value[i], true));
}
return res;
}
// All non-repeated fields expect no array
if (!this.repeated && ProtoBuf.Util.isArray(value)) {
throw(new Error("Illegal value for "+this.toString(true)+": "+value+" (no array expected)"));
}
// Signed 32bit
if (this.type == ProtoBuf.TYPES["int32"] || this.type == ProtoBuf.TYPES["sint32"] || this.type == ProtoBuf.TYPES["sfixed32"]) {
return isNaN(i = parseInt(value, 10)) ? i : i | 0; // Do not cast NaN as it'd become 0
}
// Unsigned 32bit
if (this.type == ProtoBuf.TYPES["uint32"] || this.type == ProtoBuf.TYPES["fixed32"]) {
return isNaN(i = parseInt(value, 10)) ? i : i >>> 0; // Do not cast NaN as it'd become 0
}
if (ProtoBuf.Long) {
// Signed 64bit
if (this.type == ProtoBuf.TYPES["int64"] || this.type == ProtoBuf.TYPES["sint64"] || this.type == ProtoBuf.TYPES["sfixed64"]) {
if (!(typeof value == 'object' && value instanceof ProtoBuf.Long)) {
return ProtoBuf.Long.fromNumber(value, false);
}
return value.unsigned ? value.toSigned() : value;
}
// Unsigned 64bit
if (this.type == ProtoBuf.TYPES["uint64"] || this.type == ProtoBuf.TYPES["fixed64"]) {
if (!(typeof value == 'object' && value instanceof ProtoBuf.Long)) {
return ProtoBuf.Long.fromNumber(value, true);
}
return value.unsigned ? value : value.toUnsigned();
}
}
// Bool
if (this.type == ProtoBuf.TYPES["bool"]) {
if (typeof value === 'string') return value === 'true';
else return !!value;
}
// Float
if (this.type == ProtoBuf.TYPES["float"] || this.type == ProtoBuf.TYPES["double"]) {
return parseFloat(value); // May also become NaN, +Infinity, -Infinity
}
// Length-delimited string
if (this.type == ProtoBuf.TYPES["string"]) {
return ""+value;
}
// Length-delimited bytes
if (this.type == ProtoBuf.TYPES["bytes"]) {
if (value && value instanceof ByteBuffer) {
return value;
}
return ByteBuffer.wrap(value);
}
// Constant enum value
if (this.type == ProtoBuf.TYPES["enum"]) {
var values = this.resolvedType.getChildren(Enum.Value);
for (i=0; i<values.length; i++) {
if (values[i].name == value) {
return values[i].id;
} else if (values[i].id == value) {
return values[i].id;
}
}
throw(new Error("Illegal value for "+this.toString(true)+": "+value+" (not a valid enum value)"));
}
// Embedded message
if (this.type == ProtoBuf.TYPES["message"]) {
if (typeof value !== 'object') {
throw(new Error("Illegal value for "+this.toString(true)+": "+value+" (object expected)"));
}
if (value instanceof this.resolvedType.clazz) {
return value;
}
// Else let's try to construct one from a key-value object
return new (this.resolvedType.clazz)(value); // May throw for a hundred of reasons
}
// We should never end here
throw(new Error("[INTERNAL] Illegal value for "+this.toString(true)+": "+value+" (undefined type "+this.type+")"));
};
/**
* Encodes the specified field value to the specified buffer.
* @param {*} value Field value
* @param {ByteBuffer} buffer ByteBuffer to encode to
* @return {ByteBuffer} The ByteBuffer for chaining
* @throws {Error} If the field cannot be encoded
* @expose
*/
Field.prototype.encode = function(value, buffer) {
value = this.verifyValue(value); // May throw
if (this.type == null || typeof this.type != 'object') {
throw(new Error("[INTERNAL] Unresolved type in "+this.toString(true)+": "+this.type));
}
if (value === null || (this.repeated && value.length == 0)) return buffer; // Optional omitted
try {
if (this.repeated) {
var i;
if (this.options["packed"]) {
// "All of the elements of the field are packed into a single key-value pair with wire type 2
// (length-delimited). Each element is encoded the same way it would be normally, except without a
// tag preceding it."
buffer.writeVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM);
buffer.ensureCapacity(buffer.offset += 1); // We do not know the length yet, so let's assume a varint of length 1
var start = buffer.offset; // Remember where the contents begin
for (i=0; i<value.length; i++) {
this.encodeValue(value[i], buffer);
}
var len = buffer.offset-start;
var varintLen = ByteBuffer.calculateVarint32(len);
if (varintLen > 1) { // We need to move the contents
var contents = buffer.slice(start, buffer.offset);
start += varintLen-1;
buffer.offset = start;
buffer.append(contents);
}
buffer.writeVarint32(len, start-varintLen);
} else {
// "If your message definition has repeated elements (without the [packed=true] option), the encoded
// message has zero or more key-value pairs with the same tag number"
for (i=0; i<value.length; i++) {
buffer.writeVarint32((this.id << 3) | this.type.wireType);
this.encodeValue(value[i], buffer);
}
}
} else {
buffer.writeVarint32((this.id << 3) | this.type.wireType);
this.encodeValue(value, buffer);
}
} catch (e) {
throw(new Error("Illegal value for "+this.toString(true)+": "+value+" ("+e+")"));
}
return buffer;
};
/**
* Encodes a value to the specified buffer. Does not encode the key.
* @param {*} value Field value
* @param {ByteBuffer} buffer ByteBuffer to encode to
* @return {ByteBuffer} The ByteBuffer for chaining
* @throws {Error} If the value cannot be encoded
* @expose
*/
Field.prototype.encodeValue = function(value, buffer) {
if (value === null) return; // Nothing to encode
// Tag has already been written
// 32bit varint as-is
if (this.type == ProtoBuf.TYPES["int32"] || this.type == ProtoBuf.TYPES["uint32"]) {
buffer.writeVarint32(value);
// 32bit varint zig-zag
} else if (this.type == ProtoBuf.TYPES["sint32"]) {
buffer.writeZigZagVarint32(value);
// Fixed unsigned 32bit
} else if (this.type == ProtoBuf.TYPES["fixed32"]) {
buffer.writeUint32(value);
// Fixed signed 32bit
} else if (this.type == ProtoBuf.TYPES["sfixed32"]) {
buffer.writeInt32(value);
// 64bit varint as-is
} else if (this.type == ProtoBuf.TYPES["int64"] || this.type == ProtoBuf.TYPES["uint64"]) {
buffer.writeVarint64(value); // throws
// 64bit varint zig-zag
} else if (this.type == ProtoBuf.TYPES["sint64"]) {
buffer.writeZigZagVarint64(value); // throws
// Fixed unsigned 64bit
} else if (this.type == ProtoBuf.TYPES["fixed64"]) {
buffer.writeUint64(value); // throws
// Fixed signed 64bit
} else if (this.type == ProtoBuf.TYPES["sfixed64"]) {
buffer.writeInt64(value); // throws
// Bool
} else if (this.type == ProtoBuf.TYPES["bool"]) {
if (typeof value === 'string') buffer.writeVarint32(value.toLowerCase() === 'false' ? 0 : !!value);
else buffer.writeVarint32(value ? 1 : 0);
// Constant enum value
} else if (this.type == ProtoBuf.TYPES["enum"]) {
buffer.writeVarint32(value);
// 32bit float
} else if (this.type == ProtoBuf.TYPES["float"]) {
buffer.writeFloat32(value);
// 64bit float
} else if (this.type == ProtoBuf.TYPES["double"]) {
buffer.writeFloat64(value);
// Length-delimited string
} else if (this.type == ProtoBuf.TYPES["string"]) {
buffer.writeVString(value);
// Length-delimited bytes
} else if (this.type == ProtoBuf.TYPES["bytes"]) {
if (value.offset > value.length) { // Forgot to flip?
buffer = buffer.clone().flip();
}
buffer.writeVarint32(value.remaining());
buffer.append(value);
// Embedded message
} else if (this.type == ProtoBuf.TYPES["message"]) {
var bb = new ByteBuffer().LE();
this.resolvedType.encode(value, bb);
buffer.writeVarint32(bb.offset);
buffer.append(bb.flip());
} else {
// We should never end here
throw(new Error("[INTERNAL] Illegal value to encode in "+this.toString(true)+": "+value+" (unknown type)"));
}
return buffer;
};
/**
* Decode the field value from the specified buffer.
* @param {number} wireType Leading wire type
* @param {ByteBuffer} buffer ByteBuffer to decode from
* @param {boolean=} skipRepeated Whether to skip the repeated check or not. Defaults to false.
* @return {*} Decoded value
* @throws {Error} If the field cannot be decoded
* @expose
*/
Field.prototype.decode = function(wireType, buffer, skipRepeated) {
var value, nBytes;
if (wireType != this.type.wireType && (skipRepeated || (wireType != ProtoBuf.WIRE_TYPES.LDELIM || !this.repeated))) {
throw(new Error("Illegal wire type for field "+this.toString(true)+": "+wireType+" ("+this.type.wireType+" expected)"));
}
if (wireType == ProtoBuf.WIRE_TYPES.LDELIM && this.repeated && this.options["packed"]) {
if (!skipRepeated) {
nBytes = buffer.readVarint32();
nBytes = buffer.offset + nBytes; // Limit
var values = [];
while (buffer.offset < nBytes) {
values.push(this.decode(this.type.wireType, buffer, true));
}
return values;
}
// Read the next value otherwise...
}
// 32bit signed varint
if (this.type == ProtoBuf.TYPES["int32"]) {
return buffer.readVarint32() | 0;
}
// 32bit unsigned varint
if (this.type == ProtoBuf.TYPES["uint32"]) {
return buffer.readVarint32() >>> 0;
}
// 32bit signed varint zig-zag
if (this.type == ProtoBuf.TYPES["sint32"]) {
return buffer.readZigZagVarint32() | 0;
}
// Fixed 32bit unsigned
if (this.type == ProtoBuf.TYPES["fixed32"]) {
return buffer.readUint32() >>> 0;
}
// Fixed 32bit signed
if (this.type == ProtoBuf.TYPES["sfixed32"]) {
return buffer.readInt32() | 0;
}
// 64bit signed varint
if (this.type == ProtoBuf.TYPES["int64"]) {
return buffer.readVarint64();
}
// 64bit unsigned varint
if (this.type == ProtoBuf.TYPES["uint64"]) {
return buffer.readVarint64().toUnsigned();
}
// 64bit signed varint zig-zag
if (this.type == ProtoBuf.TYPES["sint64"]) {
return buffer.readZigZagVarint64();
}
// Fixed 64bit unsigned
if (this.type == ProtoBuf.TYPES["fixed64"]) {
return buffer.readUint64();
}
// Fixed 64bit signed
if (this.type == ProtoBuf.TYPES["sfixed64"]) {
return buffer.readInt64();
}
// Bool varint
if (this.type == ProtoBuf.TYPES["bool"]) {
return !!buffer.readVarint32();
}
// Constant enum value varint)
if (this.type == ProtoBuf.TYPES["enum"]) {
return buffer.readVarint32(); // The following Builder.Message#set will already throw
}
// 32bit float
if (this.type == ProtoBuf.TYPES["float"]) {
return buffer.readFloat();
}
// 64bit float
if (this.type == ProtoBuf.TYPES["double"]) {
return buffer.readDouble();
}
// Length-delimited string
if (this.type == ProtoBuf.TYPES["string"]){
return buffer.readVString();
}
// Length-delimited bytes
if (this.type == ProtoBuf.TYPES["bytes"]) {
nBytes = buffer.readVarint32();
if (buffer.remaining() < nBytes) {
throw(new Error("Illegal number of bytes for "+this.toString(true)+": "+nBytes+" required but got only "+buffer.remaining()));
}
value = buffer.clone(); // Offset already set
value.length = value.offset+nBytes;
buffer.offset += nBytes;
return value;
}
// Length-delimited embedded message
if (this.type == ProtoBuf.TYPES["message"]) {
nBytes = buffer.readVarint32();
return this.resolvedType.decode(buffer, nBytes);
}
// We should never end here
throw(new Error("[INTERNAL] Illegal wire type for "+this.toString(true)+": "+wireType));
};
/**
* @alias ProtoBuf.Reflect.Message.Field
* @expose
*/
Reflect.Message.Field = Field;
/**
* Constructs a new Enum.
* @exports ProtoBuf.Reflect.Enum
* @param {!ProtoBuf.Reflect.T} parent Parent Reflect object
* @param {string} name Enum name
* @param {Object.<string.*>=} options Enum options
* @constructor
* @extends ProtoBuf.Reflect.Namespace
*/
var Enum = function(parent, name, options) {
Namespace.call(this, parent, name, options);
/**
* Runtime enum object.
* @type {Object.<string,number>|null}
* @expose
*/
this.object = null;
};
// Extends Namespace
Enum.prototype = Object.create(Namespace.prototype);
/**
* Builds this enum and returns the runtime counterpart.
* @return {Object<string,*>}
* @expose
*/
Enum.prototype.build = function() {
var enm = {};
var values = this.getChildren(Enum.Value);
for (var i=0; i<values.length; i++) {
enm[values[i]['name']] = values[i]['id'];
}
if (Object.defineProperty) {
Object.defineProperty(enm, '$options', {
'value': this.buildOpt(),
'enumerable': false,
'configurable': false,
'writable': false
});
}
return this.object = enm;
};
/**
* @alias ProtoBuf.Reflect.Enum
* @expose
*/
Reflect.Enum = Enum;
/**
* Constructs a new Enum Value.
* @exports ProtoBuf.Reflect.Enum.Value
* @param {!ProtoBuf.Reflect.Enum} enm Enum reference
* @param {string} name Field name
* @param {number} id Unique field id
* @constructor
* @extends ProtoBuf.Reflect.T
*/
var Value = function(enm, name, id) {
T.call(this, enm, name);
/**
* Unique enum value id.
* @type {number}
* @expose
*/
this.id = id;
};
// Extends T
Value.prototype = Object.create(T.prototype);
/**
* @alias ProtoBuf.Reflect.Enum.Value
* @expose
*/
Reflect.Enum.Value = Value;
/**
* Constructs a new Service.
* @exports ProtoBuf.Reflect.Service
* @param {!ProtoBuf.Reflect.Namespace} root Root
* @param {string} name Service name
* @param {Object.<string,*>=} options Options
* @constructor
* @extends ProtoBuf.Reflect.Namespace
*/
var Service = function(root, name, options) {
Namespace.call(this, root, name, options);
/**
* Built runtime service class.
* @type {?function(new:ProtoBuf.Builder.Service)}
*/
this.clazz = null;
};
// Extends Namespace
Service.prototype = Object.create(Namespace.prototype);
/**
* Builds the service and returns the runtime counterpart, which is a fully functional class.
* @see ProtoBuf.Builder.Service
* @param {boolean=} rebuild Whether to rebuild or not
* @return {Function} Service class
* @throws {Error} If the message cannot be built
* @expose
*/
Service.prototype.build = function(rebuild) {
if (this.clazz && !rebuild) return this.clazz;
return this.clazz = (function(ProtoBuf, T) {
/**
* Constructs a new runtime Service.
* @name ProtoBuf.Builder.Service
* @param {function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))=} rpcImpl RPC implementation receiving the method name and the message
* @class Barebone of all runtime services.
* @constructor
* @throws {Error} If the service cannot be created
*/
var Service = function(rpcImpl) {
ProtoBuf.Builder.Service.call(this);
/**
* Service implementation.
* @name ProtoBuf.Builder.Service#rpcImpl
* @type {!function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))}
* @expose
*/
this.rpcImpl = rpcImpl || function(name, msg, callback) {
// This is what a user has to implement: A function receiving the method name, the actual message to
// send (type checked) and the callback that's either provided with the error as its first
// argument or null and the actual response message.
setTimeout(callback.bind(this, new Error("Not implemented, see: https://github.com/dcodeIO/ProtoBuf.js/wiki/Services")), 0); // Must be async!
};
};
// Extends ProtoBuf.Builder.Service
Service.prototype = Object.create(ProtoBuf.Builder.Service.prototype);
if (Object.defineProperty) {
Object.defineProperty(Service, "$options", {
"value": T.buildOpt(),
"enumerable": false,
"configurable": false,
"writable": false
});
Object.defineProperty(Service.prototype, "$options", {
"value": Service["$options"],
"enumerable": false,
"configurable": false,
"writable": false
});
}
/**
* Asynchronously performs an RPC call using the given RPC implementation.
* @name ProtoBuf.Builder.Service.[Method]
* @function
* @param {!function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))} rpcImpl RPC implementation
* @param {ProtoBuf.Builder.Message} req Request
* @param {function(Error, (ProtoBuf.Builder.Message|ByteBuffer|Buffer|string)=)} callback Callback receiving
* the error if any and the response either as a pre-parsed message or as its raw bytes
* @abstract
*/
/**
* Asynchronously performs an RPC call using the instance's RPC implementation.
* @name ProtoBuf.Builder.Service#[Method]
* @function
* @param {ProtoBuf.Builder.Message} req Request
* @param {function(Error, (ProtoBuf.Builder.Message|ByteBuffer|Buffer|string)=)} callback Callback receiving
* the error if any and the response either as a pre-parsed message or as its raw bytes
* @abstract
*/
var rpc = T.getChildren(Reflect.Service.RPCMethod);
for (var i=0; i<rpc.length; i++) {
(function(method) {
// service#Method(message, callback)
Service.prototype[method.name] = function(req, callback) {
try {
if (!req || !(req instanceof method.resolvedRequestType.clazz)) {
setTimeout(callback.bind(this, new Error("Illegal request type provided to service method "+T.name+"#"+method.name)));
}
this.rpcImpl(method.fqn(), req, function(err, res) { // Assumes that this is properly async
if (err) {
callback(err);
return;
}
try { res = method.resolvedResponseType.clazz.decode(res); } catch (notABuffer) {}
if (!res || !(res instanceof method.resolvedResponseType.clazz)) {
callback(new Error("Illegal response type received in service method "+ T.name+"#"+method.name));
return;
}
callback(null, res);
});
} catch (err) {
setTimeout(callback.bind(this, err), 0);
}
};
// Service.Method(rpcImpl, message, callback)
Service[method.name] = function(rpcImpl, req, callback) {
new Service(rpcImpl)[method.name](req, callback);
};
if (Object.defineProperty) {
Object.defineProperty(Service[method.name], "$options", {
"value": method.buildOpt(),
"enumerable": false,
"configurable": false,
"writable": false
});
Object.defineProperty(Service.prototype[method.name], "$options", {
"value": Service[method.name]["$options"],
"enumerable": false,
"configurable": false,
"writable": false
});
}
})(rpc[i]);
}
return Service;
})(ProtoBuf, this);
};
Reflect.Service = Service;
/**
* Abstract service method.
* @exports ProtoBuf.Reflect.Service.Method
* @param {!ProtoBuf.Reflect.Service} svc Service
* @param {string} name Method name
* @param {Object.<string,*>=} options Options
* @constructor
* @extends ProtoBuf.Reflect.T
*/
var Method = function(svc, name, options) {
T.call(this, svc, name);
/**
* Options.
* @type {Object.<string, *>}
* @expose
*/
this.options = options || {};
};
// Extends T
Method.prototype = Object.create(T.prototype);
/**
* Builds the method's '$options' property.
* @name ProtoBuf.Reflect.Service.Method#buildOpt
* @function
* @return {Object.<string,*>}
*/
Method.prototype.buildOpt = Namespace.prototype.buildOpt;
/**
* @alias ProtoBuf.Reflect.Service.Method
* @expose
*/
Reflect.Service.Method = Method;
/**
* RPC service method.
* @exports ProtoBuf.Reflect.Service.RPCMethod
* @param {!ProtoBuf.Reflect.Service} svc Service
* @param {string} name Method name
* @param {string} request Request message name
* @param {string} response Response message name
* @param {Object.<string,*>=} options Options
* @constructor
* @extends ProtoBuf.Reflect.Service.Method
*/
var RPCMethod = function(svc, name, request, response, options) {
Method.call(this, svc, name, options);
/**
* Request message name.
* @type {string}
* @expose
*/
this.requestName = request;
/**
* Response message name.
* @type {string}
* @expose
*/
this.responseName = response;
/**
* Resolved request message type.
* @type {ProtoBuf.Reflect.Message}
* @expose
*/
this.resolvedRequestType = null;
/**
* Resolved response message type.
* @type {ProtoBuf.Reflect.Message}
* @expose
*/
this.resolvedResponseType = null;
};
// Extends Method
RPCMethod.prototype = Object.create(Method.prototype);
/**
* @alias ProtoBuf.Reflect.Service.RPCMethod
* @expose
*/
Reflect.Service.RPCMethod = RPCMethod;
return Reflect;
})(ProtoBuf);
/**
* @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.
* @constructor
*/
var Builder = function() {
/**
* Namespace.
* @type {ProtoBuf.Reflect.Namespace}
* @expose
*/
this.ns = new Reflect.Namespace(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;
};
/**
* Resets the pointer to the global namespace.
* @expose
*/
Builder.prototype.reset = function() {
this.ptr = this.ns;
};
/**
* Defines a package on top of the current pointer position and places the pointer on it.
* @param {string} pkg
* @param {Object.<string,*>=} options
* @return {ProtoBuf.Builder} this
* @throws {Error} If the package name is invalid
* @expose
*/
Builder.prototype.define = function(pkg, options) {
if (typeof pkg !== 'string' || !Lang.TYPEREF.test(pkg)) {
throw(new Error("Illegal package name: "+pkg));
}
var part = pkg.split("."), i;
for (i=0; i<part.length; i++) { // To be absolutely sure
if (!Lang.NAME.test(part[i])) {
throw(new Error("Illegal package name: "+part[i]));
}
}
for (i=0; i<part.length; i++) {
if (!this.ptr.hasChild(part[i])) { // Keep existing namespace
this.ptr.addChild(new Reflect.Namespace(this.ptr, part[i], options));
}
this.ptr = this.ptr.getChild(part[i]);
}
return this;
};
/**
* Tests if a definition is a valid message definition.
* @param {Object.<string,*>} def Definition
* @return {boolean} true if valid, else false
* @expose
*/
Builder.isValidMessage = function(def) {
// Messages require a string name
if (typeof def["name"] !== 'string' || !Lang.NAME.test(def["name"])) {
return false;
}
// Messages must not contain values (that'd be an enum) or methods (that'd be a service)
if (typeof def["values"] !== 'undefined' || typeof def["rpc"] !== 'undefined') {
return false;
}
// Fields, enums and messages are arrays if provided
var i;
if (typeof def["fields"] !== 'undefined') {
if (!ProtoBuf.Util.isArray(def["fields"])) {
return false;
}
var ids = [], id; // IDs must be unique
for (i=0; i<def["fields"].length; i++) {
if (!Builder.isValidMessageField(def["fields"][i])) {
return false;
}
id = parseInt(def["id"], 10);
if (ids.indexOf(id) >= 0) {
return false;
}
ids.push(id);
}
ids = null;
}
if (typeof def["enums"] !== 'undefined') {
if (!ProtoBuf.Util.isArray(def["enums"])) {
return false;
}
for (i=0; i<def["enums"].length; i++) {
if (!Builder.isValidEnum(def["enums"][i])) {
return false;
}
}
}
if (typeof def["messages"] !== 'undefined') {
if (!ProtoBuf.Util.isArray(def["messages"])) {
return false;
}
for (i=0; i<def["messages"].length; i++) {
if (!Builder.isValidMessage(def["messages"][i]) && !Builder.isValidExtend(def["messages"][i])) {
return false;
}
}
}
if (typeof def["extensions"] !== 'undefined') {
if (!ProtoBuf.Util.isArray(def["extensions"]) || def["extensions"].length !== 2 || typeof def["extensions"][0] !== 'number' || typeof def["extensions"][1] !== 'number') {
return false;
}
}
return true;
};
/**
* Tests if a definition is a valid message field definition.
* @param {Object} def Definition
* @return {boolean} true if valid, else false
* @expose
*/
Builder.isValidMessageField = 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;
}
if (!Lang.RULE.test(def["rule"]) || !Lang.NAME.test(def["name"]) || !Lang.TYPEREF.test(def["type"]) || !Lang.ID.test(""+def["id"])) {
return false;
}
if (typeof def["options"] != 'undefined') {
// Options are objects
if (typeof def["options"] != 'object') {
return false;
}
// Options are <string,*>
var keys = Object.keys(def["options"]);
for (var i=0; i<keys.length; i++) {
if (!Lang.OPTNAME.test(keys[i]) || (typeof def["options"][keys[i]] !== 'string' && typeof def["options"][keys[i]] !== 'number' && typeof def["options"][keys[i]] !== 'boolean')) {
return false;
}
}
}
return true;
};
/**
* Tests if a definition is a valid enum definition.
* @param {Object} def Definition
* @return {boolean} true if valid, else false
* @expose
*/
Builder.isValidEnum = function(def) {
// Enums require a string name
if (typeof def["name"] !== 'string' || !Lang.NAME.test(def["name"])) {
return false;
}
// Enums require at least one value
if (typeof def["values"] === 'undefined' || !ProtoBuf.Util.isArray(def["values"]) || def["values"].length == 0) {
return false;
}
for (var i=0; i<def["values"].length; i++) {
// Values are objects
if (typeof def["values"][i] != "object") {
return false;
}
// Values require a string name and an id
if (typeof def["values"][i]["name"] !== 'string' || typeof def["values"][i]["id"] === 'undefined') {
return false;
}
if (!Lang.NAME.test(def["values"][i]["name"]) || !Lang.NEGID.test(""+def["values"][i]["id"])) {
return false;
}
}
// It's not important if there are other fields because ["values"] is already unique
return true;
};
/**
* Creates ths specified protocol types at the current pointer position.
* @param {Array.<Object.<string,*>>} defs Messages, enums or services to create
* @return {ProtoBuf.Builder} this
* @throws {Error} If a message definition is invalid
* @expose
*/
Builder.prototype.create = function(defs) {
if (!defs) return; // Nothing to create
if (!ProtoBuf.Util.isArray(defs)) {
defs = [defs];
}
if (defs.length == 0) return;
// It's quite hard to keep track of scopes and memory here, so let's do this iteratively.
var stack = [], def, obj, subObj, i, j;
stack.push(defs); // One level [a, b, c]
while (stack.length > 0) {
defs = stack.pop();
if (ProtoBuf.Util.isArray(defs)) { // Stack always contains entire namespaces
while (defs.length > 0) {
def = defs.shift(); // Namespace always contains an array of messages, enums and services
if (Builder.isValidMessage(def)) {
obj = new Reflect.Message(this.ptr, def["name"], def["options"]);
// Create fields
if (def["fields"] && def["fields"].length > 0) {
for (i=0; i<def["fields"].length; i++) { // i=Fields
if (obj.hasChild(def['fields'][i]['id'])) {
throw(new Error("Duplicate field id in message "+obj.name+": "+def['fields'][i]['id']));
}
if (def["fields"][i]["options"]) {
subObj = Object.keys(def["fields"][i]["options"]);
for (j=0; j<subObj.length; j++) { // j=Option names
if (!Lang.OPTNAME.test(subObj[j])) {
throw(new Error("Illegal field option name in message "+obj.name+"#"+def["fields"][i]["name"]+": "+subObj[j]));
}
if (typeof def["fields"][i]["options"][subObj[j]] !== 'string' && typeof def["fields"][i]["options"][subObj[j]] !== 'number' && typeof def["fields"][i]["options"][subObj[j]] !== 'boolean') {
throw(new Error("Illegal field option value in message "+obj.name+"#"+def["fields"][i]["name"]+"#"+subObj[j]+": "+def["fields"][i]["options"][subObj[j]]));
}
}
subObj = null;
}
obj.addChild(new Reflect.Message.Field(obj, def["fields"][i]["rule"], def["fields"][i]["type"], def["fields"][i]["name"], def["fields"][i]["id"], def["fields"][i]["options"]));
}
}
// Push enums and messages to stack
subObj = [];
if (typeof def["enums"] !== 'undefined' && def['enums'].length > 0) {
for (i=0; i<def["enums"].length; i++) {
subObj.push(def["enums"][i]);
}
}
if (def["messages"] && def["messages"].length > 0) {
for (i=0; i<def["messages"].length; i++) {
subObj.push(def["messages"][i]);
}
}
// Set extension range
if (def["extensions"]) {
obj.extensions = def["extensions"];
if (obj.extensions[0] < ProtoBuf.Lang.ID_MIN) {
obj.extensions[0] = ProtoBuf.Lang.ID_MIN;
}
if (obj.extensions[1] > ProtoBuf.Lang.ID_MAX) {
obj.extensions[1] = ProtoBuf.Lang.ID_MAX;
}
}
this.ptr.addChild(obj); // Add to current namespace
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;
obj = null;
} else if (Builder.isValidEnum(def)) {
obj = new Reflect.Enum(this.ptr, def["name"], def["options"]);
for (i=0; i<def["values"].length; i++) {
obj.addChild(new Reflect.Enum.Value(obj, def["values"][i]["name"], def["values"][i]["id"]));
}
this.ptr.addChild(obj);
obj = null;
} else if (Builder.isValidService(def)) {
obj = new Reflect.Service(this.ptr, def["name"], def["options"]);
for (i in def["rpc"]) {
if (def["rpc"].hasOwnProperty(i)) {
obj.addChild(new Reflect.Service.RPCMethod(obj, i, def["rpc"][i]["request"], def["rpc"][i]["response"], def["rpc"][i]["options"]));
}
}
this.ptr.addChild(obj);
obj = null;
} else if (Builder.isValidExtend(def)) {
obj = this.ptr.resolve(def["ref"]);
if (obj) {
for (i=0; i<def["fields"].length; i++) { // i=Fields
if (obj.hasChild(def['fields'][i]['id'])) {
throw(new Error("Duplicate extended field id in message "+obj.name+": "+def['fields'][i]['id']));
}
if (def['fields'][i]['id'] < obj.extensions[0] || def['fields'][i]['id'] > obj.extensions[1]) {
throw(new Error("Illegal extended field id in message "+obj.name+": "+def['fields'][i]['id']+" ("+obj.extensions.join(' to ')+" expected)"));
}
obj.addChild(new Reflect.Message.Field(obj, def["fields"][i]["rule"], def["fields"][i]["type"], def["fields"][i]["name"], def["fields"][i]["id"], def["fields"][i]["options"]));
}
/* if (this.ptr instanceof Reflect.Message) {
this.ptr.addChild(obj); // Reference the extended message here to enable proper lookups
} */
} else {
if (!/\.?google\.protobuf\./.test(def["ref"])) { // Silently skip internal extensions
throw(new Error("Extended message "+def["ref"]+" is not defined"));
}
}
} else {
throw(new Error("Not a valid message, enum, service or extend definition: "+JSON.stringify(def)));
}
def = null;
}
// Break goes here
} else {
throw(new Error("Not a valid namespace definition: "+JSON.stringify(defs)));
}
defs = null;
this.ptr = this.ptr.parent; // This namespace is s done
}
this.resolved = false; // Require re-resolve
this.result = null; // Require re-build
return this;
};
/**
* Tests if the specified file is a valid import.
* @param {string} filename
* @returns {boolean} true if valid, false if it should be skipped
* @expose
*/
Builder.isValidImport = function(filename) {
// Ignore google/protobuf/descriptor.proto (for example) as it makes use of low-level
// bootstrapping directives that are not required and therefore cannot be parsed by ProtoBuf.js.
return !(/google\/protobuf\//.test(filename));
};
/**
* Imports another definition into this builder.
* @param {Object.<string,*>} json Parsed import
* @param {(string|{root: string, file: string})=} filename Imported file name
* @return {ProtoBuf.Builder} this
* @throws {Error} If the definition or file cannot be imported
* @expose
*/
Builder.prototype["import"] = function(json, filename) {
if (typeof filename === 'string') {
if (ProtoBuf.Util.IS_NODE) {
var path = require("path");
filename = path.resolve(filename);
}
if (!!this.files[filename]) {
this.reset();
return this; // Skip duplicate imports
}
this.files[filename] = true;
}
if (!!json['imports'] && json['imports'].length > 0) {
var importRoot, delim = '/', 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(new Error("Cannot determine import root: File name is unknown"));
}
var importFilename = importRoot+delim+json['imports'][i];
if (!Builder.isValidImport(importFilename)) continue; // e.g. google/protobuf/*
if (/\.proto$/i.test(importFilename) && !ProtoBuf.DotProto) { // If this is a NOPARSE build
importFilename = importFilename.replace(/\.proto$/, ".json"); // always load the JSON file
}
var contents = ProtoBuf.Util.fetch(importFilename);
if (contents === null) {
throw(new 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"]((new ProtoBuf.DotProto.Parser(contents+"")).parse(), 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;
}
}
if (!!json['messages']) {
if (!!json['package']) this.define(json['package'], json["options"]);
this.create(json['messages']);
this.reset();
}
if (!!json['enums']) {
if (!!json['package']) this.define(json['package'], json["options"]);
this.create(json['enums']);
this.reset();
}
if (!!json['services']) {
if (!!json['package']) this.define(json['package'], json["options"]);
this.create(json['services']);
this.reset();
}
if (!!json['extends']) {
if (!!json['package']) this.define(json['package'], json["options"]);
this.create(json['extends']);
this.reset();
}
return this;
};
/**
* Tests if a definition is a valid service definition.
* @param {Object} def Definition
* @return {boolean} true if valid, else false
* @expose
*/
Builder.isValidService = function(def) {
// Services require a string name
if (typeof def["name"] !== 'string' || !Lang.NAME.test(def["name"]) || typeof def["rpc"] !== 'object') {
return false;
}
return true;
};
/**
* Tests if a definition is a valid extension.
* @param {Object} def Definition
* @returns {boolean} true if valid, else false
* @expose
*/
Builder.isValidExtend = function(def) {
if (typeof def["ref"] !== 'string' || !Lang.TYPEREF.test(def["name"])) {
return false;
}
var i;
if (typeof def["fields"] !== 'undefined') {
if (!ProtoBuf.Util.isArray(def["fields"])) {
return false;
}
var ids = [], id; // IDs must be unique (does not yet test for the extended message's ids)
for (i=0; i<def["fields"].length; i++) {
if (!Builder.isValidMessageField(def["fields"][i])) {
return false;
}
id = parseInt(def["id"], 10);
if (ids.indexOf(id) >= 0) {
return false;
}
ids.push(id);
}
ids = null;
}
return true;
};
/**
* Resolves all namespace objects.
* @throws {Error} If a type cannot be resolved
* @expose
*/
Builder.prototype.resolveAll = function() {
// Resolve all reflected objects
var res;
if (this.ptr == null || typeof this.ptr.type === 'object') return; // Done (already resolved)
if (this.ptr instanceof Reflect.Namespace) {
// Build all children
var children = this.ptr.getChildren();
for (var i=0; i<children.length; i++) {
this.ptr = children[i];
this.resolveAll();
}
} else if (this.ptr instanceof Reflect.Message.Field) {
if (!Lang.TYPE.test(this.ptr.type)) { // Resolve type...
if (!Lang.TYPEREF.test(this.ptr.type)) {
throw(new Error("Illegal type reference in "+this.ptr.toString(true)+": "+this.ptr.type));
}
res = this.ptr.parent.resolve(this.ptr.type, true);
if (!res) {
throw(new 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"];
} else if (res instanceof Reflect.Message) {
this.ptr.type = ProtoBuf.TYPES["message"];
} else {
throw(new Error("Illegal type reference in "+this.ptr.toString(true)+": "+this.ptr.type));
}
} else {
this.ptr.type = ProtoBuf.TYPES[this.ptr.type];
}
} else if (this.ptr instanceof ProtoBuf.Reflect.Enum.Value) {
// No need to build enum values (built in enum)
} 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);
if (!res || !(res instanceof ProtoBuf.Reflect.Message)) {
throw(new Error("Illegal request type reference in "+this.ptr.toString(true)+": "+this.ptr.requestName));
}
this.ptr.resolvedRequestType = res;
res = this.ptr.parent.resolve(this.ptr.responseName);
if (!res || !(res instanceof ProtoBuf.Reflect.Message)) {
throw(new Error("Illegal response type reference in "+this.ptr.toString(true)+": "+this.ptr.responseName));
}
this.ptr.resolvedResponseType = res;
} else {
// Should not happen as nothing else is implemented
throw(new Error("Illegal service method type in "+this.ptr.toString(true)));
}
} else {
throw(new Error("Illegal object type in namespace: "+typeof(this.ptr)+":"+this.ptr));
}
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=} path Specifies what to return. If omitted, the entire namespace will be returned.
* @return {ProtoBuf.Builder.Message|Object.<string,*>}
* @throws {Error} If a type could not be resolved
* @expose
*/
Builder.prototype.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;
} else {
var part = path.split(".");
var 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.
* @return {ProtoBuf.Reflect.T} Reflection descriptor or `null` if not found
*/
Builder.prototype.lookup = function(path) {
return path ? this.ns.resolve(path) : this.ns;
};
/**
* Returns a string representation of this object.
* @return {string} String representation as of "Builder"
* @expose
*/
Builder.prototype.toString = function() {
return "Builder";
};
// Pseudo types documented in Reflect.js.
// Exist for the sole purpose of being able to "... instanceof ProtoBuf.Builder.Message" etc.
Builder.Message = function() {};
Builder.Service = function() {};
return Builder;
})(ProtoBuf, ProtoBuf.Lang, ProtoBuf.Reflect);
/**
* Loads a .proto string and returns the Builder.
* @param {string} proto .proto file contents
* @param {(ProtoBuf.Builder|string|{root: string, file: string})=} builder Builder to append to. Will create a new one if omitted.
* @param {(string|{root: string, file: string})=} filename The corresponding file name if known. Must be specified for imports.
* @return {ProtoBuf.Builder} Builder to create new messages
* @throws {Error} If the definition cannot be parsed or built
* @expose
*/
ProtoBuf.loadProto = function(proto, builder, filename) {
if (typeof builder == 'string' || (builder && typeof builder["file"] === 'string' && typeof builder["root"] === 'string')) {
filename = builder;
builder = null;
}
return ProtoBuf.loadJson((new ProtoBuf.DotProto.Parser(proto+"")).parse(), builder, filename);
};
/**
* Loads a .proto string and returns the Builder. This is an alias of {@link ProtoBuf.loadProto}.
* @function
* @param {string} proto .proto file contents
* @param {(ProtoBuf.Builder|string)=} builder Builder to append to. Will create a new one if omitted.
* @param {(string|{root: string, file: string})=} filename The corresponding file name if known. Must be specified for imports.
* @return {ProtoBuf.Builder} Builder to create new messages
* @throws {Error} If the definition cannot be parsed or built
* @expose
*/
ProtoBuf.protoFromString = ProtoBuf.loadProto; // Legacy
/**
* Loads a .proto file and returns the Builder.
* @param {string|{root: string, file: string}} filename Path to proto file or an object specifying 'file' with
* an overridden 'root' path for all imported files.
* @param {function(ProtoBuf.Builder)=} callback Callback that will receive the Builder as its first argument.
* If the request has failed, builder will be NULL. If omitted, the file will be read synchronously and this
* function will return the Builder or NULL if the request has failed.
* @param {ProtoBuf.Builder=} builder Builder to append to. Will create a new one if omitted.
* @return {?ProtoBuf.Builder|undefined} The Builder if synchronous (no callback specified, will be NULL if the
* request has failed), else undefined
* @expose
*/
ProtoBuf.loadProtoFile = function(filename, callback, builder) {
if (callback && typeof callback === 'object') {
builder = callback;
callback = null;
} else if (!callback || typeof callback !== 'function') {
callback = null;
}
if (callback) {
ProtoBuf.Util.fetch(typeof filename === 'object' ? filename["root"]+"/"+filename["file"] : filename, function(contents) {
callback(ProtoBuf.loadProto(contents, builder, filename));
});
} else {
var contents = ProtoBuf.Util.fetch(typeof filename === 'object' ? filename["root"]+"/"+filename["file"] : filename);
return contents !== null ? ProtoBuf.protoFromString(contents, builder, filename) : null;
}
};
/**
* Loads a .proto file and returns the Builder. This is an alias of {@link ProtoBuf.loadProtoFile}.
* @function
* @param {string|{root: string, file: string}} filename Path to proto file or an object specifying 'file' with
* an overridden 'root' path for all imported files.
* @param {function(ProtoBuf.Builder)=} callback Callback that will receive the Builder as its first argument.
* If the request has failed, builder will be NULL. If omitted, the file will be read synchronously and this
* function will return the Builder or NULL if the request has failed.
* @param {ProtoBuf.Builder=} builder Builder to append to. Will create a new one if omitted.
* @return {?ProtoBuf.Builder|undefined} The Builder if synchronous (no callback specified, will be NULL if the
* request has failed), else undefined
* @expose
*/
ProtoBuf.protoFromFile = ProtoBuf.loadProtoFile; // Legacy
/**
* Constructs a new Builder with the specified package defined.
* @param {string=} pkg Package name as fully qualified name, e.g. "My.Game". If no package is specified, the
* builder will only contain a global namespace.
* @param {Object.<string,*>=} options Top level options
* @return {ProtoBuf.Builder} New Builder
* @expose
*/
ProtoBuf.newBuilder = function(pkg, options) {
var builder = new ProtoBuf.Builder();
if (typeof pkg !== 'undefined' && pkg !== null) {
builder.define(pkg, options);
}
return builder;
};
/**
* Loads a .json definition and returns the Builder.
* @param {!*|string} json JSON definition
* @param {(ProtoBuf.Builder|string|{root: string, file: string})=} builder Builder to append to. Will create a new one if omitted.
* @param {(string|{root: string, file: string})=} filename The corresponding file name if known. Must be specified for imports.
* @return {ProtoBuf.Builder} Builder to create new messages
* @throws {Error} If the definition cannot be parsed or built
* @expose
*/
ProtoBuf.loadJson = function(json, builder, filename) {
if (typeof builder === 'string' || (builder && typeof builder["file"] === 'string' && typeof builder["root"] === 'string')) {
filename = builder;
builder = null;
}
if (!builder || typeof builder !== 'object') builder = ProtoBuf.newBuilder();
if (typeof json === 'string') json = JSON.parse(json);
builder["import"](json, filename);
builder.resolveAll();
builder.build();
return builder;
};
/**
* Loads a .json file and returns the Builder.
* @param {string|{root: string, file: string}} filename Path to json file or an object specifying 'file' with
* an overridden 'root' path for all imported files.
* @param {function(ProtoBuf.Builder)=} callback Callback that will receive the Builder as its first argument.
* If the request has failed, builder will be NULL. If omitted, the file will be read synchronously and this
* function will return the Builder or NULL if the request has failed.
* @param {ProtoBuf.Builder=} builder Builder to append to. Will create a new one if omitted.
* @return {?ProtoBuf.Builder|undefined} The Builder if synchronous (no callback specified, will be NULL if the
* request has failed), else undefined
* @expose
*/
ProtoBuf.loadJsonFile = function(filename, callback, builder) {
if (callback && typeof callback === 'object') {
builder = callback;
callback = null;
} else if (!callback || typeof callback !== 'function') {
callback = null;
}
if (callback) {
ProtoBuf.Util.fetch(typeof filename === 'object' ? filename["root"]+"/"+filename["file"] : filename, function(contents) {
try {
callback(ProtoBuf.loadJson(JSON.parse(contents), builder, filename));
} catch (err) {
callback(err);
}
});
} else {
var contents = ProtoBuf.Util.fetch(typeof filename === 'object' ? filename["root"]+"/"+filename["file"] : filename);
return contents !== null ? ProtoBuf.loadJson(JSON.parse(contents), builder, filename) : null;
}
};
return ProtoBuf;
}
// Enable module loading if available
if (typeof module != 'undefined' && module["exports"]) { // CommonJS
module["exports"] = loadProtoBuf(require("bytebuffer"));
} else if (typeof define != 'undefined' && define["amd"]) { // AMD
define("ProtoBuf", ["ByteBuffer"], loadProtoBuf);
} else { // Shim
if (!global["dcodeIO"]) {
global["dcodeIO"] = {};
}
global["dcodeIO"]["ProtoBuf"] = loadProtoBuf(global["dcodeIO"]["ByteBuffer"]);
}
})(this);