OLD | NEW |
| (Empty) |
1 // 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 | |
31 goog.provide('jspb.Map'); | |
32 | |
33 goog.require('goog.asserts'); | |
34 | |
35 goog.forwardDeclare('jspb.BinaryReader'); | |
36 goog.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 */ | |
59 jspb.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 */ | |
84 jspb.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 */ | |
99 jspb.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 */ | |
143 jspb.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 */ | |
174 jspb.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: return an iterator over an array. | |
187 * @template T | |
188 * @param {!Array<T>} arr the array | |
189 * @return {!Iterator<T>} an iterator | |
190 * @private | |
191 */ | |
192 jspb.Map.arrayIterator_ = function(arr) { | |
193 var idx = 0; | |
194 return /** @type {!Iterator} */ ({ | |
195 next: function() { | |
196 if (idx < arr.length) { | |
197 return { done: false, value: arr[idx++] }; | |
198 } else { | |
199 return { done: true }; | |
200 } | |
201 } | |
202 }); | |
203 }; | |
204 | |
205 | |
206 /** | |
207 * Returns the map's length (number of key/value pairs). | |
208 * @return {number} | |
209 */ | |
210 jspb.Map.prototype.getLength = function() { | |
211 return this.stringKeys_().length; | |
212 }; | |
213 | |
214 | |
215 /** | |
216 * Clears the map. | |
217 */ | |
218 jspb.Map.prototype.clear = function() { | |
219 this.map_ = {}; | |
220 this.arrClean = false; | |
221 }; | |
222 | |
223 | |
224 /** | |
225 * Deletes a particular key from the map. | |
226 * N.B.: differs in name from ES6 Map's `delete` because IE8 does not support | |
227 * reserved words as property names. | |
228 * @this {jspb.Map} | |
229 * @param {K} key | |
230 * @return {boolean} Whether any entry with this key was deleted. | |
231 */ | |
232 jspb.Map.prototype.del = function(key) { | |
233 var keyValue = key.toString(); | |
234 var hadKey = this.map_.hasOwnProperty(keyValue); | |
235 delete this.map_[keyValue]; | |
236 this.arrClean = false; | |
237 return hadKey; | |
238 }; | |
239 | |
240 | |
241 /** | |
242 * Returns an array of [key, value] pairs in the map. | |
243 * | |
244 * This is redundant compared to the plain entries() method, but we provide this | |
245 * to help out Angular 1.x users. Still evaluating whether this is the best | |
246 * option. | |
247 * | |
248 * @return {!Array<!Array<K|V>>} | |
249 */ | |
250 jspb.Map.prototype.getEntryList = function() { | |
251 var entries = []; | |
252 var strKeys = this.stringKeys_(); | |
253 strKeys.sort(); | |
254 for (var i = 0; i < strKeys.length; i++) { | |
255 var entry = this.map_[strKeys[i]]; | |
256 entries.push([entry.key, entry.value]); | |
257 } | |
258 return entries; | |
259 }; | |
260 | |
261 | |
262 /** | |
263 * Returns an iterator over [key, value] pairs in the map. | |
264 * Closure compiler sadly doesn't support tuples, ie. Iterator<[K,V]>. | |
265 * @return {!Iterator<!Array<K|V>>} | |
266 * The iterator | |
267 */ | |
268 jspb.Map.prototype.entries = function() { | |
269 var entries = []; | |
270 var strKeys = this.stringKeys_(); | |
271 strKeys.sort(); | |
272 for (var i = 0; i < strKeys.length; i++) { | |
273 var entry = this.map_[strKeys[i]]; | |
274 entries.push([entry.key, this.wrapEntry_(entry)]); | |
275 } | |
276 return jspb.Map.arrayIterator_(entries); | |
277 }; | |
278 | |
279 | |
280 /** | |
281 * Returns an iterator over keys in the map. | |
282 * @return {!Iterator<K>} The iterator | |
283 */ | |
284 jspb.Map.prototype.keys = function() { | |
285 var keys = []; | |
286 var strKeys = this.stringKeys_(); | |
287 strKeys.sort(); | |
288 for (var i = 0; i < strKeys.length; i++) { | |
289 var entry = this.map_[strKeys[i]]; | |
290 keys.push(entry.key); | |
291 } | |
292 return jspb.Map.arrayIterator_(keys); | |
293 }; | |
294 | |
295 | |
296 /** | |
297 * Returns an iterator over values in the map. | |
298 * @return {!Iterator<V>} The iterator | |
299 */ | |
300 jspb.Map.prototype.values = function() { | |
301 var values = []; | |
302 var strKeys = this.stringKeys_(); | |
303 strKeys.sort(); | |
304 for (var i = 0; i < strKeys.length; i++) { | |
305 var entry = this.map_[strKeys[i]]; | |
306 values.push(this.wrapEntry_(entry)); | |
307 } | |
308 return jspb.Map.arrayIterator_(values); | |
309 }; | |
310 | |
311 | |
312 /** | |
313 * Iterates over entries in the map, calling a function on each. | |
314 * @template T | |
315 * @param {function(this:T, V, K, ?jspb.Map<K, V>)} cb | |
316 * @param {T=} opt_thisArg | |
317 */ | |
318 jspb.Map.prototype.forEach = function(cb, opt_thisArg) { | |
319 var strKeys = this.stringKeys_(); | |
320 strKeys.sort(); | |
321 for (var i = 0; i < strKeys.length; i++) { | |
322 var entry = this.map_[strKeys[i]]; | |
323 cb.call(opt_thisArg, this.wrapEntry_(entry), entry.key, this); | |
324 } | |
325 }; | |
326 | |
327 | |
328 /** | |
329 * Sets a key in the map to the given value. | |
330 * @param {K} key The key | |
331 * @param {V} value The value | |
332 * @return {!jspb.Map<K,V>} | |
333 */ | |
334 jspb.Map.prototype.set = function(key, value) { | |
335 var entry = new jspb.Map.Entry_(key); | |
336 if (this.valueCtor_) { | |
337 entry.valueWrapper = value; | |
338 // .toArray() on a message returns a reference to the underlying array | |
339 // rather than a copy. | |
340 entry.value = value.toArray(); | |
341 } else { | |
342 entry.value = value; | |
343 } | |
344 this.map_[key.toString()] = entry; | |
345 this.arrClean = false; | |
346 return this; | |
347 }; | |
348 | |
349 | |
350 /** | |
351 * Helper: lazily construct a wrapper around an entry, if needed, and return the | |
352 * user-visible type. | |
353 * @param {!jspb.Map.Entry_<K,V>} entry | |
354 * @return {V} | |
355 * @private | |
356 */ | |
357 jspb.Map.prototype.wrapEntry_ = function(entry) { | |
358 if (this.valueCtor_) { | |
359 if (!entry.valueWrapper) { | |
360 entry.valueWrapper = new this.valueCtor_(entry.value); | |
361 } | |
362 return /** @type {V} */ (entry.valueWrapper); | |
363 } else { | |
364 return entry.value; | |
365 } | |
366 }; | |
367 | |
368 | |
369 /** | |
370 * Gets the value corresponding to a key in the map. | |
371 * @param {K} key | |
372 * @return {V|undefined} The value, or `undefined` if key not present | |
373 */ | |
374 jspb.Map.prototype.get = function(key) { | |
375 var keyValue = key.toString(); | |
376 var entry = this.map_[keyValue]; | |
377 if (entry) { | |
378 return this.wrapEntry_(entry); | |
379 } else { | |
380 return undefined; | |
381 } | |
382 }; | |
383 | |
384 | |
385 /** | |
386 * Determines whether the given key is present in the map. | |
387 * @param {K} key | |
388 * @return {boolean} `true` if the key is present | |
389 */ | |
390 jspb.Map.prototype.has = function(key) { | |
391 var keyValue = key.toString(); | |
392 return (keyValue in this.map_); | |
393 }; | |
394 | |
395 | |
396 /** | |
397 * Write this Map field in wire format to a BinaryWriter, using the given field | |
398 * number. | |
399 * @param {number} fieldNumber | |
400 * @param {!jspb.BinaryWriter} writer | |
401 * @param {!function(this:jspb.BinaryWriter,number,K)} keyWriterFn | |
402 * The method on BinaryWriter that writes type K to the stream. | |
403 * @param {!function(this:jspb.BinaryWriter,number,V)| | |
404 * function(this:jspb.BinaryReader,V,?)} valueWriterFn | |
405 * The method on BinaryWriter that writes type V to the stream. May be | |
406 * writeMessage, in which case the second callback arg form is used. | |
407 * @param {function(V,!jspb.BinaryWriter)=} opt_valueWriterCallback | |
408 * The BinaryWriter serialization callback for type V, if V is a message | |
409 * type. | |
410 */ | |
411 jspb.Map.prototype.serializeBinary = function( | |
412 fieldNumber, writer, keyWriterFn, valueWriterFn, opt_valueWriterCallback) { | |
413 var strKeys = this.stringKeys_(); | |
414 strKeys.sort(); | |
415 for (var i = 0; i < strKeys.length; i++) { | |
416 var entry = this.map_[strKeys[i]]; | |
417 writer.beginSubMessage(fieldNumber); | |
418 keyWriterFn.call(writer, 1, entry.key); | |
419 if (this.valueCtor_) { | |
420 valueWriterFn.call(writer, 2, this.wrapEntry_(entry), | |
421 opt_valueWriterCallback); | |
422 } else { | |
423 valueWriterFn.call(writer, 2, entry.value); | |
424 } | |
425 writer.endSubMessage(); | |
426 } | |
427 }; | |
428 | |
429 | |
430 /** | |
431 * Read one key/value message from the given BinaryReader. Compatible as the | |
432 * `reader` callback parameter to jspb.BinaryReader.readMessage, to be called | |
433 * when a key/value pair submessage is encountered. | |
434 * @template K, V | |
435 * @param {!jspb.Map} map | |
436 * @param {!jspb.BinaryReader} reader | |
437 * @param {!function(this:jspb.BinaryReader):K} keyReaderFn | |
438 * The method on BinaryReader that reads type K from the stream. | |
439 * | |
440 * @param {!function(this:jspb.BinaryReader):V| | |
441 * function(this:jspb.BinaryReader,V, | |
442 * function(V,!jspb.BinaryReader))} valueReaderFn | |
443 * The method on BinaryReader that reads type V from the stream. May be | |
444 * readMessage, in which case the second callback arg form is used. | |
445 * | |
446 * @param {?function(V,!jspb.BinaryReader)=} opt_valueReaderCallback | |
447 * The BinaryReader parsing callback for type V, if V is a message type. | |
448 * | |
449 */ | |
450 jspb.Map.deserializeBinary = function(map, reader, keyReaderFn, valueReaderFn, | |
451 opt_valueReaderCallback) { | |
452 var key = undefined; | |
453 var value = undefined; | |
454 | |
455 while (reader.nextField()) { | |
456 if (reader.isEndGroup()) { | |
457 break; | |
458 } | |
459 var field = reader.getFieldNumber(); | |
460 if (field == 1) { | |
461 // Key. | |
462 key = keyReaderFn.call(reader); | |
463 } else if (field == 2) { | |
464 // Value. | |
465 if (map.valueCtor_) { | |
466 value = new map.valueCtor_(); | |
467 valueReaderFn.call(reader, value, opt_valueReaderCallback); | |
468 } else { | |
469 value = valueReaderFn.call(reader); | |
470 } | |
471 } | |
472 } | |
473 | |
474 goog.asserts.assert(key != undefined); | |
475 goog.asserts.assert(value != undefined); | |
476 map.set(key, value); | |
477 }; | |
478 | |
479 | |
480 /** | |
481 * Helper: compute the list of all stringified keys in the underlying Object | |
482 * map. | |
483 * @return {!Array<string>} | |
484 * @private | |
485 */ | |
486 jspb.Map.prototype.stringKeys_ = function() { | |
487 var m = this.map_; | |
488 var ret = []; | |
489 for (var p in m) { | |
490 if (Object.prototype.hasOwnProperty.call(m, p)) { | |
491 ret.push(p); | |
492 } | |
493 } | |
494 return ret; | |
495 }; | |
496 | |
497 | |
498 | |
499 /** | |
500 * @param {!K} key The entry's key. | |
501 * @param {V=} opt_value The entry's value wrapper. | |
502 * @constructor | |
503 * @struct | |
504 * @template K, V | |
505 * @private | |
506 */ | |
507 jspb.Map.Entry_ = function(key, opt_value) { | |
508 /** @const {K} */ | |
509 this.key = key; | |
510 | |
511 // The JSPB-serializable value. For primitive types this will be of type V. | |
512 // For message types it will be an array. | |
513 /** @type {V} */ | |
514 this.value = opt_value; | |
515 | |
516 // Only used for submessage values. | |
517 /** @type {V} */ | |
518 this.valueWrapper = undefined; | |
519 }; | |
OLD | NEW |