map.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. // Copyright 2012 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview A map data structure that offers a convenient API to
  16. * manipulate a key, value map. The key must be a string.
  17. *
  18. * This implementation also ensure that you can use keys that would
  19. * not be usable using a normal object literal {}. Some examples
  20. * include __proto__ (all newer browsers), toString/hasOwnProperty (IE
  21. * <= 8).
  22. * @author chrishenry@google.com (Chris Henry)
  23. */
  24. goog.provide('goog.labs.structs.Map');
  25. goog.require('goog.array');
  26. goog.require('goog.asserts');
  27. goog.require('goog.object');
  28. /**
  29. * Creates a new map.
  30. * @constructor
  31. * @struct
  32. * @final
  33. */
  34. goog.labs.structs.Map = function() {
  35. // clear() initializes the map to the empty state.
  36. this.clear();
  37. };
  38. /**
  39. * @type {function(this: Object, string): boolean}
  40. * @private
  41. */
  42. goog.labs.structs.Map.objectPropertyIsEnumerable_ =
  43. Object.prototype.propertyIsEnumerable;
  44. /**
  45. * @type {function(this: Object, string): boolean}
  46. * @private
  47. */
  48. goog.labs.structs.Map.objectHasOwnProperty_ = Object.prototype.hasOwnProperty;
  49. /**
  50. * Primary backing store of this map.
  51. * @type {!Object}
  52. * @private
  53. */
  54. goog.labs.structs.Map.prototype.map_;
  55. /**
  56. * Secondary backing store for keys. The index corresponds to the
  57. * index for secondaryStoreValues_.
  58. * @type {!Array<string>}
  59. * @private
  60. */
  61. goog.labs.structs.Map.prototype.secondaryStoreKeys_;
  62. /**
  63. * Secondary backing store for keys. The index corresponds to the
  64. * index for secondaryStoreValues_.
  65. * @type {!Array<*>}
  66. * @private
  67. */
  68. goog.labs.structs.Map.prototype.secondaryStoreValues_;
  69. /**
  70. * @private {number}
  71. */
  72. goog.labs.structs.Map.prototype.count_;
  73. /**
  74. * Adds the (key, value) pair, overriding previous entry with the same
  75. * key, if any.
  76. * @param {string} key The key.
  77. * @param {*} value The value.
  78. */
  79. goog.labs.structs.Map.prototype.set = function(key, value) {
  80. this.assertKeyIsString_(key);
  81. var newKey = !this.hasKeyInPrimaryStore_(key);
  82. this.map_[key] = value;
  83. // __proto__ is not settable on object.
  84. if (key == '__proto__' ||
  85. // Shadows for built-in properties are not enumerable in IE <= 8 .
  86. (!goog.labs.structs.Map.BrowserFeature.OBJECT_CREATE_SUPPORTED &&
  87. !goog.labs.structs.Map.objectPropertyIsEnumerable_.call(
  88. this.map_, key))) {
  89. delete this.map_[key];
  90. var index = goog.array.indexOf(this.secondaryStoreKeys_, key);
  91. if ((newKey = index < 0)) {
  92. index = this.secondaryStoreKeys_.length;
  93. }
  94. this.secondaryStoreKeys_[index] = key;
  95. this.secondaryStoreValues_[index] = value;
  96. }
  97. if (newKey) this.count_++;
  98. };
  99. /**
  100. * Gets the value for the given key.
  101. * @param {string} key The key whose value we want to retrieve.
  102. * @param {*=} opt_default The default value to return if the key does
  103. * not exist in the map, default to undefined.
  104. * @return {*} The value corresponding to the given key, or opt_default
  105. * if the key does not exist in this map.
  106. */
  107. goog.labs.structs.Map.prototype.get = function(key, opt_default) {
  108. this.assertKeyIsString_(key);
  109. if (this.hasKeyInPrimaryStore_(key)) {
  110. return this.map_[key];
  111. }
  112. var index = goog.array.indexOf(this.secondaryStoreKeys_, key);
  113. return index >= 0 ? this.secondaryStoreValues_[index] : opt_default;
  114. };
  115. /**
  116. * Removes the map entry with the given key.
  117. * @param {string} key The key to remove.
  118. * @return {boolean} True if the entry is removed.
  119. */
  120. goog.labs.structs.Map.prototype.remove = function(key) {
  121. this.assertKeyIsString_(key);
  122. if (this.hasKeyInPrimaryStore_(key)) {
  123. this.count_--;
  124. delete this.map_[key];
  125. return true;
  126. } else {
  127. var index = goog.array.indexOf(this.secondaryStoreKeys_, key);
  128. if (index >= 0) {
  129. this.count_--;
  130. goog.array.removeAt(this.secondaryStoreKeys_, index);
  131. goog.array.removeAt(this.secondaryStoreValues_, index);
  132. return true;
  133. }
  134. }
  135. return false;
  136. };
  137. /**
  138. * Adds the content of the map to this map. If a new entry uses a key
  139. * that already exists in this map, the existing key is replaced.
  140. * @param {!goog.labs.structs.Map} map The map to add.
  141. */
  142. goog.labs.structs.Map.prototype.addAll = function(map) {
  143. goog.array.forEach(
  144. map.getKeys(), function(key) { this.set(key, map.get(key)); }, this);
  145. };
  146. /**
  147. * @return {boolean} True if the map is empty.
  148. */
  149. goog.labs.structs.Map.prototype.isEmpty = function() {
  150. return !this.count_;
  151. };
  152. /**
  153. * @return {number} The number of the entries in this map.
  154. */
  155. goog.labs.structs.Map.prototype.getCount = function() {
  156. return this.count_;
  157. };
  158. /**
  159. * @param {string} key The key to check.
  160. * @return {boolean} True if the map contains the given key.
  161. */
  162. goog.labs.structs.Map.prototype.containsKey = function(key) {
  163. this.assertKeyIsString_(key);
  164. return this.hasKeyInPrimaryStore_(key) ||
  165. goog.array.contains(this.secondaryStoreKeys_, key);
  166. };
  167. /**
  168. * Whether the map contains the given value. The comparison is done
  169. * using !== comparator. Also returns true if the passed value is NaN
  170. * and a NaN value exists in the map.
  171. * @param {*} value Value to check.
  172. * @return {boolean} True if the map contains the given value.
  173. */
  174. goog.labs.structs.Map.prototype.containsValue = function(value) {
  175. var found = goog.object.some(this.map_, function(v, k) {
  176. return this.hasKeyInPrimaryStore_(k) && goog.object.is(v, value);
  177. }, this);
  178. return found || goog.array.contains(this.secondaryStoreValues_, value);
  179. };
  180. /**
  181. * @return {!Array<string>} An array of all the keys contained in this map.
  182. */
  183. goog.labs.structs.Map.prototype.getKeys = function() {
  184. var keys;
  185. if (goog.labs.structs.Map.BrowserFeature.OBJECT_KEYS_SUPPORTED) {
  186. keys = goog.array.clone(Object.keys(this.map_));
  187. } else {
  188. keys = [];
  189. for (var key in this.map_) {
  190. if (goog.labs.structs.Map.objectHasOwnProperty_.call(this.map_, key)) {
  191. keys.push(key);
  192. }
  193. }
  194. }
  195. goog.array.extend(keys, this.secondaryStoreKeys_);
  196. return keys;
  197. };
  198. /**
  199. * @return {!Array<*>} An array of all the values contained in this map.
  200. * There may be duplicates.
  201. */
  202. goog.labs.structs.Map.prototype.getValues = function() {
  203. var values = [];
  204. var keys = this.getKeys();
  205. for (var i = 0; i < keys.length; i++) {
  206. values.push(this.get(keys[i]));
  207. }
  208. return values;
  209. };
  210. /**
  211. * @return {!Array<Array<?>>} An array of entries. Each entry is of the
  212. * form [key, value]. Do not rely on consistent ordering of entries.
  213. */
  214. goog.labs.structs.Map.prototype.getEntries = function() {
  215. var entries = [];
  216. var keys = this.getKeys();
  217. for (var i = 0; i < keys.length; i++) {
  218. var key = keys[i];
  219. entries.push([key, this.get(key)]);
  220. }
  221. return entries;
  222. };
  223. /**
  224. * Clears the map to the initial state.
  225. */
  226. goog.labs.structs.Map.prototype.clear = function() {
  227. this.map_ = goog.labs.structs.Map.BrowserFeature.OBJECT_CREATE_SUPPORTED ?
  228. Object.create(null) :
  229. {};
  230. this.secondaryStoreKeys_ = [];
  231. this.secondaryStoreValues_ = [];
  232. this.count_ = 0;
  233. };
  234. /**
  235. * Clones this map.
  236. * @return {!goog.labs.structs.Map} The clone of this map.
  237. */
  238. goog.labs.structs.Map.prototype.clone = function() {
  239. var map = new goog.labs.structs.Map();
  240. map.addAll(this);
  241. return map;
  242. };
  243. /**
  244. * @param {string} key The key to check.
  245. * @return {boolean} True if the given key has been added successfully
  246. * to the primary store.
  247. * @private
  248. */
  249. goog.labs.structs.Map.prototype.hasKeyInPrimaryStore_ = function(key) {
  250. // New browsers that support Object.create do not allow setting of
  251. // __proto__. In other browsers, hasOwnProperty will return true for
  252. // __proto__ for object created with literal {}, so we need to
  253. // special case it.
  254. if (key == '__proto__') {
  255. return false;
  256. }
  257. if (goog.labs.structs.Map.BrowserFeature.OBJECT_CREATE_SUPPORTED) {
  258. return key in this.map_;
  259. }
  260. return goog.labs.structs.Map.objectHasOwnProperty_.call(this.map_, key);
  261. };
  262. /**
  263. * Asserts that the given key is a string.
  264. * @param {string} key The key to check.
  265. * @private
  266. */
  267. goog.labs.structs.Map.prototype.assertKeyIsString_ = function(key) {
  268. goog.asserts.assert(goog.isString(key), 'key must be a string.');
  269. };
  270. /**
  271. * Browser feature enum necessary for map.
  272. * @enum {boolean}
  273. */
  274. goog.labs.structs.Map.BrowserFeature = {
  275. // TODO(chrishenry): Replace with goog.userAgent detection.
  276. /**
  277. * Whether Object.create method is supported.
  278. */
  279. OBJECT_CREATE_SUPPORTED: !!Object.create,
  280. /**
  281. * Whether Object.keys method is supported.
  282. */
  283. OBJECT_KEYS_SUPPORTED: !!Object.keys
  284. };