@agile-dev/wx-protobuf
Version:
微信小程序使用的 protobuf 协议开发工具
584 lines (498 loc) • 19.5 kB
JavaScript
/**
* Created by zhangmiao on 2018/3/13.
*/
module.exports = Type;
var Namespace = require("./namespace");
((Type.prototype = Object.create(Namespace.prototype)).constructor = Type).className = "Type";
var Enum,
Field,
Message,
OneOf,
Writer,
Reader,
util,
verifier,
encoder,
decoder,
Service,
wrappers,
converter,
MapField;
function Type(name, options) {
Namespace.call(this, name, options);
/**
* Message fields.
* @type {Object.<string,Field>}
*/
this.fields = {}; // toJSON, marker
/**
* Oneofs declared within this namespace, if any.
* @type {Object.<string,OneOf>}
*/
this.oneofs = undefined; // toJSON
/**
* Extension ranges, if any.
* @type {number[][]}
*/
this.extensions = undefined; // toJSON
/**
* Reserved ranges, if any.
* @type {Array.<number[]|string>}
*/
this.reserved = undefined; // toJSON
/*?
* Whether this type is a legacy group.
* @type {boolean|undefined}
*/
this.group = undefined; // toJSON 这个可能不需要,因为不支持
/**
* Cached fields by id.
* @type {Object.<number,Field>|null}
* @private
*/
this._fieldsById = null;
/**
* Cached fields as an array.
* @type {Field[]|null}
* @private
*/
this._fieldsArray = null;
/**
* Cached oneofs as an array.
* @type {OneOf[]|null}
* @private
*/
this._oneofsArray = null;
/**
* Cached constructor.
* @type {Constructor<{}>}
* @private
*/
this._ctor = null;
}
Object.defineProperties(Type.prototype, {
/**
* Message fields by id.
* @name Type#fieldsById
* @type {Object.<number,Field>}
* @readonly
*/
fieldsById: {
get: function() {
/* istanbul ignore if */
if (this._fieldsById)
return this._fieldsById;
this._fieldsById = {};
for (var names = Object.keys(this.fields), i = 0; i < names.length; ++i) {
var field = this.fields[names[i]],
id = field.id;
/* istanbul ignore if */
if (this._fieldsById[id])
throw Error("duplicate id " + id + " in " + this);
this._fieldsById[id] = field;
}
return this._fieldsById;
}
},
/**
* Fields of this message as an array for iteration.
* @name Type#fieldsArray
* @type {Field[]}
* @readonly
*/
fieldsArray: {
get: function() {
return this._fieldsArray || (this._fieldsArray = util.toArray(this.fields));
}
},
/**
* Oneofs of this message as an array for iteration.
* @name Type#oneofsArray
* @type {OneOf[]}
* @readonly
*/
oneofsArray: {
get: function() {
return this._oneofsArray || (this._oneofsArray = util.toArray(this.oneofs));
}
},
/**
* The registered constructor, if any registered, otherwise a generic constructor.
* Assigning a function replaces the internal constructor. If the function does not extend {@link Message} yet, its prototype will be setup accordingly and static methods will be populated. If it already extends {@link Message}, it will just replace the internal constructor.
* @name Type#ctor
* @type {Constructor<{}>}
*/
ctor: {
get: function() {
return this._ctor || (this.ctor = Type.generateConstructor(this));
},
set: function(ctor) {
// Ensure proper prototype
var prototype = ctor.prototype;
if (!(prototype instanceof Message)) {
(ctor.prototype = new Message()).constructor = ctor;
util.merge(ctor.prototype, prototype);
}
// Classes and messages reference their reflected type
ctor.$type = ctor.prototype.$type = this;
// Mix in static methods
util.merge(ctor, Message, true);
util.merge(ctor.prototype, Message, true);
this._ctor = ctor;
// Messages have non-enumerable default values on their prototype
var i = 0;
for (; i < /* initializes */ this.fieldsArray.length; ++i)
this._fieldsArray[i].resolve(); // ensures a proper value
// Messages have non-enumerable getters and setters for each virtual oneof field
var ctorProperties = {};
for (i = 0; i < /* initializes */ this.oneofsArray.length; ++i) {
var oneofName = this._oneofsArray[i].resolve().name;
var oneOfGetAndSet = (function(fieldNames){
var fieldMap = {};
for (var i = 0; i < fieldNames.length; ++i)
fieldMap[fieldNames[i]] = 0;
return {
setter : function (name){
if(fieldNames.indexOf(name) < 0) return;
fieldMap[name] = 1;
for (var i = 0; i < fieldNames.length; ++i)
if (fieldNames[i] !== name)
delete this[/*"_"+*/fieldNames[i]];
},
getter : function (){
for (var keys = Object.keys(this), i = keys.length - 1; i > -1; --i)
if (fieldMap[keys[i]] === 1 && this[keys[i]] !== undefined && this[keys[i]] !== null)
return keys[i];
}
}
})(this._oneofsArray[i].oneof);
ctorProperties[oneofName] = {
get: oneOfGetAndSet.getter,
set: oneOfGetAndSet.setter
};
//var fieldNames = this._oneofsArray[i].oneof;
//for (var j = 0 ; j < fieldNames.length; j++){
// var fieldName = fieldNames[j];
// ctorProperties[fieldName] = {
// set : (function(oneofName, fieldName){
// return function (value){
// this[oneofName] = fieldName;
// this["_"+fieldName] = value;
// }
// })(oneofName , fieldName),
// get : (function(fieldName){
// return function (){
// return this["_"+fieldName];
// }
// })(fieldName)
// }
//}
}
if (i) {
//util.merge(ctor.prototype, ctorProperties, true);
Object.defineProperties(ctor.prototype, ctorProperties);
}
}
}
});
//生成一个构造函数
Type.generateConstructor = function generateConstructor(mtype) {
return function (p){
for (var i = 0, field; i < mtype.fieldsArray.length; i++){
if((field = mtype._fieldsArray[i]).map){
this[field.name] = {};
}else if(field.repeated){
this[field.name] = [];
}
}
if(p){
for (var ks = Object.keys(p), j = 0; j < ks.length; ++j) {
if (p[ks[j]] != null) {
this[ks[j]] = p[ks[j]]
}
}
}
};
};
function clearCache(type) {
type._fieldsById = type._fieldsArray = type._oneofsArray = null;
delete type.encode;
delete type.decode;
delete type.verify;
return type;
}
Type.fromJSON = function fromJSON(name, json) {
var type = new Type(name, json.options);
type.extensions = json.extensions;
type.reserved = json.reserved;
var names = Object.keys(json.fields),
i = 0;
for (; i < names.length; ++i)
type.add(
( typeof json.fields[names[i]].keyType !== "undefined"
? MapField.fromJSON
: Field.fromJSON )(names[i], json.fields[names[i]])
);
if (json.oneofs)
for (names = Object.keys(json.oneofs), i = 0; i < names.length; ++i)
type.add(OneOf.fromJSON(names[i], json.oneofs[names[i]]));
if (json.nested)
for (names = Object.keys(json.nested), i = 0; i < names.length; ++i) {
var nested = json.nested[names[i]];
type.add( // most to least likely
( nested.id !== undefined
? Field.fromJSON
: nested.fields !== undefined
? Type.fromJSON
: nested.values !== undefined
? Enum.fromJSON
: nested.methods !== undefined
? Service.fromJSON
: Namespace.fromJSON )(names[i], nested)
);
}
if (json.extensions && json.extensions.length)
type.extensions = json.extensions;
if (json.reserved && json.reserved.length)
type.reserved = json.reserved;
if (json.group)
type.group = true;
if (json.comment)
type.comment = json.comment;
return type;
};
Type.prototype.toJSON = function toJSON(toJSONOptions) {
var inherited = Namespace.prototype.toJSON.call(this, toJSONOptions);
var keepComments = toJSONOptions ? Boolean(toJSONOptions.keepComments) : false;
return {
"options" : inherited && inherited.options || undefined,
"oneofs" : Namespace.arrayToJSON(this.oneofsArray, toJSONOptions),
"fields" : Namespace.arrayToJSON(this.fieldsArray.filter(function(obj) { return !obj.declaringField; }), toJSONOptions) || {},
"extensions" : this.extensions && this.extensions.length ? this.extensions : undefined,
"reserved" : this.reserved && this.reserved.length ? this.reserved : undefined,
"group" : this.group || undefined,
"nested" : inherited && inherited.nested || undefined,
"comment" : keepComments ? this.comment : undefined
};
};
Type.prototype.resolveAll = function resolveAll() {
var fields = this.fieldsArray, i = 0;
while (i < fields.length)
fields[i++].resolve();
var oneofs = this.oneofsArray; i = 0;
while (i < oneofs.length)
oneofs[i++].resolve();
return Namespace.prototype.resolveAll.call(this);
};
Type.prototype.get = function get(name) {
return this.fields[name]
|| this.oneofs && this.oneofs[name]
|| this.nested && this.nested[name]
|| null;
};
Type.prototype.add = function add(object) {
if (this.get(object.name))
throw Error("duplicate name '" + object.name + "' in " + this);
if (object instanceof Field && object.extend === undefined) {
if (this._fieldsById && this._fieldsById[object.id])
throw Error("duplicate id " + object.id + " in " + this);
if (this.isReservedId(object.id))
throw Error("id " + object.id + " is reserved in " + this);
if (this.isReservedName(object.name))
throw Error("name '" + object.name + "' is reserved in " + this);
if (object.parent)
object.parent.remove(object);
this.fields[object.name] = object;
object.message = this;
object.onAdd(this);
return clearCache(this);
}
if (object instanceof OneOf) {
if (!this.oneofs)
this.oneofs = {};
this.oneofs[object.name] = object;
object.onAdd(this);
return clearCache(this);
}
return Namespace.prototype.add.call(this, object);
};
Type.prototype.remove = function remove(object) {
if (object instanceof Field && object.extend === undefined) {
// See Type#add for the reason why extension fields are excluded here.
/* istanbul ignore if */
if (!this.fields || this.fields[object.name] !== object)
throw Error(object + " is not a member of " + this);
delete this.fields[object.name];
object.parent = null;
object.onRemove(this);
return clearCache(this);
}
if (object instanceof OneOf) {
/* istanbul ignore if */
if (!this.oneofs || this.oneofs[object.name] !== object)
throw Error(object + " is not a member of " + this);
delete this.oneofs[object.name];
object.parent = null;
object.onRemove(this);
return clearCache(this);
}
return Namespace.prototype.remove.call(this, object);
};
Type.prototype.isReservedId = function isReservedId(id) {
return Namespace.isReservedId(this.reserved, id);
};
Type.prototype.isReservedName = function isReservedName(name) {
return Namespace.isReservedName(this.reserved, name);
};
Type.prototype.create = function create(properties) {
return new this.ctor(properties);
};
Type.prototype.setup = function setup() {
// Sets up everything at once so that the prototype chain does not have to be re-evaluated
// multiple times (V8, soft-deopt prototype-check).
var fullName = this.fullName,
types = [];
for (var i = 0; i < /* initializes */ this.fieldsArray.length; ++i)
types.push(this._fieldsArray[i].resolve().resolvedType);
// Replace setup methods with type-specific generated functions
this.encode = encoder(this)({
Writer : Writer,
types : types,
util : util
});
this.decode = decoder(this)({
Reader : Reader,
types : types,
util : util
});
this.verify = verifier(this)({
types : types,
util : util
});
this.fromObject = converter.fromObject(this)({
types : types,
util : util
});
this.toObject = converter.toObject(this)({
types : types,
util : util
});
// Inject custom wrappers for common types
var wrapper = wrappers[fullName];
if (wrapper) {
var originalThis = Object.create(this);
// if (wrapper.fromObject) {
originalThis.fromObject = this.fromObject;
this.fromObject = wrapper.fromObject.bind(originalThis);
// }
// if (wrapper.toObject) {
originalThis.toObject = this.toObject;
this.toObject = wrapper.toObject.bind(originalThis);
// }
}
return this;
};
/**
* Encodes a message of this type. Does not implicitly {@link Type#verify|verify} messages.
* @param {Message<{}>|Object.<string,*>} message Message instance or plain object
* @param {Writer} [writer] Writer to encode to
* @returns {Writer} writer
*/
Type.prototype.encode = function encode_setup(message, writer) {
return this.setup().encode(message, writer); // overrides this method
};
/**
* Encodes a message of this type preceeded by its byte length as a varint. Does not implicitly {@link Type#verify|verify} messages.
* @param {Message<{}>|Object.<string,*>} message Message instance or plain object
* @param {Writer} [writer] Writer to encode to
* @returns {Writer} writer
*/
Type.prototype.encodeDelimited = function encodeDelimited(message, writer) {
return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim();
};
/**
* Decodes a message of this type.
* @param {Reader|Uint8Array|ArrayBuffer} reader Reader or buffer to decode from
* @param {number} [length] Length of the message, if known beforehand
* @returns {Message<{}>} Decoded message
* @throws {Error} If the payload is not a reader or valid buffer
*/
Type.prototype.decode = function decode_setup(reader, length) {
return this.setup().decode(reader, length); // overrides this method
};
/**
* Decodes a message of this type preceeded by its byte length as a varint.
* @param {Reader|Uint8Array} reader Reader or buffer to decode from
* @returns {Message<{}>} Decoded message
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {util.ProtocolError} If required fields are missing
*/
Type.prototype.decodeDelimited = function decodeDelimited(reader) {
if (!(reader instanceof Reader))
reader = Reader.create(reader);
return this.decode(reader, reader.uint32());
};
/**
* Verifies that field values are valid and that required fields are present.
* @param {Object.<string,*>} message Plain object to verify
* @returns {null|string} `null` if valid, otherwise the reason why it is not
*/
Type.prototype.verify = function verify_setup(message) {
return this.setup().verify(message); // overrides this method
};
/**
* Creates a new message of this type from a plain object. Also converts values to their respective internal types.
* @param {Object.<string,*>} object Plain object to convert
* @returns {Message<{}>} Message instance
*/
Type.prototype.fromObject = function fromObject(object) {
return this.setup().fromObject(object);
};
/**
* Conversion options as used by {@link Type#toObject} and {@link Message.toObject}.
* @interface IConversionOptions
* @property {Function} [longs] Long conversion type.
* Valid values are `String` and `Number` (the global types).
* Defaults to copy the present value, which is a possibly unsafe number without and a {@link Long} with a long library.
* @property {Function} [enums] Enum value conversion type.
* Only valid value is `String` (the global type).
* Defaults to copy the present value, which is the numeric id.
* @property {Function} [bytes] Bytes value conversion type.
* Valid values are `Array` and (a base64 encoded) `String` (the global types).
* Defaults to copy the present value, which usually is a Buffer under node and an Uint8Array in the browser.
* @property {boolean} [defaults=false] Also sets default values on the resulting object
* @property {boolean} [arrays=false] Sets empty arrays for missing repeated fields even if `defaults=false`
* @property {boolean} [objects=false] Sets empty objects for missing map fields even if `defaults=false`
* @property {boolean} [oneofs=false] Includes virtual oneof properties set to the present field's name, if any
* @property {boolean} [json=false] Performs additional JSON compatibility conversions, i.e. NaN and Infinity to strings
*/
/**
* Creates a plain object from a message of this type. Also converts values to other types if specified.
* @param {Message<{}>} message Message instance
* @param {IConversionOptions} [options] Conversion options
* @returns {Object.<string,*>} Plain object
*/
Type.prototype.toObject = function toObject(message, options) {
return this.setup().toObject(message, options);
};
Type.d = function decorateType(typeName) {
return function typeDecorator(target) {
util.decorateType(target, typeName);
};
};
Type._configure = function (){
Enum = require("./enum");
Field = require("./field");
Message = require("./message");
OneOf = require("./oneof");
Writer = require("./writer");
Reader = require("./reader");
util = require("./util");
verifier = require("./verifier");
encoder = require("./encoder");
decoder = require("./decoder");
Service = require("./service");
wrappers = require("./wrappers");
converter = require("./converter");
MapField = require("./mapField");
};