map.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  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. goog.provide('jspb.Map');
  31. goog.require('goog.asserts');
  32. goog.forwardDeclare('jspb.BinaryReader');
  33. goog.forwardDeclare('jspb.BinaryWriter');
  34. /**
  35. * Constructs a new Map. A Map is a container that is used to implement map
  36. * fields on message objects. It closely follows the ES6 Map API; however,
  37. * it is distinct because we do not want to depend on external polyfills or
  38. * on ES6 itself.
  39. *
  40. * This constructor should only be called from generated message code. It is not
  41. * intended for general use by library consumers.
  42. *
  43. * @template K, V
  44. *
  45. * @param {!Array<!Array<!Object>>} arr
  46. *
  47. * @param {?function(new:V)|function(new:V,?)=} opt_valueCtor
  48. * The constructor for type V, if type V is a message type.
  49. *
  50. * @constructor
  51. * @struct
  52. */
  53. jspb.Map = function(arr, opt_valueCtor) {
  54. /** @const @private */
  55. this.arr_ = arr;
  56. /** @const @private */
  57. this.valueCtor_ = opt_valueCtor;
  58. /** @type {!Object<string, !jspb.Map.Entry_<K,V>>} @private */
  59. this.map_ = {};
  60. /**
  61. * Is `this.arr_ updated with respect to `this.map_`?
  62. * @type {boolean}
  63. */
  64. this.arrClean = true;
  65. if (this.arr_.length > 0) {
  66. this.loadFromArray_();
  67. }
  68. };
  69. /**
  70. * Load initial content from underlying array.
  71. * @private
  72. */
  73. jspb.Map.prototype.loadFromArray_ = function() {
  74. for (var i = 0; i < this.arr_.length; i++) {
  75. var record = this.arr_[i];
  76. var key = record[0];
  77. var value = record[1];
  78. this.map_[key.toString()] = new jspb.Map.Entry_(key, value);
  79. }
  80. this.arrClean = true;
  81. };
  82. /**
  83. * Synchronize content to underlying array, if needed, and return it.
  84. * @return {!Array<!Array<!Object>>}
  85. */
  86. jspb.Map.prototype.toArray = function() {
  87. if (this.arrClean) {
  88. if (this.valueCtor_) {
  89. // We need to recursively sync maps in submessages to their arrays.
  90. var m = this.map_;
  91. for (var p in m) {
  92. if (Object.prototype.hasOwnProperty.call(m, p)) {
  93. m[p].valueWrapper.toArray();
  94. }
  95. }
  96. }
  97. } else {
  98. // Delete all elements.
  99. this.arr_.length = 0;
  100. var strKeys = this.stringKeys_();
  101. // Output keys in deterministic (sorted) order.
  102. strKeys.sort();
  103. for (var i = 0; i < strKeys.length; i++) {
  104. var entry = this.map_[strKeys[i]];
  105. var valueWrapper = /** @type {!Object} */ (entry.valueWrapper);
  106. if (valueWrapper) {
  107. valueWrapper.toArray();
  108. }
  109. this.arr_.push([entry.key, entry.value]);
  110. }
  111. this.arrClean = true;
  112. }
  113. return this.arr_;
  114. };
  115. /**
  116. * Helper: return an iterator over an array.
  117. * @template T
  118. * @param {!Array<T>} arr the array
  119. * @return {!Iterator<T>} an iterator
  120. * @private
  121. */
  122. jspb.Map.arrayIterator_ = function(arr) {
  123. var idx = 0;
  124. return /** @type {!Iterator} */ ({
  125. next: function() {
  126. if (idx < arr.length) {
  127. return { done: false, value: arr[idx++] };
  128. } else {
  129. return { done: true };
  130. }
  131. }
  132. });
  133. };
  134. /**
  135. * Returns the map's length (number of key/value pairs).
  136. * @return {number}
  137. */
  138. jspb.Map.prototype.getLength = function() {
  139. return this.stringKeys_().length;
  140. };
  141. /**
  142. * Clears the map.
  143. */
  144. jspb.Map.prototype.clear = function() {
  145. this.map_ = {};
  146. this.arrClean = false;
  147. };
  148. /**
  149. * Deletes a particular key from the map.
  150. * N.B.: differs in name from ES6 Map's `delete` because IE8 does not support
  151. * reserved words as property names.
  152. * @this {jspb.Map}
  153. * @param {K} key
  154. * @return {boolean} Whether any entry with this key was deleted.
  155. */
  156. jspb.Map.prototype.del = function(key) {
  157. var keyValue = key.toString();
  158. var hadKey = this.map_.hasOwnProperty(keyValue);
  159. delete this.map_[keyValue];
  160. this.arrClean = false;
  161. return hadKey;
  162. };
  163. /**
  164. * Returns an array of [key, value] pairs in the map.
  165. *
  166. * This is redundant compared to the plain entries() method, but we provide this
  167. * to help out Angular 1.x users. Still evaluating whether this is the best
  168. * option.
  169. *
  170. * @return {!Array<K|V>}
  171. */
  172. jspb.Map.prototype.getEntryList = function() {
  173. var entries = [];
  174. var strKeys = this.stringKeys_();
  175. strKeys.sort();
  176. for (var i = 0; i < strKeys.length; i++) {
  177. var entry = this.map_[strKeys[i]];
  178. entries.push([entry.key, entry.value]);
  179. }
  180. return entries;
  181. };
  182. /**
  183. * Returns an iterator over [key, value] pairs in the map.
  184. * Closure compiler sadly doesn't support tuples, ie. Iterator<[K,V]>.
  185. * @return {!Iterator<!Array<K|V>>}
  186. * The iterator
  187. */
  188. jspb.Map.prototype.entries = function() {
  189. var entries = [];
  190. var strKeys = this.stringKeys_();
  191. strKeys.sort();
  192. for (var i = 0; i < strKeys.length; i++) {
  193. var entry = this.map_[strKeys[i]];
  194. entries.push([entry.key, this.wrapEntry_(entry)]);
  195. }
  196. return jspb.Map.arrayIterator_(entries);
  197. };
  198. /**
  199. * Returns an iterator over keys in the map.
  200. * @return {!Iterator<K>} The iterator
  201. */
  202. jspb.Map.prototype.keys = function() {
  203. var keys = [];
  204. var strKeys = this.stringKeys_();
  205. strKeys.sort();
  206. for (var i = 0; i < strKeys.length; i++) {
  207. var entry = this.map_[strKeys[i]];
  208. keys.push(entry.key);
  209. }
  210. return jspb.Map.arrayIterator_(keys);
  211. };
  212. /**
  213. * Returns an iterator over values in the map.
  214. * @return {!Iterator<V>} The iterator
  215. */
  216. jspb.Map.prototype.values = function() {
  217. var values = [];
  218. var strKeys = this.stringKeys_();
  219. strKeys.sort();
  220. for (var i = 0; i < strKeys.length; i++) {
  221. var entry = this.map_[strKeys[i]];
  222. values.push(this.wrapEntry_(entry));
  223. }
  224. return jspb.Map.arrayIterator_(values);
  225. };
  226. /**
  227. * Iterates over entries in the map, calling a function on each.
  228. * @template T
  229. * @param {function(this:T, V, K, ?jspb.Map<K, V>)} cb
  230. * @param {T=} opt_thisArg
  231. */
  232. jspb.Map.prototype.forEach = function(cb, opt_thisArg) {
  233. var strKeys = this.stringKeys_();
  234. strKeys.sort();
  235. for (var i = 0; i < strKeys.length; i++) {
  236. var entry = this.map_[strKeys[i]];
  237. cb.call(opt_thisArg, this.wrapEntry_(entry), entry.key, this);
  238. }
  239. };
  240. /**
  241. * Sets a key in the map to the given value.
  242. * @param {K} key The key
  243. * @param {V} value The value
  244. * @return {!jspb.Map<K,V>}
  245. */
  246. jspb.Map.prototype.set = function(key, value) {
  247. var entry = new jspb.Map.Entry_(key);
  248. if (this.valueCtor_) {
  249. entry.valueWrapper = value;
  250. // .toArray() on a message returns a reference to the underlying array
  251. // rather than a copy.
  252. entry.value = value.toArray();
  253. } else {
  254. entry.value = value;
  255. }
  256. this.map_[key.toString()] = entry;
  257. this.arrClean = false;
  258. return this;
  259. };
  260. /**
  261. * Helper: lazily construct a wrapper around an entry, if needed, and return the
  262. * user-visible type.
  263. * @param {!jspb.Map.Entry_<K,V>} entry
  264. * @return {V}
  265. * @private
  266. */
  267. jspb.Map.prototype.wrapEntry_ = function(entry) {
  268. if (this.valueCtor_) {
  269. if (!entry.valueWrapper) {
  270. entry.valueWrapper = new this.valueCtor_(entry.value);
  271. }
  272. return /** @type {V} */ (entry.valueWrapper);
  273. } else {
  274. return entry.value;
  275. }
  276. };
  277. /**
  278. * Gets the value corresponding to a key in the map.
  279. * @param {K} key
  280. * @return {V|undefined} The value, or `undefined` if key not present
  281. */
  282. jspb.Map.prototype.get = function(key) {
  283. var keyValue = key.toString();
  284. var entry = this.map_[keyValue];
  285. if (entry) {
  286. return this.wrapEntry_(entry);
  287. } else {
  288. return undefined;
  289. }
  290. };
  291. /**
  292. * Determines whether the given key is present in the map.
  293. * @param {K} key
  294. * @return {boolean} `true` if the key is present
  295. */
  296. jspb.Map.prototype.has = function(key) {
  297. var keyValue = key.toString();
  298. return (keyValue in this.map_);
  299. };
  300. /**
  301. * Write this Map field in wire format to a BinaryWriter, using the given field
  302. * number.
  303. * @param {number} fieldNumber
  304. * @param {!jspb.BinaryWriter} writer
  305. * @param {function(this:jspb.BinaryWriter,number,K)=} keyWriterFn
  306. * The method on BinaryWriter that writes type K to the stream.
  307. * @param {function(this:jspb.BinaryWriter,number,V)|
  308. * function(this:jspb.BinaryReader,V,?)=} valueWriterFn
  309. * The method on BinaryWriter that writes type V to the stream. May be
  310. * writeMessage, in which case the second callback arg form is used.
  311. * @param {?function(V,!jspb.BinaryWriter)=} opt_valueWriterCallback
  312. * The BinaryWriter serialization callback for type V, if V is a message
  313. * type.
  314. */
  315. jspb.Map.prototype.serializeBinary = function(
  316. fieldNumber, writer, keyWriterFn, valueWriterFn, opt_valueWriterCallback) {
  317. var strKeys = this.stringKeys_();
  318. strKeys.sort();
  319. for (var i = 0; i < strKeys.length; i++) {
  320. var entry = this.map_[strKeys[i]];
  321. writer.beginSubMessage(fieldNumber);
  322. keyWriterFn.call(writer, 1, entry.key);
  323. if (this.valueCtor_) {
  324. valueWriterFn.call(writer, 2, this.wrapEntry_(entry),
  325. opt_valueWriterCallback);
  326. } else {
  327. valueWriterFn.call(writer, 2, entry.value);
  328. }
  329. writer.endSubMessage();
  330. }
  331. };
  332. /**
  333. * Read one key/value message from the given BinaryReader. Compatible as the
  334. * `reader` callback parameter to jspb.BinaryReader.readMessage, to be called
  335. * when a key/value pair submessage is encountered.
  336. * @param {!jspb.Map} map
  337. * @param {!jspb.BinaryReader} reader
  338. * @param {function(this:jspb.BinaryReader):K=} keyReaderFn
  339. * The method on BinaryReader that reads type K from the stream.
  340. *
  341. * @param {function(this:jspb.BinaryReader):V|
  342. * function(this:jspb.BinaryReader,V,
  343. * function(V,!jspb.BinaryReader))=} valueReaderFn
  344. * The method on BinaryReader that reads type V from the stream. May be
  345. * readMessage, in which case the second callback arg form is used.
  346. *
  347. * @param {?function(V,!jspb.BinaryReader)=} opt_valueReaderCallback
  348. * The BinaryReader parsing callback for type V, if V is a message type.
  349. *
  350. */
  351. jspb.Map.deserializeBinary = function(map, reader, keyReaderFn, valueReaderFn,
  352. opt_valueReaderCallback) {
  353. var key = undefined;
  354. var value = undefined;
  355. while (reader.nextField()) {
  356. if (reader.isEndGroup()) {
  357. break;
  358. }
  359. var field = reader.getFieldNumber();
  360. if (field == 1) {
  361. // Key.
  362. key = keyReaderFn.call(reader);
  363. } else if (field == 2) {
  364. // Value.
  365. if (map.valueCtor_) {
  366. value = new map.valueCtor_();
  367. valueReaderFn.call(reader, value, opt_valueReaderCallback);
  368. } else {
  369. value = valueReaderFn.call(reader);
  370. }
  371. }
  372. }
  373. goog.asserts.assert(key != undefined);
  374. goog.asserts.assert(value != undefined);
  375. map.set(key, value);
  376. };
  377. /**
  378. * Helper: compute the list of all stringified keys in the underlying Object
  379. * map.
  380. * @return {!Array<string>}
  381. * @private
  382. */
  383. jspb.Map.prototype.stringKeys_ = function() {
  384. var m = this.map_;
  385. var ret = [];
  386. for (var p in m) {
  387. if (Object.prototype.hasOwnProperty.call(m, p)) {
  388. ret.push(p);
  389. }
  390. }
  391. return ret;
  392. };
  393. /**
  394. * @param {!K} key The entry's key.
  395. * @param {V=} opt_value The entry's value wrapper.
  396. * @constructor
  397. * @struct
  398. * @template K, V
  399. * @private
  400. */
  401. jspb.Map.Entry_ = function(key, opt_value) {
  402. /** @const {K} */
  403. this.key = key;
  404. // The JSPB-serializable value. For primitive types this will be of type V.
  405. // For message types it will be an array.
  406. /** @type {V} */
  407. this.value = opt_value;
  408. // Only used for submessage values.
  409. /** @type {V} */
  410. this.valueWrapper = undefined;
  411. };