зеркало из https://github.com/mozilla/pjs.git
Bug 621204 - Add Dict.jsm to toolkit, r=dtownsend
This commit is contained in:
Родитель
ed0ca76d07
Коммит
daa59234ea
|
@ -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();
|
||||
}
|
Загрузка…
Ссылка в новой задаче