Path: blob/trunk/third_party/closure/goog/labs/structs/map.js
2868 views
// Copyright 2012 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview A map data structure that offers a convenient API to16* manipulate a key, value map. The key must be a string.17*18* This implementation also ensure that you can use keys that would19* not be usable using a normal object literal {}. Some examples20* include __proto__ (all newer browsers), toString/hasOwnProperty (IE21* <= 8).22* @author [email protected] (Chris Henry)23*/2425goog.provide('goog.labs.structs.Map');2627goog.require('goog.array');28goog.require('goog.asserts');29goog.require('goog.object');30313233/**34* Creates a new map.35* @constructor36* @struct37* @final38*/39goog.labs.structs.Map = function() {40// clear() initializes the map to the empty state.41this.clear();42};434445/**46* @type {function(this: Object, string): boolean}47* @private48*/49goog.labs.structs.Map.objectPropertyIsEnumerable_ =50Object.prototype.propertyIsEnumerable;515253/**54* @type {function(this: Object, string): boolean}55* @private56*/57goog.labs.structs.Map.objectHasOwnProperty_ = Object.prototype.hasOwnProperty;585960/**61* Primary backing store of this map.62* @type {!Object}63* @private64*/65goog.labs.structs.Map.prototype.map_;666768/**69* Secondary backing store for keys. The index corresponds to the70* index for secondaryStoreValues_.71* @type {!Array<string>}72* @private73*/74goog.labs.structs.Map.prototype.secondaryStoreKeys_;757677/**78* Secondary backing store for keys. The index corresponds to the79* index for secondaryStoreValues_.80* @type {!Array<*>}81* @private82*/83goog.labs.structs.Map.prototype.secondaryStoreValues_;848586/**87* @private {number}88*/89goog.labs.structs.Map.prototype.count_;909192/**93* Adds the (key, value) pair, overriding previous entry with the same94* key, if any.95* @param {string} key The key.96* @param {*} value The value.97*/98goog.labs.structs.Map.prototype.set = function(key, value) {99this.assertKeyIsString_(key);100101var newKey = !this.hasKeyInPrimaryStore_(key);102this.map_[key] = value;103104// __proto__ is not settable on object.105if (key == '__proto__' ||106// Shadows for built-in properties are not enumerable in IE <= 8 .107(!goog.labs.structs.Map.BrowserFeature.OBJECT_CREATE_SUPPORTED &&108!goog.labs.structs.Map.objectPropertyIsEnumerable_.call(109this.map_, key))) {110delete this.map_[key];111var index = goog.array.indexOf(this.secondaryStoreKeys_, key);112if ((newKey = index < 0)) {113index = this.secondaryStoreKeys_.length;114}115116this.secondaryStoreKeys_[index] = key;117this.secondaryStoreValues_[index] = value;118}119120if (newKey) this.count_++;121};122123124/**125* Gets the value for the given key.126* @param {string} key The key whose value we want to retrieve.127* @param {*=} opt_default The default value to return if the key does128* not exist in the map, default to undefined.129* @return {*} The value corresponding to the given key, or opt_default130* if the key does not exist in this map.131*/132goog.labs.structs.Map.prototype.get = function(key, opt_default) {133this.assertKeyIsString_(key);134135if (this.hasKeyInPrimaryStore_(key)) {136return this.map_[key];137}138139var index = goog.array.indexOf(this.secondaryStoreKeys_, key);140return index >= 0 ? this.secondaryStoreValues_[index] : opt_default;141};142143144/**145* Removes the map entry with the given key.146* @param {string} key The key to remove.147* @return {boolean} True if the entry is removed.148*/149goog.labs.structs.Map.prototype.remove = function(key) {150this.assertKeyIsString_(key);151152if (this.hasKeyInPrimaryStore_(key)) {153this.count_--;154delete this.map_[key];155return true;156} else {157var index = goog.array.indexOf(this.secondaryStoreKeys_, key);158if (index >= 0) {159this.count_--;160goog.array.removeAt(this.secondaryStoreKeys_, index);161goog.array.removeAt(this.secondaryStoreValues_, index);162return true;163}164}165return false;166};167168169/**170* Adds the content of the map to this map. If a new entry uses a key171* that already exists in this map, the existing key is replaced.172* @param {!goog.labs.structs.Map} map The map to add.173*/174goog.labs.structs.Map.prototype.addAll = function(map) {175goog.array.forEach(176map.getKeys(), function(key) { this.set(key, map.get(key)); }, this);177};178179180/**181* @return {boolean} True if the map is empty.182*/183goog.labs.structs.Map.prototype.isEmpty = function() {184return !this.count_;185};186187188/**189* @return {number} The number of the entries in this map.190*/191goog.labs.structs.Map.prototype.getCount = function() {192return this.count_;193};194195196/**197* @param {string} key The key to check.198* @return {boolean} True if the map contains the given key.199*/200goog.labs.structs.Map.prototype.containsKey = function(key) {201this.assertKeyIsString_(key);202return this.hasKeyInPrimaryStore_(key) ||203goog.array.contains(this.secondaryStoreKeys_, key);204};205206207/**208* Whether the map contains the given value. The comparison is done209* using !== comparator. Also returns true if the passed value is NaN210* and a NaN value exists in the map.211* @param {*} value Value to check.212* @return {boolean} True if the map contains the given value.213*/214goog.labs.structs.Map.prototype.containsValue = function(value) {215var found = goog.object.some(this.map_, function(v, k) {216return this.hasKeyInPrimaryStore_(k) && goog.object.is(v, value);217}, this);218return found || goog.array.contains(this.secondaryStoreValues_, value);219};220221222/**223* @return {!Array<string>} An array of all the keys contained in this map.224*/225goog.labs.structs.Map.prototype.getKeys = function() {226var keys;227if (goog.labs.structs.Map.BrowserFeature.OBJECT_KEYS_SUPPORTED) {228keys = goog.array.clone(Object.keys(this.map_));229} else {230keys = [];231for (var key in this.map_) {232if (goog.labs.structs.Map.objectHasOwnProperty_.call(this.map_, key)) {233keys.push(key);234}235}236}237238goog.array.extend(keys, this.secondaryStoreKeys_);239return keys;240};241242243/**244* @return {!Array<*>} An array of all the values contained in this map.245* There may be duplicates.246*/247goog.labs.structs.Map.prototype.getValues = function() {248var values = [];249var keys = this.getKeys();250for (var i = 0; i < keys.length; i++) {251values.push(this.get(keys[i]));252}253return values;254};255256257/**258* @return {!Array<Array<?>>} An array of entries. Each entry is of the259* form [key, value]. Do not rely on consistent ordering of entries.260*/261goog.labs.structs.Map.prototype.getEntries = function() {262var entries = [];263var keys = this.getKeys();264for (var i = 0; i < keys.length; i++) {265var key = keys[i];266entries.push([key, this.get(key)]);267}268return entries;269};270271272/**273* Clears the map to the initial state.274*/275goog.labs.structs.Map.prototype.clear = function() {276this.map_ = goog.labs.structs.Map.BrowserFeature.OBJECT_CREATE_SUPPORTED ?277Object.create(null) :278{};279this.secondaryStoreKeys_ = [];280this.secondaryStoreValues_ = [];281this.count_ = 0;282};283284285/**286* Clones this map.287* @return {!goog.labs.structs.Map} The clone of this map.288*/289goog.labs.structs.Map.prototype.clone = function() {290var map = new goog.labs.structs.Map();291map.addAll(this);292return map;293};294295296/**297* @param {string} key The key to check.298* @return {boolean} True if the given key has been added successfully299* to the primary store.300* @private301*/302goog.labs.structs.Map.prototype.hasKeyInPrimaryStore_ = function(key) {303// New browsers that support Object.create do not allow setting of304// __proto__. In other browsers, hasOwnProperty will return true for305// __proto__ for object created with literal {}, so we need to306// special case it.307if (key == '__proto__') {308return false;309}310311if (goog.labs.structs.Map.BrowserFeature.OBJECT_CREATE_SUPPORTED) {312return key in this.map_;313}314315return goog.labs.structs.Map.objectHasOwnProperty_.call(this.map_, key);316};317318319/**320* Asserts that the given key is a string.321* @param {string} key The key to check.322* @private323*/324goog.labs.structs.Map.prototype.assertKeyIsString_ = function(key) {325goog.asserts.assert(goog.isString(key), 'key must be a string.');326};327328329/**330* Browser feature enum necessary for map.331* @enum {boolean}332*/333goog.labs.structs.Map.BrowserFeature = {334// TODO(chrishenry): Replace with goog.userAgent detection.335/**336* Whether Object.create method is supported.337*/338OBJECT_CREATE_SUPPORTED: !!Object.create,339340/**341* Whether Object.keys method is supported.342*/343OBJECT_KEYS_SUPPORTED: !!Object.keys344};345346347