123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 |
- // Copyright 2012 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- /**
- * @fileoverview A map data structure that offers a convenient API to
- * manipulate a key, value map. The key must be a string.
- *
- * This implementation also ensure that you can use keys that would
- * not be usable using a normal object literal {}. Some examples
- * include __proto__ (all newer browsers), toString/hasOwnProperty (IE
- * <= 8).
- * @author chrishenry@google.com (Chris Henry)
- */
- goog.provide('goog.labs.structs.Map');
- goog.require('goog.array');
- goog.require('goog.asserts');
- goog.require('goog.object');
- /**
- * Creates a new map.
- * @constructor
- * @struct
- * @final
- */
- goog.labs.structs.Map = function() {
- // clear() initializes the map to the empty state.
- this.clear();
- };
- /**
- * @type {function(this: Object, string): boolean}
- * @private
- */
- goog.labs.structs.Map.objectPropertyIsEnumerable_ =
- Object.prototype.propertyIsEnumerable;
- /**
- * @type {function(this: Object, string): boolean}
- * @private
- */
- goog.labs.structs.Map.objectHasOwnProperty_ = Object.prototype.hasOwnProperty;
- /**
- * Primary backing store of this map.
- * @type {!Object}
- * @private
- */
- goog.labs.structs.Map.prototype.map_;
- /**
- * Secondary backing store for keys. The index corresponds to the
- * index for secondaryStoreValues_.
- * @type {!Array<string>}
- * @private
- */
- goog.labs.structs.Map.prototype.secondaryStoreKeys_;
- /**
- * Secondary backing store for keys. The index corresponds to the
- * index for secondaryStoreValues_.
- * @type {!Array<*>}
- * @private
- */
- goog.labs.structs.Map.prototype.secondaryStoreValues_;
- /**
- * @private {number}
- */
- goog.labs.structs.Map.prototype.count_;
- /**
- * Adds the (key, value) pair, overriding previous entry with the same
- * key, if any.
- * @param {string} key The key.
- * @param {*} value The value.
- */
- goog.labs.structs.Map.prototype.set = function(key, value) {
- this.assertKeyIsString_(key);
- var newKey = !this.hasKeyInPrimaryStore_(key);
- this.map_[key] = value;
- // __proto__ is not settable on object.
- if (key == '__proto__' ||
- // Shadows for built-in properties are not enumerable in IE <= 8 .
- (!goog.labs.structs.Map.BrowserFeature.OBJECT_CREATE_SUPPORTED &&
- !goog.labs.structs.Map.objectPropertyIsEnumerable_.call(
- this.map_, key))) {
- delete this.map_[key];
- var index = goog.array.indexOf(this.secondaryStoreKeys_, key);
- if ((newKey = index < 0)) {
- index = this.secondaryStoreKeys_.length;
- }
- this.secondaryStoreKeys_[index] = key;
- this.secondaryStoreValues_[index] = value;
- }
- if (newKey) this.count_++;
- };
- /**
- * Gets the value for the given key.
- * @param {string} key The key whose value we want to retrieve.
- * @param {*=} opt_default The default value to return if the key does
- * not exist in the map, default to undefined.
- * @return {*} The value corresponding to the given key, or opt_default
- * if the key does not exist in this map.
- */
- goog.labs.structs.Map.prototype.get = function(key, opt_default) {
- this.assertKeyIsString_(key);
- if (this.hasKeyInPrimaryStore_(key)) {
- return this.map_[key];
- }
- var index = goog.array.indexOf(this.secondaryStoreKeys_, key);
- return index >= 0 ? this.secondaryStoreValues_[index] : opt_default;
- };
- /**
- * Removes the map entry with the given key.
- * @param {string} key The key to remove.
- * @return {boolean} True if the entry is removed.
- */
- goog.labs.structs.Map.prototype.remove = function(key) {
- this.assertKeyIsString_(key);
- if (this.hasKeyInPrimaryStore_(key)) {
- this.count_--;
- delete this.map_[key];
- return true;
- } else {
- var index = goog.array.indexOf(this.secondaryStoreKeys_, key);
- if (index >= 0) {
- this.count_--;
- goog.array.removeAt(this.secondaryStoreKeys_, index);
- goog.array.removeAt(this.secondaryStoreValues_, index);
- return true;
- }
- }
- return false;
- };
- /**
- * Adds the content of the map to this map. If a new entry uses a key
- * that already exists in this map, the existing key is replaced.
- * @param {!goog.labs.structs.Map} map The map to add.
- */
- goog.labs.structs.Map.prototype.addAll = function(map) {
- goog.array.forEach(
- map.getKeys(), function(key) { this.set(key, map.get(key)); }, this);
- };
- /**
- * @return {boolean} True if the map is empty.
- */
- goog.labs.structs.Map.prototype.isEmpty = function() {
- return !this.count_;
- };
- /**
- * @return {number} The number of the entries in this map.
- */
- goog.labs.structs.Map.prototype.getCount = function() {
- return this.count_;
- };
- /**
- * @param {string} key The key to check.
- * @return {boolean} True if the map contains the given key.
- */
- goog.labs.structs.Map.prototype.containsKey = function(key) {
- this.assertKeyIsString_(key);
- return this.hasKeyInPrimaryStore_(key) ||
- goog.array.contains(this.secondaryStoreKeys_, key);
- };
- /**
- * Whether the map contains the given value. The comparison is done
- * using !== comparator. Also returns true if the passed value is NaN
- * and a NaN value exists in the map.
- * @param {*} value Value to check.
- * @return {boolean} True if the map contains the given value.
- */
- goog.labs.structs.Map.prototype.containsValue = function(value) {
- var found = goog.object.some(this.map_, function(v, k) {
- return this.hasKeyInPrimaryStore_(k) && goog.object.is(v, value);
- }, this);
- return found || goog.array.contains(this.secondaryStoreValues_, value);
- };
- /**
- * @return {!Array<string>} An array of all the keys contained in this map.
- */
- goog.labs.structs.Map.prototype.getKeys = function() {
- var keys;
- if (goog.labs.structs.Map.BrowserFeature.OBJECT_KEYS_SUPPORTED) {
- keys = goog.array.clone(Object.keys(this.map_));
- } else {
- keys = [];
- for (var key in this.map_) {
- if (goog.labs.structs.Map.objectHasOwnProperty_.call(this.map_, key)) {
- keys.push(key);
- }
- }
- }
- goog.array.extend(keys, this.secondaryStoreKeys_);
- return keys;
- };
- /**
- * @return {!Array<*>} An array of all the values contained in this map.
- * There may be duplicates.
- */
- goog.labs.structs.Map.prototype.getValues = function() {
- var values = [];
- var keys = this.getKeys();
- for (var i = 0; i < keys.length; i++) {
- values.push(this.get(keys[i]));
- }
- return values;
- };
- /**
- * @return {!Array<Array<?>>} An array of entries. Each entry is of the
- * form [key, value]. Do not rely on consistent ordering of entries.
- */
- goog.labs.structs.Map.prototype.getEntries = function() {
- var entries = [];
- var keys = this.getKeys();
- for (var i = 0; i < keys.length; i++) {
- var key = keys[i];
- entries.push([key, this.get(key)]);
- }
- return entries;
- };
- /**
- * Clears the map to the initial state.
- */
- goog.labs.structs.Map.prototype.clear = function() {
- this.map_ = goog.labs.structs.Map.BrowserFeature.OBJECT_CREATE_SUPPORTED ?
- Object.create(null) :
- {};
- this.secondaryStoreKeys_ = [];
- this.secondaryStoreValues_ = [];
- this.count_ = 0;
- };
- /**
- * Clones this map.
- * @return {!goog.labs.structs.Map} The clone of this map.
- */
- goog.labs.structs.Map.prototype.clone = function() {
- var map = new goog.labs.structs.Map();
- map.addAll(this);
- return map;
- };
- /**
- * @param {string} key The key to check.
- * @return {boolean} True if the given key has been added successfully
- * to the primary store.
- * @private
- */
- goog.labs.structs.Map.prototype.hasKeyInPrimaryStore_ = function(key) {
- // New browsers that support Object.create do not allow setting of
- // __proto__. In other browsers, hasOwnProperty will return true for
- // __proto__ for object created with literal {}, so we need to
- // special case it.
- if (key == '__proto__') {
- return false;
- }
- if (goog.labs.structs.Map.BrowserFeature.OBJECT_CREATE_SUPPORTED) {
- return key in this.map_;
- }
- return goog.labs.structs.Map.objectHasOwnProperty_.call(this.map_, key);
- };
- /**
- * Asserts that the given key is a string.
- * @param {string} key The key to check.
- * @private
- */
- goog.labs.structs.Map.prototype.assertKeyIsString_ = function(key) {
- goog.asserts.assert(goog.isString(key), 'key must be a string.');
- };
- /**
- * Browser feature enum necessary for map.
- * @enum {boolean}
- */
- goog.labs.structs.Map.BrowserFeature = {
- // TODO(chrishenry): Replace with goog.userAgent detection.
- /**
- * Whether Object.create method is supported.
- */
- OBJECT_CREATE_SUPPORTED: !!Object.create,
- /**
- * Whether Object.keys method is supported.
- */
- OBJECT_KEYS_SUPPORTED: !!Object.keys
- };
|