Bug 621204 - Add Dict.jsm to toolkit, r=dtownsend

This commit is contained in:
Siddarth Agarwal 2011-03-23 15:13:41 -04:00
Родитель ed0ca76d07
Коммит daa59234ea
3 изменённых файлов: 449 добавлений и 0 удалений

222
toolkit/content/Dict.jsm Normal file
Просмотреть файл

@ -0,0 +1,222 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is dict.js.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Siddharth Agarwal <sid.bugzilla@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
"use strict";
var EXPORTED_SYMBOLS = ["Dict"];
/**
* Transforms a given key into a property name guaranteed not to collide with
* any built-ins.
*/
function convert(aKey) {
return ":" + aKey;
}
/**
* Transforms a property into a key suitable for providing to the outside world.
*/
function unconvert(aProp) {
return aProp.substr(1);
}
/**
* A dictionary of strings to arbitrary JS objects. This should be used whenever
* the keys are potentially arbitrary, to avoid collisions with built-in
* properties.
*
* @param aInitial An object containing the initial keys and values of this
* dictionary. Only the "own" enumerable properties of the
* object are considered.
*/
function Dict(aInitial) {
if (aInitial === undefined)
aInitial = {};
var items = {}, count = 0;
// That we don't look up the prototype chain is guaranteed by Iterator.
for (var [key, val] in Iterator(aInitial)) {
items[convert(key)] = val;
count++;
}
this._state = {count: count, items: items};
return Object.freeze(this);
}
Dict.prototype = Object.freeze({
/**
* The number of items in the dictionary.
*/
get count() {
return this._state.count;
},
/**
* Gets the value for a key from the dictionary. If the key is not a string,
* it will be converted to a string before the lookup happens.
*
* @param aKey The key to look up
* @param [aDefault] An optional default value to return if the key is not
* present. Defaults to |undefined|.
* @returns The item, or aDefault if it isn't found.
*/
get: function Dict_get(aKey, aDefault) {
var prop = convert(aKey);
var items = this._state.items;
return items.hasOwnProperty(prop) ? items[prop] : aDefault;
},
/**
* Sets the value for a key in the dictionary. If the key is a not a string,
* it will be converted to a string before the set happens.
*/
set: function Dict_set(aKey, aValue) {
var prop = convert(aKey);
var items = this._state.items;
if (!items.hasOwnProperty(prop))
this._state.count++;
items[prop] = aValue;
},
/**
* Returns whether a key is in the dictionary. If the key is a not a string,
* it will be converted to a string before the lookup happens.
*/
has: function Dict_has(aKey) {
return (this._state.items.hasOwnProperty(convert(aKey)));
},
/**
* Deletes a key from the dictionary. If the key is a not a string, it will be
* converted to a string before the delete happens.
*
* @returns true if the key was found, false if it wasn't.
*/
del: function Dict_del(aKey) {
var prop = convert(aKey);
if (this._state.items.hasOwnProperty(prop)) {
delete this._state.items[prop];
this._state.count--;
return true;
}
return false;
},
/**
* Returns a shallow copy of this dictionary.
*/
copy: function Dict_copy() {
var newItems = {};
for (var [key, val] in this.items)
newItems[key] = val;
return new Dict(newItems);
},
/*
* List and iterator functions
*
* No guarantees whatsoever are made about the order of elements.
*/
/**
* Returns a list of all the keys in the dictionary in an arbitrary order.
*/
listkeys: function Dict_listkeys() {
return [unconvert(k) for (k in this._state.items)];
},
/**
* Returns a list of all the values in the dictionary in an arbitrary order.
*/
listvalues: function Dict_listvalues() {
var items = this._state.items;
return [items[k] for (k in items)];
},
/**
* Returns a list of all the items in the dictionary as key-value pairs
* in an arbitrary order.
*/
listitems: function Dict_listitems() {
var items = this._state.items;
return [[unconvert(k), items[k]] for (k in items)];
},
/**
* Returns an iterator over all the keys in the dictionary in an arbitrary
* order. No guarantees are made about what happens if the dictionary is
* mutated during iteration.
*/
get keys() {
// If we don't capture this._state.items here then the this-binding will be
// incorrect when the generator is executed
var items = this._state.items;
return (unconvert(k) for (k in items));
},
/**
* Returns an iterator over all the values in the dictionary in an arbitrary
* order. No guarantees are made about what happens if the dictionary is
* mutated during iteration.
*/
get values() {
// If we don't capture this._state.items here then the this-binding will be
// incorrect when the generator is executed
var items = this._state.items;
return (items[k] for (k in items));
},
/**
* Returns an iterator over all the items in the dictionary as key-value pairs
* in an arbitrary order. No guarantees are made about what happens if the
* dictionary is mutated during iteration.
*/
get items() {
// If we don't capture this._state.items here then the this-binding will be
// incorrect when the generator is executed
var items = this._state.items;
return ([unconvert(k), items[k]] for (k in items));
},
/**
* Returns a string representation of this dictionary.
*/
toString: function Dict_toString() {
return "{" +
[(key + ": " + val) for ([key, val] in this.items)].join(", ") +
"}";
},
});

Просмотреть файл

@ -85,6 +85,7 @@ EXTRA_JS_MODULES = \
Geometry.jsm \
InlineSpellChecker.jsm \
PopupNotifications.jsm \
Dict.jsm \
$(NULL)
EXTRA_PP_JS_MODULES = \

Просмотреть файл

@ -0,0 +1,226 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Siddharth Agarwal <sid.bugzilla@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
Components.utils.import("resource://gre/modules/Dict.jsm");
/**
* Test that a few basic get, set, has and del operations work.
*/
function test_get_set_has_del() {
let dict = new Dict({foo: "bar"});
dict.set("baz", 200);
do_check_eq(dict.get("foo"), "bar");
do_check_eq(dict.get("baz"), 200);
do_check_true(dict.has("foo"));
do_check_true(dict.has("baz"));
// Now delete the entries
do_check_true(dict.del("foo"));
do_check_true(dict.del("baz"));
do_check_false(dict.has("foo"));
do_check_false(dict.has("baz"));
// and make sure del returns false
do_check_false(dict.del("foo"));
do_check_false(dict.del("baz"));
}
/**
* Test that the second parameter of get (default value) works.
*/
function test_get_default() {
let dict = new Dict();
do_check_true(dict.get("foo") === undefined);
do_check_eq(dict.get("foo", "bar"), "bar");
}
/**
* Test that there are no collisions with builtins.
*/
function test_collisions_with_builtins() {
let dict = new Dict();
// First check that a new dictionary doesn't already have builtins.
do_check_false(dict.has("toString"));
do_check_false(dict.has("watch"));
do_check_false(dict.has("__proto__"));
// Add elements in an attempt to collide with builtins.
dict.set("toString", "toString");
dict.set("watch", "watch");
// This is a little evil. We set __proto__ to an object to try to make it look
// up the prototype chain.
dict.set("__proto__", {prototest: "prototest"});
// Now check that the entries exist.
do_check_true(dict.has("toString"));
do_check_true(dict.has("watch"));
do_check_true(dict.has("__proto__"));
// ...and that we aren't looking up the prototype chain.
do_check_false(dict.has("prototest"));
}
/**
* Test that the "count" property works as expected.
*/
function test_count() {
let dict = new Dict({foo: "bar"});
do_check_eq(dict.count, 1);
dict.set("baz", "quux");
do_check_eq(dict.count, 2);
// This shouldn't change the count
dict.set("baz", "quux2");
do_check_eq(dict.count, 2);
do_check_true(dict.del("baz"));
do_check_eq(dict.count, 1);
// This shouldn't change the count either
do_check_false(dict.del("not"));
do_check_eq(dict.count, 1);
do_check_true(dict.del("foo"));
do_check_eq(dict.count, 0);
}
/**
* Test that the copy function works as expected.
*/
function test_copy() {
let obj = {};
let dict1 = new Dict({foo: "bar", baz: obj});
let dict2 = dict1.copy();
do_check_eq(dict2.get("foo"), "bar");
do_check_eq(dict2.get("baz"), obj);
// Make sure the two update independent of each other.
dict1.del("foo");
do_check_false(dict1.has("foo"));
do_check_true(dict2.has("foo"));
dict2.set("test", 400);
do_check_true(dict2.has("test"));
do_check_false(dict1.has("test"));
// Check that the copy is shallow and not deep.
dict1.get("baz").prop = "proptest";
do_check_eq(dict2.get("baz").prop, "proptest");
}
// This is used by both test_listers and test_iterators.
function _check_lists(keys, values, items) {
do_check_eq(keys.length, 2);
do_check_true(keys.indexOf("x") != -1);
do_check_true(keys.indexOf("y") != -1);
do_check_eq(values.length, 2);
do_check_true(values.indexOf("a") != -1);
do_check_true(values.indexOf("b") != -1);
// This is a little more tricky -- we need to check that one of the two
// entries is ["x", "a"] and the other is ["y", "b"].
do_check_eq(items.length, 2);
do_check_eq(items[0].length, 2);
do_check_eq(items[1].length, 2);
let ix = (items[0][0] == "x") ? 0 : 1;
let iy = (ix == 0) ? 1 : 0;
do_check_eq(items[ix][0], "x");
do_check_eq(items[ix][1], "a");
do_check_eq(items[iy][0], "y");
do_check_eq(items[iy][1], "b");
}
/**
* Test the list functions.
*/
function test_listers() {
let dict = new Dict({"x": "a", "y": "b"});
let keys = dict.listkeys();
let values = dict.listvalues();
let items = dict.listitems();
_check_lists(keys, values, items);
}
/**
* Test the iterator functions.
*/
function test_iterators() {
let dict = new Dict({"x": "a", "y": "b"});
// Convert the generators to lists
let keys = [x for (x in dict.keys)];
let values = [x for (x in dict.values)];
let items = [x for (x in dict.items)];
_check_lists(keys, values, items);
}
/**
* Test that setting a property throws an exception in strict mode.
*/
function test_set_property_strict() {
"use strict";
var dict = new Dict();
var thrown = false;
try {
dict.foo = "bar";
}
catch (ex) {
thrown = true;
}
do_check_true(thrown);
}
/**
* Test that setting a property has no effect in non-strict mode.
*/
function test_set_property_non_strict() {
let dict = new Dict();
dict.foo = "bar";
do_check_false("foo" in dict);
let realget = dict.get;
dict.get = "baz";
do_check_eq(dict.get, realget);
}
var tests = [
test_get_set_has_del,
test_get_default,
test_collisions_with_builtins,
test_count,
test_copy,
test_listers,
test_iterators,
test_set_property_strict,
test_set_property_non_strict,
];
function run_test() {
for (let [, test] in Iterator(tests))
test();
}