Adding mapbox-gl branch

This commit is contained in:
Andreas Hocevar
2015-03-16 18:50:27 +01:00
parent 7985f030fa
commit 57ee7f52fd
3109 changed files with 943365 additions and 0 deletions
@@ -0,0 +1,348 @@
// 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.labs.object');
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.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(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
};
@@ -0,0 +1,204 @@
// 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 Performance test for goog.structs.Map and
* goog.labs.structs.Map. To run this test fairly, you would have to
* compile this via JsCompiler (with --export_test_functions), and
* pull the compiled JS into an empty HTML file.
* @author chrishenry@google.com (Chris Henry)
*/
goog.provide('goog.labs.structs.MapPerf');
goog.setTestOnly('goog.labs.structs.MapPerf');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.labs.structs.Map');
goog.require('goog.structs.Map');
goog.require('goog.testing.PerformanceTable');
goog.require('goog.testing.jsunit');
goog.scope(function() {
var MapPerf = goog.labs.structs.MapPerf;
/**
* @typedef {goog.labs.structs.Map|goog.structs.Map}
*/
MapPerf.MapType;
/**
* @type {goog.testing.PerformanceTable}
*/
MapPerf.perfTable;
/**
* A key list. This maps loop index to key name to be used during
* benchmark. This ensure that we do not need to pay the cost of
* string concatenation/GC whenever we derive a key from loop index.
*
* This is filled once in setUpPage and then remain unchanged for the
* rest of the test case.
*
* @type {!Array<string>}
*/
MapPerf.keyList = [];
/**
* Maxium number of keys in keyList (and, by extension, the map under
* test).
* @type {number}
*/
MapPerf.MAX_NUM_KEY = 10000;
/**
* Fills the given map with generated key-value pair.
* @param {MapPerf.MapType} map The map to fill.
* @param {number} numKeys The number of key-value pair to fill.
*/
MapPerf.fillMap = function(map, numKeys) {
goog.asserts.assert(numKeys <= MapPerf.MAX_NUM_KEY);
for (var i = 0; i < numKeys; ++i) {
map.set(MapPerf.keyList[i], i);
}
};
/**
* Primes the given map with deletion of keys.
* @param {MapPerf.MapType} map The map to prime.
* @return {MapPerf.MapType} The primed map (for chaining).
*/
MapPerf.primeMapWithDeletion = function(map) {
for (var i = 0; i < 1000; ++i) {
map.set(MapPerf.keyList[i], i);
}
for (var i = 0; i < 1000; ++i) {
map.remove(MapPerf.keyList[i]);
}
return map;
};
/**
* Runs performance test for Map#get with the given map.
* @param {MapPerf.MapType} map The map to stress.
* @param {string} message Message to be put in performance table.
*/
MapPerf.runPerformanceTestForMapGet = function(map, message) {
MapPerf.fillMap(map, 10000);
MapPerf.perfTable.run(
function() {
// Creates local alias for map and keyList.
var localMap = map;
var localKeyList = MapPerf.keyList;
for (var i = 0; i < 500; ++i) {
var sum = 0;
for (var j = 0; j < 10000; ++j) {
sum += localMap.get(localKeyList[j]);
}
}
},
message);
};
/**
* Runs performance test for Map#set with the given map.
* @param {MapPerf.MapType} map The map to stress.
* @param {string} message Message to be put in performance table.
*/
MapPerf.runPerformanceTestForMapSet = function(map, message) {
MapPerf.perfTable.run(
function() {
// Creates local alias for map and keyList.
var localMap = map;
var localKeyList = MapPerf.keyList;
for (var i = 0; i < 500; ++i) {
for (var j = 0; j < 10000; ++j) {
localMap.set(localKeyList[i], i);
}
}
},
message);
};
goog.global['setUpPage'] = function() {
var content = goog.dom.createDom('div');
goog.dom.insertChildAt(document.body, content, 0);
var ua = navigator.userAgent;
content.innerHTML =
'<h1>Closure Performance Tests - Map</h1>' +
'<p><strong>User-agent: </strong><span id="ua">' + ua + '</span></p>' +
'<div id="perf-table"></div>' +
'<hr>';
MapPerf.perfTable = new goog.testing.PerformanceTable(
goog.dom.getElement('perf-table'));
// Fills keyList.
for (var i = 0; i < MapPerf.MAX_NUM_KEY; ++i) {
MapPerf.keyList.push('k' + i);
}
};
goog.global['testGetFromLabsMap'] = function() {
MapPerf.runPerformanceTestForMapGet(
new goog.labs.structs.Map(), '#get: no previous deletion (Labs)');
};
goog.global['testGetFromOriginalMap'] = function() {
MapPerf.runPerformanceTestForMapGet(
new goog.structs.Map(), '#get: no previous deletion (Original)');
};
goog.global['testGetWithPreviousDeletionFromLabsMap'] = function() {
MapPerf.runPerformanceTestForMapGet(
MapPerf.primeMapWithDeletion(new goog.labs.structs.Map()),
'#get: with previous deletion (Labs)');
};
goog.global['testGetWithPreviousDeletionFromOriginalMap'] = function() {
MapPerf.runPerformanceTestForMapGet(
MapPerf.primeMapWithDeletion(new goog.structs.Map()),
'#get: with previous deletion (Original)');
};
goog.global['testSetFromLabsMap'] = function() {
MapPerf.runPerformanceTestForMapSet(
new goog.labs.structs.Map(), '#set: no previous deletion (Labs)');
};
goog.global['testSetFromOriginalMap'] = function() {
MapPerf.runPerformanceTestForMapSet(
new goog.structs.Map(), '#set: no previous deletion (Original)');
};
}); // goog.scope
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2012 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
Author: chrishenry@google.com (Chris Henry)
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.labs.structs.Map
</title>
<script src="../../base.js">
</script>
<script>
goog.require('goog.labs.structs.MapTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,432 @@
// 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.
goog.provide('goog.labs.structs.MapTest');
goog.setTestOnly('goog.labs.structs.MapTest');
goog.require('goog.labs.structs.Map');
goog.require('goog.testing.PropertyReplacer');
goog.require('goog.testing.jsunit');
var map;
var stubs;
function setUpPage() {
stubs = new goog.testing.PropertyReplacer();
}
function setUp() {
map = new goog.labs.structs.Map();
}
function testSet() {
var key = 'test';
var value = 'value';
map.set(key, value);
assertEquals(value, map.get(key));
}
function testSetsWithSameKey() {
var key = 'test';
var value = 'value';
var value2 = 'value2';
map.set(key, value);
map.set(key, value2);
assertEquals(value2, map.get(key));
}
function testSetWithUndefinedValue() {
var key = 'test';
map.set(key, undefined);
assertUndefined(map.get(key));
}
function testSetWithUnderUnderProtoUnderUnder() {
var key = '__proto__';
var value = 'value';
var value2 = 'value2';
map.set(key, value);
assertEquals(value, map.get(key));
map.set(key, value2);
assertEquals(value2, map.get(key));
}
function testSetWithBuiltInPropertyShadows() {
var key = 'toString';
var value = 'value';
var key2 = 'hasOwnProperty';
var value2 = 'value2';
map.set(key, value);
map.set(key2, value2);
assertEquals(value, map.get(key));
assertEquals(value2, map.get(key2));
map.set(key, value2);
map.set(key2, value);
assertEquals(value2, map.get(key));
assertEquals(value, map.get(key2));
}
function testGetBeforeSetOfUnderUnderProtoUnderUnder() {
assertUndefined(map.get('__proto__'));
}
function testContainsKey() {
assertFalse(map.containsKey('key'));
assertFalse(map.containsKey('__proto__'));
assertFalse(map.containsKey('toString'));
assertFalse(map.containsKey('hasOwnProperty'));
assertFalse(map.containsKey('key2'));
assertFalse(map.containsKey('key3'));
assertFalse(map.containsKey('key4'));
map.set('key', 'v');
map.set('__proto__', 'v');
map.set('toString', 'v');
map.set('hasOwnProperty', 'v');
map.set('key2', undefined);
map.set('key3', null);
map.set('key4', '');
assertTrue(map.containsKey('key'));
assertTrue(map.containsKey('__proto__'));
assertTrue(map.containsKey('toString'));
assertTrue(map.containsKey('hasOwnProperty'));
assertTrue(map.containsKey('key2'));
assertTrue(map.containsKey('key3'));
assertTrue(map.containsKey('key4'));
}
function testContainsValueWithShadowKeys() {
assertFalse(map.containsValue('v2'));
assertFalse(map.containsValue('v3'));
assertFalse(map.containsValue('v4'));
map.set('__proto__', 'v2');
map.set('toString', 'v3');
map.set('hasOwnProperty', 'v4');
assertTrue(map.containsValue('v2'));
assertTrue(map.containsValue('v3'));
assertTrue(map.containsValue('v4'));
assertFalse(map.containsValue(Object.prototype.toString));
assertFalse(map.containsValue(Object.prototype.hasOwnProperty));
}
function testContainsValueWithNullAndUndefined() {
assertFalse(map.containsValue(undefined));
assertFalse(map.containsValue(null));
map.set('key2', undefined);
map.set('key3', null);
assertTrue(map.containsValue(undefined));
assertTrue(map.containsValue(null));
}
function testContainsValueWithNumber() {
assertFalse(map.containsValue(-1));
assertFalse(map.containsValue(0));
assertFalse(map.containsValue(1));
map.set('key', -1);
map.set('key2', 0);
map.set('key3', 1);
assertTrue(map.containsValue(-1));
assertTrue(map.containsValue(0));
assertTrue(map.containsValue(1));
}
function testContainsValueWithNaN() {
assertFalse(map.containsValue(NaN));
map.set('key', NaN);
assertTrue(map.containsValue(NaN));
}
function testContainsValueWithNegativeZero() {
assertFalse(map.containsValue(-0));
map.set('key', -0);
assertTrue(map.containsValue(-0));
assertFalse(map.containsValue(0));
map.set('key', 0);
assertFalse(map.containsValue(-0));
assertTrue(map.containsValue(0));
}
function testContainsValueWithStrings() {
assertFalse(map.containsValue(''));
assertFalse(map.containsValue('v'));
map.set('key', '');
map.set('key2', 'v');
assertTrue(map.containsValue(''));
assertTrue(map.containsValue('v'));
}
function testRemove() {
map.set('key', 'v');
map.set('__proto__', 'v2');
map.set('toString', 'v3');
map.set('hasOwnProperty', 'v4');
map.set('key2', undefined);
map.set('key3', null);
map.set('key4', '');
assertFalse(map.remove('key do not exist'));
assertTrue(map.remove('key'));
assertFalse(map.containsKey('key'));
assertFalse(map.remove('key'));
assertTrue(map.remove('__proto__'));
assertFalse(map.containsKey('__proto__'));
assertFalse(map.remove('__proto__'));
assertTrue(map.remove('toString'));
assertFalse(map.containsKey('toString'));
assertFalse(map.remove('toString'));
assertTrue(map.remove('hasOwnProperty'));
assertFalse(map.containsKey('hasOwnProperty'));
assertFalse(map.remove('hasOwnProperty'));
assertTrue(map.remove('key2'));
assertFalse(map.containsKey('key2'));
assertFalse(map.remove('key2'));
assertTrue(map.remove('key3'));
assertFalse(map.containsKey('key3'));
assertFalse(map.remove('key3'));
assertTrue('', map.remove('key4'));
assertFalse(map.containsKey('key4'));
assertFalse(map.remove('key4'));
}
function testGetCountAndIsEmpty() {
assertEquals(0, map.getCount());
assertTrue(map.isEmpty());
map.set('key', 'v');
assertEquals(1, map.getCount());
map.set('__proto__', 'v2');
assertEquals(2, map.getCount());
map.set('toString', 'v3');
assertEquals(3, map.getCount());
map.set('hasOwnProperty', 'v4');
assertEquals(4, map.getCount());
map.set('key', 'a');
assertEquals(4, map.getCount());
map.set('__proto__', 'a2');
assertEquals(4, map.getCount());
map.set('toString', 'a3');
assertEquals(4, map.getCount());
map.set('hasOwnProperty', 'a4');
assertEquals(4, map.getCount());
map.remove('key');
assertEquals(3, map.getCount());
map.remove('__proto__');
assertEquals(2, map.getCount());
map.remove('toString');
assertEquals(1, map.getCount());
map.remove('hasOwnProperty');
assertEquals(0, map.getCount());
}
function testClear() {
map.set('key', 'v');
map.set('__proto__', 'v');
map.set('toString', 'v');
map.set('hasOwnProperty', 'v');
map.set('key2', undefined);
map.set('key3', null);
map.set('key4', '');
map.clear();
assertFalse(map.containsKey('key'));
assertFalse(map.containsKey('__proto__'));
assertFalse(map.containsKey('toString'));
assertFalse(map.containsKey('hasOwnProperty'));
assertFalse(map.containsKey('key2'));
assertFalse(map.containsKey('key3'));
assertFalse(map.containsKey('key4'));
}
function testGetEntries() {
map.set('key', 'v');
map.set('__proto__', 'v');
map.set('toString', 'v');
map.set('hasOwnProperty', 'v');
map.set('key2', undefined);
map.set('key3', null);
map.set('key4', '');
var entries = map.getEntries();
assertEquals(7, entries.length);
assertContainsEntry(['key', 'v'], entries);
assertContainsEntry(['__proto__', 'v'], entries);
assertContainsEntry(['toString', 'v'], entries);
assertContainsEntry(['hasOwnProperty', 'v'], entries);
assertContainsEntry(['key2', undefined], entries);
assertContainsEntry(['key3', null], entries);
assertContainsEntry(['key4', ''], entries);
}
function testGetKeys() {
map.set('key', 'v');
map.set('__proto__', 'v');
map.set('toString', 'v');
map.set('hasOwnProperty', 'v');
map.set('key2', undefined);
map.set('key3', null);
map.set('k4', '');
var values = map.getKeys();
assertSameElements(
['key', '__proto__', 'toString', 'hasOwnProperty', 'key2', 'key3', 'k4'],
values);
}
function testGetValues() {
map.set('key', 'v');
map.set('__proto__', 'v');
map.set('toString', 'v');
map.set('hasOwnProperty', 'v');
map.set('key2', undefined);
map.set('key3', null);
map.set('key4', '');
var values = map.getValues();
assertSameElements(['v', 'v', 'v', 'v', undefined, null, ''], values);
}
function testAddAllToEmptyMap() {
map.set('key', 'v');
map.set('key2', 'v2');
map.set('key3', 'v3');
map.set('key4', 'v4');
var map2 = new goog.labs.structs.Map();
map2.addAll(map);
assertEquals(4, map2.getCount());
assertEquals('v', map2.get('key'));
assertEquals('v2', map2.get('key2'));
assertEquals('v3', map2.get('key3'));
assertEquals('v4', map2.get('key4'));
}
function testAddAllToNonEmptyMap() {
map.set('key', 'v');
map.set('key2', 'v2');
map.set('key3', 'v3');
map.set('key4', 'v4');
var map2 = new goog.labs.structs.Map();
map2.set('key0', 'o');
map2.set('key', 'o');
map2.set('key2', 'o2');
map2.set('key3', 'o3');
map2.addAll(map);
assertEquals(5, map2.getCount());
assertEquals('o', map2.get('key0'));
assertEquals('v', map2.get('key'));
assertEquals('v2', map2.get('key2'));
assertEquals('v3', map2.get('key3'));
assertEquals('v4', map2.get('key4'));
}
function testClone() {
map.set('key', 'v');
map.set('key2', 'v2');
map.set('key3', 'v3');
map.set('key4', 'v4');
var map2 = map.clone();
assertEquals(4, map2.getCount());
assertEquals('v', map2.get('key'));
assertEquals('v2', map2.get('key2'));
assertEquals('v3', map2.get('key3'));
assertEquals('v4', map2.get('key4'));
}
function testMapWithModifiedObjectPrototype() {
stubs.set(Object.prototype, 'toString', function() {});
stubs.set(Object.prototype, 'foo', function() {});
stubs.set(Object.prototype, 'field', 100);
stubs.set(Object.prototype, 'fooKey', function() {});
map = new goog.labs.structs.Map();
map.set('key', 'v');
map.set('key2', 'v2');
map.set('fooKey', 'v3');
assertTrue(map.containsKey('key'));
assertTrue(map.containsKey('key2'));
assertTrue(map.containsKey('fooKey'));
assertFalse(map.containsKey('toString'));
assertFalse(map.containsKey('foo'));
assertFalse(map.containsKey('field'));
assertTrue(map.containsValue('v'));
assertTrue(map.containsValue('v2'));
assertTrue(map.containsValue('v3'));
assertFalse(map.containsValue(100));
var entries = map.getEntries();
assertEquals(3, entries.length);
assertContainsEntry(['key', 'v'], entries);
assertContainsEntry(['key2', 'v2'], entries);
assertContainsEntry(['fooKey', 'v3'], entries);
}
function assertContainsEntry(entry, entryList) {
for (var i = 0; i < entryList.length; ++i) {
if (entry[0] == entryList[i][0] && entry[1] === entryList[i][1]) {
return;
}
}
fail('Did not find entry: ' + entry + ' in: ' + entryList);
}
@@ -0,0 +1,282 @@
// 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 collection similar to
* {@code goog.labs.structs.Map}, but also allows associating multiple
* values with a single key.
*
* This implementation ensures that you can use any string keys.
*
* @author chrishenry@google.com (Chris Henry)
*/
goog.provide('goog.labs.structs.Multimap');
goog.require('goog.array');
goog.require('goog.labs.object');
goog.require('goog.labs.structs.Map');
/**
* Creates a new multimap.
* @constructor
* @struct
* @final
*/
goog.labs.structs.Multimap = function() {
this.clear();
};
/**
* The backing map.
* @type {!goog.labs.structs.Map}
* @private
*/
goog.labs.structs.Multimap.prototype.map_;
/**
* @type {number}
* @private
*/
goog.labs.structs.Multimap.prototype.count_ = 0;
/**
* Clears the multimap.
*/
goog.labs.structs.Multimap.prototype.clear = function() {
this.count_ = 0;
this.map_ = new goog.labs.structs.Map();
};
/**
* Clones this multimap.
* @return {!goog.labs.structs.Multimap} A multimap that contains all
* the mapping this multimap has.
*/
goog.labs.structs.Multimap.prototype.clone = function() {
var map = new goog.labs.structs.Multimap();
map.addAllFromMultimap(this);
return map;
};
/**
* Adds the given (key, value) pair to the map. The (key, value) pair
* is guaranteed to be added.
* @param {string} key The key to add.
* @param {*} value The value to add.
*/
goog.labs.structs.Multimap.prototype.add = function(key, value) {
var values = this.map_.get(key);
if (!values) {
this.map_.set(key, (values = []));
}
values.push(value);
this.count_++;
};
/**
* Stores a collection of values to the given key. Does not replace
* existing (key, value) pairs.
* @param {string} key The key to add.
* @param {!Array<*>} values The values to add.
*/
goog.labs.structs.Multimap.prototype.addAllValues = function(key, values) {
goog.array.forEach(values, function(v) {
this.add(key, v);
}, this);
};
/**
* Adds the contents of the given map/multimap to this multimap.
* @param {!(goog.labs.structs.Map|goog.labs.structs.Multimap)} map The
* map to add.
*/
goog.labs.structs.Multimap.prototype.addAllFromMultimap = function(map) {
goog.array.forEach(map.getEntries(), function(entry) {
this.add(entry[0], entry[1]);
}, this);
};
/**
* Replaces all the values for the given key with the given values.
* @param {string} key The key whose values are to be replaced.
* @param {!Array<*>} values The new values. If empty, this is
* equivalent to {@code removaAll(key)}.
*/
goog.labs.structs.Multimap.prototype.replaceValues = function(key, values) {
this.removeAll(key);
this.addAllValues(key, values);
};
/**
* Gets the values correspond to the given key.
* @param {string} key The key to retrieve.
* @return {!Array<*>} An array of values corresponding to the given
* key. May be empty. Note that the ordering of values are not
* guaranteed to be consistent.
*/
goog.labs.structs.Multimap.prototype.get = function(key) {
var values = /** @type {Array<*>} */ (this.map_.get(key));
return values ? goog.array.clone(values) : [];
};
/**
* Removes a single occurrence of (key, value) pair.
* @param {string} key The key to remove.
* @param {*} value The value to remove.
* @return {boolean} Whether any matching (key, value) pair is removed.
*/
goog.labs.structs.Multimap.prototype.remove = function(key, value) {
var values = /** @type {Array<*>} */ (this.map_.get(key));
if (!values) {
return false;
}
var removed = goog.array.removeIf(values, function(v) {
return goog.labs.object.is(value, v);
});
if (removed) {
this.count_--;
if (values.length == 0) {
this.map_.remove(key);
}
}
return removed;
};
/**
* Removes all values corresponding to the given key.
* @param {string} key The key whose values are to be removed.
* @return {boolean} Whether any value is removed.
*/
goog.labs.structs.Multimap.prototype.removeAll = function(key) {
// We have to first retrieve the values from the backing map because
// we need to keep track of count (and correctly calculates the
// return value). values may be undefined.
var values = this.map_.get(key);
if (this.map_.remove(key)) {
this.count_ -= values.length;
return true;
}
return false;
};
/**
* @return {boolean} Whether the multimap is empty.
*/
goog.labs.structs.Multimap.prototype.isEmpty = function() {
return !this.count_;
};
/**
* @return {number} The count of (key, value) pairs in the map.
*/
goog.labs.structs.Multimap.prototype.getCount = function() {
return this.count_;
};
/**
* @param {string} key The key to check.
* @param {*} value The value to check.
* @return {boolean} Whether the (key, value) pair exists in the multimap.
*/
goog.labs.structs.Multimap.prototype.containsEntry = function(key, value) {
var values = /** @type {Array<*>} */ (this.map_.get(key));
if (!values) {
return false;
}
var index = goog.array.findIndex(values, function(v) {
return goog.labs.object.is(v, value);
});
return index >= 0;
};
/**
* @param {string} key The key to check.
* @return {boolean} Whether the multimap contains at least one (key,
* value) pair with the given key.
*/
goog.labs.structs.Multimap.prototype.containsKey = function(key) {
return this.map_.containsKey(key);
};
/**
* @param {*} value The value to check.
* @return {boolean} Whether the multimap contains at least one (key,
* value) pair with the given value.
*/
goog.labs.structs.Multimap.prototype.containsValue = function(value) {
return goog.array.some(this.map_.getValues(),
function(values) {
return goog.array.some(/** @type {Array<?>} */ (values), function(v) {
return goog.labs.object.is(v, value);
});
});
};
/**
* @return {!Array<string>} An array of unique keys.
*/
goog.labs.structs.Multimap.prototype.getKeys = function() {
return this.map_.getKeys();
};
/**
* @return {!Array<*>} An array of values. There may be duplicates.
*/
goog.labs.structs.Multimap.prototype.getValues = function() {
return goog.array.flatten(this.map_.getValues());
};
/**
* @return {!Array<!Array<?>>} An array of entries. Each entry is of the
* form [key, value].
*/
goog.labs.structs.Multimap.prototype.getEntries = function() {
var keys = this.getKeys();
var entries = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var values = this.get(key);
for (var j = 0; j < values.length; j++) {
entries.push([key, values[j]]);
}
}
return entries;
};
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2012 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
Author: chrishenry@google.com (Chris Henry)
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.labs.structs.Multimap
</title>
<script src="../../base.js">
</script>
<script>
goog.require('goog.labs.structs.MultimapTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,328 @@
// 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.
goog.provide('goog.labs.structs.MultimapTest');
goog.setTestOnly('goog.labs.structs.MultimapTest');
goog.require('goog.labs.structs.Map');
goog.require('goog.labs.structs.Multimap');
goog.require('goog.testing.jsunit');
var map;
function setUp() {
map = new goog.labs.structs.Multimap();
}
function testGetCountWithEmptyMultimap() {
assertEquals(0, map.getCount());
assertTrue(map.isEmpty());
}
function testClone() {
map.add('k', 'v');
map.addAllValues('k2', ['v', 'v1', 'v2']);
var map2 = map.clone();
assertSameElements(['v'], map.get('k'));
assertSameElements(['v', 'v1', 'v2'], map.get('k2'));
}
function testAdd() {
map.add('key', 'v');
assertEquals(1, map.getCount());
map.add('key', 'v2');
assertEquals(2, map.getCount());
map.add('key', 'v3');
assertEquals(3, map.getCount());
var values = map.get('key');
assertEquals(3, values.length);
assertContains('v', values);
assertContains('v2', values);
assertContains('v3', values);
}
function testAddValues() {
map.addAllValues('key', ['v', 'v2', 'v3']);
assertSameElements(['v', 'v2', 'v3'], map.get('key'));
map.add('key2', 'a');
map.addAllValues('key2', ['v', 'v2', 'v3']);
assertSameElements(['a', 'v', 'v2', 'v3'], map.get('key2'));
}
function testAddAllWithMultimap() {
map.add('k', 'v');
map.addAllValues('k2', ['v', 'v1', 'v2']);
var map2 = new goog.labs.structs.Multimap();
map2.add('k2', 'v');
map2.addAllValues('k3', ['a', 'a1', 'a2']);
map.addAllFromMultimap(map2);
assertSameElements(['v'], map.get('k'));
assertSameElements(['v', 'v1', 'v2', 'v'], map.get('k2'));
assertSameElements(['a', 'a1', 'a2'], map.get('k3'));
}
function testAddAllWithMap() {
map.add('k', 'v');
map.addAllValues('k2', ['v', 'v1', 'v2']);
var map2 = new goog.labs.structs.Map();
map2.set('k2', 'v');
map2.set('k3', 'a');
map.addAllFromMultimap(map2);
assertSameElements(['v'], map.get('k'));
assertSameElements(['v', 'v1', 'v2', 'v'], map.get('k2'));
assertSameElements(['a'], map.get('k3'));
}
function testReplaceValues() {
map.add('key', 'v');
map.add('key', 'v2');
map.replaceValues('key', [0, 1, 2]);
assertSameElements([0, 1, 2], map.get('key'));
assertEquals(3, map.getCount());
map.replaceValues('key', ['v']);
assertSameElements(['v'], map.get('key'));
assertEquals(1, map.getCount());
map.replaceValues('key', []);
assertSameElements([], map.get('key'));
assertEquals(0, map.getCount());
}
function testRemove() {
map.add('key', 'v');
map.add('key', 'v2');
map.add('key', 'v3');
assertTrue(map.remove('key', 'v'));
var values = map.get('key');
assertEquals(2, map.getCount());
assertEquals(2, values.length);
assertContains('v2', values);
assertContains('v3', values);
assertFalse(map.remove('key', 'v'));
assertTrue(map.remove('key', 'v2'));
values = map.get('key');
assertEquals(1, map.getCount());
assertEquals(1, values.length);
assertContains('v3', values);
assertFalse(map.remove('key', 'v2'));
assertTrue(map.remove('key', 'v3'));
map.remove('key', 'v3');
assertTrue(map.isEmpty());
assertEquals(0, map.get('key').length);
assertFalse(map.remove('key', 'v2'));
}
function testRemoveWithNaN() {
map.add('key', NaN);
map.add('key', NaN);
assertTrue(map.remove('key', NaN));
var values = map.get('key');
assertEquals(1, values.length);
assertTrue(isNaN(values[0]));
assertTrue(map.remove('key', NaN));
assertEquals(0, map.get('key').length);
assertFalse(map.remove('key', NaN));
}
function testRemoveWithNegativeZero() {
map.add('key', 0);
map.add('key', -0);
assertTrue(map.remove('key', -0));
var values = map.get('key');
assertEquals(1, values.length);
assertTrue(1 / values[0] === 1 / 0);
assertFalse(map.remove('key', -0));
map.add('key', -0);
assertTrue(map.remove('key', 0));
var values = map.get('key');
assertEquals(1, values.length);
assertTrue(1 / values[0] === 1 / -0);
assertFalse(map.remove('key', 0));
assertTrue(map.remove('key', -0));
assertEquals(0, map.get('key').length);
}
function testRemoveAll() {
map.add('key', 'v');
map.add('key', 'v2');
map.add('key', 'v3');
map.add('key', 'v4');
map.add('key2', 'v');
assertTrue(map.removeAll('key'));
assertSameElements([], map.get('key'));
assertSameElements(['v'], map.get('key2'));
assertFalse(map.removeAll('key'));
assertEquals(1, map.getCount());
assertTrue(map.removeAll('key2'));
assertSameElements([], map.get('key2'));
assertFalse(map.removeAll('key2'));
assertTrue(map.isEmpty());
}
function testAddWithDuplicateValue() {
map.add('key', 'v');
map.add('key', 'v');
map.add('key', 'v');
assertArrayEquals(['v', 'v', 'v'], map.get('key'));
}
function testContainsEntry() {
assertFalse(map.containsEntry('k', 'v'));
assertFalse(map.containsEntry('k', 'v2'));
assertFalse(map.containsEntry('k2', 'v'));
map.add('k', 'v');
assertTrue(map.containsEntry('k', 'v'));
assertFalse(map.containsEntry('k', 'v2'));
assertFalse(map.containsEntry('k2', 'v'));
map.add('k', 'v2');
assertTrue(map.containsEntry('k', 'v'));
assertTrue(map.containsEntry('k', 'v2'));
assertFalse(map.containsEntry('k2', 'v'));
map.add('k2', 'v');
assertTrue(map.containsEntry('k', 'v'));
assertTrue(map.containsEntry('k', 'v2'));
assertTrue(map.containsEntry('k2', 'v'));
}
function testContainsKey() {
assertFalse(map.containsKey('k'));
assertFalse(map.containsKey('k2'));
map.add('k', 'v');
assertTrue(map.containsKey('k'));
map.add('k2', 'v');
assertTrue(map.containsKey('k2'));
map.remove('k', 'v');
assertFalse(map.containsKey('k'));
map.remove('k2', 'v');
assertFalse(map.containsKey('k2'));
}
function testContainsValue() {
assertFalse(map.containsValue('v'));
assertFalse(map.containsValue('v2'));
map.add('key', 'v');
assertTrue(map.containsValue('v'));
map.add('key', 'v2');
assertTrue(map.containsValue('v2'));
}
function testGetEntries() {
map.add('key', 'v');
map.add('key', 'v2');
map.add('key2', 'v3');
var entries = map.getEntries();
assertEquals(3, entries.length);
assertContainsEntry(['key', 'v'], entries);
assertContainsEntry(['key', 'v2'], entries);
assertContainsEntry(['key2', 'v3'], entries);
}
function testGetKeys() {
map.add('key', 'v');
map.add('key', 'v2');
map.add('key2', 'v3');
map.add('key3', 'v4');
map.removeAll('key3');
assertSameElements(['key', 'key2'], map.getKeys());
}
function testGetKeys() {
map.add('key', 'v');
map.add('key', 'v2');
map.add('key2', 'v2');
map.add('key3', 'v4');
map.removeAll('key3');
assertSameElements(['v', 'v2', 'v2'], map.getValues());
}
function testGetReturnsDefensiveCopyOfUnderlyingData() {
map.add('key', 'v');
map.add('key', 'v2');
map.add('key', 'v3');
var values = map.get('key');
values.push('v4');
assertFalse(map.containsEntry('key', 'v4'));
}
function testClear() {
map.add('key', 'v');
map.add('key', 'v2');
map.add('key2', 'v3');
map.clear();
assertTrue(map.isEmpty());
assertSameElements([], map.getEntries());
}
function assertContainsEntry(entry, entryList) {
for (var i = 0; i < entryList.length; ++i) {
if (entry[0] == entryList[i][0] && entry[1] === entryList[i][1]) {
return;
}
}
fail('Did not find entry: ' + entry + ' in: ' + entryList);
}