OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 // Patch file for dart:collection classes. | 5 // Patch file for dart:collection classes. |
6 import 'dart:_foreign_helper' show JS; | 6 import 'dart:_foreign_helper' show JS; |
7 | 7 |
8 patch class HashMap<K, V> { | 8 patch class HashMap<K, V> { |
9 patch factory HashMap({ bool equals(K key1, K key2), int hashCode(K key) }) { | 9 patch factory HashMap({ bool equals(K key1, K key2), |
10 if (hashCode == null) { | 10 int hashCode(K key), |
| 11 bool isValidKey(potentialKey) }) { |
| 12 if (isValidKey == null) { |
| 13 if (hashCode == null) { |
| 14 if (equals == null) { |
| 15 return new _HashMap<K, V>(); |
| 16 } |
| 17 if (identical(identical, equals)) { |
| 18 return new _IdentityHashMap<K, V>(); |
| 19 } |
| 20 hashCode = _defaultHashCode; |
| 21 } else if (equals == null) { |
| 22 equals = _defaultEquals; |
| 23 } |
| 24 } else { |
| 25 if (hashCode == null) { |
| 26 hashCode = _defaultHashCode; |
| 27 } |
11 if (equals == null) { | 28 if (equals == null) { |
12 return new _HashMapImpl<K, V>(); | 29 equals = _defaultEquals; |
13 } | 30 } |
14 if (identical(identical, equals)) { | |
15 return new _IdentityHashMap<K, V>(); | |
16 } | |
17 hashCode = _defaultHashCode; | |
18 } else if (equals == null) { | |
19 equals = _defaultEquals; | |
20 } | 31 } |
21 return new _CustomHashMap<K, V>(equals, hashCode); | 32 return new _CustomHashMap<K, V>(equals, hashCode, isValidKey); |
22 } | 33 } |
23 } | 34 } |
24 | 35 |
25 class _HashMapImpl<K, V> implements HashMap<K, V> { | 36 class _HashMap<K, V> implements HashMap<K, V> { |
26 int _length = 0; | 37 int _length = 0; |
27 | 38 |
28 // The hash map contents are divided into three parts: one part for | 39 // The hash map contents are divided into three parts: one part for |
29 // string keys, one for numeric keys, and one for the rest. String | 40 // string keys, one for numeric keys, and one for the rest. String |
30 // and numeric keys map directly to their values, but the rest of | 41 // and numeric keys map directly to their values, but the rest of |
31 // the entries are stored in bucket lists of the form: | 42 // the entries are stored in bucket lists of the form: |
32 // | 43 // |
33 // [key-0, value-0, key-1, value-1, ...] | 44 // [key-0, value-0, key-1, value-1, ...] |
34 // | 45 // |
35 // where all keys in the same bucket share the same hash code. | 46 // where all keys in the same bucket share the same hash code. |
36 var _strings; | 47 var _strings; |
37 var _nums; | 48 var _nums; |
38 var _rest; | 49 var _rest; |
39 | 50 |
40 // When iterating over the hash map, it is very convenient to have a | 51 // When iterating over the hash map, it is very convenient to have a |
41 // list of all the keys. We cache that on the instance and clear the | 52 // list of all the keys. We cache that on the instance and clear the |
42 // the cache whenever the key set changes. This is also used to | 53 // the cache whenever the key set changes. This is also used to |
43 // guard against concurrent modifications. | 54 // guard against concurrent modifications. |
44 List _keys; | 55 List _keys; |
45 | 56 |
46 _HashMapImpl(); | 57 _Hash(); |
47 | 58 |
48 int get length => _length; | 59 int get length => _length; |
49 bool get isEmpty => _length == 0; | 60 bool get isEmpty => _length == 0; |
50 bool get isNotEmpty => !isEmpty; | 61 bool get isNotEmpty => !isEmpty; |
51 | 62 |
52 Iterable<K> get keys { | 63 Iterable<K> get keys { |
53 return new HashMapKeyIterable<K>(this); | 64 return new HashMapKeyIterable<K>(this); |
54 } | 65 } |
55 | 66 |
56 Iterable<V> get values { | 67 Iterable<V> get values { |
(...skipping 170 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
227 } | 238 } |
228 | 239 |
229 void _addHashTableEntry(var table, K key, V value) { | 240 void _addHashTableEntry(var table, K key, V value) { |
230 if (!_hasTableEntry(table, key)) { | 241 if (!_hasTableEntry(table, key)) { |
231 _length++; | 242 _length++; |
232 _keys = null; | 243 _keys = null; |
233 } | 244 } |
234 _setTableEntry(table, key, value); | 245 _setTableEntry(table, key, value); |
235 } | 246 } |
236 | 247 |
237 V _removeHashTableEntry(var table, K key) { | 248 V _removeHashTableEntry(var table, Object key) { |
238 if (table != null && _hasTableEntry(table, key)) { | 249 if (table != null && _hasTableEntry(table, key)) { |
239 V value = _getTableEntry(table, key); | 250 V value = _getTableEntry(table, key); |
240 _deleteTableEntry(table, key); | 251 _deleteTableEntry(table, key); |
241 _length--; | 252 _length--; |
242 _keys = null; | 253 _keys = null; |
243 return value; | 254 return value; |
244 } else { | 255 } else { |
245 return null; | 256 return null; |
246 } | 257 } |
247 } | 258 } |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
318 var table = JS('var', 'Object.create(null)'); | 329 var table = JS('var', 'Object.create(null)'); |
319 // Attempt to force the hash table into 'dictionary' mode by | 330 // Attempt to force the hash table into 'dictionary' mode by |
320 // adding a property to it and deleting it again. | 331 // adding a property to it and deleting it again. |
321 var temporaryKey = '<non-identifier-key>'; | 332 var temporaryKey = '<non-identifier-key>'; |
322 _setTableEntry(table, temporaryKey, table); | 333 _setTableEntry(table, temporaryKey, table); |
323 _deleteTableEntry(table, temporaryKey); | 334 _deleteTableEntry(table, temporaryKey); |
324 return table; | 335 return table; |
325 } | 336 } |
326 } | 337 } |
327 | 338 |
328 class _IdentityHashMap<K, V> extends _HashMapImpl<K, V> { | 339 class _IdentityHashMap<K, V> extends _HashMap<K, V> { |
329 int _findBucketIndex(var bucket, var key) { | 340 int _findBucketIndex(var bucket, var key) { |
330 if (bucket == null) return -1; | 341 if (bucket == null) return -1; |
331 int length = JS('int', '#.length', bucket); | 342 int length = JS('int', '#.length', bucket); |
332 for (int i = 0; i < length; i += 2) { | 343 for (int i = 0; i < length; i += 2) { |
333 if (identical(JS('var', '#[#]', bucket, i), key)) return i; | 344 if (identical(JS('var', '#[#]', bucket, i), key)) return i; |
334 } | 345 } |
335 return -1; | 346 return -1; |
336 } | 347 } |
337 } | 348 } |
338 | 349 |
339 class _CustomHashMap<K, V> extends _HashMapImpl<K, V> { | 350 class _CustomHashMap<K, V> extends _HashMap<K, V> { |
340 final _Equality<K> _equals; | 351 final _Equality<K> _equals; |
341 final _Hasher<K> _hashCode; | 352 final _Hasher<K> _hashCode; |
342 _CustomHashMap(this._equals, this._hashCode); | 353 final _Predicate _validKey; |
| 354 _CustomHashMap(this._equals, this._hashCode, bool validKey(potentialKey)) |
| 355 : _validKey = (validKey != null) ? validKey : ((v) => v is K); |
| 356 |
| 357 V operator[](Object key) { |
| 358 if (!_validKey(key)) return null; |
| 359 return super[key]; |
| 360 } |
| 361 |
| 362 bool containsKey(Object key) { |
| 363 if (!_validKey(key)) return false; |
| 364 return super.containsKey(key); |
| 365 } |
| 366 |
| 367 V remove(Object key) { |
| 368 if (!_validKey(key)) return null; |
| 369 return super.remove(key); |
| 370 } |
343 | 371 |
344 int _computeHashCode(var key) { | 372 int _computeHashCode(var key) { |
345 // We force the hash codes to be unsigned 30-bit integers to avoid | 373 // We force the hash codes to be unsigned 30-bit integers to avoid |
346 // issues with problematic keys like '__proto__'. Another option | 374 // issues with problematic keys like '__proto__'. Another option |
347 // would be to throw an exception if the hash code isn't a number. | 375 // would be to throw an exception if the hash code isn't a number. |
348 return JS('int', '# & 0x3ffffff', _hashCode(key)); | 376 return JS('int', '# & 0x3ffffff', _hashCode(key)); |
349 } | 377 } |
350 | 378 |
351 int _findBucketIndex(var bucket, var key) { | 379 int _findBucketIndex(var bucket, var key) { |
352 if (bucket == null) return -1; | 380 if (bucket == null) return -1; |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
409 // TODO(kasperl): For now, we have to tell the type inferrer to | 437 // TODO(kasperl): For now, we have to tell the type inferrer to |
410 // treat the result of doing offset + 1 as an int. Otherwise, we | 438 // treat the result of doing offset + 1 as an int. Otherwise, we |
411 // get unnecessary bailout code. | 439 // get unnecessary bailout code. |
412 _offset = JS('int', '#', offset + 1); | 440 _offset = JS('int', '#', offset + 1); |
413 return true; | 441 return true; |
414 } | 442 } |
415 } | 443 } |
416 } | 444 } |
417 | 445 |
418 patch class LinkedHashMap<K, V> { | 446 patch class LinkedHashMap<K, V> { |
| 447 patch factory LinkedHashMap({ bool equals(K key1, K key2), |
| 448 int hashCode(K key), |
| 449 bool isValidKey(potentialKey) }) { |
| 450 if (isValidKey == null) { |
| 451 if (hashCode == null) { |
| 452 if (equals == null) { |
| 453 return new _LinkedHashMap<K, V>(); |
| 454 } |
| 455 if (identical(identical, equals)) { |
| 456 return new _LinkedIdentityHashMap<K, V>(); |
| 457 } |
| 458 hashCode = _defaultHashCode; |
| 459 } else if (equals == null) { |
| 460 equals = _defaultEquals; |
| 461 } |
| 462 } else { |
| 463 if (hashCode == null) { |
| 464 hashCode = _defaultHashCode; |
| 465 } |
| 466 if (equals == null) { |
| 467 equals = _defaultEquals; |
| 468 } |
| 469 } |
| 470 return new _LinkedCustomHashMap<K, V>(equals, hashCode, isValidKey); |
| 471 } |
| 472 } |
| 473 |
| 474 class _LinkedHashMap<K, V> implements LinkedHashMap<K, V> { |
419 int _length = 0; | 475 int _length = 0; |
420 | 476 |
421 // The hash map contents are divided into three parts: one part for | 477 // The hash map contents are divided into three parts: one part for |
422 // string keys, one for numeric keys, and one for the rest. String | 478 // string keys, one for numeric keys, and one for the rest. String |
423 // and numeric keys map directly to their linked cells, but the rest | 479 // and numeric keys map directly to their linked cells, but the rest |
424 // of the entries are stored in bucket lists of the form: | 480 // of the entries are stored in bucket lists of the form: |
425 // | 481 // |
426 // [cell-0, cell-1, ...] | 482 // [cell-0, cell-1, ...] |
427 // | 483 // |
428 // where all keys in the same bucket share the same hash code. | 484 // where all keys in the same bucket share the same hash code. |
429 var _strings; | 485 var _strings; |
430 var _nums; | 486 var _nums; |
431 var _rest; | 487 var _rest; |
432 | 488 |
433 // The keys and values are stored in cells that are linked together | 489 // The keys and values are stored in cells that are linked together |
434 // to form a double linked list. | 490 // to form a double linked list. |
435 LinkedHashMapCell _first; | 491 LinkedHashMapCell _first; |
436 LinkedHashMapCell _last; | 492 LinkedHashMapCell _last; |
437 | 493 |
438 // We track the number of modifications done to the key set of the | 494 // We track the number of modifications done to the key set of the |
439 // hash map to be able to throw when the map is modified while being | 495 // hash map to be able to throw when the map is modified while being |
440 // iterated over. | 496 // iterated over. |
441 int _modifications = 0; | 497 int _modifications = 0; |
442 | 498 |
443 patch LinkedHashMap(); | 499 _LinkedHash(); |
444 | 500 |
445 patch int get length => _length; | 501 int get length => _length; |
446 patch bool get isEmpty => _length == 0; | 502 bool get isEmpty => _length == 0; |
447 patch bool get isNotEmpty => !isEmpty; | 503 bool get isNotEmpty => !isEmpty; |
448 | 504 |
449 | 505 |
450 patch Iterable<K> get keys { | 506 Iterable<K> get keys { |
451 return new LinkedHashMapKeyIterable<K>(this); | 507 return new LinkedHashMapKeyIterable<K>(this); |
452 } | 508 } |
453 | 509 |
454 patch Iterable<V> get values { | 510 Iterable<V> get values { |
455 return keys.map((each) => this[each]); | 511 return keys.map((each) => this[each]); |
456 } | 512 } |
457 | 513 |
458 patch bool containsKey(Object key) { | 514 bool containsKey(Object key) { |
459 if (_isStringKey(key)) { | 515 if (_isStringKey(key)) { |
460 var strings = _strings; | 516 var strings = _strings; |
461 if (strings == null) return false; | 517 if (strings == null) return false; |
462 LinkedHashMapCell cell = _getTableEntry(strings, key); | 518 LinkedHashMapCell cell = _getTableEntry(strings, key); |
463 return cell != null; | 519 return cell != null; |
464 } else if (_isNumericKey(key)) { | 520 } else if (_isNumericKey(key)) { |
465 var nums = _nums; | 521 var nums = _nums; |
466 if (nums == null) return false; | 522 if (nums == null) return false; |
467 LinkedHashMapCell cell = _getTableEntry(nums, key); | 523 LinkedHashMapCell cell = _getTableEntry(nums, key); |
468 return cell != null; | 524 return cell != null; |
469 } else { | 525 } else { |
470 var rest = _rest; | 526 var rest = _rest; |
471 if (rest == null) return false; | 527 if (rest == null) return false; |
472 var bucket = _getBucket(rest, key); | 528 var bucket = _getBucket(rest, key); |
473 return _findBucketIndex(bucket, key) >= 0; | 529 return _findBucketIndex(bucket, key) >= 0; |
474 } | 530 } |
475 } | 531 } |
476 | 532 |
477 patch bool containsValue(Object value) { | 533 bool containsValue(Object value) { |
478 return keys.any((each) => this[each] == value); | 534 return keys.any((each) => this[each] == value); |
479 } | 535 } |
480 | 536 |
481 patch void addAll(Map<K, V> other) { | 537 void addAll(Map<K, V> other) { |
482 other.forEach((K key, V value) { | 538 other.forEach((K key, V value) { |
483 this[key] = value; | 539 this[key] = value; |
484 }); | 540 }); |
485 } | 541 } |
486 | 542 |
487 patch V operator[](Object key) { | 543 V operator[](Object key) { |
488 if (_isStringKey(key)) { | 544 if (_isStringKey(key)) { |
489 var strings = _strings; | 545 var strings = _strings; |
490 if (strings == null) return null; | 546 if (strings == null) return null; |
491 LinkedHashMapCell cell = _getTableEntry(strings, key); | 547 LinkedHashMapCell cell = _getTableEntry(strings, key); |
492 return (cell == null) ? null : cell._value; | 548 return (cell == null) ? null : cell._value; |
493 } else if (_isNumericKey(key)) { | 549 } else if (_isNumericKey(key)) { |
494 var nums = _nums; | 550 var nums = _nums; |
495 if (nums == null) return null; | 551 if (nums == null) return null; |
496 LinkedHashMapCell cell = _getTableEntry(nums, key); | 552 LinkedHashMapCell cell = _getTableEntry(nums, key); |
497 return (cell == null) ? null : cell._value; | 553 return (cell == null) ? null : cell._value; |
498 } else { | 554 } else { |
499 var rest = _rest; | 555 var rest = _rest; |
500 if (rest == null) return null; | 556 if (rest == null) return null; |
501 var bucket = _getBucket(rest, key); | 557 var bucket = _getBucket(rest, key); |
502 int index = _findBucketIndex(bucket, key); | 558 int index = _findBucketIndex(bucket, key); |
503 if (index < 0) return null; | 559 if (index < 0) return null; |
504 LinkedHashMapCell cell = JS('var', '#[#]', bucket, index); | 560 LinkedHashMapCell cell = JS('var', '#[#]', bucket, index); |
505 return cell._value; | 561 return cell._value; |
506 } | 562 } |
507 } | 563 } |
508 | 564 |
509 patch void operator[]=(K key, V value) { | 565 void operator[]=(K key, V value) { |
510 if (_isStringKey(key)) { | 566 if (_isStringKey(key)) { |
511 var strings = _strings; | 567 var strings = _strings; |
512 if (strings == null) _strings = strings = _newHashTable(); | 568 if (strings == null) _strings = strings = _newHashTable(); |
513 _addHashTableEntry(strings, key, value); | 569 _addHashTableEntry(strings, key, value); |
514 } else if (_isNumericKey(key)) { | 570 } else if (_isNumericKey(key)) { |
515 var nums = _nums; | 571 var nums = _nums; |
516 if (nums == null) _nums = nums = _newHashTable(); | 572 if (nums == null) _nums = nums = _newHashTable(); |
517 _addHashTableEntry(nums, key, value); | 573 _addHashTableEntry(nums, key, value); |
518 } else { | 574 } else { |
519 var rest = _rest; | 575 var rest = _rest; |
520 if (rest == null) _rest = rest = _newHashTable(); | 576 if (rest == null) _rest = rest = _newHashTable(); |
521 var hash = _computeHashCode(key); | 577 var hash = _computeHashCode(key); |
522 var bucket = JS('var', '#[#]', rest, hash); | 578 var bucket = JS('var', '#[#]', rest, hash); |
523 if (bucket == null) { | 579 if (bucket == null) { |
524 LinkedHashMapCell cell = _newLinkedCell(key, value); | 580 LinkedHashMapCell cell = _newLinkedCell(key, value); |
525 _setTableEntry(rest, hash, JS('var', '[#]', cell)); | 581 _setTableEntry(rest, hash, JS('var', '[#]', cell)); |
526 } else { | 582 } else { |
527 int index = _findBucketIndex(bucket, key); | 583 int index = _findBucketIndex(bucket, key); |
528 if (index >= 0) { | 584 if (index >= 0) { |
529 LinkedHashMapCell cell = JS('var', '#[#]', bucket, index); | 585 LinkedHashMapCell cell = JS('var', '#[#]', bucket, index); |
530 cell._value = value; | 586 cell._value = value; |
531 } else { | 587 } else { |
532 LinkedHashMapCell cell = _newLinkedCell(key, value); | 588 LinkedHashMapCell cell = _newLinkedCell(key, value); |
533 JS('void', '#.push(#)', bucket, cell); | 589 JS('void', '#.push(#)', bucket, cell); |
534 } | 590 } |
535 } | 591 } |
536 } | 592 } |
537 } | 593 } |
538 | 594 |
539 patch V putIfAbsent(K key, V ifAbsent()) { | 595 V putIfAbsent(K key, V ifAbsent()) { |
540 if (containsKey(key)) return this[key]; | 596 if (containsKey(key)) return this[key]; |
541 V value = ifAbsent(); | 597 V value = ifAbsent(); |
542 this[key] = value; | 598 this[key] = value; |
543 return value; | 599 return value; |
544 } | 600 } |
545 | 601 |
546 patch V remove(Object key) { | 602 V remove(Object key) { |
547 if (_isStringKey(key)) { | 603 if (_isStringKey(key)) { |
548 return _removeHashTableEntry(_strings, key); | 604 return _removeHashTableEntry(_strings, key); |
549 } else if (_isNumericKey(key)) { | 605 } else if (_isNumericKey(key)) { |
550 return _removeHashTableEntry(_nums, key); | 606 return _removeHashTableEntry(_nums, key); |
551 } else { | 607 } else { |
552 var rest = _rest; | 608 var rest = _rest; |
553 if (rest == null) return null; | 609 if (rest == null) return null; |
554 var bucket = _getBucket(rest, key); | 610 var bucket = _getBucket(rest, key); |
555 int index = _findBucketIndex(bucket, key); | 611 int index = _findBucketIndex(bucket, key); |
556 if (index < 0) return null; | 612 if (index < 0) return null; |
557 // Use splice to remove the [cell] element at the index and | 613 // Use splice to remove the [cell] element at the index and |
558 // unlink the cell before returning its value. | 614 // unlink the cell before returning its value. |
559 LinkedHashMapCell cell = JS('var', '#.splice(#, 1)[0]', bucket, index); | 615 LinkedHashMapCell cell = JS('var', '#.splice(#, 1)[0]', bucket, index); |
560 _unlinkCell(cell); | 616 _unlinkCell(cell); |
561 // TODO(kasperl): Consider getting rid of the bucket list when | 617 // TODO(kasperl): Consider getting rid of the bucket list when |
562 // the length reaches zero. | 618 // the length reaches zero. |
563 return cell._value; | 619 return cell._value; |
564 } | 620 } |
565 } | 621 } |
566 | 622 |
567 patch void clear() { | 623 void clear() { |
568 if (_length > 0) { | 624 if (_length > 0) { |
569 _strings = _nums = _rest = _first = _last = null; | 625 _strings = _nums = _rest = _first = _last = null; |
570 _length = 0; | 626 _length = 0; |
571 _modified(); | 627 _modified(); |
572 } | 628 } |
573 } | 629 } |
574 | 630 |
575 patch void forEach(void action(K key, V value)) { | 631 void forEach(void action(K key, V value)) { |
576 LinkedHashMapCell cell = _first; | 632 LinkedHashMapCell cell = _first; |
577 int modifications = _modifications; | 633 int modifications = _modifications; |
578 while (cell != null) { | 634 while (cell != null) { |
579 action(cell._key, cell._value); | 635 action(cell._key, cell._value); |
580 if (modifications != _modifications) { | 636 if (modifications != _modifications) { |
581 throw new ConcurrentModificationError(this); | 637 throw new ConcurrentModificationError(this); |
582 } | 638 } |
583 cell = cell._next; | 639 cell = cell._next; |
584 } | 640 } |
585 } | 641 } |
586 | 642 |
587 void _addHashTableEntry(var table, K key, V value) { | 643 void _addHashTableEntry(var table, K key, V value) { |
588 LinkedHashMapCell cell = _getTableEntry(table, key); | 644 LinkedHashMapCell cell = _getTableEntry(table, key); |
589 if (cell == null) { | 645 if (cell == null) { |
590 _setTableEntry(table, key, _newLinkedCell(key, value)); | 646 _setTableEntry(table, key, _newLinkedCell(key, value)); |
591 } else { | 647 } else { |
592 cell._value = value; | 648 cell._value = value; |
593 } | 649 } |
594 } | 650 } |
595 | 651 |
596 V _removeHashTableEntry(var table, K key) { | 652 V _removeHashTableEntry(var table, Object key) { |
597 if (table == null) return null; | 653 if (table == null) return null; |
598 LinkedHashMapCell cell = _getTableEntry(table, key); | 654 LinkedHashMapCell cell = _getTableEntry(table, key); |
599 if (cell == null) return null; | 655 if (cell == null) return null; |
600 _unlinkCell(cell); | 656 _unlinkCell(cell); |
601 _deleteTableEntry(table, key); | 657 _deleteTableEntry(table, key); |
602 return cell._value; | 658 return cell._value; |
603 } | 659 } |
604 | 660 |
605 void _modified() { | 661 void _modified() { |
606 // Value cycles after 2^30 modifications. If you keep hold of an | 662 // Value cycles after 2^30 modifications. If you keep hold of an |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
648 return key is String && key != '__proto__'; | 704 return key is String && key != '__proto__'; |
649 } | 705 } |
650 | 706 |
651 static bool _isNumericKey(var key) { | 707 static bool _isNumericKey(var key) { |
652 // Only treat unsigned 30-bit integers as numeric keys. This way, | 708 // Only treat unsigned 30-bit integers as numeric keys. This way, |
653 // we avoid converting them to strings when we use them as keys in | 709 // we avoid converting them to strings when we use them as keys in |
654 // the JavaScript hash table object. | 710 // the JavaScript hash table object. |
655 return key is num && JS('bool', '(# & 0x3ffffff) === #', key, key); | 711 return key is num && JS('bool', '(# & 0x3ffffff) === #', key, key); |
656 } | 712 } |
657 | 713 |
658 static int _computeHashCode(var key) { | 714 int _computeHashCode(var key) { |
659 // We force the hash codes to be unsigned 30-bit integers to avoid | 715 // We force the hash codes to be unsigned 30-bit integers to avoid |
660 // issues with problematic keys like '__proto__'. Another option | 716 // issues with problematic keys like '__proto__'. Another option |
661 // would be to throw an exception if the hash code isn't a number. | 717 // would be to throw an exception if the hash code isn't a number. |
662 return JS('int', '# & 0x3ffffff', key.hashCode); | 718 return JS('int', '# & 0x3ffffff', key.hashCode); |
663 } | 719 } |
664 | 720 |
665 static _getTableEntry(var table, var key) { | 721 static _getTableEntry(var table, var key) { |
666 return JS('var', '#[#]', table, key); | 722 return JS('var', '#[#]', table, key); |
667 } | 723 } |
668 | 724 |
669 static void _setTableEntry(var table, var key, var value) { | 725 static void _setTableEntry(var table, var key, var value) { |
670 assert(value != null); | 726 assert(value != null); |
671 JS('void', '#[#] = #', table, key, value); | 727 JS('void', '#[#] = #', table, key, value); |
672 } | 728 } |
673 | 729 |
674 static void _deleteTableEntry(var table, var key) { | 730 static void _deleteTableEntry(var table, var key) { |
675 JS('void', 'delete #[#]', table, key); | 731 JS('void', 'delete #[#]', table, key); |
676 } | 732 } |
677 | 733 |
678 static List _getBucket(var table, var key) { | 734 List _getBucket(var table, var key) { |
679 var hash = _computeHashCode(key); | 735 var hash = _computeHashCode(key); |
680 return JS('var', '#[#]', table, hash); | 736 return JS('var', '#[#]', table, hash); |
681 } | 737 } |
682 | 738 |
683 static int _findBucketIndex(var bucket, var key) { | 739 int _findBucketIndex(var bucket, var key) { |
684 if (bucket == null) return -1; | 740 if (bucket == null) return -1; |
685 int length = JS('int', '#.length', bucket); | 741 int length = JS('int', '#.length', bucket); |
686 for (int i = 0; i < length; i++) { | 742 for (int i = 0; i < length; i++) { |
687 LinkedHashMapCell cell = JS('var', '#[#]', bucket, i); | 743 LinkedHashMapCell cell = JS('var', '#[#]', bucket, i); |
688 if (cell._key == key) return i; | 744 if (cell._key == key) return i; |
689 } | 745 } |
690 return -1; | 746 return -1; |
691 } | 747 } |
692 | 748 |
693 static _newHashTable() { | 749 static _newHashTable() { |
694 // Create a new JavaScript object to be used as a hash table. Use | 750 // Create a new JavaScript object to be used as a hash table. Use |
695 // Object.create to avoid the properties on Object.prototype | 751 // Object.create to avoid the properties on Object.prototype |
696 // showing up as entries. | 752 // showing up as entries. |
697 var table = JS('var', 'Object.create(null)'); | 753 var table = JS('var', 'Object.create(null)'); |
698 // Attempt to force the hash table into 'dictionary' mode by | 754 // Attempt to force the hash table into 'dictionary' mode by |
699 // adding a property to it and deleting it again. | 755 // adding a property to it and deleting it again. |
700 var temporaryKey = '<non-identifier-key>'; | 756 var temporaryKey = '<non-identifier-key>'; |
701 _setTableEntry(table, temporaryKey, table); | 757 _setTableEntry(table, temporaryKey, table); |
702 _deleteTableEntry(table, temporaryKey); | 758 _deleteTableEntry(table, temporaryKey); |
703 return table; | 759 return table; |
704 } | 760 } |
| 761 |
| 762 String toString() => Maps.mapToString(this); |
| 763 } |
| 764 |
| 765 class _LinkedIdentityHashMap<K, V> extends _LinkedHashMap<K, V> { |
| 766 int _findBucketIndex(var bucket, var key) { |
| 767 if (bucket == null) return -1; |
| 768 int length = JS('int', '#.length', bucket); |
| 769 for (int i = 0; i < length; i++) { |
| 770 LinkedHashMapCell cell = JS('var', '#[#]', bucket, i); |
| 771 if (identical(cell._key, key)) return i; |
| 772 } |
| 773 return -1; |
| 774 } |
| 775 } |
| 776 |
| 777 class _LinkedCustomHashMap<K, V> extends _LinkedHashMap<K, V> { |
| 778 final _Equality<K> _equals; |
| 779 final _Hasher<K> _hashCode; |
| 780 final _Predicate _validKey; |
| 781 _LinkedCustomHashMap(this._equals, this._hashCode, |
| 782 bool validKey(potentialKey)) |
| 783 : _validKey = (validKey != null) ? validKey : ((v) => v is K); |
| 784 |
| 785 V operator[](Object key) { |
| 786 if (!_validKey(key)) return null; |
| 787 return super[key]; |
| 788 } |
| 789 |
| 790 bool containsKey(Object key) { |
| 791 if (!_validKey(key)) return false; |
| 792 return super.containsKey(key); |
| 793 } |
| 794 |
| 795 V remove(Object key) { |
| 796 if (!_validKey(key)) return null; |
| 797 return super.remove(key); |
| 798 } |
| 799 |
| 800 int _computeHashCode(var key) { |
| 801 // We force the hash codes to be unsigned 30-bit integers to avoid |
| 802 // issues with problematic keys like '__proto__'. Another option |
| 803 // would be to throw an exception if the hash code isn't a number. |
| 804 return JS('int', '# & 0x3ffffff', _hashCode(key)); |
| 805 } |
| 806 |
| 807 int _findBucketIndex(var bucket, var key) { |
| 808 if (bucket == null) return -1; |
| 809 int length = JS('int', '#.length', bucket); |
| 810 for (int i = 0; i < length; i++) { |
| 811 LinkedHashMapCell cell = JS('var', '#[#]', bucket, i); |
| 812 if (_equals(cell._key, key)) return i; |
| 813 } |
| 814 return -1; |
| 815 } |
705 } | 816 } |
706 | 817 |
707 class LinkedHashMapCell { | 818 class LinkedHashMapCell { |
708 final _key; | 819 final _key; |
709 var _value; | 820 var _value; |
710 | 821 |
711 LinkedHashMapCell _next; | 822 LinkedHashMapCell _next; |
712 LinkedHashMapCell _previous; | 823 LinkedHashMapCell _previous; |
713 | 824 |
714 LinkedHashMapCell(this._key, this._value); | 825 LinkedHashMapCell(this._key, this._value); |
(...skipping 658 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1373 } else if (_cell == null) { | 1484 } else if (_cell == null) { |
1374 _current = null; | 1485 _current = null; |
1375 return false; | 1486 return false; |
1376 } else { | 1487 } else { |
1377 _current = _cell._element; | 1488 _current = _cell._element; |
1378 _cell = _cell._next; | 1489 _cell = _cell._next; |
1379 return true; | 1490 return true; |
1380 } | 1491 } |
1381 } | 1492 } |
1382 } | 1493 } |
OLD | NEW |