|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 this.EXPORTED_SYMBOLS = ["Dict"]; |
|
8 |
|
9 /** |
|
10 * Transforms a given key into a property name guaranteed not to collide with |
|
11 * any built-ins. |
|
12 */ |
|
13 function convert(aKey) { |
|
14 return ":" + aKey; |
|
15 } |
|
16 |
|
17 /** |
|
18 * Transforms a property into a key suitable for providing to the outside world. |
|
19 */ |
|
20 function unconvert(aProp) { |
|
21 return aProp.substr(1); |
|
22 } |
|
23 |
|
24 /** |
|
25 * A dictionary of strings to arbitrary JS objects. This should be used whenever |
|
26 * the keys are potentially arbitrary, to avoid collisions with built-in |
|
27 * properties. |
|
28 * |
|
29 * @param aInitial An object containing the initial keys and values of this |
|
30 * dictionary. Only the "own" enumerable properties of the |
|
31 * object are considered. |
|
32 * If |aInitial| is a string, it is assumed to be JSON and parsed into an object. |
|
33 */ |
|
34 this.Dict = function Dict(aInitial) { |
|
35 if (aInitial === undefined) |
|
36 aInitial = {}; |
|
37 if (typeof aInitial == "string") |
|
38 aInitial = JSON.parse(aInitial); |
|
39 var items = {}, count = 0; |
|
40 // That we don't look up the prototype chain is guaranteed by Iterator. |
|
41 for (var [key, val] in Iterator(aInitial)) { |
|
42 items[convert(key)] = val; |
|
43 count++; |
|
44 } |
|
45 this._state = {count: count, items: items}; |
|
46 return Object.freeze(this); |
|
47 } |
|
48 |
|
49 Dict.prototype = Object.freeze({ |
|
50 /** |
|
51 * The number of items in the dictionary. |
|
52 */ |
|
53 get count() { |
|
54 return this._state.count; |
|
55 }, |
|
56 |
|
57 /** |
|
58 * Gets the value for a key from the dictionary. If the key is not a string, |
|
59 * it will be converted to a string before the lookup happens. |
|
60 * |
|
61 * @param aKey The key to look up |
|
62 * @param [aDefault] An optional default value to return if the key is not |
|
63 * present. Defaults to |undefined|. |
|
64 * @returns The item, or aDefault if it isn't found. |
|
65 */ |
|
66 get: function Dict_get(aKey, aDefault) { |
|
67 var prop = convert(aKey); |
|
68 var items = this._state.items; |
|
69 return items.hasOwnProperty(prop) ? items[prop] : aDefault; |
|
70 }, |
|
71 |
|
72 /** |
|
73 * Sets the value for a key in the dictionary. If the key is a not a string, |
|
74 * it will be converted to a string before the set happens. |
|
75 */ |
|
76 set: function Dict_set(aKey, aValue) { |
|
77 var prop = convert(aKey); |
|
78 var items = this._state.items; |
|
79 if (!items.hasOwnProperty(prop)) |
|
80 this._state.count++; |
|
81 items[prop] = aValue; |
|
82 }, |
|
83 |
|
84 /** |
|
85 * Sets a lazy getter function for a key's value. If the key is a not a string, |
|
86 * it will be converted to a string before the set happens. |
|
87 * @param aKey |
|
88 * The key to set |
|
89 * @param aThunk |
|
90 * A getter function to be called the first time the value for aKey is |
|
91 * retrieved. It is guaranteed that aThunk wouldn't be called more |
|
92 * than once. Note that the key value may be retrieved either |
|
93 * directly, by |get|, or indirectly, by |listvalues| or by iterating |
|
94 * |values|. For the later, the value is only retrieved if and when |
|
95 * the iterator gets to the value in question. Also note that calling |
|
96 * |has| for a lazy-key does not invoke aThunk. |
|
97 * |
|
98 * @note No context is provided for aThunk when it's invoked. |
|
99 * Use Function.bind if you wish to run it in a certain context. |
|
100 */ |
|
101 setAsLazyGetter: function Dict_setAsLazyGetter(aKey, aThunk) { |
|
102 let prop = convert(aKey); |
|
103 let items = this._state.items; |
|
104 if (!items.hasOwnProperty(prop)) |
|
105 this._state.count++; |
|
106 |
|
107 Object.defineProperty(items, prop, { |
|
108 get: function() { |
|
109 delete items[prop]; |
|
110 return items[prop] = aThunk(); |
|
111 }, |
|
112 configurable: true, |
|
113 enumerable: true |
|
114 }); |
|
115 }, |
|
116 |
|
117 /** |
|
118 * Returns whether a key is set as a lazy getter. This returns |
|
119 * true only if the getter function was not called already. |
|
120 * @param aKey |
|
121 * The key to look up. |
|
122 * @returns whether aKey is set as a lazy getter. |
|
123 */ |
|
124 isLazyGetter: function Dict_isLazyGetter(aKey) { |
|
125 let descriptor = Object.getOwnPropertyDescriptor(this._state.items, |
|
126 convert(aKey)); |
|
127 return (descriptor && descriptor.get != null); |
|
128 }, |
|
129 |
|
130 /** |
|
131 * Returns whether a key is in the dictionary. If the key is a not a string, |
|
132 * it will be converted to a string before the lookup happens. |
|
133 */ |
|
134 has: function Dict_has(aKey) { |
|
135 return (this._state.items.hasOwnProperty(convert(aKey))); |
|
136 }, |
|
137 |
|
138 /** |
|
139 * Deletes a key from the dictionary. If the key is a not a string, it will be |
|
140 * converted to a string before the delete happens. |
|
141 * |
|
142 * @returns true if the key was found, false if it wasn't. |
|
143 */ |
|
144 del: function Dict_del(aKey) { |
|
145 var prop = convert(aKey); |
|
146 if (this._state.items.hasOwnProperty(prop)) { |
|
147 delete this._state.items[prop]; |
|
148 this._state.count--; |
|
149 return true; |
|
150 } |
|
151 return false; |
|
152 }, |
|
153 |
|
154 /** |
|
155 * Returns a shallow copy of this dictionary. |
|
156 */ |
|
157 copy: function Dict_copy() { |
|
158 var newItems = {}; |
|
159 for (var [key, val] in this.items) |
|
160 newItems[key] = val; |
|
161 return new Dict(newItems); |
|
162 }, |
|
163 |
|
164 /* |
|
165 * List and iterator functions |
|
166 * |
|
167 * No guarantees whatsoever are made about the order of elements. |
|
168 */ |
|
169 |
|
170 /** |
|
171 * Returns a list of all the keys in the dictionary in an arbitrary order. |
|
172 */ |
|
173 listkeys: function Dict_listkeys() { |
|
174 return [unconvert(k) for (k in this._state.items)]; |
|
175 }, |
|
176 |
|
177 /** |
|
178 * Returns a list of all the values in the dictionary in an arbitrary order. |
|
179 */ |
|
180 listvalues: function Dict_listvalues() { |
|
181 var items = this._state.items; |
|
182 return [items[k] for (k in items)]; |
|
183 }, |
|
184 |
|
185 /** |
|
186 * Returns a list of all the items in the dictionary as key-value pairs |
|
187 * in an arbitrary order. |
|
188 */ |
|
189 listitems: function Dict_listitems() { |
|
190 var items = this._state.items; |
|
191 return [[unconvert(k), items[k]] for (k in items)]; |
|
192 }, |
|
193 |
|
194 /** |
|
195 * Returns an iterator over all the keys in the dictionary in an arbitrary |
|
196 * order. No guarantees are made about what happens if the dictionary is |
|
197 * mutated during iteration. |
|
198 */ |
|
199 get keys() { |
|
200 // If we don't capture this._state.items here then the this-binding will be |
|
201 // incorrect when the generator is executed |
|
202 var items = this._state.items; |
|
203 return (unconvert(k) for (k in items)); |
|
204 }, |
|
205 |
|
206 /** |
|
207 * Returns an iterator over all the values in the dictionary in an arbitrary |
|
208 * order. No guarantees are made about what happens if the dictionary is |
|
209 * mutated during iteration. |
|
210 */ |
|
211 get values() { |
|
212 // If we don't capture this._state.items here then the this-binding will be |
|
213 // incorrect when the generator is executed |
|
214 var items = this._state.items; |
|
215 return (items[k] for (k in items)); |
|
216 }, |
|
217 |
|
218 /** |
|
219 * Returns an iterator over all the items in the dictionary as key-value pairs |
|
220 * in an arbitrary order. No guarantees are made about what happens if the |
|
221 * dictionary is mutated during iteration. |
|
222 */ |
|
223 get items() { |
|
224 // If we don't capture this._state.items here then the this-binding will be |
|
225 // incorrect when the generator is executed |
|
226 var items = this._state.items; |
|
227 return ([unconvert(k), items[k]] for (k in items)); |
|
228 }, |
|
229 |
|
230 /** |
|
231 * Returns a String representation of this dictionary. |
|
232 */ |
|
233 toString: function Dict_toString() { |
|
234 return "{" + |
|
235 [(key + ": " + val) for ([key, val] in this.items)].join(", ") + |
|
236 "}"; |
|
237 }, |
|
238 |
|
239 /** |
|
240 * Returns a JSON representation of this dictionary. |
|
241 */ |
|
242 toJSON: function Dict_toJSON() { |
|
243 let obj = {}; |
|
244 for (let [key, item] of Iterator(this._state.items)) { |
|
245 obj[unconvert(key)] = item; |
|
246 } |
|
247 return JSON.stringify(obj); |
|
248 }, |
|
249 }); |