blob: 7b5b2c389d1f2de6abb2a37c423c3f1285f3fb4f [file] [log] [blame]
Adam Cozzettec64d86e2016-07-06 12:04:49 -07001// 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
Josh Haberman923eae82016-07-18 14:46:12 -070047 * intended for general use by library consumers.
Adam Cozzettec64d86e2016-07-06 12:04:49 -070048 *
49 * @template K, V
50 *
Adam Cozzette0400cca2018-03-13 16:37:29 -070051 * @param {!Array<!Array<?>>} arr
Adam Cozzettec64d86e2016-07-06 12:04:49 -070052 *
Adam Cozzette0400cca2018-03-13 16:37:29 -070053 * @param {?function(new:V, ?=)=} opt_valueCtor
Adam Cozzettec64d86e2016-07-06 12:04:49 -070054 * The constructor for type V, if type V is a message type.
55 *
Adam Cozzettec64d86e2016-07-06 12:04:49 -070056 * @constructor
57 * @struct
58 */
Josh Haberman923eae82016-07-18 14:46:12 -070059jspb.Map = function(arr, opt_valueCtor) {
Adam Cozzettec64d86e2016-07-06 12:04:49 -070060 /** @const @private */
61 this.arr_ = arr;
62 /** @const @private */
Adam Cozzettec64d86e2016-07-06 12:04:49 -070063 this.valueCtor_ = opt_valueCtor;
Adam Cozzettec64d86e2016-07-06 12:04:49 -070064
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)) {
Bo Yangcc8ca5b2016-09-19 13:45:07 -0700106 var valueWrapper = /** @type {?jspb.Message} */ (m[p].valueWrapper);
107 if (valueWrapper) {
108 valueWrapper.toArray();
109 }
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700110 }
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]];
Adam Cozzette0400cca2018-03-13 16:37:29 -0700121 var valueWrapper = /** @type {?jspb.Message} */ (entry.valueWrapper);
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700122 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/**
Adam Cozzette5a76e632016-11-17 16:48:38 -0800134 * 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
Feng Xiaod36c0c52017-03-29 14:32:48 -0700139 * @param {!function((boolean|undefined),V):!Object=} valueToObject
Adam Cozzette5a76e632016-11-17 16:48:38 -0800140 * 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);
Feng Xiaod36c0c52017-03-29 14:32:48 -0700149 var valueWrapper = /** @type {V|undefined} */ (entry.valueWrapper);
Adam Cozzette5a76e632016-11-17 16:48:38 -0800150 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
Adam Cozzette0400cca2018-03-13 16:37:29 -0700168 * @param {!function(new:V,?=)} valueCtor
Adam Cozzette5a76e632016-11-17 16:48:38 -0800169 * 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/**
Paul Yang7f3e2372017-01-31 09:17:32 -0800186 * Helper: an IteratorIterable over an array.
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700187 * @template T
188 * @param {!Array<T>} arr the array
Paul Yang7f3e2372017-01-31 09:17:32 -0800189 * @implements {IteratorIterable<T>}
190 * @constructor @struct
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700191 * @private
192 */
Paul Yang7f3e2372017-01-31 09:17:32 -0800193jspb.Map.ArrayIteratorIterable_ = function(arr) {
194 /** @type {number} @private */
195 this.idx_ = 0;
196
197 /** @const @private */
198 this.arr_ = arr;
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700199};
200
201
Paul Yang7f3e2372017-01-31 09:17:32 -0800202/** @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
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700219/**
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 *
Adam Cozzette5a76e632016-11-17 16:48:38 -0800261 * @return {!Array<!Array<K|V>>}
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700262 */
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/**
Paul Yang7f3e2372017-01-31 09:17:32 -0800276 * Returns an iterator-iterable over [key, value] pairs in the map.
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700277 * Closure compiler sadly doesn't support tuples, ie. Iterator<[K,V]>.
Paul Yang7f3e2372017-01-31 09:17:32 -0800278 * @return {!IteratorIterable<!Array<K|V>>} The iterator-iterable.
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700279 */
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 }
Paul Yang7f3e2372017-01-31 09:17:32 -0800288 return new jspb.Map.ArrayIteratorIterable_(entries);
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700289};
290
291
292/**
Paul Yang7f3e2372017-01-31 09:17:32 -0800293 * Returns an iterator-iterable over keys in the map.
294 * @return {!IteratorIterable<K>} The iterator-iterable.
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700295 */
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 }
Paul Yang7f3e2372017-01-31 09:17:32 -0800304 return new jspb.Map.ArrayIteratorIterable_(keys);
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700305};
306
307
308/**
Paul Yang7f3e2372017-01-31 09:17:32 -0800309 * Returns an iterator-iterable over values in the map.
310 * @return {!IteratorIterable<V>} The iterator-iterable.
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700311 */
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 }
Paul Yang7f3e2372017-01-31 09:17:32 -0800320 return new jspb.Map.ArrayIteratorIterable_(values);
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700321};
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
Bo Yangcc8ca5b2016-09-19 13:45:07 -0700413 * @param {!function(this:jspb.BinaryWriter,number,K)} keyWriterFn
Josh Haberman923eae82016-07-18 14:46:12 -0700414 * The method on BinaryWriter that writes type K to the stream.
Feng Xiaod36c0c52017-03-29 14:32:48 -0700415 * @param {!function(this:jspb.BinaryWriter,number,V,?=)|
416 * function(this:jspb.BinaryWriter,number,V,?)} valueWriterFn
Josh Haberman923eae82016-07-18 14:46:12 -0700417 * 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.
Bo Yangcc8ca5b2016-09-19 13:45:07 -0700419 * @param {function(V,!jspb.BinaryWriter)=} opt_valueWriterCallback
Josh Haberman923eae82016-07-18 14:46:12 -0700420 * The BinaryWriter serialization callback for type V, if V is a message
421 * type.
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700422 */
Josh Haberman923eae82016-07-18 14:46:12 -0700423jspb.Map.prototype.serializeBinary = function(
424 fieldNumber, writer, keyWriterFn, valueWriterFn, opt_valueWriterCallback) {
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700425 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);
Josh Haberman923eae82016-07-18 14:46:12 -0700430 keyWriterFn.call(writer, 1, entry.key);
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700431 if (this.valueCtor_) {
Josh Haberman923eae82016-07-18 14:46:12 -0700432 valueWriterFn.call(writer, 2, this.wrapEntry_(entry),
433 opt_valueWriterCallback);
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700434 } else {
Adam Cozzette0400cca2018-03-13 16:37:29 -0700435 /** @type {function(this:jspb.BinaryWriter,number,?)} */ (valueWriterFn)
436 .call(writer, 2, entry.value);
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700437 }
438 writer.endSubMessage();
439 }
440};
441
442
443/**
444 * Read one key/value message from the given BinaryReader. Compatible as the
445 * `reader` callback parameter to jspb.BinaryReader.readMessage, to be called
446 * when a key/value pair submessage is encountered.
Bo Yangcc8ca5b2016-09-19 13:45:07 -0700447 * @template K, V
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700448 * @param {!jspb.Map} map
449 * @param {!jspb.BinaryReader} reader
Bo Yangcc8ca5b2016-09-19 13:45:07 -0700450 * @param {!function(this:jspb.BinaryReader):K} keyReaderFn
Josh Haberman923eae82016-07-18 14:46:12 -0700451 * The method on BinaryReader that reads type K from the stream.
452 *
Bo Yangcc8ca5b2016-09-19 13:45:07 -0700453 * @param {!function(this:jspb.BinaryReader):V|
454 * function(this:jspb.BinaryReader,V,
455 * function(V,!jspb.BinaryReader))} valueReaderFn
Josh Haberman923eae82016-07-18 14:46:12 -0700456 * The method on BinaryReader that reads type V from the stream. May be
457 * readMessage, in which case the second callback arg form is used.
458 *
459 * @param {?function(V,!jspb.BinaryReader)=} opt_valueReaderCallback
460 * The BinaryReader parsing callback for type V, if V is a message type.
461 *
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700462 */
Josh Haberman923eae82016-07-18 14:46:12 -0700463jspb.Map.deserializeBinary = function(map, reader, keyReaderFn, valueReaderFn,
464 opt_valueReaderCallback) {
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700465 var key = undefined;
466 var value = undefined;
467
468 while (reader.nextField()) {
469 if (reader.isEndGroup()) {
470 break;
471 }
472 var field = reader.getFieldNumber();
473 if (field == 1) {
474 // Key.
Josh Haberman923eae82016-07-18 14:46:12 -0700475 key = keyReaderFn.call(reader);
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700476 } else if (field == 2) {
477 // Value.
478 if (map.valueCtor_) {
Adam Cozzette0400cca2018-03-13 16:37:29 -0700479 goog.asserts.assert(opt_valueReaderCallback);
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700480 value = new map.valueCtor_();
Josh Haberman923eae82016-07-18 14:46:12 -0700481 valueReaderFn.call(reader, value, opt_valueReaderCallback);
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700482 } else {
Adam Cozzette0400cca2018-03-13 16:37:29 -0700483 value =
484 (/** @type {function(this:jspb.BinaryReader):?} */ (valueReaderFn))
485 .call(reader);
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700486 }
487 }
488 }
489
490 goog.asserts.assert(key != undefined);
491 goog.asserts.assert(value != undefined);
492 map.set(key, value);
493};
494
495
496/**
497 * Helper: compute the list of all stringified keys in the underlying Object
498 * map.
499 * @return {!Array<string>}
500 * @private
501 */
502jspb.Map.prototype.stringKeys_ = function() {
503 var m = this.map_;
504 var ret = [];
505 for (var p in m) {
506 if (Object.prototype.hasOwnProperty.call(m, p)) {
507 ret.push(p);
508 }
509 }
510 return ret;
511};
512
513
514
515/**
Feng Xiaod36c0c52017-03-29 14:32:48 -0700516 * @param {K} key The entry's key.
Adam Cozzettec64d86e2016-07-06 12:04:49 -0700517 * @param {V=} opt_value The entry's value wrapper.
518 * @constructor
519 * @struct
520 * @template K, V
521 * @private
522 */
523jspb.Map.Entry_ = function(key, opt_value) {
524 /** @const {K} */
525 this.key = key;
526
527 // The JSPB-serializable value. For primitive types this will be of type V.
528 // For message types it will be an array.
529 /** @type {V} */
530 this.value = opt_value;
531
532 // Only used for submessage values.
533 /** @type {V} */
534 this.valueWrapper = undefined;
535};