/** * @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} 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} */ 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} */ 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, };