1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/modules/Dict.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,249 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +this.EXPORTED_SYMBOLS = ["Dict"]; 1.11 + 1.12 +/** 1.13 + * Transforms a given key into a property name guaranteed not to collide with 1.14 + * any built-ins. 1.15 + */ 1.16 +function convert(aKey) { 1.17 + return ":" + aKey; 1.18 +} 1.19 + 1.20 +/** 1.21 + * Transforms a property into a key suitable for providing to the outside world. 1.22 + */ 1.23 +function unconvert(aProp) { 1.24 + return aProp.substr(1); 1.25 +} 1.26 + 1.27 +/** 1.28 + * A dictionary of strings to arbitrary JS objects. This should be used whenever 1.29 + * the keys are potentially arbitrary, to avoid collisions with built-in 1.30 + * properties. 1.31 + * 1.32 + * @param aInitial An object containing the initial keys and values of this 1.33 + * dictionary. Only the "own" enumerable properties of the 1.34 + * object are considered. 1.35 + * If |aInitial| is a string, it is assumed to be JSON and parsed into an object. 1.36 + */ 1.37 +this.Dict = function Dict(aInitial) { 1.38 + if (aInitial === undefined) 1.39 + aInitial = {}; 1.40 + if (typeof aInitial == "string") 1.41 + aInitial = JSON.parse(aInitial); 1.42 + var items = {}, count = 0; 1.43 + // That we don't look up the prototype chain is guaranteed by Iterator. 1.44 + for (var [key, val] in Iterator(aInitial)) { 1.45 + items[convert(key)] = val; 1.46 + count++; 1.47 + } 1.48 + this._state = {count: count, items: items}; 1.49 + return Object.freeze(this); 1.50 +} 1.51 + 1.52 +Dict.prototype = Object.freeze({ 1.53 + /** 1.54 + * The number of items in the dictionary. 1.55 + */ 1.56 + get count() { 1.57 + return this._state.count; 1.58 + }, 1.59 + 1.60 + /** 1.61 + * Gets the value for a key from the dictionary. If the key is not a string, 1.62 + * it will be converted to a string before the lookup happens. 1.63 + * 1.64 + * @param aKey The key to look up 1.65 + * @param [aDefault] An optional default value to return if the key is not 1.66 + * present. Defaults to |undefined|. 1.67 + * @returns The item, or aDefault if it isn't found. 1.68 + */ 1.69 + get: function Dict_get(aKey, aDefault) { 1.70 + var prop = convert(aKey); 1.71 + var items = this._state.items; 1.72 + return items.hasOwnProperty(prop) ? items[prop] : aDefault; 1.73 + }, 1.74 + 1.75 + /** 1.76 + * Sets the value for a key in the dictionary. If the key is a not a string, 1.77 + * it will be converted to a string before the set happens. 1.78 + */ 1.79 + set: function Dict_set(aKey, aValue) { 1.80 + var prop = convert(aKey); 1.81 + var items = this._state.items; 1.82 + if (!items.hasOwnProperty(prop)) 1.83 + this._state.count++; 1.84 + items[prop] = aValue; 1.85 + }, 1.86 + 1.87 + /** 1.88 + * Sets a lazy getter function for a key's value. If the key is a not a string, 1.89 + * it will be converted to a string before the set happens. 1.90 + * @param aKey 1.91 + * The key to set 1.92 + * @param aThunk 1.93 + * A getter function to be called the first time the value for aKey is 1.94 + * retrieved. It is guaranteed that aThunk wouldn't be called more 1.95 + * than once. Note that the key value may be retrieved either 1.96 + * directly, by |get|, or indirectly, by |listvalues| or by iterating 1.97 + * |values|. For the later, the value is only retrieved if and when 1.98 + * the iterator gets to the value in question. Also note that calling 1.99 + * |has| for a lazy-key does not invoke aThunk. 1.100 + * 1.101 + * @note No context is provided for aThunk when it's invoked. 1.102 + * Use Function.bind if you wish to run it in a certain context. 1.103 + */ 1.104 + setAsLazyGetter: function Dict_setAsLazyGetter(aKey, aThunk) { 1.105 + let prop = convert(aKey); 1.106 + let items = this._state.items; 1.107 + if (!items.hasOwnProperty(prop)) 1.108 + this._state.count++; 1.109 + 1.110 + Object.defineProperty(items, prop, { 1.111 + get: function() { 1.112 + delete items[prop]; 1.113 + return items[prop] = aThunk(); 1.114 + }, 1.115 + configurable: true, 1.116 + enumerable: true 1.117 + }); 1.118 + }, 1.119 + 1.120 + /** 1.121 + * Returns whether a key is set as a lazy getter. This returns 1.122 + * true only if the getter function was not called already. 1.123 + * @param aKey 1.124 + * The key to look up. 1.125 + * @returns whether aKey is set as a lazy getter. 1.126 + */ 1.127 + isLazyGetter: function Dict_isLazyGetter(aKey) { 1.128 + let descriptor = Object.getOwnPropertyDescriptor(this._state.items, 1.129 + convert(aKey)); 1.130 + return (descriptor && descriptor.get != null); 1.131 + }, 1.132 + 1.133 + /** 1.134 + * Returns whether a key is in the dictionary. If the key is a not a string, 1.135 + * it will be converted to a string before the lookup happens. 1.136 + */ 1.137 + has: function Dict_has(aKey) { 1.138 + return (this._state.items.hasOwnProperty(convert(aKey))); 1.139 + }, 1.140 + 1.141 + /** 1.142 + * Deletes a key from the dictionary. If the key is a not a string, it will be 1.143 + * converted to a string before the delete happens. 1.144 + * 1.145 + * @returns true if the key was found, false if it wasn't. 1.146 + */ 1.147 + del: function Dict_del(aKey) { 1.148 + var prop = convert(aKey); 1.149 + if (this._state.items.hasOwnProperty(prop)) { 1.150 + delete this._state.items[prop]; 1.151 + this._state.count--; 1.152 + return true; 1.153 + } 1.154 + return false; 1.155 + }, 1.156 + 1.157 + /** 1.158 + * Returns a shallow copy of this dictionary. 1.159 + */ 1.160 + copy: function Dict_copy() { 1.161 + var newItems = {}; 1.162 + for (var [key, val] in this.items) 1.163 + newItems[key] = val; 1.164 + return new Dict(newItems); 1.165 + }, 1.166 + 1.167 + /* 1.168 + * List and iterator functions 1.169 + * 1.170 + * No guarantees whatsoever are made about the order of elements. 1.171 + */ 1.172 + 1.173 + /** 1.174 + * Returns a list of all the keys in the dictionary in an arbitrary order. 1.175 + */ 1.176 + listkeys: function Dict_listkeys() { 1.177 + return [unconvert(k) for (k in this._state.items)]; 1.178 + }, 1.179 + 1.180 + /** 1.181 + * Returns a list of all the values in the dictionary in an arbitrary order. 1.182 + */ 1.183 + listvalues: function Dict_listvalues() { 1.184 + var items = this._state.items; 1.185 + return [items[k] for (k in items)]; 1.186 + }, 1.187 + 1.188 + /** 1.189 + * Returns a list of all the items in the dictionary as key-value pairs 1.190 + * in an arbitrary order. 1.191 + */ 1.192 + listitems: function Dict_listitems() { 1.193 + var items = this._state.items; 1.194 + return [[unconvert(k), items[k]] for (k in items)]; 1.195 + }, 1.196 + 1.197 + /** 1.198 + * Returns an iterator over all the keys in the dictionary in an arbitrary 1.199 + * order. No guarantees are made about what happens if the dictionary is 1.200 + * mutated during iteration. 1.201 + */ 1.202 + get keys() { 1.203 + // If we don't capture this._state.items here then the this-binding will be 1.204 + // incorrect when the generator is executed 1.205 + var items = this._state.items; 1.206 + return (unconvert(k) for (k in items)); 1.207 + }, 1.208 + 1.209 + /** 1.210 + * Returns an iterator over all the values in the dictionary in an arbitrary 1.211 + * order. No guarantees are made about what happens if the dictionary is 1.212 + * mutated during iteration. 1.213 + */ 1.214 + get values() { 1.215 + // If we don't capture this._state.items here then the this-binding will be 1.216 + // incorrect when the generator is executed 1.217 + var items = this._state.items; 1.218 + return (items[k] for (k in items)); 1.219 + }, 1.220 + 1.221 + /** 1.222 + * Returns an iterator over all the items in the dictionary as key-value pairs 1.223 + * in an arbitrary order. No guarantees are made about what happens if the 1.224 + * dictionary is mutated during iteration. 1.225 + */ 1.226 + get items() { 1.227 + // If we don't capture this._state.items here then the this-binding will be 1.228 + // incorrect when the generator is executed 1.229 + var items = this._state.items; 1.230 + return ([unconvert(k), items[k]] for (k in items)); 1.231 + }, 1.232 + 1.233 + /** 1.234 + * Returns a String representation of this dictionary. 1.235 + */ 1.236 + toString: function Dict_toString() { 1.237 + return "{" + 1.238 + [(key + ": " + val) for ([key, val] in this.items)].join(", ") + 1.239 + "}"; 1.240 + }, 1.241 + 1.242 + /** 1.243 + * Returns a JSON representation of this dictionary. 1.244 + */ 1.245 + toJSON: function Dict_toJSON() { 1.246 + let obj = {}; 1.247 + for (let [key, item] of Iterator(this._state.items)) { 1.248 + obj[unconvert(key)] = item; 1.249 + } 1.250 + return JSON.stringify(obj); 1.251 + }, 1.252 +});