blob: d423499f05e3d5a5627fb432d93d0c426be24957 [file] [log] [blame]
Artem Titov739351d2018-05-11 12:21:36 +02001// Protocol Buffers - Google's data interchange format
2// Copyright 2008 Google Inc. All rights reserved.
3// https://developers.google.com/protocol-buffers/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
8//
9// * Redistributions of source code must retain the above copyright
10// notice, this list of conditions and the following disclaimer.
11// * Redistributions in binary form must reproduce the above
12// copyright notice, this list of conditions and the following disclaimer
13// in the documentation and/or other materials provided with the
14// distribution.
15// * Neither the name of Google Inc. nor the names of its
16// contributors may be used to endorse or promote products derived from
17// this software without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31goog.provide('jspb.Map');
32
33goog.require('goog.asserts');
34
35goog.forwardDeclare('jspb.BinaryReader');
36goog.forwardDeclare('jspb.BinaryWriter');
37
38
39
40/**
41 * Constructs a new Map. A Map is a container that is used to implement map
42 * fields on message objects. It closely follows the ES6 Map API; however,
43 * it is distinct because we do not want to depend on external polyfills or
44 * on ES6 itself.
45 *
46 * This constructor should only be called from generated message code. It is not
47 * intended for general use by library consumers.
48 *
49 * @template K, V
50 *
51 * @param {!Array<!Array<!Object>>} arr
52 *
53 * @param {?function(new:V)|function(new:V,?)=} opt_valueCtor
54 * The constructor for type V, if type V is a message type.
55 *
56 * @constructor
57 * @struct
58 */
59jspb.Map = function(arr, opt_valueCtor) {
60 /** @const @private */
61 this.arr_ = arr;
62 /** @const @private */
63 this.valueCtor_ = opt_valueCtor;
64
65 /** @type {!Object<string, !jspb.Map.Entry_<K,V>>} @private */
66 this.map_ = {};
67
68 /**
69 * Is `this.arr_ updated with respect to `this.map_`?
70 * @type {boolean}
71 */
72 this.arrClean = true;
73
74 if (this.arr_.length > 0) {
75 this.loadFromArray_();
76 }
77};
78
79
80/**
81 * Load initial content from underlying array.
82 * @private
83 */
84jspb.Map.prototype.loadFromArray_ = function() {
85 for (var i = 0; i < this.arr_.length; i++) {
86 var record = this.arr_[i];
87 var key = record[0];
88 var value = record[1];
89 this.map_[key.toString()] = new jspb.Map.Entry_(key, value);
90 }
91 this.arrClean = true;
92};
93
94
95/**
96 * Synchronize content to underlying array, if needed, and return it.
97 * @return {!Array<!Array<!Object>>}
98 */
99jspb.Map.prototype.toArray = function() {
100 if (this.arrClean) {
101 if (this.valueCtor_) {
102 // We need to recursively sync maps in submessages to their arrays.
103 var m = this.map_;
104 for (var p in m) {
105 if (Object.prototype.hasOwnProperty.call(m, p)) {
106 var valueWrapper = /** @type {?jspb.Message} */ (m[p].valueWrapper);
107 if (valueWrapper) {
108 valueWrapper.toArray();
109 }
110 }
111 }
112 }
113 } else {
114 // Delete all elements.
115 this.arr_.length = 0;
116 var strKeys = this.stringKeys_();
117 // Output keys in deterministic (sorted) order.
118 strKeys.sort();
119 for (var i = 0; i < strKeys.length; i++) {
120 var entry = this.map_[strKeys[i]];
121 var valueWrapper = /** @type {!Object} */ (entry.valueWrapper);
122 if (valueWrapper) {
123 valueWrapper.toArray();
124 }
125 this.arr_.push([entry.key, entry.value]);
126 }
127 this.arrClean = true;
128 }
129 return this.arr_;
130};
131
132
133/**
134 * Returns the map formatted as an array of key-value pairs, suitable for the
135 * toObject() form of a message.
136 *
137 * @param {boolean=} includeInstance Whether to include the JSPB instance for
138 * transitional soy proto support: http://goto/soy-param-migration
139 * @param {!function((boolean|undefined),V):!Object=} valueToObject
140 * The static toObject() method, if V is a message type.
141 * @return {!Array<!Array<!Object>>}
142 */
143jspb.Map.prototype.toObject = function(includeInstance, valueToObject) {
144 var rawArray = this.toArray();
145 var entries = [];
146 for (var i = 0; i < rawArray.length; i++) {
147 var entry = this.map_[rawArray[i][0].toString()];
148 this.wrapEntry_(entry);
149 var valueWrapper = /** @type {V|undefined} */ (entry.valueWrapper);
150 if (valueWrapper) {
151 goog.asserts.assert(valueToObject);
152 entries.push([entry.key, valueToObject(includeInstance, valueWrapper)]);
153 } else {
154 entries.push([entry.key, entry.value]);
155 }
156 }
157 return entries;
158};
159
160
161/**
162 * Returns a Map from the given array of key-value pairs when the values are of
163 * message type. The values in the array must match the format returned by their
164 * message type's toObject() method.
165 *
166 * @template K, V
167 * @param {!Array<!Array<!Object>>} entries
168 * @param {!function(new:V)|function(new:V,?)} valueCtor
169 * The constructor for type V.
170 * @param {!function(!Object):V} valueFromObject
171 * The fromObject function for type V.
172 * @return {!jspb.Map<K, V>}
173 */
174jspb.Map.fromObject = function(entries, valueCtor, valueFromObject) {
175 var result = new jspb.Map([], valueCtor);
176 for (var i = 0; i < entries.length; i++) {
177 var key = entries[i][0];
178 var value = valueFromObject(entries[i][1]);
179 result.set(key, value);
180 }
181 return result;
182};
183
184
185/**
186 * Helper: an IteratorIterable over an array.
187 * @template T
188 * @param {!Array<T>} arr the array
189 * @implements {IteratorIterable<T>}
190 * @constructor @struct
191 * @private
192 */
193jspb.Map.ArrayIteratorIterable_ = function(arr) {
194 /** @type {number} @private */
195 this.idx_ = 0;
196
197 /** @const @private */
198 this.arr_ = arr;
199};
200
201
202/** @override @final */
203jspb.Map.ArrayIteratorIterable_.prototype.next = function() {
204 if (this.idx_ < this.arr_.length) {
205 return {done: false, value: this.arr_[this.idx_++]};
206 } else {
207 return {done: true, value: undefined};
208 }
209};
210
211if (typeof(Symbol) != 'undefined') {
212 /** @override */
213 jspb.Map.ArrayIteratorIterable_.prototype[Symbol.iterator] = function() {
214 return this;
215 };
216}
217
218
219/**
220 * Returns the map's length (number of key/value pairs).
221 * @return {number}
222 */
223jspb.Map.prototype.getLength = function() {
224 return this.stringKeys_().length;
225};
226
227
228/**
229 * Clears the map.
230 */
231jspb.Map.prototype.clear = function() {
232 this.map_ = {};
233 this.arrClean = false;
234};
235
236
237/**
238 * Deletes a particular key from the map.
239 * N.B.: differs in name from ES6 Map's `delete` because IE8 does not support
240 * reserved words as property names.
241 * @this {jspb.Map}
242 * @param {K} key
243 * @return {boolean} Whether any entry with this key was deleted.
244 */
245jspb.Map.prototype.del = function(key) {
246 var keyValue = key.toString();
247 var hadKey = this.map_.hasOwnProperty(keyValue);
248 delete this.map_[keyValue];
249 this.arrClean = false;
250 return hadKey;
251};
252
253
254/**
255 * Returns an array of [key, value] pairs in the map.
256 *
257 * This is redundant compared to the plain entries() method, but we provide this
258 * to help out Angular 1.x users. Still evaluating whether this is the best
259 * option.
260 *
261 * @return {!Array<!Array<K|V>>}
262 */
263jspb.Map.prototype.getEntryList = function() {
264 var entries = [];
265 var strKeys = this.stringKeys_();
266 strKeys.sort();
267 for (var i = 0; i < strKeys.length; i++) {
268 var entry = this.map_[strKeys[i]];
269 entries.push([entry.key, entry.value]);
270 }
271 return entries;
272};
273
274
275/**
276 * Returns an iterator-iterable over [key, value] pairs in the map.
277 * Closure compiler sadly doesn't support tuples, ie. Iterator<[K,V]>.
278 * @return {!IteratorIterable<!Array<K|V>>} The iterator-iterable.
279 */
280jspb.Map.prototype.entries = function() {
281 var entries = [];
282 var strKeys = this.stringKeys_();
283 strKeys.sort();
284 for (var i = 0; i < strKeys.length; i++) {
285 var entry = this.map_[strKeys[i]];
286 entries.push([entry.key, this.wrapEntry_(entry)]);
287 }
288 return new jspb.Map.ArrayIteratorIterable_(entries);
289};
290
291
292/**
293 * Returns an iterator-iterable over keys in the map.
294 * @return {!IteratorIterable<K>} The iterator-iterable.
295 */
296jspb.Map.prototype.keys = function() {
297 var keys = [];
298 var strKeys = this.stringKeys_();
299 strKeys.sort();
300 for (var i = 0; i < strKeys.length; i++) {
301 var entry = this.map_[strKeys[i]];
302 keys.push(entry.key);
303 }
304 return new jspb.Map.ArrayIteratorIterable_(keys);
305};
306
307
308/**
309 * Returns an iterator-iterable over values in the map.
310 * @return {!IteratorIterable<V>} The iterator-iterable.
311 */
312jspb.Map.prototype.values = function() {
313 var values = [];
314 var strKeys = this.stringKeys_();
315 strKeys.sort();
316 for (var i = 0; i < strKeys.length; i++) {
317 var entry = this.map_[strKeys[i]];
318 values.push(this.wrapEntry_(entry));
319 }
320 return new jspb.Map.ArrayIteratorIterable_(values);
321};
322
323
324/**
325 * Iterates over entries in the map, calling a function on each.
326 * @template T
327 * @param {function(this:T, V, K, ?jspb.Map<K, V>)} cb
328 * @param {T=} opt_thisArg
329 */
330jspb.Map.prototype.forEach = function(cb, opt_thisArg) {
331 var strKeys = this.stringKeys_();
332 strKeys.sort();
333 for (var i = 0; i < strKeys.length; i++) {
334 var entry = this.map_[strKeys[i]];
335 cb.call(opt_thisArg, this.wrapEntry_(entry), entry.key, this);
336 }
337};
338
339
340/**
341 * Sets a key in the map to the given value.
342 * @param {K} key The key
343 * @param {V} value The value
344 * @return {!jspb.Map<K,V>}
345 */
346jspb.Map.prototype.set = function(key, value) {
347 var entry = new jspb.Map.Entry_(key);
348 if (this.valueCtor_) {
349 entry.valueWrapper = value;
350 // .toArray() on a message returns a reference to the underlying array
351 // rather than a copy.
352 entry.value = value.toArray();
353 } else {
354 entry.value = value;
355 }
356 this.map_[key.toString()] = entry;
357 this.arrClean = false;
358 return this;
359};
360
361
362/**
363 * Helper: lazily construct a wrapper around an entry, if needed, and return the
364 * user-visible type.
365 * @param {!jspb.Map.Entry_<K,V>} entry
366 * @return {V}
367 * @private
368 */
369jspb.Map.prototype.wrapEntry_ = function(entry) {
370 if (this.valueCtor_) {
371 if (!entry.valueWrapper) {
372 entry.valueWrapper = new this.valueCtor_(entry.value);
373 }
374 return /** @type {V} */ (entry.valueWrapper);
375 } else {
376 return entry.value;
377 }
378};
379
380
381/**
382 * Gets the value corresponding to a key in the map.
383 * @param {K} key
384 * @return {V|undefined} The value, or `undefined` if key not present
385 */
386jspb.Map.prototype.get = function(key) {
387 var keyValue = key.toString();
388 var entry = this.map_[keyValue];
389 if (entry) {
390 return this.wrapEntry_(entry);
391 } else {
392 return undefined;
393 }
394};
395
396
397/**
398 * Determines whether the given key is present in the map.
399 * @param {K} key
400 * @return {boolean} `true` if the key is present
401 */
402jspb.Map.prototype.has = function(key) {
403 var keyValue = key.toString();
404 return (keyValue in this.map_);
405};
406
407
408/**
409 * Write this Map field in wire format to a BinaryWriter, using the given field
410 * number.
411 * @param {number} fieldNumber
412 * @param {!jspb.BinaryWriter} writer
413 * @param {!function(this:jspb.BinaryWriter,number,K)} keyWriterFn
414 * The method on BinaryWriter that writes type K to the stream.
415 * @param {!function(this:jspb.BinaryWriter,number,V,?=)|
416 * function(this:jspb.BinaryWriter,number,V,?)} valueWriterFn
417 * The method on BinaryWriter that writes type V to the stream. May be
418 * writeMessage, in which case the second callback arg form is used.
419 * @param {function(V,!jspb.BinaryWriter)=} opt_valueWriterCallback
420 * The BinaryWriter serialization callback for type V, if V is a message
421 * type.
422 */
423jspb.Map.prototype.serializeBinary = function(
424 fieldNumber, writer, keyWriterFn, valueWriterFn, opt_valueWriterCallback) {
425 var strKeys = this.stringKeys_();
426 strKeys.sort();
427 for (var i = 0; i < strKeys.length; i++) {
428 var entry = this.map_[strKeys[i]];
429 writer.beginSubMessage(fieldNumber);
430 keyWriterFn.call(writer, 1, entry.key);
431 if (this.valueCtor_) {
432 valueWriterFn.call(writer, 2, this.wrapEntry_(entry),
433 opt_valueWriterCallback);
434 } else {
435 valueWriterFn.call(writer, 2, entry.value);
436 }
437 writer.endSubMessage();
438 }
439};
440
441
442/**
443 * Read one key/value message from the given BinaryReader. Compatible as the
444 * `reader` callback parameter to jspb.BinaryReader.readMessage, to be called
445 * when a key/value pair submessage is encountered.
446 * @template K, V
447 * @param {!jspb.Map} map
448 * @param {!jspb.BinaryReader} reader
449 * @param {!function(this:jspb.BinaryReader):K} keyReaderFn
450 * The method on BinaryReader that reads type K from the stream.
451 *
452 * @param {!function(this:jspb.BinaryReader):V|
453 * function(this:jspb.BinaryReader,V,
454 * function(V,!jspb.BinaryReader))} valueReaderFn
455 * The method on BinaryReader that reads type V from the stream. May be
456 * readMessage, in which case the second callback arg form is used.
457 *
458 * @param {?function(V,!jspb.BinaryReader)=} opt_valueReaderCallback
459 * The BinaryReader parsing callback for type V, if V is a message type.
460 *
461 */
462jspb.Map.deserializeBinary = function(map, reader, keyReaderFn, valueReaderFn,
463 opt_valueReaderCallback) {
464 var key = undefined;
465 var value = undefined;
466
467 while (reader.nextField()) {
468 if (reader.isEndGroup()) {
469 break;
470 }
471 var field = reader.getFieldNumber();
472 if (field == 1) {
473 // Key.
474 key = keyReaderFn.call(reader);
475 } else if (field == 2) {
476 // Value.
477 if (map.valueCtor_) {
478 value = new map.valueCtor_();
479 valueReaderFn.call(reader, value, opt_valueReaderCallback);
480 } else {
481 value = valueReaderFn.call(reader);
482 }
483 }
484 }
485
486 goog.asserts.assert(key != undefined);
487 goog.asserts.assert(value != undefined);
488 map.set(key, value);
489};
490
491
492/**
493 * Helper: compute the list of all stringified keys in the underlying Object
494 * map.
495 * @return {!Array<string>}
496 * @private
497 */
498jspb.Map.prototype.stringKeys_ = function() {
499 var m = this.map_;
500 var ret = [];
501 for (var p in m) {
502 if (Object.prototype.hasOwnProperty.call(m, p)) {
503 ret.push(p);
504 }
505 }
506 return ret;
507};
508
509
510
511/**
512 * @param {K} key The entry's key.
513 * @param {V=} opt_value The entry's value wrapper.
514 * @constructor
515 * @struct
516 * @template K, V
517 * @private
518 */
519jspb.Map.Entry_ = function(key, opt_value) {
520 /** @const {K} */
521 this.key = key;
522
523 // The JSPB-serializable value. For primitive types this will be of type V.
524 // For message types it will be an array.
525 /** @type {V} */
526 this.value = opt_value;
527
528 // Only used for submessage values.
529 /** @type {V} */
530 this.valueWrapper = undefined;
531};