340 lines
8.8 KiB
JavaScript
340 lines
8.8 KiB
JavaScript
// 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).
|
|
*/
|
|
|
|
goog.provide('goog.labs.structs.Map');
|
|
|
|
goog.require('goog.array');
|
|
goog.require('goog.asserts');
|
|
goog.require('goog.labs.object');
|
|
goog.require('goog.object');
|
|
|
|
|
|
|
|
/**
|
|
* Creates a new map.
|
|
* @constructor
|
|
*/
|
|
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_;
|
|
|
|
|
|
/**
|
|
* 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.labs.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(user): 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
|
|
};
|