• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Contains classes that hold data for a protobuf field.
3 */
4
5goog.module('protobuf.binary.field');
6
7const WireType = goog.requireType('protobuf.binary.WireType');
8const Writer = goog.requireType('protobuf.binary.Writer');
9const {checkDefAndNotNull, checkState} = goog.require('protobuf.internal.checks');
10
11/**
12 * Number of bits taken to represent a wire type.
13 * @const {number}
14 */
15const WIRE_TYPE_LENGTH_BITS = 3;
16
17/** @const {number} */
18const WIRE_TYPE_EXTRACTOR = (1 << WIRE_TYPE_LENGTH_BITS) - 1;
19
20/**
21 * An IndexEntry consists of the wire type and the position of a field in the
22 * binary data. The wire type and the position are encoded into a single number
23 * to save memory, which can be decoded using Field.getWireType() and
24 * Field.getStartIndex() methods.
25 * @typedef {number}
26 */
27let IndexEntry;
28
29/**
30 * An entry containing the index into the binary data and/or the corresponding
31 * cached JS object(s) for a field.
32 * @template T
33 * @final
34 * @package
35 */
36class Field {
37  /**
38   * Creates a field and inserts the wireType and position of the first
39   * occurrence of a field.
40   * @param {!WireType} wireType
41   * @param {number} startIndex
42   * @return {!Field}
43   */
44  static fromFirstIndexEntry(wireType, startIndex) {
45    return new Field([Field.encodeIndexEntry(wireType, startIndex)]);
46  }
47
48  /**
49   * @param {T} decodedValue The cached JS object decoded from the binary data.
50   * @param {function(!Writer, number, T):void|undefined} encoder Write function
51   *     to encode the cache into binary bytes.
52   * @return {!Field}
53   * @template T
54   */
55  static fromDecodedValue(decodedValue, encoder) {
56    return new Field(null, decodedValue, encoder);
57  }
58
59  /**
60   * @param {!WireType} wireType
61   * @param {number} startIndex
62   * @return {!IndexEntry}
63   */
64  static encodeIndexEntry(wireType, startIndex) {
65    return startIndex << WIRE_TYPE_LENGTH_BITS | wireType;
66  }
67
68  /**
69   * @param {!IndexEntry} indexEntry
70   * @return {!WireType}
71   */
72  static getWireType(indexEntry) {
73    return /** @type {!WireType} */ (indexEntry & WIRE_TYPE_EXTRACTOR);
74  }
75
76  /**
77   * @param {!IndexEntry} indexEntry
78   * @return {number}
79   */
80  static getStartIndex(indexEntry) {
81    return indexEntry >> WIRE_TYPE_LENGTH_BITS;
82  }
83
84  /**
85   * @param {?Array<!IndexEntry>} indexArray
86   * @param {T=} decodedValue
87   * @param {function(!Writer, number, T):void=} encoder
88   * @private
89   */
90  constructor(indexArray, decodedValue = undefined, encoder = undefined) {
91    checkState(
92        !!indexArray || decodedValue !== undefined,
93        'At least one of indexArray and decodedValue must be set');
94
95    /** @private {?Array<!IndexEntry>} */
96    this.indexArray_ = indexArray;
97    /** @private {T|undefined} */
98    this.decodedValue_ = decodedValue;
99    // TODO: Consider storing an enum to represent encoder
100    /** @private {function(!Writer, number, T)|undefined} */
101    this.encoder_ = encoder;
102  }
103
104  /**
105   * Adds a new IndexEntry.
106   * @param {!WireType} wireType
107   * @param {number} startIndex
108   */
109  addIndexEntry(wireType, startIndex) {
110    checkDefAndNotNull(this.indexArray_)
111        .push(Field.encodeIndexEntry(wireType, startIndex));
112  }
113
114  /**
115   * Returns the array of IndexEntry.
116   * @return {?Array<!IndexEntry>}
117   */
118  getIndexArray() {
119    return this.indexArray_;
120  }
121
122  /**
123   * Caches the decoded value and sets the write function to encode cache into
124   * binary bytes.
125   * @param {T} decodedValue
126   * @param {function(!Writer, number, T):void|undefined} encoder
127   */
128  setCache(decodedValue, encoder) {
129    this.decodedValue_ = decodedValue;
130    this.encoder_ = encoder;
131    this.maybeRemoveIndexArray_();
132  }
133
134  /**
135   * If the decoded value has been set.
136   * @return {boolean}
137   */
138  hasDecodedValue() {
139    return this.decodedValue_ !== undefined;
140  }
141
142  /**
143   * Returns the cached decoded value. The value needs to be set when this
144   * method is called.
145   * @return {T}
146   */
147  getDecodedValue() {
148    // Makes sure that the decoded value in the cache has already been set. This
149    // prevents callers from doing `if (field.getDecodedValue()) {...}` to check
150    // if a value exist in the cache, because the check might return false even
151    // if the cache has a valid value set (e.g. 0 or empty string).
152    checkState(this.decodedValue_ !== undefined);
153    return this.decodedValue_;
154  }
155
156  /**
157   * Returns the write function to encode cache into binary bytes.
158   * @return {function(!Writer, number, T)|undefined}
159   */
160  getEncoder() {
161    return this.encoder_;
162  }
163
164  /**
165   * Returns a copy of the field, containing the original index entries and a
166   * shallow copy of the cache.
167   * @return {!Field}
168   */
169  shallowCopy() {
170    // Repeated fields are arrays in the cache.
171    // We have to copy the array to make sure that modifications to a repeated
172    // field (e.g. add) are not seen on a cloned accessor.
173    const copiedCache = this.hasDecodedValue() ?
174        (Array.isArray(this.getDecodedValue()) ? [...this.getDecodedValue()] :
175                                                 this.getDecodedValue()) :
176        undefined;
177    return new Field(this.getIndexArray(), copiedCache, this.getEncoder());
178  }
179
180  /**
181   * @private
182   */
183  maybeRemoveIndexArray_() {
184    checkState(
185        this.encoder_ === undefined || this.decodedValue_ !== undefined,
186        'Encoder exists but decoded value doesn\'t');
187    if (this.encoder_ !== undefined) {
188      this.indexArray_ = null;
189    }
190  }
191}
192
193exports = {
194  IndexEntry,
195  Field,
196};
197