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