Bug 955366 - Use Maps and Sets in IRC code, create a NormalizedMap object. r=aleth,florian
authorPatrick Cloke <clokep@gmail.com>
Wed, 07 May 2014 20:15:45 -0400
changeset 16178 fe1feb4737361f3e6b13543959d0a8073a66d7a3
parent 16177 75cacce22b79a016d8f218e444f55594e8d2fb87
child 16179 c6b03e55cb0ffe097bb33e8452ca9b32fe9f7767
push id10115
push userclokep@gmail.com
push dateThu, 08 May 2014 14:05:31 +0000
treeherdercomm-central@da34b03c7e57 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaleth, florian
bugs955366
Bug 955366 - Use Maps and Sets in IRC code, create a NormalizedMap object. r=aleth,florian
chat/modules/NormalizedMap.jsm
chat/modules/moz.build
chat/modules/test/test_NormalizedMap.js
chat/modules/test/xpcshell.ini
new file mode 100644
--- /dev/null
+++ b/chat/modules/NormalizedMap.jsm
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const EXPORTED_SYMBOLS = ["NormalizedMap"];
+
+/*
+ * A Map that automatically normalizes keys before accessing the values.
+ *
+ * The constructor takes two parameters:
+ *  aNormalize:   A function which takes a string and returns the "normalized"
+ *                version of it.
+ *  aIterable:    A iterable to prefill the map with, keys will be normalized.
+ *
+ * Returns a Map object that will automatically run aNormalize on any operations
+ * involving keys.
+ *
+ * This implementation should be able to be significantly simplified once bug
+ * 838540 is fixed and native inheritance of a JavaScript built-in is possible.
+ */
+function NormalizedMap(aNormalize, aIterable = []) {
+  if (typeof(aNormalize) != "function")
+    throw "NormalizedMap must have a normalize function!";
+  this._normalize = aNormalize;
+  // Create the wrapped Map; use the provided iterable after normalizing the
+  // keys.
+  this._map = new Map([[aNormalize(key), val] for ([key, val] of aIterable)]);
+}
+NormalizedMap.prototype = {
+  _map: null,
+  // The function to apply to all keys.
+  _normalize: null,
+
+  // Anything that accepts a key as an input needs to be manually overridden.
+  delete: function(aKey) this._map.delete(this._normalize(aKey)),
+  get: function(aKey) this._map.get(this._normalize(aKey)),
+  has: function(aKey) this._map.has(this._normalize(aKey)),
+  set: function(aKey, aValue) this._map.set(this._normalize(aKey), aValue),
+
+  // Properties must be manually forwarded.
+  get size() this._map.size,
+
+  // Here's where the magic happens. If a method is called that isn't defined
+  // here, just pass it to the internal _map object.
+  __noSuchMethod__: function(aId, aArgs) this._map[aId].apply(this._map, aArgs)
+};
--- a/chat/modules/moz.build
+++ b/chat/modules/moz.build
@@ -9,16 +9,17 @@ EXTRA_JS_MODULES += [
     'ArrayBufferUtils.jsm',
     'imContentSink.jsm',
     'imServices.jsm',
     'imSmileys.jsm',
     'imStatusUtils.jsm',
     'imThemes.jsm',
     'imXPCOMUtils.jsm',
     'jsProtoHelper.jsm',
+    'NormalizedMap.jsm',
     'socket.jsm',
 ]
 
 EXTRA_PP_JS_MODULES += [
     'hiddenWindow.jsm',
     'imTextboxUtils.jsm',
 ]
 
new file mode 100644
--- /dev/null
+++ b/chat/modules/test/test_NormalizedMap.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource:///modules/NormalizedMap.jsm");
+
+function test_setter_getter() {
+  let m = new NormalizedMap(aStr => aStr.toLowerCase());
+  m.set("foo", "bar");
+  m.set("BaZ", "blah");
+  do_check_eq(m.get("FOO"), "bar");
+
+  let keys = [v for (v of m.keys())];
+  do_check_eq(keys[0], "foo");
+  do_check_eq(keys[1], "baz");
+
+  let values = [v for (v of m.values())];
+  do_check_eq(values[0], "bar");
+  do_check_eq(values[1], "blah");
+
+  do_check_eq(m.size, 2);
+
+  run_next_test();
+}
+
+function test_constructor() {
+  let k = new NormalizedMap(aStr => aStr.toLowerCase(), [["A", 2], ["b", 3]]);
+  do_check_eq(k.get("b"), 3);
+  do_check_eq(k.get("a"), 2);
+  do_check_eq(k.get("B"), 3);
+  do_check_eq(k.get("A"), 2);
+
+  run_next_test();
+}
+
+function test_iterator() {
+  let k = new NormalizedMap(aStr => aStr.toLowerCase());
+  k.set("FoO", "bar");
+
+  for (let [key, value] of k) {
+    do_check_eq(key, "foo");
+    do_check_eq(value, "bar");
+  }
+
+  run_next_test();
+}
+
+function run_test() {
+  add_test(test_setter_getter);
+  add_test(test_constructor);
+  add_test(test_iterator);
+
+  run_next_test();
+}
--- a/chat/modules/test/xpcshell.ini
+++ b/chat/modules/test/xpcshell.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 head =
 tail =
 
 [test_ArrayBufferUtils.js]
 [test_filtering.js]
+[test_NormalizedMap.js]