197 lines
5.4 KiB
JavaScript
197 lines
5.4 KiB
JavaScript
/**
|
|
* @fileoverview Contains classes that hold data for a protobuf field.
|
|
*/
|
|
|
|
goog.module('protobuf.binary.field');
|
|
|
|
const WireType = goog.requireType('protobuf.binary.WireType');
|
|
const Writer = goog.requireType('protobuf.binary.Writer');
|
|
const {checkDefAndNotNull, checkState} = goog.require('protobuf.internal.checks');
|
|
|
|
/**
|
|
* Number of bits taken to represent a wire type.
|
|
* @const {number}
|
|
*/
|
|
const WIRE_TYPE_LENGTH_BITS = 3;
|
|
|
|
/** @const {number} */
|
|
const WIRE_TYPE_EXTRACTOR = (1 << WIRE_TYPE_LENGTH_BITS) - 1;
|
|
|
|
/**
|
|
* An IndexEntry consists of the wire type and the position of a field in the
|
|
* binary data. The wire type and the position are encoded into a single number
|
|
* to save memory, which can be decoded using Field.getWireType() and
|
|
* Field.getStartIndex() methods.
|
|
* @typedef {number}
|
|
*/
|
|
let IndexEntry;
|
|
|
|
/**
|
|
* An entry containing the index into the binary data and/or the corresponding
|
|
* cached JS object(s) for a field.
|
|
* @template T
|
|
* @final
|
|
* @package
|
|
*/
|
|
class Field {
|
|
/**
|
|
* Creates a field and inserts the wireType and position of the first
|
|
* occurrence of a field.
|
|
* @param {!WireType} wireType
|
|
* @param {number} startIndex
|
|
* @return {!Field}
|
|
*/
|
|
static fromFirstIndexEntry(wireType, startIndex) {
|
|
return new Field([Field.encodeIndexEntry(wireType, startIndex)]);
|
|
}
|
|
|
|
/**
|
|
* @param {T} decodedValue The cached JS object decoded from the binary data.
|
|
* @param {function(!Writer, number, T):void|undefined} encoder Write function
|
|
* to encode the cache into binary bytes.
|
|
* @return {!Field}
|
|
* @template T
|
|
*/
|
|
static fromDecodedValue(decodedValue, encoder) {
|
|
return new Field(null, decodedValue, encoder);
|
|
}
|
|
|
|
/**
|
|
* @param {!WireType} wireType
|
|
* @param {number} startIndex
|
|
* @return {!IndexEntry}
|
|
*/
|
|
static encodeIndexEntry(wireType, startIndex) {
|
|
return startIndex << WIRE_TYPE_LENGTH_BITS | wireType;
|
|
}
|
|
|
|
/**
|
|
* @param {!IndexEntry} indexEntry
|
|
* @return {!WireType}
|
|
*/
|
|
static getWireType(indexEntry) {
|
|
return /** @type {!WireType} */ (indexEntry & WIRE_TYPE_EXTRACTOR);
|
|
}
|
|
|
|
/**
|
|
* @param {!IndexEntry} indexEntry
|
|
* @return {number}
|
|
*/
|
|
static getStartIndex(indexEntry) {
|
|
return indexEntry >> WIRE_TYPE_LENGTH_BITS;
|
|
}
|
|
|
|
/**
|
|
* @param {?Array<!IndexEntry>} indexArray
|
|
* @param {T=} decodedValue
|
|
* @param {function(!Writer, number, T):void=} encoder
|
|
* @private
|
|
*/
|
|
constructor(indexArray, decodedValue = undefined, encoder = undefined) {
|
|
checkState(
|
|
!!indexArray || decodedValue !== undefined,
|
|
'At least one of indexArray and decodedValue must be set');
|
|
|
|
/** @private {?Array<!IndexEntry>} */
|
|
this.indexArray_ = indexArray;
|
|
/** @private {T|undefined} */
|
|
this.decodedValue_ = decodedValue;
|
|
// TODO: Consider storing an enum to represent encoder
|
|
/** @private {function(!Writer, number, T)|undefined} */
|
|
this.encoder_ = encoder;
|
|
}
|
|
|
|
/**
|
|
* Adds a new IndexEntry.
|
|
* @param {!WireType} wireType
|
|
* @param {number} startIndex
|
|
*/
|
|
addIndexEntry(wireType, startIndex) {
|
|
checkDefAndNotNull(this.indexArray_)
|
|
.push(Field.encodeIndexEntry(wireType, startIndex));
|
|
}
|
|
|
|
/**
|
|
* Returns the array of IndexEntry.
|
|
* @return {?Array<!IndexEntry>}
|
|
*/
|
|
getIndexArray() {
|
|
return this.indexArray_;
|
|
}
|
|
|
|
/**
|
|
* Caches the decoded value and sets the write function to encode cache into
|
|
* binary bytes.
|
|
* @param {T} decodedValue
|
|
* @param {function(!Writer, number, T):void|undefined} encoder
|
|
*/
|
|
setCache(decodedValue, encoder) {
|
|
this.decodedValue_ = decodedValue;
|
|
this.encoder_ = encoder;
|
|
this.maybeRemoveIndexArray_();
|
|
}
|
|
|
|
/**
|
|
* If the decoded value has been set.
|
|
* @return {boolean}
|
|
*/
|
|
hasDecodedValue() {
|
|
return this.decodedValue_ !== undefined;
|
|
}
|
|
|
|
/**
|
|
* Returns the cached decoded value. The value needs to be set when this
|
|
* method is called.
|
|
* @return {T}
|
|
*/
|
|
getDecodedValue() {
|
|
// Makes sure that the decoded value in the cache has already been set. This
|
|
// prevents callers from doing `if (field.getDecodedValue()) {...}` to check
|
|
// if a value exist in the cache, because the check might return false even
|
|
// if the cache has a valid value set (e.g. 0 or empty string).
|
|
checkState(this.decodedValue_ !== undefined);
|
|
return this.decodedValue_;
|
|
}
|
|
|
|
/**
|
|
* Returns the write function to encode cache into binary bytes.
|
|
* @return {function(!Writer, number, T)|undefined}
|
|
*/
|
|
getEncoder() {
|
|
return this.encoder_;
|
|
}
|
|
|
|
/**
|
|
* Returns a copy of the field, containing the original index entries and a
|
|
* shallow copy of the cache.
|
|
* @return {!Field}
|
|
*/
|
|
shallowCopy() {
|
|
// Repeated fields are arrays in the cache.
|
|
// We have to copy the array to make sure that modifications to a repeated
|
|
// field (e.g. add) are not seen on a cloned accessor.
|
|
const copiedCache = this.hasDecodedValue() ?
|
|
(Array.isArray(this.getDecodedValue()) ? [...this.getDecodedValue()] :
|
|
this.getDecodedValue()) :
|
|
undefined;
|
|
return new Field(this.getIndexArray(), copiedCache, this.getEncoder());
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
maybeRemoveIndexArray_() {
|
|
checkState(
|
|
this.encoder_ === undefined || this.decodedValue_ !== undefined,
|
|
'Encoder exists but decoded value doesn\'t');
|
|
if (this.encoder_ !== undefined) {
|
|
this.indexArray_ = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
exports = {
|
|
IndexEntry,
|
|
Field,
|
|
};
|