OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 // Patch file for dart:collection classes. | |
6 import 'dart:_foreign_helper' show JS; | |
7 import 'dart:_js_helper' show | |
8 fillLiteralMap, InternalMap, NoInline, NoSideEffects, NoThrows, patch, | |
9 JsLinkedHashMap, LinkedHashMapCell, LinkedHashMapKeyIterable, | |
10 LinkedHashMapKeyIterator; | |
11 | |
12 const _USE_ES6_MAPS = const bool.fromEnvironment("dart2js.use.es6.maps"); | |
13 | |
14 @patch | |
15 class HashMap<K, V> { | |
16 @patch | |
17 factory HashMap({ bool equals(K key1, K key2), | |
18 int hashCode(K key), | |
19 bool isValidKey(potentialKey) }) { | |
20 if (isValidKey == null) { | |
21 if (hashCode == null) { | |
22 if (equals == null) { | |
23 return new _HashMap<K, V>(); | |
24 } | |
25 hashCode = _defaultHashCode; | |
26 } else { | |
27 if (identical(identityHashCode, hashCode) && | |
28 identical(identical, equals)) { | |
29 return new _IdentityHashMap<K, V>(); | |
30 } | |
31 if (equals == null) { | |
32 equals = _defaultEquals; | |
33 } | |
34 } | |
35 } else { | |
36 if (hashCode == null) { | |
37 hashCode = _defaultHashCode; | |
38 } | |
39 if (equals == null) { | |
40 equals = _defaultEquals; | |
41 } | |
42 } | |
43 return new _CustomHashMap<K, V>(equals, hashCode, isValidKey); | |
44 } | |
45 | |
46 @patch | |
47 factory HashMap.identity() = _IdentityHashMap<K, V>; | |
48 } | |
49 | |
50 class _HashMap<K, V> implements HashMap<K, V> { | |
51 int _length = 0; | |
52 | |
53 // The hash map contents are divided into three parts: one part for | |
54 // string keys, one for numeric keys, and one for the rest. String | |
55 // and numeric keys map directly to their values, but the rest of | |
56 // the entries are stored in bucket lists of the form: | |
57 // | |
58 // [key-0, value-0, key-1, value-1, ...] | |
59 // | |
60 // where all keys in the same bucket share the same hash code. | |
61 var _strings; | |
62 var _nums; | |
63 var _rest; | |
64 | |
65 // When iterating over the hash map, it is very convenient to have a | |
66 // list of all the keys. We cache that on the instance and clear the | |
67 // the cache whenever the key set changes. This is also used to | |
68 // guard against concurrent modifications. | |
69 List _keys; | |
70 | |
71 _HashMap(); | |
72 | |
73 | |
74 int get length => _length; | |
75 bool get isEmpty => _length == 0; | |
76 bool get isNotEmpty => !isEmpty; | |
77 | |
78 Iterable<K> get keys { | |
79 return new HashMapKeyIterable<K>(this); | |
80 } | |
81 | |
82 Iterable<V> get values { | |
83 return new MappedIterable<K, V>(keys, (each) => this[each]); | |
84 } | |
85 | |
86 bool containsKey(Object key) { | |
87 if (_isStringKey(key)) { | |
88 var strings = _strings; | |
89 return (strings == null) ? false : _hasTableEntry(strings, key); | |
90 } else if (_isNumericKey(key)) { | |
91 var nums = _nums; | |
92 return (nums == null) ? false : _hasTableEntry(nums, key); | |
93 } else { | |
94 return _containsKey(key); | |
95 } | |
96 } | |
97 | |
98 bool _containsKey(Object key) { | |
99 var rest = _rest; | |
100 if (rest == null) return false; | |
101 var bucket = _getBucket(rest, key); | |
102 return _findBucketIndex(bucket, key) >= 0; | |
103 } | |
104 | |
105 bool containsValue(Object value) { | |
106 return _computeKeys().any((each) => this[each] == value); | |
107 } | |
108 | |
109 void addAll(Map<K, V> other) { | |
110 other.forEach((K key, V value) { | |
111 this[key] = value; | |
112 }); | |
113 } | |
114 | |
115 V operator[](Object key) { | |
116 if (_isStringKey(key)) { | |
117 var strings = _strings; | |
118 return (strings == null) ? null : _getTableEntry(strings, key); | |
119 } else if (_isNumericKey(key)) { | |
120 var nums = _nums; | |
121 return (nums == null) ? null : _getTableEntry(nums, key); | |
122 } else { | |
123 return _get(key); | |
124 } | |
125 } | |
126 | |
127 V _get(Object key) { | |
128 var rest = _rest; | |
129 if (rest == null) return null; | |
130 var bucket = _getBucket(rest, key); | |
131 int index = _findBucketIndex(bucket, key); | |
132 return (index < 0) ? null : JS('var', '#[#]', bucket, index + 1); | |
133 } | |
134 | |
135 void operator[]=(K key, V value) { | |
136 if (_isStringKey(key)) { | |
137 var strings = _strings; | |
138 if (strings == null) _strings = strings = _newHashTable(); | |
139 _addHashTableEntry(strings, key, value); | |
140 } else if (_isNumericKey(key)) { | |
141 var nums = _nums; | |
142 if (nums == null) _nums = nums = _newHashTable(); | |
143 _addHashTableEntry(nums, key, value); | |
144 } else { | |
145 _set(key, value); | |
146 } | |
147 } | |
148 | |
149 void _set(K key, V value) { | |
150 var rest = _rest; | |
151 if (rest == null) _rest = rest = _newHashTable(); | |
152 var hash = _computeHashCode(key); | |
153 var bucket = JS('var', '#[#]', rest, hash); | |
154 if (bucket == null) { | |
155 _setTableEntry(rest, hash, JS('var', '[#, #]', key, value)); | |
156 _length++; | |
157 _keys = null; | |
158 } else { | |
159 int index = _findBucketIndex(bucket, key); | |
160 if (index >= 0) { | |
161 JS('void', '#[#] = #', bucket, index + 1, value); | |
162 } else { | |
163 JS('void', '#.push(#, #)', bucket, key, value); | |
164 _length++; | |
165 _keys = null; | |
166 } | |
167 } | |
168 } | |
169 | |
170 V putIfAbsent(K key, V ifAbsent()) { | |
171 if (containsKey(key)) return this[key]; | |
172 V value = ifAbsent(); | |
173 this[key] = value; | |
174 return value; | |
175 } | |
176 | |
177 V remove(Object key) { | |
178 if (_isStringKey(key)) { | |
179 return _removeHashTableEntry(_strings, key); | |
180 } else if (_isNumericKey(key)) { | |
181 return _removeHashTableEntry(_nums, key); | |
182 } else { | |
183 return _remove(key); | |
184 } | |
185 } | |
186 | |
187 V _remove(Object key) { | |
188 var rest = _rest; | |
189 if (rest == null) return null; | |
190 var bucket = _getBucket(rest, key); | |
191 int index = _findBucketIndex(bucket, key); | |
192 if (index < 0) return null; | |
193 // TODO(kasperl): Consider getting rid of the bucket list when | |
194 // the length reaches zero. | |
195 _length--; | |
196 _keys = null; | |
197 // Use splice to remove the two [key, value] elements at the | |
198 // index and return the value. | |
199 return JS('var', '#.splice(#, 2)[1]', bucket, index); | |
200 } | |
201 | |
202 void clear() { | |
203 if (_length > 0) { | |
204 _strings = _nums = _rest = _keys = null; | |
205 _length = 0; | |
206 } | |
207 } | |
208 | |
209 void forEach(void action(K key, V value)) { | |
210 List keys = _computeKeys(); | |
211 for (int i = 0, length = keys.length; i < length; i++) { | |
212 var key = JS('var', '#[#]', keys, i); | |
213 action(key, this[key]); | |
214 if (JS('bool', '# !== #', keys, _keys)) { | |
215 throw new ConcurrentModificationError(this); | |
216 } | |
217 } | |
218 } | |
219 | |
220 List _computeKeys() { | |
221 if (_keys != null) return _keys; | |
222 List result = new List(_length); | |
223 int index = 0; | |
224 | |
225 // Add all string keys to the list. | |
226 var strings = _strings; | |
227 if (strings != null) { | |
228 var names = JS('var', 'Object.getOwnPropertyNames(#)', strings); | |
229 int entries = JS('int', '#.length', names); | |
230 for (int i = 0; i < entries; i++) { | |
231 String key = JS('String', '#[#]', names, i); | |
232 JS('void', '#[#] = #', result, index, key); | |
233 index++; | |
234 } | |
235 } | |
236 | |
237 // Add all numeric keys to the list. | |
238 var nums = _nums; | |
239 if (nums != null) { | |
240 var names = JS('var', 'Object.getOwnPropertyNames(#)', nums); | |
241 int entries = JS('int', '#.length', names); | |
242 for (int i = 0; i < entries; i++) { | |
243 // Object.getOwnPropertyNames returns a list of strings, so we | |
244 // have to convert the keys back to numbers (+). | |
245 num key = JS('num', '+#[#]', names, i); | |
246 JS('void', '#[#] = #', result, index, key); | |
247 index++; | |
248 } | |
249 } | |
250 | |
251 // Add all the remaining keys to the list. | |
252 var rest = _rest; | |
253 if (rest != null) { | |
254 var names = JS('var', 'Object.getOwnPropertyNames(#)', rest); | |
255 int entries = JS('int', '#.length', names); | |
256 for (int i = 0; i < entries; i++) { | |
257 var key = JS('String', '#[#]', names, i); | |
258 var bucket = JS('var', '#[#]', rest, key); | |
259 int length = JS('int', '#.length', bucket); | |
260 for (int i = 0; i < length; i += 2) { | |
261 var key = JS('var', '#[#]', bucket, i); | |
262 JS('void', '#[#] = #', result, index, key); | |
263 index++; | |
264 } | |
265 } | |
266 } | |
267 assert(index == _length); | |
268 return _keys = result; | |
269 } | |
270 | |
271 void _addHashTableEntry(var table, K key, V value) { | |
272 if (!_hasTableEntry(table, key)) { | |
273 _length++; | |
274 _keys = null; | |
275 } | |
276 _setTableEntry(table, key, value); | |
277 } | |
278 | |
279 V _removeHashTableEntry(var table, Object key) { | |
280 if (table != null && _hasTableEntry(table, key)) { | |
281 V value = _getTableEntry(table, key); | |
282 _deleteTableEntry(table, key); | |
283 _length--; | |
284 _keys = null; | |
285 return value; | |
286 } else { | |
287 return null; | |
288 } | |
289 } | |
290 | |
291 static bool _isStringKey(var key) { | |
292 return key is String && key != '__proto__'; | |
293 } | |
294 | |
295 static bool _isNumericKey(var key) { | |
296 // Only treat unsigned 30-bit integers as numeric keys. This way, | |
297 // we avoid converting them to strings when we use them as keys in | |
298 // the JavaScript hash table object. | |
299 return key is num && JS('bool', '(# & 0x3ffffff) === #', key, key); | |
300 } | |
301 | |
302 int _computeHashCode(var key) { | |
303 // We force the hash codes to be unsigned 30-bit integers to avoid | |
304 // issues with problematic keys like '__proto__'. Another option | |
305 // would be to throw an exception if the hash code isn't a number. | |
306 return JS('int', '# & 0x3ffffff', key.hashCode); | |
307 } | |
308 | |
309 static bool _hasTableEntry(var table, var key) { | |
310 var entry = JS('var', '#[#]', table, key); | |
311 // We take care to only store non-null entries in the table, so we | |
312 // can check if the table has an entry for the given key with a | |
313 // simple null check. | |
314 return entry != null; | |
315 } | |
316 | |
317 static _getTableEntry(var table, var key) { | |
318 var entry = JS('var', '#[#]', table, key); | |
319 // We store the table itself as the entry to signal that it really | |
320 // is a null value, so we have to map back to null here. | |
321 return JS('bool', '# === #', entry, table) ? null : entry; | |
322 } | |
323 | |
324 static void _setTableEntry(var table, var key, var value) { | |
325 // We only store non-null entries in the table, so we have to | |
326 // change null values to refer to the table itself. Such values | |
327 // will be recognized and mapped back to null on access. | |
328 if (value == null) { | |
329 // Do not update [value] with [table], otherwise our | |
330 // optimizations could be confused by this opaque object being | |
331 // now used for more things than storing and fetching from it. | |
332 JS('void', '#[#] = #', table, key, table); | |
333 } else { | |
334 JS('void', '#[#] = #', table, key, value); | |
335 } | |
336 } | |
337 | |
338 static void _deleteTableEntry(var table, var key) { | |
339 JS('void', 'delete #[#]', table, key); | |
340 } | |
341 | |
342 List _getBucket(var table, var key) { | |
343 var hash = _computeHashCode(key); | |
344 return JS('var', '#[#]', table, hash); | |
345 } | |
346 | |
347 int _findBucketIndex(var bucket, var key) { | |
348 if (bucket == null) return -1; | |
349 int length = JS('int', '#.length', bucket); | |
350 for (int i = 0; i < length; i += 2) { | |
351 if (JS('var', '#[#]', bucket, i) == key) return i; | |
352 } | |
353 return -1; | |
354 } | |
355 | |
356 static _newHashTable() { | |
357 // Create a new JavaScript object to be used as a hash table. Use | |
358 // Object.create to avoid the properties on Object.prototype | |
359 // showing up as entries. | |
360 var table = JS('var', 'Object.create(null)'); | |
361 // Attempt to force the hash table into 'dictionary' mode by | |
362 // adding a property to it and deleting it again. | |
363 var temporaryKey = '<non-identifier-key>'; | |
364 _setTableEntry(table, temporaryKey, table); | |
365 _deleteTableEntry(table, temporaryKey); | |
366 return table; | |
367 } | |
368 } | |
369 | |
370 class _IdentityHashMap<K, V> extends _HashMap<K, V> { | |
371 int _computeHashCode(var key) { | |
372 // We force the hash codes to be unsigned 30-bit integers to avoid | |
373 // issues with problematic keys like '__proto__'. Another option | |
374 // would be to throw an exception if the hash code isn't a number. | |
375 return JS('int', '# & 0x3ffffff', identityHashCode(key)); | |
376 } | |
377 | |
378 int _findBucketIndex(var bucket, var key) { | |
379 if (bucket == null) return -1; | |
380 int length = JS('int', '#.length', bucket); | |
381 for (int i = 0; i < length; i += 2) { | |
382 if (identical(JS('var', '#[#]', bucket, i), key)) return i; | |
383 } | |
384 return -1; | |
385 } | |
386 } | |
387 | |
388 class _CustomHashMap<K, V> extends _HashMap<K, V> { | |
389 final _Equality<K> _equals; | |
390 final _Hasher<K> _hashCode; | |
391 final _Predicate _validKey; | |
392 | |
393 _CustomHashMap(this._equals, this._hashCode, bool validKey(potentialKey)) | |
394 : _validKey = (validKey != null) ? validKey : ((v) => v is K); | |
395 | |
396 V operator[](Object key) { | |
397 if (!_validKey(key)) return null; | |
398 return super._get(key); | |
399 } | |
400 | |
401 void operator[]=(K key, V value) { | |
402 super._set(key, value); | |
403 } | |
404 | |
405 bool containsKey(Object key) { | |
406 if (!_validKey(key)) return false; | |
407 return super._containsKey(key); | |
408 } | |
409 | |
410 V remove(Object key) { | |
411 if (!_validKey(key)) return null; | |
412 return super._remove(key); | |
413 } | |
414 | |
415 int _computeHashCode(var key) { | |
416 // We force the hash codes to be unsigned 30-bit integers to avoid | |
417 // issues with problematic keys like '__proto__'. Another option | |
418 // would be to throw an exception if the hash code isn't a number. | |
419 return JS('int', '# & 0x3ffffff', _hashCode(key)); | |
420 } | |
421 | |
422 int _findBucketIndex(var bucket, var key) { | |
423 if (bucket == null) return -1; | |
424 int length = JS('int', '#.length', bucket); | |
425 for (int i = 0; i < length; i += 2) { | |
426 if (_equals(JS('var', '#[#]', bucket, i), key)) return i; | |
427 } | |
428 return -1; | |
429 } | |
430 | |
431 String toString() => Maps.mapToString(this); | |
432 } | |
433 | |
434 class HashMapKeyIterable<E> extends Iterable<E> | |
435 implements EfficientLength { | |
436 final _map; | |
437 HashMapKeyIterable(this._map); | |
438 | |
439 int get length => _map._length; | |
440 bool get isEmpty => _map._length == 0; | |
441 | |
442 Iterator<E> get iterator { | |
443 return new HashMapKeyIterator<E>(_map, _map._computeKeys()); | |
444 } | |
445 | |
446 bool contains(Object element) { | |
447 return _map.containsKey(element); | |
448 } | |
449 | |
450 void forEach(void f(E element)) { | |
451 List keys = _map._computeKeys(); | |
452 for (int i = 0, length = JS('int', '#.length', keys); i < length; i++) { | |
453 f(JS('var', '#[#]', keys, i)); | |
454 if (JS('bool', '# !== #', keys, _map._keys)) { | |
455 throw new ConcurrentModificationError(_map); | |
456 } | |
457 } | |
458 } | |
459 } | |
460 | |
461 class HashMapKeyIterator<E> implements Iterator<E> { | |
462 final _map; | |
463 final List _keys; | |
464 int _offset = 0; | |
465 E _current; | |
466 | |
467 HashMapKeyIterator(this._map, this._keys); | |
468 | |
469 E get current => _current; | |
470 | |
471 bool moveNext() { | |
472 var keys = _keys; | |
473 int offset = _offset; | |
474 if (JS('bool', '# !== #', keys, _map._keys)) { | |
475 throw new ConcurrentModificationError(_map); | |
476 } else if (offset >= JS('int', '#.length', keys)) { | |
477 _current = null; | |
478 return false; | |
479 } else { | |
480 _current = JS('var', '#[#]', keys, offset); | |
481 // TODO(kasperl): For now, we have to tell the type inferrer to | |
482 // treat the result of doing offset + 1 as an int. Otherwise, we | |
483 // get unnecessary bailout code. | |
484 _offset = JS('int', '#', offset + 1); | |
485 return true; | |
486 } | |
487 } | |
488 } | |
489 | |
490 @patch | |
491 class LinkedHashMap<K, V> { | |
492 @patch | |
493 factory LinkedHashMap({ bool equals(K key1, K key2), | |
494 int hashCode(K key), | |
495 bool isValidKey(potentialKey) }) { | |
496 if (isValidKey == null) { | |
497 if (hashCode == null) { | |
498 if (equals == null) { | |
499 return new JsLinkedHashMap<K, V>.es6(); | |
500 } | |
501 hashCode = _defaultHashCode; | |
502 } else { | |
503 if (identical(identityHashCode, hashCode) && | |
504 identical(identical, equals)) { | |
505 return new _LinkedIdentityHashMap<K, V>.es6(); | |
506 } | |
507 if (equals == null) { | |
508 equals = _defaultEquals; | |
509 } | |
510 } | |
511 } else { | |
512 if (hashCode == null) { | |
513 hashCode = _defaultHashCode; | |
514 } | |
515 if (equals == null) { | |
516 equals = _defaultEquals; | |
517 } | |
518 } | |
519 return new _LinkedCustomHashMap<K, V>(equals, hashCode, isValidKey); | |
520 } | |
521 | |
522 @patch | |
523 factory LinkedHashMap.identity() = _LinkedIdentityHashMap<K, V>.es6; | |
524 | |
525 // Private factory constructor called by generated code for map literals. | |
526 @NoInline() | |
527 factory LinkedHashMap._literal(List keyValuePairs) { | |
528 return fillLiteralMap(keyValuePairs, new JsLinkedHashMap<K, V>.es6()); | |
529 } | |
530 | |
531 // Private factory constructor called by generated code for map literals. | |
532 @NoThrows() @NoInline() @NoSideEffects() | |
533 factory LinkedHashMap._empty() { | |
534 return new JsLinkedHashMap<K, V>.es6(); | |
535 } | |
536 | |
537 // Private factory static function called by generated code for map literals. | |
538 // This version is for map literals without type parameters. | |
539 @NoInline() | |
540 static _makeEmpty() => new JsLinkedHashMap(); | |
541 | |
542 // Private factory static function called by generated code for map literals. | |
543 // This version is for map literals without type parameters. | |
544 @NoInline() | |
545 static _makeLiteral(keyValuePairs) => | |
546 fillLiteralMap(keyValuePairs, new JsLinkedHashMap()); | |
547 } | |
548 | |
549 class _LinkedIdentityHashMap<K, V> extends JsLinkedHashMap<K, V> { | |
550 static bool get _supportsEs6Maps { | |
551 return JS('returns:bool;depends:none;effects:none;throws:never;gvn:true', | |
552 'typeof Map != "undefined"'); | |
553 } | |
554 | |
555 factory _LinkedIdentityHashMap.es6() { | |
556 return (_USE_ES6_MAPS && _LinkedIdentityHashMap._supportsEs6Maps) | |
557 ? new _Es6LinkedIdentityHashMap<K, V>() | |
558 : new _LinkedIdentityHashMap<K, V>(); | |
559 } | |
560 | |
561 _LinkedIdentityHashMap(); | |
562 | |
563 int internalComputeHashCode(var key) { | |
564 // We force the hash codes to be unsigned 30-bit integers to avoid | |
565 // issues with problematic keys like '__proto__'. Another option | |
566 // would be to throw an exception if the hash code isn't a number. | |
567 return JS('int', '# & 0x3ffffff', identityHashCode(key)); | |
568 } | |
569 | |
570 int internalFindBucketIndex(var bucket, var key) { | |
571 if (bucket == null) return -1; | |
572 int length = JS('int', '#.length', bucket); | |
573 for (int i = 0; i < length; i++) { | |
574 LinkedHashMapCell cell = JS('var', '#[#]', bucket, i); | |
575 if (identical(cell.hashMapCellKey, key)) return i; | |
576 } | |
577 return -1; | |
578 } | |
579 } | |
580 | |
581 class _Es6LinkedIdentityHashMap<K, V> | |
582 extends _LinkedIdentityHashMap<K, V> implements InternalMap { | |
583 final _map; | |
584 int _modifications = 0; | |
585 | |
586 _Es6LinkedIdentityHashMap() : _map = JS('var', 'new Map()'); | |
587 | |
588 int get length => JS('int', '#.size', _map); | |
589 bool get isEmpty => length == 0; | |
590 bool get isNotEmpty => !isEmpty; | |
591 | |
592 Iterable<K> get keys => new _Es6MapIterable<K>(this, true); | |
593 | |
594 Iterable<V> get values => | |
595 new _Es6MapIterable<V>(this, false); | |
596 | |
597 bool containsKey(Object key) { | |
598 return JS('bool', '#.has(#)', _map, key); | |
599 } | |
600 | |
601 bool containsValue(Object value) { | |
602 return values.any((each) => each == value); | |
603 } | |
604 | |
605 void addAll(Map<K, V> other) { | |
606 other.forEach((K key, V value) { | |
607 this[key] = value; | |
608 }); | |
609 } | |
610 | |
611 V operator[](Object key) { | |
612 return JS('var', '#.get(#)', _map, key); | |
613 } | |
614 | |
615 void operator[]=(K key, V value) { | |
616 JS('var', '#.set(#, #)', _map, key, value); | |
617 _modified(); | |
618 } | |
619 | |
620 V putIfAbsent(K key, V ifAbsent()) { | |
621 if (containsKey(key)) return this[key]; | |
622 V value = ifAbsent(); | |
623 this[key] = value; | |
624 return value; | |
625 } | |
626 | |
627 V remove(Object key) { | |
628 V value = this[key]; | |
629 JS('bool', '#.delete(#)', _map, key); | |
630 _modified(); | |
631 return value; | |
632 } | |
633 | |
634 void clear() { | |
635 JS('void', '#.clear()', _map); | |
636 _modified(); | |
637 } | |
638 | |
639 void forEach(void action(K key, V value)) { | |
640 var jsEntries = JS('var', '#.entries()', _map); | |
641 int modifications = _modifications; | |
642 while (true) { | |
643 var next = JS('var', '#.next()', jsEntries); | |
644 bool done = JS('bool', '#.done', next); | |
645 if (done) break; | |
646 var entry = JS('var', '#.value', next); | |
647 var key = JS('var', '#[0]', entry); | |
648 var value = JS('var', '#[1]', entry); | |
649 action(key, value); | |
650 if (modifications != _modifications) { | |
651 throw new ConcurrentModificationError(this); | |
652 } | |
653 } | |
654 } | |
655 | |
656 void _modified() { | |
657 // Value cycles after 2^30 modifications so that modification counts are | |
658 // always unboxed (Smi) values. Modification detection will be missed if you | |
659 // make exactly some multiple of 2^30 modifications between advances of an | |
660 // iterator. | |
661 _modifications = (_modifications + 1) & 0x3ffffff; | |
662 } | |
663 | |
664 String toString() => Maps.mapToString(this); | |
665 } | |
666 | |
667 class _Es6MapIterable<E> extends Iterable<E> | |
668 implements EfficientLength { | |
669 final _map; | |
670 final bool _isKeys; | |
671 | |
672 _Es6MapIterable(this._map, this._isKeys); | |
673 | |
674 int get length => _map.length; | |
675 bool get isEmpty => _map.isEmpty; | |
676 | |
677 Iterator<E> get iterator => | |
678 new _Es6MapIterator<E>(_map, _map._modifications, _isKeys); | |
679 | |
680 bool contains(Object element) => _map.containsKey(element); | |
681 | |
682 void forEach(void f(E element)) { | |
683 var jsIterator; | |
684 if (_isKeys) { | |
685 jsIterator = JS('var', '#.keys()', _map._map); | |
686 } else { | |
687 jsIterator = JS('var', '#.values()', _map._map); | |
688 } | |
689 int modifications = _map._modifications; | |
690 while (true) { | |
691 var next = JS('var', '#.next()', jsIterator); | |
692 bool done = JS('bool', '#.done', next); | |
693 if (done) break; | |
694 var value = JS('var', '#.value', next); | |
695 f(value); | |
696 if (modifications != _map._modifications) { | |
697 throw new ConcurrentModificationError(_map); | |
698 } | |
699 } | |
700 } | |
701 } | |
702 | |
703 class _Es6MapIterator<E> implements Iterator<E> { | |
704 final _map; | |
705 final int _modifications; | |
706 final bool _isKeys; | |
707 var _jsIterator; | |
708 var _next; | |
709 E _current; | |
710 bool _done; | |
711 | |
712 _Es6MapIterator(this._map, this._modifications, this._isKeys) { | |
713 if (_isKeys) { | |
714 _jsIterator = JS('var', '#.keys()', _map._map); | |
715 } else { | |
716 _jsIterator = JS('var', '#.values()', _map._map); | |
717 } | |
718 _done = false; | |
719 } | |
720 | |
721 E get current => _current; | |
722 | |
723 bool moveNext() { | |
724 if (_modifications != _map._modifications) { | |
725 throw new ConcurrentModificationError(_map); | |
726 } | |
727 if (_done) return false; | |
728 _next = JS('var', '#.next()', _jsIterator); | |
729 bool done = JS('bool', '#.done', _next); | |
730 if (done) { | |
731 _current = null; | |
732 _done = true; | |
733 return false; | |
734 } else { | |
735 _current = JS('var', '#.value', _next); | |
736 return true; | |
737 } | |
738 } | |
739 } | |
740 | |
741 // TODO(floitsch): use ES6 maps when available. | |
742 class _LinkedCustomHashMap<K, V> extends JsLinkedHashMap<K, V> { | |
743 final _Equality<K> _equals; | |
744 final _Hasher<K> _hashCode; | |
745 final _Predicate _validKey; | |
746 | |
747 _LinkedCustomHashMap(this._equals, this._hashCode, | |
748 bool validKey(potentialKey)) | |
749 : _validKey = (validKey != null) ? validKey : ((v) => v is K); | |
750 | |
751 V operator[](Object key) { | |
752 if (!_validKey(key)) return null; | |
753 return super.internalGet(key); | |
754 } | |
755 | |
756 void operator[]=(K key, V value) { | |
757 super.internalSet(key, value); | |
758 } | |
759 | |
760 bool containsKey(Object key) { | |
761 if (!_validKey(key)) return false; | |
762 return super.internalContainsKey(key); | |
763 } | |
764 | |
765 V remove(Object key) { | |
766 if (!_validKey(key)) return null; | |
767 return super.internalRemove(key); | |
768 } | |
769 | |
770 int internalComputeHashCode(var key) { | |
771 // We force the hash codes to be unsigned 30-bit integers to avoid | |
772 // issues with problematic keys like '__proto__'. Another option | |
773 // would be to throw an exception if the hash code isn't a number. | |
774 return JS('int', '# & 0x3ffffff', _hashCode(key)); | |
775 } | |
776 | |
777 int internalFindBucketIndex(var bucket, var key) { | |
778 if (bucket == null) return -1; | |
779 int length = JS('int', '#.length', bucket); | |
780 for (int i = 0; i < length; i++) { | |
781 LinkedHashMapCell cell = JS('var', '#[#]', bucket, i); | |
782 if (_equals(cell.hashMapCellKey, key)) return i; | |
783 } | |
784 return -1; | |
785 } | |
786 } | |
787 | |
788 @patch | |
789 class HashSet<E> { | |
790 @patch | |
791 factory HashSet({ bool equals(E e1, E e2), | |
792 int hashCode(E e), | |
793 bool isValidKey(potentialKey) }) { | |
794 if (isValidKey == null) { | |
795 if (hashCode == null) { | |
796 if (equals == null) { | |
797 return new _HashSet<E>(); | |
798 } | |
799 hashCode = _defaultHashCode; | |
800 } else { | |
801 if (identical(identityHashCode, hashCode) && | |
802 identical(identical, equals)) { | |
803 return new _IdentityHashSet<E>(); | |
804 } | |
805 if (equals == null) { | |
806 equals = _defaultEquals; | |
807 } | |
808 } | |
809 } else { | |
810 if (hashCode == null) { | |
811 hashCode = _defaultHashCode; | |
812 } | |
813 if (equals == null) { | |
814 equals = _defaultEquals; | |
815 } | |
816 } | |
817 return new _CustomHashSet<E>(equals, hashCode, isValidKey); | |
818 } | |
819 | |
820 @patch | |
821 factory HashSet.identity() = _IdentityHashSet<E>; | |
822 } | |
823 | |
824 class _HashSet<E> extends _HashSetBase<E> implements HashSet<E> { | |
825 int _length = 0; | |
826 | |
827 // The hash set contents are divided into three parts: one part for | |
828 // string elements, one for numeric elements, and one for the | |
829 // rest. String and numeric elements map directly to a sentinel | |
830 // value, but the rest of the entries are stored in bucket lists of | |
831 // the form: | |
832 // | |
833 // [element-0, element-1, element-2, ...] | |
834 // | |
835 // where all elements in the same bucket share the same hash code. | |
836 var _strings; | |
837 var _nums; | |
838 var _rest; | |
839 | |
840 // When iterating over the hash set, it is very convenient to have a | |
841 // list of all the elements. We cache that on the instance and clear | |
842 // the the cache whenever the set changes. This is also used to | |
843 // guard against concurrent modifications. | |
844 List _elements; | |
845 | |
846 _HashSet(); | |
847 | |
848 Set<E> _newSet() => new _HashSet<E>(); | |
849 | |
850 // Iterable. | |
851 Iterator<E> get iterator { | |
852 return new HashSetIterator<E>(this, _computeElements()); | |
853 } | |
854 | |
855 int get length => _length; | |
856 bool get isEmpty => _length == 0; | |
857 bool get isNotEmpty => !isEmpty; | |
858 | |
859 bool contains(Object object) { | |
860 if (_isStringElement(object)) { | |
861 var strings = _strings; | |
862 return (strings == null) ? false : _hasTableEntry(strings, object); | |
863 } else if (_isNumericElement(object)) { | |
864 var nums = _nums; | |
865 return (nums == null) ? false : _hasTableEntry(nums, object); | |
866 } else { | |
867 return _contains(object); | |
868 } | |
869 } | |
870 | |
871 bool _contains(Object object) { | |
872 var rest = _rest; | |
873 if (rest == null) return false; | |
874 var bucket = _getBucket(rest, object); | |
875 return _findBucketIndex(bucket, object) >= 0; | |
876 } | |
877 | |
878 E lookup(Object object) { | |
879 if (_isStringElement(object) || _isNumericElement(object)) { | |
880 return this.contains(object) ? object : null; | |
881 } | |
882 return _lookup(object); | |
883 } | |
884 | |
885 E _lookup(Object object) { | |
886 var rest = _rest; | |
887 if (rest == null) return null; | |
888 var bucket = _getBucket(rest, object); | |
889 var index = _findBucketIndex(bucket, object); | |
890 if (index < 0) return null; | |
891 return bucket[index]; | |
892 } | |
893 | |
894 // Collection. | |
895 bool add(E element) { | |
896 if (_isStringElement(element)) { | |
897 var strings = _strings; | |
898 if (strings == null) _strings = strings = _newHashTable(); | |
899 return _addHashTableEntry(strings, element); | |
900 } else if (_isNumericElement(element)) { | |
901 var nums = _nums; | |
902 if (nums == null) _nums = nums = _newHashTable(); | |
903 return _addHashTableEntry(nums, element); | |
904 } else { | |
905 return _add(element); | |
906 } | |
907 } | |
908 | |
909 bool _add(E element) { | |
910 var rest = _rest; | |
911 if (rest == null) _rest = rest = _newHashTable(); | |
912 var hash = _computeHashCode(element); | |
913 var bucket = JS('var', '#[#]', rest, hash); | |
914 if (bucket == null) { | |
915 _setTableEntry(rest, hash, JS('var', '[#]', element)); | |
916 } else { | |
917 int index = _findBucketIndex(bucket, element); | |
918 if (index >= 0) return false; | |
919 JS('void', '#.push(#)', bucket, element); | |
920 } | |
921 _length++; | |
922 _elements = null; | |
923 return true; | |
924 } | |
925 | |
926 void addAll(Iterable<E> objects) { | |
927 for (E each in objects) { | |
928 add(each); | |
929 } | |
930 } | |
931 | |
932 bool remove(Object object) { | |
933 if (_isStringElement(object)) { | |
934 return _removeHashTableEntry(_strings, object); | |
935 } else if (_isNumericElement(object)) { | |
936 return _removeHashTableEntry(_nums, object); | |
937 } else { | |
938 return _remove(object); | |
939 } | |
940 } | |
941 | |
942 bool _remove(Object object) { | |
943 var rest = _rest; | |
944 if (rest == null) return false; | |
945 var bucket = _getBucket(rest, object); | |
946 int index = _findBucketIndex(bucket, object); | |
947 if (index < 0) return false; | |
948 // TODO(kasperl): Consider getting rid of the bucket list when | |
949 // the length reaches zero. | |
950 _length--; | |
951 _elements = null; | |
952 // TODO(kasperl): It would probably be faster to move the | |
953 // element to the end and reduce the length of the bucket list. | |
954 JS('void', '#.splice(#, 1)', bucket, index); | |
955 return true; | |
956 } | |
957 | |
958 void clear() { | |
959 if (_length > 0) { | |
960 _strings = _nums = _rest = _elements = null; | |
961 _length = 0; | |
962 } | |
963 } | |
964 | |
965 List _computeElements() { | |
966 if (_elements != null) return _elements; | |
967 List result = new List(_length); | |
968 int index = 0; | |
969 | |
970 // Add all string elements to the list. | |
971 var strings = _strings; | |
972 if (strings != null) { | |
973 var names = JS('var', 'Object.getOwnPropertyNames(#)', strings); | |
974 int entries = JS('int', '#.length', names); | |
975 for (int i = 0; i < entries; i++) { | |
976 String element = JS('String', '#[#]', names, i); | |
977 JS('void', '#[#] = #', result, index, element); | |
978 index++; | |
979 } | |
980 } | |
981 | |
982 // Add all numeric elements to the list. | |
983 var nums = _nums; | |
984 if (nums != null) { | |
985 var names = JS('var', 'Object.getOwnPropertyNames(#)', nums); | |
986 int entries = JS('int', '#.length', names); | |
987 for (int i = 0; i < entries; i++) { | |
988 // Object.getOwnPropertyNames returns a list of strings, so we | |
989 // have to convert the elements back to numbers (+). | |
990 num element = JS('num', '+#[#]', names, i); | |
991 JS('void', '#[#] = #', result, index, element); | |
992 index++; | |
993 } | |
994 } | |
995 | |
996 // Add all the remaining elements to the list. | |
997 var rest = _rest; | |
998 if (rest != null) { | |
999 var names = JS('var', 'Object.getOwnPropertyNames(#)', rest); | |
1000 int entries = JS('int', '#.length', names); | |
1001 for (int i = 0; i < entries; i++) { | |
1002 var entry = JS('String', '#[#]', names, i); | |
1003 var bucket = JS('var', '#[#]', rest, entry); | |
1004 int length = JS('int', '#.length', bucket); | |
1005 for (int i = 0; i < length; i++) { | |
1006 JS('void', '#[#] = #[#]', result, index, bucket, i); | |
1007 index++; | |
1008 } | |
1009 } | |
1010 } | |
1011 assert(index == _length); | |
1012 return _elements = result; | |
1013 } | |
1014 | |
1015 bool _addHashTableEntry(var table, E element) { | |
1016 if (_hasTableEntry(table, element)) return false; | |
1017 _setTableEntry(table, element, 0); | |
1018 _length++; | |
1019 _elements = null; | |
1020 return true; | |
1021 } | |
1022 | |
1023 bool _removeHashTableEntry(var table, Object element) { | |
1024 if (table != null && _hasTableEntry(table, element)) { | |
1025 _deleteTableEntry(table, element); | |
1026 _length--; | |
1027 _elements = null; | |
1028 return true; | |
1029 } else { | |
1030 return false; | |
1031 } | |
1032 } | |
1033 | |
1034 static bool _isStringElement(var element) { | |
1035 return element is String && element != '__proto__'; | |
1036 } | |
1037 | |
1038 static bool _isNumericElement(var element) { | |
1039 // Only treat unsigned 30-bit integers as numeric elements. This | |
1040 // way, we avoid converting them to strings when we use them as | |
1041 // keys in the JavaScript hash table object. | |
1042 return element is num && | |
1043 JS('bool', '(# & 0x3ffffff) === #', element, element); | |
1044 } | |
1045 | |
1046 int _computeHashCode(var element) { | |
1047 // We force the hash codes to be unsigned 30-bit integers to avoid | |
1048 // issues with problematic elements like '__proto__'. Another | |
1049 // option would be to throw an exception if the hash code isn't a | |
1050 // number. | |
1051 return JS('int', '# & 0x3ffffff', element.hashCode); | |
1052 } | |
1053 | |
1054 static bool _hasTableEntry(var table, var key) { | |
1055 var entry = JS('var', '#[#]', table, key); | |
1056 // We take care to only store non-null entries in the table, so we | |
1057 // can check if the table has an entry for the given key with a | |
1058 // simple null check. | |
1059 return entry != null; | |
1060 } | |
1061 | |
1062 static void _setTableEntry(var table, var key, var value) { | |
1063 assert(value != null); | |
1064 JS('void', '#[#] = #', table, key, value); | |
1065 } | |
1066 | |
1067 static void _deleteTableEntry(var table, var key) { | |
1068 JS('void', 'delete #[#]', table, key); | |
1069 } | |
1070 | |
1071 List _getBucket(var table, var element) { | |
1072 var hash = _computeHashCode(element); | |
1073 return JS('var', '#[#]', table, hash); | |
1074 } | |
1075 | |
1076 int _findBucketIndex(var bucket, var element) { | |
1077 if (bucket == null) return -1; | |
1078 int length = JS('int', '#.length', bucket); | |
1079 for (int i = 0; i < length; i++) { | |
1080 if (JS('var', '#[#]', bucket, i) == element) return i; | |
1081 } | |
1082 return -1; | |
1083 } | |
1084 | |
1085 static _newHashTable() { | |
1086 // Create a new JavaScript object to be used as a hash table. Use | |
1087 // Object.create to avoid the properties on Object.prototype | |
1088 // showing up as entries. | |
1089 var table = JS('var', 'Object.create(null)'); | |
1090 // Attempt to force the hash table into 'dictionary' mode by | |
1091 // adding a property to it and deleting it again. | |
1092 var temporaryKey = '<non-identifier-key>'; | |
1093 _setTableEntry(table, temporaryKey, table); | |
1094 _deleteTableEntry(table, temporaryKey); | |
1095 return table; | |
1096 } | |
1097 } | |
1098 | |
1099 class _IdentityHashSet<E> extends _HashSet<E> { | |
1100 Set<E> _newSet() => new _IdentityHashSet<E>(); | |
1101 | |
1102 int _computeHashCode(var key) { | |
1103 // We force the hash codes to be unsigned 30-bit integers to avoid | |
1104 // issues with problematic keys like '__proto__'. Another option | |
1105 // would be to throw an exception if the hash code isn't a number. | |
1106 return JS('int', '# & 0x3ffffff', identityHashCode(key)); | |
1107 } | |
1108 | |
1109 int _findBucketIndex(var bucket, var element) { | |
1110 if (bucket == null) return -1; | |
1111 int length = JS('int', '#.length', bucket); | |
1112 for (int i = 0; i < length; i++) { | |
1113 if (identical(JS('var', '#[#]', bucket, i), element)) return i; | |
1114 } | |
1115 return -1; | |
1116 } | |
1117 } | |
1118 | |
1119 class _CustomHashSet<E> extends _HashSet<E> { | |
1120 _Equality<E> _equality; | |
1121 _Hasher<E> _hasher; | |
1122 _Predicate _validKey; | |
1123 _CustomHashSet(this._equality, this._hasher, bool validKey(potentialKey)) | |
1124 : _validKey = (validKey != null) ? validKey : ((x) => x is E); | |
1125 | |
1126 Set<E> _newSet() => new _CustomHashSet<E>(_equality, _hasher, _validKey); | |
1127 | |
1128 int _findBucketIndex(var bucket, var element) { | |
1129 if (bucket == null) return -1; | |
1130 int length = JS('int', '#.length', bucket); | |
1131 for (int i = 0; i < length; i++) { | |
1132 if (_equality(JS('var', '#[#]', bucket, i), element)) return i; | |
1133 } | |
1134 return -1; | |
1135 } | |
1136 | |
1137 int _computeHashCode(var element) { | |
1138 // We force the hash codes to be unsigned 30-bit integers to avoid | |
1139 // issues with problematic elements like '__proto__'. Another | |
1140 // option would be to throw an exception if the hash code isn't a | |
1141 // number. | |
1142 return JS('int', '# & 0x3ffffff', _hasher(element)); | |
1143 } | |
1144 | |
1145 bool add(E object) => super._add(object); | |
1146 | |
1147 bool contains(Object object) { | |
1148 if (!_validKey(object)) return false; | |
1149 return super._contains(object); | |
1150 } | |
1151 | |
1152 E lookup(Object object) { | |
1153 if (!_validKey(object)) return null; | |
1154 return super._lookup(object); | |
1155 } | |
1156 | |
1157 bool remove(Object object) { | |
1158 if (!_validKey(object)) return false; | |
1159 return super._remove(object); | |
1160 } | |
1161 } | |
1162 | |
1163 // TODO(kasperl): Share this code with HashMapKeyIterator<E>? | |
1164 class HashSetIterator<E> implements Iterator<E> { | |
1165 final _set; | |
1166 final List _elements; | |
1167 int _offset = 0; | |
1168 E _current; | |
1169 | |
1170 HashSetIterator(this._set, this._elements); | |
1171 | |
1172 E get current => _current; | |
1173 | |
1174 bool moveNext() { | |
1175 var elements = _elements; | |
1176 int offset = _offset; | |
1177 if (JS('bool', '# !== #', elements, _set._elements)) { | |
1178 throw new ConcurrentModificationError(_set); | |
1179 } else if (offset >= JS('int', '#.length', elements)) { | |
1180 _current = null; | |
1181 return false; | |
1182 } else { | |
1183 _current = JS('var', '#[#]', elements, offset); | |
1184 // TODO(kasperl): For now, we have to tell the type inferrer to | |
1185 // treat the result of doing offset + 1 as an int. Otherwise, we | |
1186 // get unnecessary bailout code. | |
1187 _offset = JS('int', '#', offset + 1); | |
1188 return true; | |
1189 } | |
1190 } | |
1191 } | |
1192 | |
1193 @patch | |
1194 class LinkedHashSet<E> { | |
1195 @patch | |
1196 factory LinkedHashSet({ bool equals(E e1, E e2), | |
1197 int hashCode(E e), | |
1198 bool isValidKey(potentialKey) }) { | |
1199 if (isValidKey == null) { | |
1200 if (hashCode == null) { | |
1201 if (equals == null) { | |
1202 return new _LinkedHashSet<E>(); | |
1203 } | |
1204 hashCode = _defaultHashCode; | |
1205 } else { | |
1206 if (identical(identityHashCode, hashCode) && | |
1207 identical(identical, equals)) { | |
1208 return new _LinkedIdentityHashSet<E>(); | |
1209 } | |
1210 if (equals == null) { | |
1211 equals = _defaultEquals; | |
1212 } | |
1213 } | |
1214 } else { | |
1215 if (hashCode == null) { | |
1216 hashCode = _defaultHashCode; | |
1217 } | |
1218 if (equals == null) { | |
1219 equals = _defaultEquals; | |
1220 } | |
1221 } | |
1222 return new _LinkedCustomHashSet<E>(equals, hashCode, isValidKey); | |
1223 } | |
1224 | |
1225 @patch | |
1226 factory LinkedHashSet.identity() = _LinkedIdentityHashSet<E>; | |
1227 } | |
1228 | |
1229 class _LinkedHashSet<E> extends _HashSetBase<E> implements LinkedHashSet<E> { | |
1230 int _length = 0; | |
1231 | |
1232 // The hash set contents are divided into three parts: one part for | |
1233 // string elements, one for numeric elements, and one for the | |
1234 // rest. String and numeric elements map directly to their linked | |
1235 // cells, but the rest of the entries are stored in bucket lists of | |
1236 // the form: | |
1237 // | |
1238 // [cell-0, cell-1, ...] | |
1239 // | |
1240 // where all elements in the same bucket share the same hash code. | |
1241 var _strings; | |
1242 var _nums; | |
1243 var _rest; | |
1244 | |
1245 // The elements are stored in cells that are linked together | |
1246 // to form a double linked list. | |
1247 LinkedHashSetCell _first; | |
1248 LinkedHashSetCell _last; | |
1249 | |
1250 // We track the number of modifications done to the element set to | |
1251 // be able to throw when the set is modified while being iterated | |
1252 // over. | |
1253 int _modifications = 0; | |
1254 | |
1255 _LinkedHashSet(); | |
1256 | |
1257 Set<E> _newSet() => new _LinkedHashSet<E>(); | |
1258 | |
1259 void _unsupported(String operation) { | |
1260 throw 'LinkedHashSet: unsupported $operation'; | |
1261 } | |
1262 | |
1263 // Iterable. | |
1264 Iterator<E> get iterator { | |
1265 return new LinkedHashSetIterator(this, _modifications); | |
1266 } | |
1267 | |
1268 int get length => _length; | |
1269 bool get isEmpty => _length == 0; | |
1270 bool get isNotEmpty => !isEmpty; | |
1271 | |
1272 bool contains(Object object) { | |
1273 if (_isStringElement(object)) { | |
1274 var strings = _strings; | |
1275 if (strings == null) return false; | |
1276 LinkedHashSetCell cell = _getTableEntry(strings, object); | |
1277 return cell != null; | |
1278 } else if (_isNumericElement(object)) { | |
1279 var nums = _nums; | |
1280 if (nums == null) return false; | |
1281 LinkedHashSetCell cell = _getTableEntry(nums, object); | |
1282 return cell != null; | |
1283 } else { | |
1284 return _contains(object); | |
1285 } | |
1286 } | |
1287 | |
1288 bool _contains(Object object) { | |
1289 var rest = _rest; | |
1290 if (rest == null) return false; | |
1291 var bucket = _getBucket(rest, object); | |
1292 return _findBucketIndex(bucket, object) >= 0; | |
1293 } | |
1294 | |
1295 E lookup(Object object) { | |
1296 if (_isStringElement(object) || _isNumericElement(object)) { | |
1297 return this.contains(object) ? object : null; | |
1298 } else { | |
1299 return _lookup(object); | |
1300 } | |
1301 } | |
1302 | |
1303 E _lookup(Object object) { | |
1304 var rest = _rest; | |
1305 if (rest == null) return null; | |
1306 var bucket = _getBucket(rest, object); | |
1307 var index = _findBucketIndex(bucket, object); | |
1308 if (index < 0) return null; | |
1309 return bucket[index]._element; | |
1310 } | |
1311 | |
1312 void forEach(void action(E element)) { | |
1313 LinkedHashSetCell cell = _first; | |
1314 int modifications = _modifications; | |
1315 while (cell != null) { | |
1316 action(cell._element); | |
1317 if (modifications != _modifications) { | |
1318 throw new ConcurrentModificationError(this); | |
1319 } | |
1320 cell = cell._next; | |
1321 } | |
1322 } | |
1323 | |
1324 E get first { | |
1325 if (_first == null) throw new StateError("No elements"); | |
1326 return _first._element; | |
1327 } | |
1328 | |
1329 E get last { | |
1330 if (_last == null) throw new StateError("No elements"); | |
1331 return _last._element; | |
1332 } | |
1333 | |
1334 // Collection. | |
1335 bool add(E element) { | |
1336 if (_isStringElement(element)) { | |
1337 var strings = _strings; | |
1338 if (strings == null) _strings = strings = _newHashTable(); | |
1339 return _addHashTableEntry(strings, element); | |
1340 } else if (_isNumericElement(element)) { | |
1341 var nums = _nums; | |
1342 if (nums == null) _nums = nums = _newHashTable(); | |
1343 return _addHashTableEntry(nums, element); | |
1344 } else { | |
1345 return _add(element); | |
1346 } | |
1347 } | |
1348 | |
1349 bool _add(E element) { | |
1350 var rest = _rest; | |
1351 if (rest == null) _rest = rest = _newHashTable(); | |
1352 var hash = _computeHashCode(element); | |
1353 var bucket = JS('var', '#[#]', rest, hash); | |
1354 if (bucket == null) { | |
1355 LinkedHashSetCell cell = _newLinkedCell(element); | |
1356 _setTableEntry(rest, hash, JS('var', '[#]', cell)); | |
1357 } else { | |
1358 int index = _findBucketIndex(bucket, element); | |
1359 if (index >= 0) return false; | |
1360 LinkedHashSetCell cell = _newLinkedCell(element); | |
1361 JS('void', '#.push(#)', bucket, cell); | |
1362 } | |
1363 return true; | |
1364 } | |
1365 | |
1366 bool remove(Object object) { | |
1367 if (_isStringElement(object)) { | |
1368 return _removeHashTableEntry(_strings, object); | |
1369 } else if (_isNumericElement(object)) { | |
1370 return _removeHashTableEntry(_nums, object); | |
1371 } else { | |
1372 return _remove(object); | |
1373 } | |
1374 } | |
1375 | |
1376 bool _remove(Object object) { | |
1377 var rest = _rest; | |
1378 if (rest == null) return false; | |
1379 var bucket = _getBucket(rest, object); | |
1380 int index = _findBucketIndex(bucket, object); | |
1381 if (index < 0) return false; | |
1382 // Use splice to remove the [cell] element at the index and | |
1383 // unlink it. | |
1384 LinkedHashSetCell cell = JS('var', '#.splice(#, 1)[0]', bucket, index); | |
1385 _unlinkCell(cell); | |
1386 return true; | |
1387 } | |
1388 | |
1389 void removeWhere(bool test(E element)) { | |
1390 _filterWhere(test, true); | |
1391 } | |
1392 | |
1393 void retainWhere(bool test(E element)) { | |
1394 _filterWhere(test, false); | |
1395 } | |
1396 | |
1397 void _filterWhere(bool test(E element), bool removeMatching) { | |
1398 LinkedHashSetCell cell = _first; | |
1399 while (cell != null) { | |
1400 E element = cell._element; | |
1401 LinkedHashSetCell next = cell._next; | |
1402 int modifications = _modifications; | |
1403 bool shouldRemove = (removeMatching == test(element)); | |
1404 if (modifications != _modifications) { | |
1405 throw new ConcurrentModificationError(this); | |
1406 } | |
1407 if (shouldRemove) remove(element); | |
1408 cell = next; | |
1409 } | |
1410 } | |
1411 | |
1412 void clear() { | |
1413 if (_length > 0) { | |
1414 _strings = _nums = _rest = _first = _last = null; | |
1415 _length = 0; | |
1416 _modified(); | |
1417 } | |
1418 } | |
1419 | |
1420 bool _addHashTableEntry(var table, E element) { | |
1421 LinkedHashSetCell cell = _getTableEntry(table, element); | |
1422 if (cell != null) return false; | |
1423 _setTableEntry(table, element, _newLinkedCell(element)); | |
1424 return true; | |
1425 } | |
1426 | |
1427 bool _removeHashTableEntry(var table, Object element) { | |
1428 if (table == null) return false; | |
1429 LinkedHashSetCell cell = _getTableEntry(table, element); | |
1430 if (cell == null) return false; | |
1431 _unlinkCell(cell); | |
1432 _deleteTableEntry(table, element); | |
1433 return true; | |
1434 } | |
1435 | |
1436 void _modified() { | |
1437 // Value cycles after 2^30 modifications. If you keep hold of an | |
1438 // iterator for that long, you might miss a modification | |
1439 // detection, and iteration can go sour. Don't do that. | |
1440 _modifications = (_modifications + 1) & 0x3ffffff; | |
1441 } | |
1442 | |
1443 // Create a new cell and link it in as the last one in the list. | |
1444 LinkedHashSetCell _newLinkedCell(E element) { | |
1445 LinkedHashSetCell cell = new LinkedHashSetCell(element); | |
1446 if (_first == null) { | |
1447 _first = _last = cell; | |
1448 } else { | |
1449 LinkedHashSetCell last = _last; | |
1450 cell._previous = last; | |
1451 _last = last._next = cell; | |
1452 } | |
1453 _length++; | |
1454 _modified(); | |
1455 return cell; | |
1456 } | |
1457 | |
1458 // Unlink the given cell from the linked list of cells. | |
1459 void _unlinkCell(LinkedHashSetCell cell) { | |
1460 LinkedHashSetCell previous = cell._previous; | |
1461 LinkedHashSetCell next = cell._next; | |
1462 if (previous == null) { | |
1463 assert(cell == _first); | |
1464 _first = next; | |
1465 } else { | |
1466 previous._next = next; | |
1467 } | |
1468 if (next == null) { | |
1469 assert(cell == _last); | |
1470 _last = previous; | |
1471 } else { | |
1472 next._previous = previous; | |
1473 } | |
1474 _length--; | |
1475 _modified(); | |
1476 } | |
1477 | |
1478 static bool _isStringElement(var element) { | |
1479 return element is String && element != '__proto__'; | |
1480 } | |
1481 | |
1482 static bool _isNumericElement(var element) { | |
1483 // Only treat unsigned 30-bit integers as numeric elements. This | |
1484 // way, we avoid converting them to strings when we use them as | |
1485 // keys in the JavaScript hash table object. | |
1486 return element is num && | |
1487 JS('bool', '(# & 0x3ffffff) === #', element, element); | |
1488 } | |
1489 | |
1490 int _computeHashCode(var element) { | |
1491 // We force the hash codes to be unsigned 30-bit integers to avoid | |
1492 // issues with problematic elements like '__proto__'. Another | |
1493 // option would be to throw an exception if the hash code isn't a | |
1494 // number. | |
1495 return JS('int', '# & 0x3ffffff', element.hashCode); | |
1496 } | |
1497 | |
1498 static _getTableEntry(var table, var key) { | |
1499 return JS('var', '#[#]', table, key); | |
1500 } | |
1501 | |
1502 static void _setTableEntry(var table, var key, var value) { | |
1503 assert(value != null); | |
1504 JS('void', '#[#] = #', table, key, value); | |
1505 } | |
1506 | |
1507 static void _deleteTableEntry(var table, var key) { | |
1508 JS('void', 'delete #[#]', table, key); | |
1509 } | |
1510 | |
1511 List _getBucket(var table, var element) { | |
1512 var hash = _computeHashCode(element); | |
1513 return JS('var', '#[#]', table, hash); | |
1514 } | |
1515 | |
1516 int _findBucketIndex(var bucket, var element) { | |
1517 if (bucket == null) return -1; | |
1518 int length = JS('int', '#.length', bucket); | |
1519 for (int i = 0; i < length; i++) { | |
1520 LinkedHashSetCell cell = JS('var', '#[#]', bucket, i); | |
1521 if (cell._element == element) return i; | |
1522 } | |
1523 return -1; | |
1524 } | |
1525 | |
1526 static _newHashTable() { | |
1527 // Create a new JavaScript object to be used as a hash table. Use | |
1528 // Object.create to avoid the properties on Object.prototype | |
1529 // showing up as entries. | |
1530 var table = JS('var', 'Object.create(null)'); | |
1531 // Attempt to force the hash table into 'dictionary' mode by | |
1532 // adding a property to it and deleting it again. | |
1533 var temporaryKey = '<non-identifier-key>'; | |
1534 _setTableEntry(table, temporaryKey, table); | |
1535 _deleteTableEntry(table, temporaryKey); | |
1536 return table; | |
1537 } | |
1538 } | |
1539 | |
1540 class _LinkedIdentityHashSet<E> extends _LinkedHashSet<E> { | |
1541 Set<E> _newSet() => new _LinkedIdentityHashSet<E>(); | |
1542 | |
1543 int _computeHashCode(var key) { | |
1544 // We force the hash codes to be unsigned 30-bit integers to avoid | |
1545 // issues with problematic keys like '__proto__'. Another option | |
1546 // would be to throw an exception if the hash code isn't a number. | |
1547 return JS('int', '# & 0x3ffffff', identityHashCode(key)); | |
1548 } | |
1549 | |
1550 int _findBucketIndex(var bucket, var element) { | |
1551 if (bucket == null) return -1; | |
1552 int length = JS('int', '#.length', bucket); | |
1553 for (int i = 0; i < length; i++) { | |
1554 LinkedHashSetCell cell = JS('var', '#[#]', bucket, i); | |
1555 if (identical(cell._element, element)) return i; | |
1556 } | |
1557 return -1; | |
1558 } | |
1559 } | |
1560 | |
1561 class _LinkedCustomHashSet<E> extends _LinkedHashSet<E> { | |
1562 _Equality<E> _equality; | |
1563 _Hasher<E> _hasher; | |
1564 _Predicate _validKey; | |
1565 _LinkedCustomHashSet(this._equality, this._hasher, | |
1566 bool validKey(potentialKey)) | |
1567 : _validKey = (validKey != null) ? validKey : ((x) => x is E); | |
1568 | |
1569 Set<E> _newSet() => | |
1570 new _LinkedCustomHashSet<E>(_equality, _hasher, _validKey); | |
1571 | |
1572 int _findBucketIndex(var bucket, var element) { | |
1573 if (bucket == null) return -1; | |
1574 int length = JS('int', '#.length', bucket); | |
1575 for (int i = 0; i < length; i++) { | |
1576 LinkedHashSetCell cell = JS('var', '#[#]', bucket, i); | |
1577 if (_equality(cell._element, element)) return i; | |
1578 } | |
1579 return -1; | |
1580 } | |
1581 | |
1582 int _computeHashCode(var element) { | |
1583 // We force the hash codes to be unsigned 30-bit integers to avoid | |
1584 // issues with problematic elements like '__proto__'. Another | |
1585 // option would be to throw an exception if the hash code isn't a | |
1586 // number. | |
1587 return JS('int', '# & 0x3ffffff', _hasher(element)); | |
1588 } | |
1589 | |
1590 bool add(E element) => super._add(element); | |
1591 | |
1592 bool contains(Object object) { | |
1593 if (!_validKey(object)) return false; | |
1594 return super._contains(object); | |
1595 } | |
1596 | |
1597 E lookup(Object object) { | |
1598 if (!_validKey(object)) return null; | |
1599 return super._lookup(object); | |
1600 } | |
1601 | |
1602 bool remove(Object object) { | |
1603 if (!_validKey(object)) return false; | |
1604 return super._remove(object); | |
1605 } | |
1606 | |
1607 bool containsAll(Iterable<Object> elements) { | |
1608 for (Object element in elements) { | |
1609 if (!_validKey(element) || !this.contains(element)) return false; | |
1610 } | |
1611 return true; | |
1612 } | |
1613 | |
1614 void removeAll(Iterable<Object> elements) { | |
1615 for (Object element in elements) { | |
1616 if (_validKey(element)) { | |
1617 super._remove(element); | |
1618 } | |
1619 } | |
1620 } | |
1621 } | |
1622 | |
1623 class LinkedHashSetCell { | |
1624 final _element; | |
1625 | |
1626 LinkedHashSetCell _next; | |
1627 LinkedHashSetCell _previous; | |
1628 | |
1629 LinkedHashSetCell(this._element); | |
1630 } | |
1631 | |
1632 // TODO(kasperl): Share this code with LinkedHashMapKeyIterator<E>? | |
1633 class LinkedHashSetIterator<E> implements Iterator<E> { | |
1634 final _set; | |
1635 final int _modifications; | |
1636 LinkedHashSetCell _cell; | |
1637 E _current; | |
1638 | |
1639 LinkedHashSetIterator(this._set, this._modifications) { | |
1640 _cell = _set._first; | |
1641 } | |
1642 | |
1643 E get current => _current; | |
1644 | |
1645 bool moveNext() { | |
1646 if (_modifications != _set._modifications) { | |
1647 throw new ConcurrentModificationError(_set); | |
1648 } else if (_cell == null) { | |
1649 _current = null; | |
1650 return false; | |
1651 } else { | |
1652 _current = _cell._element; | |
1653 _cell = _cell._next; | |
1654 return true; | |
1655 } | |
1656 } | |
1657 } | |
OLD | NEW |