Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(30)

Side by Side Diff: sdk/lib/_internal/compiler/implementation/lib/collection_patch.dart

Issue 16538002: Move lib from sdk/lib/_internal/compiler/implementation to _internal root. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 7 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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
8 patch class HashMap<K, V> {
9 int _length = 0;
10
11 // The hash map contents are divided into three parts: one part for
12 // string keys, one for numeric keys, and one for the rest. String
13 // and numeric keys map directly to their values, but the rest of
14 // the entries are stored in bucket lists of the form:
15 //
16 // [key-0, value-0, key-1, value-1, ...]
17 //
18 // where all keys in the same bucket share the same hash code.
19 var _strings;
20 var _nums;
21 var _rest;
22
23 // When iterating over the hash map, it is very convenient to have a
24 // list of all the keys. We cache that on the instance and clear the
25 // the cache whenever the key set changes. This is also used to
26 // guard against concurrent modifications.
27 List _keys;
28
29 patch HashMap();
30
31 patch int get length => _length;
32 patch bool get isEmpty => _length == 0;
33 patch bool get isNotEmpty => !isEmpty;
34
35 patch Iterable<K> get keys {
36 return new HashMapKeyIterable<K>(this);
37 }
38
39 patch Iterable<V> get values {
40 return keys.map((each) => this[each]);
41 }
42
43 patch bool containsKey(K key) {
44 if (_isStringKey(key)) {
45 var strings = _strings;
46 return (strings == null) ? false : _hasTableEntry(strings, key);
47 } else if (_isNumericKey(key)) {
48 var nums = _nums;
49 return (nums == null) ? false : _hasTableEntry(nums, key);
50 } else {
51 var rest = _rest;
52 if (rest == null) return false;
53 var bucket = _getBucket(rest, key);
54 return _findBucketIndex(bucket, key) >= 0;
55 }
56 }
57
58 patch bool containsValue(V value) {
59 return _computeKeys().any((each) => this[each] == value);
60 }
61
62 patch void addAll(Map<K, V> other) {
63 other.forEach((K key, V value) {
64 this[key] = value;
65 });
66 }
67
68 patch V operator[](K key) {
69 if (_isStringKey(key)) {
70 var strings = _strings;
71 return (strings == null) ? null : _getTableEntry(strings, key);
72 } else if (_isNumericKey(key)) {
73 var nums = _nums;
74 return (nums == null) ? null : _getTableEntry(nums, key);
75 } else {
76 var rest = _rest;
77 if (rest == null) return null;
78 var bucket = _getBucket(rest, key);
79 int index = _findBucketIndex(bucket, key);
80 return (index < 0) ? null : JS('var', '#[#]', bucket, index + 1);
81 }
82 }
83
84 patch void operator[]=(K key, V value) {
85 if (_isStringKey(key)) {
86 var strings = _strings;
87 if (strings == null) _strings = strings = _newHashTable();
88 _addHashTableEntry(strings, key, value);
89 } else if (_isNumericKey(key)) {
90 var nums = _nums;
91 if (nums == null) _nums = nums = _newHashTable();
92 _addHashTableEntry(nums, key, value);
93 } else {
94 var rest = _rest;
95 if (rest == null) _rest = rest = _newHashTable();
96 var hash = _computeHashCode(key);
97 var bucket = JS('var', '#[#]', rest, hash);
98 if (bucket == null) {
99 _setTableEntry(rest, hash, JS('var', '[#, #]', key, value));
100 _length++;
101 _keys = null;
102 } else {
103 int index = _findBucketIndex(bucket, key);
104 if (index >= 0) {
105 JS('void', '#[#] = #', bucket, index + 1, value);
106 } else {
107 JS('void', '#.push(#, #)', bucket, key, value);
108 _length++;
109 _keys = null;
110 }
111 }
112 }
113 }
114
115 patch V putIfAbsent(K key, V ifAbsent()) {
116 if (containsKey(key)) return this[key];
117 V value = ifAbsent();
118 this[key] = value;
119 return value;
120 }
121
122 patch V remove(K key) {
123 if (_isStringKey(key)) {
124 return _removeHashTableEntry(_strings, key);
125 } else if (_isNumericKey(key)) {
126 return _removeHashTableEntry(_nums, key);
127 } else {
128 var rest = _rest;
129 if (rest == null) return null;
130 var bucket = _getBucket(rest, key);
131 int index = _findBucketIndex(bucket, key);
132 if (index < 0) return null;
133 // TODO(kasperl): Consider getting rid of the bucket list when
134 // the length reaches zero.
135 _length--;
136 _keys = null;
137 // Use splice to remove the two [key, value] elements at the
138 // index and return the value.
139 return JS('var', '#.splice(#, 2)[1]', bucket, index);
140 }
141 }
142
143 patch void clear() {
144 if (_length > 0) {
145 _strings = _nums = _rest = _keys = null;
146 _length = 0;
147 }
148 }
149
150 patch void forEach(void action(K key, V value)) {
151 List keys = _computeKeys();
152 for (int i = 0, length = keys.length; i < length; i++) {
153 var key = JS('var', '#[#]', keys, i);
154 action(key, this[key]);
155 if (JS('bool', '# !== #', keys, _keys)) {
156 throw new ConcurrentModificationError(this);
157 }
158 }
159 }
160
161 List _computeKeys() {
162 if (_keys != null) return _keys;
163 List result = new List(_length);
164 int index = 0;
165
166 // Add all string keys to the list.
167 var strings = _strings;
168 if (strings != null) {
169 var names = JS('var', 'Object.getOwnPropertyNames(#)', strings);
170 int entries = JS('int', '#.length', names);
171 for (int i = 0; i < entries; i++) {
172 String key = JS('String', '#[#]', names, i);
173 JS('void', '#[#] = #', result, index, key);
174 index++;
175 }
176 }
177
178 // Add all numeric keys to the list.
179 var nums = _nums;
180 if (nums != null) {
181 var names = JS('var', 'Object.getOwnPropertyNames(#)', nums);
182 int entries = JS('int', '#.length', names);
183 for (int i = 0; i < entries; i++) {
184 // Object.getOwnPropertyNames returns a list of strings, so we
185 // have to convert the keys back to numbers (+).
186 num key = JS('num', '+#[#]', names, i);
187 JS('void', '#[#] = #', result, index, key);
188 index++;
189 }
190 }
191
192 // Add all the remaining keys to the list.
193 var rest = _rest;
194 if (rest != null) {
195 var names = JS('var', 'Object.getOwnPropertyNames(#)', rest);
196 int entries = JS('int', '#.length', names);
197 for (int i = 0; i < entries; i++) {
198 var key = JS('String', '#[#]', names, i);
199 var bucket = JS('var', '#[#]', rest, key);
200 int length = JS('int', '#.length', bucket);
201 for (int i = 0; i < length; i += 2) {
202 var key = JS('var', '#[#]', bucket, i);
203 JS('void', '#[#] = #', result, index, key);
204 index++;
205 }
206 }
207 }
208 assert(index == _length);
209 return _keys = result;
210 }
211
212 void _addHashTableEntry(var table, K key, V value) {
213 if (!_hasTableEntry(table, key)) {
214 _length++;
215 _keys = null;
216 }
217 _setTableEntry(table, key, value);
218 }
219
220 V _removeHashTableEntry(var table, K key) {
221 if (table != null && _hasTableEntry(table, key)) {
222 V value = _getTableEntry(table, key);
223 _deleteTableEntry(table, key);
224 _length--;
225 _keys = null;
226 return value;
227 } else {
228 return null;
229 }
230 }
231
232 static bool _isStringKey(var key) {
233 return key is String && key != '__proto__';
234 }
235
236 static bool _isNumericKey(var key) {
237 // Only treat unsigned 30-bit integers as numeric keys. This way,
238 // we avoid converting them to strings when we use them as keys in
239 // the JavaScript hash table object.
240 return key is num && JS('bool', '(# & 0x3ffffff) === #', key, key);
241 }
242
243 static int _computeHashCode(var key) {
244 // We force the hash codes to be unsigned 30-bit integers to avoid
245 // issues with problematic keys like '__proto__'. Another option
246 // would be to throw an exception if the hash code isn't a number.
247 return JS('int', '# & 0x3ffffff', key.hashCode);
248 }
249
250 static bool _hasTableEntry(var table, var key) {
251 var entry = JS('var', '#[#]', table, key);
252 // We take care to only store non-null entries in the table, so we
253 // can check if the table has an entry for the given key with a
254 // simple null check.
255 return entry != null;
256 }
257
258 static _getTableEntry(var table, var key) {
259 var entry = JS('var', '#[#]', table, key);
260 // We store the table itself as the entry to signal that it really
261 // is a null value, so we have to map back to null here.
262 return JS('bool', '# === #', entry, table) ? null : entry;
263 }
264
265 static void _setTableEntry(var table, var key, var value) {
266 // We only store non-null entries in the table, so we have to
267 // change null values to refer to the table itself. Such values
268 // will be recognized and mapped back to null on access.
269 if (value == null) {
270 // Do not update [value] with [table], otherwise our
271 // optimizations could be confused by this opaque object being
272 // now used for more things than storing and fetching from it.
273 JS('void', '#[#] = #', table, key, table);
274 } else {
275 JS('void', '#[#] = #', table, key, value);
276 }
277 }
278
279 static void _deleteTableEntry(var table, var key) {
280 JS('void', 'delete #[#]', table, key);
281 }
282
283 static List _getBucket(var table, var key) {
284 var hash = _computeHashCode(key);
285 return JS('var', '#[#]', table, hash);
286 }
287
288 static int _findBucketIndex(var bucket, var key) {
289 if (bucket == null) return -1;
290 int length = JS('int', '#.length', bucket);
291 for (int i = 0; i < length; i += 2) {
292 if (JS('var', '#[#]', bucket, i) == key) return i;
293 }
294 return -1;
295 }
296
297 static _newHashTable() {
298 // Create a new JavaScript object to be used as a hash table. Use
299 // Object.create to avoid the properties on Object.prototype
300 // showing up as entries.
301 var table = JS('var', 'Object.create(null)');
302 // Attempt to force the hash table into 'dictionary' mode by
303 // adding a property to it and deleting it again.
304 var temporaryKey = '<non-identifier-key>';
305 _setTableEntry(table, temporaryKey, table);
306 _deleteTableEntry(table, temporaryKey);
307 return table;
308 }
309 }
310
311 class HashMapKeyIterable<E> extends IterableBase<E> {
312 final _map;
313 HashMapKeyIterable(this._map);
314
315 int get length => _map._length;
316 bool get isEmpty => _map._length == 0;
317
318 Iterator<E> get iterator {
319 return new HashMapKeyIterator<E>(_map, _map._computeKeys());
320 }
321
322 bool contains(E element) {
323 return _map.containsKey(element);
324 }
325
326 void forEach(void f(E element)) {
327 List keys = _map._computeKeys();
328 for (int i = 0, length = JS('int', '#.length', keys); i < length; i++) {
329 f(JS('var', '#[#]', keys, i));
330 if (JS('bool', '# !== #', keys, _map._keys)) {
331 throw new ConcurrentModificationError(_map);
332 }
333 }
334 }
335 }
336
337 class HashMapKeyIterator<E> implements Iterator<E> {
338 final _map;
339 final List _keys;
340 int _offset = 0;
341 E _current;
342
343 HashMapKeyIterator(this._map, this._keys);
344
345 E get current => _current;
346
347 bool moveNext() {
348 var keys = _keys;
349 int offset = _offset;
350 if (JS('bool', '# !== #', keys, _map._keys)) {
351 throw new ConcurrentModificationError(_map);
352 } else if (offset >= JS('int', '#.length', keys)) {
353 _current = null;
354 return false;
355 } else {
356 _current = JS('var', '#[#]', keys, offset);
357 // TODO(kasperl): For now, we have to tell the type inferrer to
358 // treat the result of doing offset + 1 as an int. Otherwise, we
359 // get unnecessary bailout code.
360 _offset = JS('int', '#', offset + 1);
361 return true;
362 }
363 }
364 }
365
366 patch class LinkedHashMap<K, V> {
367 int _length = 0;
368
369 // The hash map contents are divided into three parts: one part for
370 // string keys, one for numeric keys, and one for the rest. String
371 // and numeric keys map directly to their linked cells, but the rest
372 // of the entries are stored in bucket lists of the form:
373 //
374 // [cell-0, cell-1, ...]
375 //
376 // where all keys in the same bucket share the same hash code.
377 var _strings;
378 var _nums;
379 var _rest;
380
381 // The keys and values are stored in cells that are linked together
382 // to form a double linked list.
383 LinkedHashMapCell _first;
384 LinkedHashMapCell _last;
385
386 // We track the number of modifications done to the key set of the
387 // hash map to be able to throw when the map is modified while being
388 // iterated over.
389 int _modifications = 0;
390
391 patch LinkedHashMap();
392
393 patch int get length => _length;
394 patch bool get isEmpty => _length == 0;
395 patch bool get isNotEmpty => !isEmpty;
396
397
398 patch Iterable<K> get keys {
399 return new LinkedHashMapKeyIterable<K>(this);
400 }
401
402 patch Iterable<V> get values {
403 return keys.map((each) => this[each]);
404 }
405
406 patch bool containsKey(K key) {
407 if (_isStringKey(key)) {
408 var strings = _strings;
409 if (strings == null) return false;
410 LinkedHashMapCell cell = _getTableEntry(strings, key);
411 return cell != null;
412 } else if (_isNumericKey(key)) {
413 var nums = _nums;
414 if (nums == null) return false;
415 LinkedHashMapCell cell = _getTableEntry(nums, key);
416 return cell != null;
417 } else {
418 var rest = _rest;
419 if (rest == null) return false;
420 var bucket = _getBucket(rest, key);
421 return _findBucketIndex(bucket, key) >= 0;
422 }
423 }
424
425 patch bool containsValue(V value) {
426 return keys.any((each) => this[each] == value);
427 }
428
429 patch void addAll(Map<K, V> other) {
430 other.forEach((K key, V value) {
431 this[key] = value;
432 });
433 }
434
435 patch V operator[](K key) {
436 if (_isStringKey(key)) {
437 var strings = _strings;
438 if (strings == null) return null;
439 LinkedHashMapCell cell = _getTableEntry(strings, key);
440 return (cell == null) ? null : cell._value;
441 } else if (_isNumericKey(key)) {
442 var nums = _nums;
443 if (nums == null) return null;
444 LinkedHashMapCell cell = _getTableEntry(nums, key);
445 return (cell == null) ? null : cell._value;
446 } else {
447 var rest = _rest;
448 if (rest == null) return null;
449 var bucket = _getBucket(rest, key);
450 int index = _findBucketIndex(bucket, key);
451 if (index < 0) return null;
452 LinkedHashMapCell cell = JS('var', '#[#]', bucket, index);
453 return cell._value;
454 }
455 }
456
457 patch void operator[]=(K key, V value) {
458 if (_isStringKey(key)) {
459 var strings = _strings;
460 if (strings == null) _strings = strings = _newHashTable();
461 _addHashTableEntry(strings, key, value);
462 } else if (_isNumericKey(key)) {
463 var nums = _nums;
464 if (nums == null) _nums = nums = _newHashTable();
465 _addHashTableEntry(nums, key, value);
466 } else {
467 var rest = _rest;
468 if (rest == null) _rest = rest = _newHashTable();
469 var hash = _computeHashCode(key);
470 var bucket = JS('var', '#[#]', rest, hash);
471 if (bucket == null) {
472 LinkedHashMapCell cell = _newLinkedCell(key, value);
473 _setTableEntry(rest, hash, JS('var', '[#]', cell));
474 } else {
475 int index = _findBucketIndex(bucket, key);
476 if (index >= 0) {
477 LinkedHashMapCell cell = JS('var', '#[#]', bucket, index);
478 cell._value = value;
479 } else {
480 LinkedHashMapCell cell = _newLinkedCell(key, value);
481 JS('void', '#.push(#)', bucket, cell);
482 }
483 }
484 }
485 }
486
487 patch V putIfAbsent(K key, V ifAbsent()) {
488 if (containsKey(key)) return this[key];
489 V value = ifAbsent();
490 this[key] = value;
491 return value;
492 }
493
494 patch V remove(K key) {
495 if (_isStringKey(key)) {
496 return _removeHashTableEntry(_strings, key);
497 } else if (_isNumericKey(key)) {
498 return _removeHashTableEntry(_nums, key);
499 } else {
500 var rest = _rest;
501 if (rest == null) return null;
502 var bucket = _getBucket(rest, key);
503 int index = _findBucketIndex(bucket, key);
504 if (index < 0) return null;
505 // Use splice to remove the [cell] element at the index and
506 // unlink the cell before returning its value.
507 LinkedHashMapCell cell = JS('var', '#.splice(#, 1)[0]', bucket, index);
508 _unlinkCell(cell);
509 // TODO(kasperl): Consider getting rid of the bucket list when
510 // the length reaches zero.
511 return cell._value;
512 }
513 }
514
515 patch void clear() {
516 if (_length > 0) {
517 _strings = _nums = _rest = _first = _last = null;
518 _length = 0;
519 _modified();
520 }
521 }
522
523 patch void forEach(void action(K key, V value)) {
524 LinkedHashMapCell cell = _first;
525 int modifications = _modifications;
526 while (cell != null) {
527 action(cell._key, cell._value);
528 if (modifications != _modifications) {
529 throw new ConcurrentModificationError(this);
530 }
531 cell = cell._next;
532 }
533 }
534
535 void _addHashTableEntry(var table, K key, V value) {
536 LinkedHashMapCell cell = _getTableEntry(table, key);
537 if (cell == null) {
538 _setTableEntry(table, key, _newLinkedCell(key, value));
539 } else {
540 cell._value = value;
541 }
542 }
543
544 V _removeHashTableEntry(var table, K key) {
545 if (table == null) return null;
546 LinkedHashMapCell cell = _getTableEntry(table, key);
547 if (cell == null) return null;
548 _unlinkCell(cell);
549 _deleteTableEntry(table, key);
550 return cell._value;
551 }
552
553 void _modified() {
554 // Value cycles after 2^30 modifications. If you keep hold of an
555 // iterator for that long, you might miss a modification
556 // detection, and iteration can go sour. Don't do that.
557 _modifications = (_modifications + 1) & 0x3ffffff;
558 }
559
560 // Create a new cell and link it in as the last one in the list.
561 LinkedHashMapCell _newLinkedCell(K key, V value) {
562 LinkedHashMapCell cell = new LinkedHashMapCell(key, value);
563 if (_first == null) {
564 _first = _last = cell;
565 } else {
566 LinkedHashMapCell last = _last;
567 cell._previous = last;
568 _last = last._next = cell;
569 }
570 _length++;
571 _modified();
572 return cell;
573 }
574
575 // Unlink the given cell from the linked list of cells.
576 void _unlinkCell(LinkedHashMapCell cell) {
577 LinkedHashMapCell previous = cell._previous;
578 LinkedHashMapCell next = cell._next;
579 if (previous == null) {
580 assert(cell == _first);
581 _first = next;
582 } else {
583 previous._next = next;
584 }
585 if (next == null) {
586 assert(cell == _last);
587 _last = previous;
588 } else {
589 next._previous = previous;
590 }
591 _length--;
592 _modified();
593 }
594
595 static bool _isStringKey(var key) {
596 return key is String && key != '__proto__';
597 }
598
599 static bool _isNumericKey(var key) {
600 // Only treat unsigned 30-bit integers as numeric keys. This way,
601 // we avoid converting them to strings when we use them as keys in
602 // the JavaScript hash table object.
603 return key is num && JS('bool', '(# & 0x3ffffff) === #', key, key);
604 }
605
606 static int _computeHashCode(var key) {
607 // We force the hash codes to be unsigned 30-bit integers to avoid
608 // issues with problematic keys like '__proto__'. Another option
609 // would be to throw an exception if the hash code isn't a number.
610 return JS('int', '# & 0x3ffffff', key.hashCode);
611 }
612
613 static _getTableEntry(var table, var key) {
614 return JS('var', '#[#]', table, key);
615 }
616
617 static void _setTableEntry(var table, var key, var value) {
618 assert(value != null);
619 JS('void', '#[#] = #', table, key, value);
620 }
621
622 static void _deleteTableEntry(var table, var key) {
623 JS('void', 'delete #[#]', table, key);
624 }
625
626 static List _getBucket(var table, var key) {
627 var hash = _computeHashCode(key);
628 return JS('var', '#[#]', table, hash);
629 }
630
631 static int _findBucketIndex(var bucket, var key) {
632 if (bucket == null) return -1;
633 int length = JS('int', '#.length', bucket);
634 for (int i = 0; i < length; i++) {
635 LinkedHashMapCell cell = JS('var', '#[#]', bucket, i);
636 if (cell._key == key) return i;
637 }
638 return -1;
639 }
640
641 static _newHashTable() {
642 // Create a new JavaScript object to be used as a hash table. Use
643 // Object.create to avoid the properties on Object.prototype
644 // showing up as entries.
645 var table = JS('var', 'Object.create(null)');
646 // Attempt to force the hash table into 'dictionary' mode by
647 // adding a property to it and deleting it again.
648 var temporaryKey = '<non-identifier-key>';
649 _setTableEntry(table, temporaryKey, table);
650 _deleteTableEntry(table, temporaryKey);
651 return table;
652 }
653 }
654
655 class LinkedHashMapCell {
656 final _key;
657 var _value;
658
659 LinkedHashMapCell _next;
660 LinkedHashMapCell _previous;
661
662 LinkedHashMapCell(this._key, this._value);
663 }
664
665 class LinkedHashMapKeyIterable<E> extends IterableBase<E> {
666 final _map;
667 LinkedHashMapKeyIterable(this._map);
668
669 int get length => _map._length;
670 bool get isEmpty => _map._length == 0;
671
672 Iterator<E> get iterator {
673 return new LinkedHashMapKeyIterator<E>(_map, _map._modifications);
674 }
675
676 bool contains(E element) {
677 return _map.containsKey(element);
678 }
679
680 void forEach(void f(E element)) {
681 LinkedHashMapCell cell = _map._first;
682 int modifications = _map._modifications;
683 while (cell != null) {
684 f(cell._key);
685 if (modifications != _map._modifications) {
686 throw new ConcurrentModificationError(_map);
687 }
688 cell = cell._next;
689 }
690 }
691 }
692
693 class LinkedHashMapKeyIterator<E> implements Iterator<E> {
694 final _map;
695 final int _modifications;
696 LinkedHashMapCell _cell;
697 E _current;
698
699 LinkedHashMapKeyIterator(this._map, this._modifications) {
700 _cell = _map._first;
701 }
702
703 E get current => _current;
704
705 bool moveNext() {
706 if (_modifications != _map._modifications) {
707 throw new ConcurrentModificationError(_map);
708 } else if (_cell == null) {
709 _current = null;
710 return false;
711 } else {
712 _current = _cell._key;
713 _cell = _cell._next;
714 return true;
715 }
716 }
717 }
718
719 patch class HashSet<E> {
720 int _length = 0;
721
722 // The hash set contents are divided into three parts: one part for
723 // string elements, one for numeric elements, and one for the
724 // rest. String and numeric elements map directly to a sentinel
725 // value, but the rest of the entries are stored in bucket lists of
726 // the form:
727 //
728 // [element-0, element-1, element-2, ...]
729 //
730 // where all elements in the same bucket share the same hash code.
731 var _strings;
732 var _nums;
733 var _rest;
734
735 // When iterating over the hash set, it is very convenient to have a
736 // list of all the elements. We cache that on the instance and clear
737 // the the cache whenever the set changes. This is also used to
738 // guard against concurrent modifications.
739 List _elements;
740
741 patch HashSet();
742
743 // Iterable.
744 patch Iterator<E> get iterator {
745 return new HashSetIterator<E>(this, _computeElements());
746 }
747
748 patch int get length => _length;
749 patch bool get isEmpty => _length == 0;
750 patch bool get isNotEmpty => !isEmpty;
751
752 patch bool contains(Object object) {
753 if (_isStringElement(object)) {
754 var strings = _strings;
755 return (strings == null) ? false : _hasTableEntry(strings, object);
756 } else if (_isNumericElement(object)) {
757 var nums = _nums;
758 return (nums == null) ? false : _hasTableEntry(nums, object);
759 } else {
760 var rest = _rest;
761 if (rest == null) return false;
762 var bucket = _getBucket(rest, object);
763 return _findBucketIndex(bucket, object) >= 0;
764 }
765 }
766
767 // Collection.
768 patch void add(E element) {
769 if (_isStringElement(element)) {
770 var strings = _strings;
771 if (strings == null) _strings = strings = _newHashTable();
772 _addHashTableEntry(strings, element);
773 } else if (_isNumericElement(element)) {
774 var nums = _nums;
775 if (nums == null) _nums = nums = _newHashTable();
776 _addHashTableEntry(nums, element);
777 } else {
778 var rest = _rest;
779 if (rest == null) _rest = rest = _newHashTable();
780 var hash = _computeHashCode(element);
781 var bucket = JS('var', '#[#]', rest, hash);
782 if (bucket == null) {
783 _setTableEntry(rest, hash, JS('var', '[#]', element));
784 } else {
785 int index = _findBucketIndex(bucket, element);
786 if (index >= 0) return;
787 JS('void', '#.push(#)', bucket, element);
788 }
789 _length++;
790 _elements = null;
791 }
792 }
793
794 patch void addAll(Iterable<E> objects) {
795 for (E each in objects) {
796 add(each);
797 }
798 }
799
800 patch bool remove(Object object) {
801 if (_isStringElement(object)) {
802 return _removeHashTableEntry(_strings, object);
803 } else if (_isNumericElement(object)) {
804 return _removeHashTableEntry(_nums, object);
805 } else {
806 var rest = _rest;
807 if (rest == null) return false;
808 var bucket = _getBucket(rest, object);
809 int index = _findBucketIndex(bucket, object);
810 if (index < 0) return false;
811 // TODO(kasperl): Consider getting rid of the bucket list when
812 // the length reaches zero.
813 _length--;
814 _elements = null;
815 // TODO(kasperl): It would probably be faster to move the
816 // element to the end and reduce the length of the bucket list.
817 JS('void', '#.splice(#, 1)', bucket, index);
818 return true;
819 }
820 }
821
822 patch void removeAll(Iterable objectsToRemove) {
823 for (var each in objectsToRemove) {
824 remove(each);
825 }
826 }
827
828 patch void removeWhere(bool test(E element)) {
829 removeAll(_computeElements().where(test));
830 }
831
832 patch void retainWhere(bool test(E element)) {
833 removeAll(_computeElements().where((E element) => !test(element)));
834 }
835
836 patch void clear() {
837 if (_length > 0) {
838 _strings = _nums = _rest = _elements = null;
839 _length = 0;
840 }
841 }
842
843 List _computeElements() {
844 if (_elements != null) return _elements;
845 List result = new List(_length);
846 int index = 0;
847
848 // Add all string elements to the list.
849 var strings = _strings;
850 if (strings != null) {
851 var names = JS('var', 'Object.getOwnPropertyNames(#)', strings);
852 int entries = JS('int', '#.length', names);
853 for (int i = 0; i < entries; i++) {
854 String element = JS('String', '#[#]', names, i);
855 JS('void', '#[#] = #', result, index, element);
856 index++;
857 }
858 }
859
860 // Add all numeric elements to the list.
861 var nums = _nums;
862 if (nums != null) {
863 var names = JS('var', 'Object.getOwnPropertyNames(#)', nums);
864 int entries = JS('int', '#.length', names);
865 for (int i = 0; i < entries; i++) {
866 // Object.getOwnPropertyNames returns a list of strings, so we
867 // have to convert the elements back to numbers (+).
868 num element = JS('num', '+#[#]', names, i);
869 JS('void', '#[#] = #', result, index, element);
870 index++;
871 }
872 }
873
874 // Add all the remaining elements to the list.
875 var rest = _rest;
876 if (rest != null) {
877 var names = JS('var', 'Object.getOwnPropertyNames(#)', rest);
878 int entries = JS('int', '#.length', names);
879 for (int i = 0; i < entries; i++) {
880 var entry = JS('String', '#[#]', names, i);
881 var bucket = JS('var', '#[#]', rest, entry);
882 int length = JS('int', '#.length', bucket);
883 for (int i = 0; i < length; i++) {
884 JS('void', '#[#] = #[#]', result, index, bucket, i);
885 index++;
886 }
887 }
888 }
889 assert(index == _length);
890 return _elements = result;
891 }
892
893 void _addHashTableEntry(var table, E element) {
894 if (_hasTableEntry(table, element)) return;
895 _setTableEntry(table, element, 0);
896 _length++;
897 _elements = null;
898 }
899
900 bool _removeHashTableEntry(var table, E element) {
901 if (table != null && _hasTableEntry(table, element)) {
902 _deleteTableEntry(table, element);
903 _length--;
904 _elements = null;
905 return true;
906 } else {
907 return false;
908 }
909 }
910
911 static bool _isStringElement(var element) {
912 return element is String && element != '__proto__';
913 }
914
915 static bool _isNumericElement(var element) {
916 // Only treat unsigned 30-bit integers as numeric elements. This
917 // way, we avoid converting them to strings when we use them as
918 // keys in the JavaScript hash table object.
919 return element is num &&
920 JS('bool', '(# & 0x3ffffff) === #', element, element);
921 }
922
923 static int _computeHashCode(var element) {
924 // We force the hash codes to be unsigned 30-bit integers to avoid
925 // issues with problematic elements like '__proto__'. Another
926 // option would be to throw an exception if the hash code isn't a
927 // number.
928 return JS('int', '# & 0x3ffffff', element.hashCode);
929 }
930
931 static bool _hasTableEntry(var table, var key) {
932 var entry = JS('var', '#[#]', table, key);
933 // We take care to only store non-null entries in the table, so we
934 // can check if the table has an entry for the given key with a
935 // simple null check.
936 return entry != null;
937 }
938
939 static void _setTableEntry(var table, var key, var value) {
940 assert(value != null);
941 JS('void', '#[#] = #', table, key, value);
942 }
943
944 static void _deleteTableEntry(var table, var key) {
945 JS('void', 'delete #[#]', table, key);
946 }
947
948 static List _getBucket(var table, var element) {
949 var hash = _computeHashCode(element);
950 return JS('var', '#[#]', table, hash);
951 }
952
953 static int _findBucketIndex(var bucket, var element) {
954 if (bucket == null) return -1;
955 int length = JS('int', '#.length', bucket);
956 for (int i = 0; i < length; i++) {
957 if (JS('var', '#[#]', bucket, i) == element) return i;
958 }
959 return -1;
960 }
961
962 static _newHashTable() {
963 // Create a new JavaScript object to be used as a hash table. Use
964 // Object.create to avoid the properties on Object.prototype
965 // showing up as entries.
966 var table = JS('var', 'Object.create(null)');
967 // Attempt to force the hash table into 'dictionary' mode by
968 // adding a property to it and deleting it again.
969 var temporaryKey = '<non-identifier-key>';
970 _setTableEntry(table, temporaryKey, table);
971 _deleteTableEntry(table, temporaryKey);
972 return table;
973 }
974 }
975
976 // TODO(kasperl): Share this code with HashMapKeyIterator<E>?
977 class HashSetIterator<E> implements Iterator<E> {
978 final _set;
979 final List _elements;
980 int _offset = 0;
981 E _current;
982
983 HashSetIterator(this._set, this._elements);
984
985 E get current => _current;
986
987 bool moveNext() {
988 var elements = _elements;
989 int offset = _offset;
990 if (JS('bool', '# !== #', elements, _set._elements)) {
991 throw new ConcurrentModificationError(_set);
992 } else if (offset >= JS('int', '#.length', elements)) {
993 _current = null;
994 return false;
995 } else {
996 _current = JS('var', '#[#]', elements, offset);
997 // TODO(kasperl): For now, we have to tell the type inferrer to
998 // treat the result of doing offset + 1 as an int. Otherwise, we
999 // get unnecessary bailout code.
1000 _offset = JS('int', '#', offset + 1);
1001 return true;
1002 }
1003 }
1004 }
1005
1006 patch class LinkedHashSet<E> extends _HashSetBase<E> {
1007 int _length = 0;
1008
1009 // The hash set contents are divided into three parts: one part for
1010 // string elements, one for numeric elements, and one for the
1011 // rest. String and numeric elements map directly to their linked
1012 // cells, but the rest of the entries are stored in bucket lists of
1013 // the form:
1014 //
1015 // [cell-0, cell-1, ...]
1016 //
1017 // where all elements in the same bucket share the same hash code.
1018 var _strings;
1019 var _nums;
1020 var _rest;
1021
1022 // The elements are stored in cells that are linked together
1023 // to form a double linked list.
1024 LinkedHashSetCell _first;
1025 LinkedHashSetCell _last;
1026
1027 // We track the number of modifications done to the element set to
1028 // be able to throw when the set is modified while being iterated
1029 // over.
1030 int _modifications = 0;
1031
1032 patch LinkedHashSet();
1033
1034 void _unsupported(String operation) {
1035 throw 'LinkedHashSet: unsupported $operation';
1036 }
1037
1038 // Iterable.
1039 patch Iterator<E> get iterator {
1040 return new LinkedHashSetIterator(this, _modifications);
1041 }
1042
1043 patch int get length => _length;
1044 patch bool get isEmpty => _length == 0;
1045 patch bool get isNotEmpty => !isEmpty;
1046
1047 patch bool contains(Object object) {
1048 if (_isStringElement(object)) {
1049 var strings = _strings;
1050 if (strings == null) return false;
1051 LinkedHashSetCell cell = _getTableEntry(strings, object);
1052 return cell != null;
1053 } else if (_isNumericElement(object)) {
1054 var nums = _nums;
1055 if (nums == null) return false;
1056 LinkedHashSetCell cell = _getTableEntry(nums, object);
1057 return cell != null;
1058 } else {
1059 var rest = _rest;
1060 if (rest == null) return false;
1061 var bucket = _getBucket(rest, object);
1062 return _findBucketIndex(bucket, object) >= 0;
1063 }
1064 }
1065
1066 patch void forEach(void action(E element)) {
1067 LinkedHashSetCell cell = _first;
1068 int modifications = _modifications;
1069 while (cell != null) {
1070 action(cell._element);
1071 if (modifications != _modifications) {
1072 throw new ConcurrentModificationError(this);
1073 }
1074 cell = cell._next;
1075 }
1076 }
1077
1078 patch E get first {
1079 if (_first == null) throw new StateError("No elements");
1080 return _first._element;
1081 }
1082
1083 patch E get last {
1084 if (_last == null) throw new StateError("No elements");
1085 return _last._element;
1086 }
1087
1088 // Collection.
1089 patch void add(E element) {
1090 if (_isStringElement(element)) {
1091 var strings = _strings;
1092 if (strings == null) _strings = strings = _newHashTable();
1093 _addHashTableEntry(strings, element);
1094 } else if (_isNumericElement(element)) {
1095 var nums = _nums;
1096 if (nums == null) _nums = nums = _newHashTable();
1097 _addHashTableEntry(nums, element);
1098 } else {
1099 var rest = _rest;
1100 if (rest == null) _rest = rest = _newHashTable();
1101 var hash = _computeHashCode(element);
1102 var bucket = JS('var', '#[#]', rest, hash);
1103 if (bucket == null) {
1104 LinkedHashSetCell cell = _newLinkedCell(element);
1105 _setTableEntry(rest, hash, JS('var', '[#]', cell));
1106 } else {
1107 int index = _findBucketIndex(bucket, element);
1108 if (index >= 0) return;
1109 LinkedHashSetCell cell = _newLinkedCell(element);
1110 JS('void', '#.push(#)', bucket, cell);
1111 }
1112 }
1113 }
1114
1115 patch void addAll(Iterable<E> objects) {
1116 for (E object in objects) {
1117 add(object);
1118 }
1119 }
1120
1121 patch bool remove(Object object) {
1122 if (_isStringElement(object)) {
1123 return _removeHashTableEntry(_strings, object);
1124 } else if (_isNumericElement(object)) {
1125 return _removeHashTableEntry(_nums, object);
1126 } else {
1127 var rest = _rest;
1128 if (rest == null) return false;
1129 var bucket = _getBucket(rest, object);
1130 int index = _findBucketIndex(bucket, object);
1131 if (index < 0) return false;
1132 // Use splice to remove the [cell] element at the index and
1133 // unlink it.
1134 LinkedHashSetCell cell = JS('var', '#.splice(#, 1)[0]', bucket, index);
1135 _unlinkCell(cell);
1136 return true;
1137 }
1138 }
1139
1140 patch void removeAll(Iterable objectsToRemove) {
1141 for (var each in objectsToRemove) {
1142 remove(each);
1143 }
1144 }
1145
1146 patch void removeWhere(bool test(E element)) {
1147 _filterWhere(test, true);
1148 }
1149
1150 patch void retainWhere(bool test(E element)) {
1151 _filterWhere(test, false);
1152 }
1153
1154 void _filterWhere(bool test(E element), bool removeMatching) {
1155 LinkedHashSetCell cell = _first;
1156 while (cell != null) {
1157 E element = cell._element;
1158 LinkedHashSetCell next = cell._next;
1159 int modifications = _modifications;
1160 bool shouldRemove = (removeMatching == test(element));
1161 if (modifications != _modifications) {
1162 throw new ConcurrentModificationError(this);
1163 }
1164 if (shouldRemove) remove(element);
1165 cell = next;
1166 }
1167 }
1168
1169 patch void clear() {
1170 if (_length > 0) {
1171 _strings = _nums = _rest = _first = _last = null;
1172 _length = 0;
1173 _modified();
1174 }
1175 }
1176
1177 void _addHashTableEntry(var table, E element) {
1178 LinkedHashSetCell cell = _getTableEntry(table, element);
1179 if (cell != null) return;
1180 _setTableEntry(table, element, _newLinkedCell(element));
1181 }
1182
1183 bool _removeHashTableEntry(var table, E element) {
1184 if (table == null) return false;
1185 LinkedHashSetCell cell = _getTableEntry(table, element);
1186 if (cell == null) return false;
1187 _unlinkCell(cell);
1188 _deleteTableEntry(table, element);
1189 return true;
1190 }
1191
1192 void _modified() {
1193 // Value cycles after 2^30 modifications. If you keep hold of an
1194 // iterator for that long, you might miss a modification
1195 // detection, and iteration can go sour. Don't do that.
1196 _modifications = (_modifications + 1) & 0x3ffffff;
1197 }
1198
1199 // Create a new cell and link it in as the last one in the list.
1200 LinkedHashSetCell _newLinkedCell(E element) {
1201 LinkedHashSetCell cell = new LinkedHashSetCell(element);
1202 if (_first == null) {
1203 _first = _last = cell;
1204 } else {
1205 LinkedHashSetCell last = _last;
1206 cell._previous = last;
1207 _last = last._next = cell;
1208 }
1209 _length++;
1210 _modified();
1211 return cell;
1212 }
1213
1214 // Unlink the given cell from the linked list of cells.
1215 void _unlinkCell(LinkedHashSetCell cell) {
1216 LinkedHashSetCell previous = cell._previous;
1217 LinkedHashSetCell next = cell._next;
1218 if (previous == null) {
1219 assert(cell == _first);
1220 _first = next;
1221 } else {
1222 previous._next = next;
1223 }
1224 if (next == null) {
1225 assert(cell == _last);
1226 _last = previous;
1227 } else {
1228 next._previous = previous;
1229 }
1230 _length--;
1231 _modified();
1232 }
1233
1234 static bool _isStringElement(var element) {
1235 return element is String && element != '__proto__';
1236 }
1237
1238 static bool _isNumericElement(var element) {
1239 // Only treat unsigned 30-bit integers as numeric elements. This
1240 // way, we avoid converting them to strings when we use them as
1241 // keys in the JavaScript hash table object.
1242 return element is num &&
1243 JS('bool', '(# & 0x3ffffff) === #', element, element);
1244 }
1245
1246 static int _computeHashCode(var element) {
1247 // We force the hash codes to be unsigned 30-bit integers to avoid
1248 // issues with problematic elements like '__proto__'. Another
1249 // option would be to throw an exception if the hash code isn't a
1250 // number.
1251 return JS('int', '# & 0x3ffffff', element.hashCode);
1252 }
1253
1254 static _getTableEntry(var table, var key) {
1255 return JS('var', '#[#]', table, key);
1256 }
1257
1258 static void _setTableEntry(var table, var key, var value) {
1259 assert(value != null);
1260 JS('void', '#[#] = #', table, key, value);
1261 }
1262
1263 static void _deleteTableEntry(var table, var key) {
1264 JS('void', 'delete #[#]', table, key);
1265 }
1266
1267 static List _getBucket(var table, var element) {
1268 var hash = _computeHashCode(element);
1269 return JS('var', '#[#]', table, hash);
1270 }
1271
1272 static int _findBucketIndex(var bucket, var element) {
1273 if (bucket == null) return -1;
1274 int length = JS('int', '#.length', bucket);
1275 for (int i = 0; i < length; i++) {
1276 LinkedHashSetCell cell = JS('var', '#[#]', bucket, i);
1277 if (cell._element == element) return i;
1278 }
1279 return -1;
1280 }
1281
1282 static _newHashTable() {
1283 // Create a new JavaScript object to be used as a hash table. Use
1284 // Object.create to avoid the properties on Object.prototype
1285 // showing up as entries.
1286 var table = JS('var', 'Object.create(null)');
1287 // Attempt to force the hash table into 'dictionary' mode by
1288 // adding a property to it and deleting it again.
1289 var temporaryKey = '<non-identifier-key>';
1290 _setTableEntry(table, temporaryKey, table);
1291 _deleteTableEntry(table, temporaryKey);
1292 return table;
1293 }
1294 }
1295
1296 class LinkedHashSetCell {
1297 final _element;
1298
1299 LinkedHashSetCell _next;
1300 LinkedHashSetCell _previous;
1301
1302 LinkedHashSetCell(this._element);
1303 }
1304
1305 // TODO(kasperl): Share this code with LinkedHashMapKeyIterator<E>?
1306 class LinkedHashSetIterator<E> implements Iterator<E> {
1307 final _set;
1308 final int _modifications;
1309 LinkedHashSetCell _cell;
1310 E _current;
1311
1312 LinkedHashSetIterator(this._set, this._modifications) {
1313 _cell = _set._first;
1314 }
1315
1316 E get current => _current;
1317
1318 bool moveNext() {
1319 if (_modifications != _set._modifications) {
1320 throw new ConcurrentModificationError(_set);
1321 } else if (_cell == null) {
1322 _current = null;
1323 return false;
1324 } else {
1325 _current = _cell._element;
1326 _cell = _cell._next;
1327 return true;
1328 }
1329 }
1330 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698