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