// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. // Patch file for dart:collection classes. import 'dart:_foreign_helper' show JS, JSExportName; import 'dart:_runtime' as dart; import 'dart:_js_helper' show NoInline, NoSideEffects, NoThrows, patch, LinkedMap, IdentityMap, CustomHashMap, CustomKeyHashMap, DartIterator, notNull, putLinkedMapKey; @patch class HashMap { @patch factory HashMap( {bool equals(K key1, K key2), int hashCode(K key), bool isValidKey(Object potentialKey)}) { if (isValidKey == null) { if (hashCode == null) { if (equals == null) { if (identical(K, String) || identical(K, int)) { return new IdentityMap(); } return new LinkedMap(); } hashCode = dart.hashCode; } else if (identical(identityHashCode, hashCode) && identical(identical, equals)) { return new IdentityMap(); } return new CustomHashMap(equals ?? dart.equals, hashCode); } return new CustomKeyHashMap( equals ?? dart.equals, hashCode ?? dart.hashCode, isValidKey); } @patch factory HashMap.identity() = IdentityMap; } @patch class LinkedHashMap { @patch factory LinkedHashMap( {bool equals(K key1, K key2), int hashCode(K key), bool isValidKey(Object potentialKey)}) { if (isValidKey == null) { if (hashCode == null) { if (equals == null) { if (identical(K, String) || identical(K, int)) { return new IdentityMap(); } return new LinkedMap(); } hashCode = dart.hashCode; } else if (identical(identityHashCode, hashCode) && identical(identical, equals)) { return new IdentityMap(); } return new CustomHashMap(equals ?? dart.equals, hashCode); } return new CustomKeyHashMap( equals ?? dart.equals, hashCode ?? dart.hashCode, isValidKey); } @patch factory LinkedHashMap.identity() = IdentityMap; } @patch class HashSet { @patch factory HashSet( {bool equals(E e1, E e2), int hashCode(E e), bool isValidKey(Object potentialKey)}) { if (isValidKey == null) { if (hashCode == null) { if (equals == null) { if (identical(E, String) || identical(E, int)) { return new _IdentityHashSet(); } return new _HashSet(); } hashCode = dart.hashCode; } else if (identical(identityHashCode, hashCode) && identical(identical, equals)) { return new _IdentityHashSet(); } return new _CustomHashSet( equals ?? dart.equals, hashCode ?? dart.hashCode); } return new _CustomKeyHashSet( equals ?? dart.equals, hashCode ?? dart.hashCode, isValidKey); } @patch factory HashSet.identity() = _IdentityHashSet; } @patch class LinkedHashSet { @patch factory LinkedHashSet( {bool equals(E e1, E e2), int hashCode(E e), bool isValidKey(Object potentialKey)}) { if (isValidKey == null) { if (hashCode == null) { if (equals == null) { if (identical(E, String) || identical(E, int)) { return new _IdentityHashSet(); } return new _HashSet(); } hashCode = dart.hashCode; } else if (identical(identityHashCode, hashCode) && identical(identical, equals)) { return new _IdentityHashSet(); } return new _CustomHashSet( equals ?? dart.equals, hashCode ?? dart.hashCode); } return new _CustomKeyHashSet( equals ?? dart.equals, hashCode ?? dart.hashCode, isValidKey); } @patch factory LinkedHashSet.identity() = _IdentityHashSet; } class _HashSet extends _InternalSet implements HashSet, LinkedHashSet { /// The backing store for this set. /// /// Keys that use identity equality are stored directly. For other types of /// keys, we first look them up (by hashCode) in the [_keyMap] map, then /// we lookup the key in this map. @notNull final _map = JS('', 'new Set()'); /// Items that use custom equality semantics. /// /// This maps from the item's hashCode to the canonical key, which is then /// used to lookup the item in [_map]. Keeping the data in our primary backing /// map gives us the ordering semantics requred by [LinkedHashMap], while /// also providing convenient access to keys/values. @notNull final _keyMap = JS('', 'new Map()'); // We track the number of modifications done to the key set of the // hash map to be able to throw when the map is modified while being // iterated over. // // Value cycles after 2^30 modifications so that modification counts are // always unboxed (Smi) values. Modification detection will be missed if you // make exactly some multiple of 2^30 modifications between advances of an // iterator. @notNull int _modifications = 0; _HashSet(); Set _newSet() => new _HashSet(); Set _newSimilarSet() => new _HashSet(); bool contains(Object key) { if (key == null) { key = null; } else if (JS('bool', '#[#] !== #', key, dart.extensionSymbol('_equals'), dart.identityEquals)) { @notNull var k = key; var buckets = JS('', '#.get(# & 0x3ffffff)', _keyMap, k.hashCode); if (buckets != null) { for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) { k = JS('', '#[#]', buckets, i); if (k == key) return true; } } return false; } return JS('bool', '#.has(#)', _map, key); } E lookup(Object key) { if (key == null) return null; if (JS('bool', '#[#] !== #', key, dart.extensionSymbol('_equals'), dart.identityEquals)) { @notNull var k = key; var buckets = JS('', '#.get(# & 0x3ffffff)', _keyMap, k.hashCode); if (buckets != null) { for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) { k = JS('', '#[#]', buckets, i); if (k == key) return JS('', '#', k); } } return null; } return JS('', '#.has(#) ? # : null', _map, key, key); } bool add(E key) { var map = _map; if (key == null) { if (JS('', '#.has(null)', map)) return false; key = null; } else if (JS('bool', '#[#] !== #', key, dart.extensionSymbol('_equals'), dart.identityEquals)) { var keyMap = _keyMap; @notNull var k = key; int hash = JS('!', '# & 0x3ffffff', k.hashCode); var buckets = JS('', '#.get(#)', keyMap, hash); if (buckets == null) { JS('', '#.set(#, [#])', keyMap, hash, key); } else { for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) { k = JS('', '#[#]', buckets, i); if (k == key) return false; } JS('', '#.push(#)', buckets, key); } } else if (JS('', '#.has(#)', map, key)) { return false; } JS('', '#.add(#)', map, key); _modifications = (_modifications + 1) & 0x3ffffff; return true; } void addAll(Iterable objects) { var map = _map; int length = JS('', '#.size', map); for (E key in objects) { if (key == null) { key = null; } else if (JS('bool', '#[#] !== #', key, dart.extensionSymbol('_equals'), dart.identityEquals)) { key = putLinkedMapKey(key, _keyMap); } JS('', '#.add(#)', map, key); } if (length != JS('!', '#.size', map)) { _modifications = (_modifications + 1) & 0x3ffffff; } } bool remove(Object key) { if (key == null) { key = null; } else if (JS('bool', '#[#] !== #', key, dart.extensionSymbol('_equals'), dart.identityEquals)) { @notNull var k = key; int hash = JS('!', '# & 0x3ffffff', k.hashCode); var buckets = JS('', '#.get(#)', _keyMap, hash); if (buckets == null) return false; // not found for (int i = 0, n = JS('!', '#.length', buckets);;) { k = JS('', '#[#]', buckets, i); if (k == key) { key = k; if (n == 1) { JS('', '#.delete(#)', _keyMap, hash); } else { JS('', '#.splice(#, 1)', buckets, i); } break; } if (++i >= n) return false; // not found } } var map = _map; if (JS('bool', '#.delete(#)', map, key)) { _modifications = (_modifications + 1) & 0x3ffffff; return true; } return false; } void clear() { var map = _map; if (JS('!', '#.size', map) > 0) { JS('', '#.clear()', map); JS('', '#.clear()', _keyMap); _modifications = (_modifications + 1) & 0x3ffffff; } } } class _IdentityHashSet extends _InternalSet implements HashSet, LinkedHashSet { /// The backing store for this set. @notNull final _map = JS('', 'new Set()'); @notNull int _modifications = 0; _IdentityHashSet(); Set _newSet() => new _IdentityHashSet(); Set _newSimilarSet() => new _IdentityHashSet(); bool contains(Object element) { return JS('', '#.has(#)', _map, element); } E lookup(Object element) { return JS('', '#.has(#)', _map, element) ? element : null; } bool add(E element) { var map = _map; if (JS('bool', '#.has(#)', map, element)) return false; JS('', '#.add(#)', map, element); _modifications = (_modifications + 1) & 0x3ffffff; return true; } void addAll(Iterable objects) { var map = _map; int length = JS('', '#.size', map); for (E key in objects) { JS('', '#.add(#)', map, key); } if (length != JS('!', '#.size', map)) { _modifications = (_modifications + 1) & 0x3ffffff; } } bool remove(Object element) { if (JS('bool', '#.delete(#)', _map, element)) { _modifications = (_modifications + 1) & 0x3ffffff; return true; } return false; } void clear() { var map = _map; if (JS('!', '#.size', map) > 0) { JS('', '#.clear()', map); _modifications = (_modifications + 1) & 0x3ffffff; } } } class _CustomKeyHashSet extends _CustomHashSet { _Predicate _validKey; _CustomKeyHashSet(_Equality equals, _Hasher hashCode, this._validKey) : super(equals, hashCode); Set _newSet() => new _CustomKeyHashSet(_equals, _hashCode, _validKey); Set _newSimilarSet() => new _HashSet(); bool contains(Object element) { // TODO(jmesserly): there is a subtle difference here compared to Dart 1. // See the comment on CustomKeyHashMap.containsKey for more information. // Treatment of `null` is different due to strong mode's requirement to // perform an `element is E` check before calling equals/hashCode. if (!_validKey(element)) return false; return super.contains(element); } E lookup(Object element) { if (!_validKey(element)) return null; return super.lookup(element); } bool remove(Object element) { if (!_validKey(element)) return false; return super.remove(element); } } class _CustomHashSet extends _InternalSet implements HashSet, LinkedHashSet { _Equality _equals; _Hasher _hashCode; // We track the number of modifications done to the key set of the // hash map to be able to throw when the map is modified while being // iterated over. // // Value cycles after 2^30 modifications so that modification counts are // always unboxed (Smi) values. Modification detection will be missed if you // make exactly some multiple of 2^30 modifications between advances of an // iterator. @notNull int _modifications = 0; /// The backing store for this set, used to handle ordering. // TODO(jmesserly): a non-linked custom hash set could skip this. @notNull final _map = JS('', 'new Set()'); /// Our map used to map keys onto the canonical key that is stored in [_map]. @notNull final _keyMap = JS('', 'new Map()'); _CustomHashSet(this._equals, this._hashCode); Set _newSet() => new _CustomHashSet(_equals, _hashCode); Set _newSimilarSet() => new _HashSet(); bool contains(Object key) { if (key is E) { var buckets = JS('', '#.get(# & 0x3ffffff)', _keyMap, _hashCode(key)); if (buckets != null) { var equals = _equals; for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) { E k = JS('', '#[#]', buckets, i); if (equals(k, key)) return true; } } } return false; } E lookup(Object key) { if (key is E) { var buckets = JS('', '#.get(# & 0x3ffffff)', _keyMap, _hashCode(key)); if (buckets != null) { var equals = _equals; for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) { E k = JS('', '#[#]', buckets, i); if (equals(k, key)) return JS('', '#', k); } } } return null; } bool add(E key) { var keyMap = _keyMap; var hash = JS('!', '# & 0x3ffffff', _hashCode(key)); var buckets = JS('', '#.get(#)', keyMap, hash); if (buckets == null) { JS('', '#.set(#, [#])', keyMap, hash, key); } else { var equals = _equals; for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) { E k = JS('', '#[#]', buckets, i); if (equals(k, key)) return false; } JS('', '#.push(#)', buckets, key); } JS('', '#.add(#)', _map, key); _modifications = (_modifications + 1) & 0x3ffffff; return true; } void addAll(Iterable objects) { // TODO(jmesserly): it'd be nice to skip the covariance check here. for (E element in objects) add(element); } bool remove(Object key) { if (key is E) { var hash = JS('!', '# & 0x3ffffff', _hashCode(key)); var keyMap = _keyMap; var buckets = JS('', '#.get(#)', keyMap, hash); if (buckets == null) return false; // not found var equals = _equals; for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) { E k = JS('', '#[#]', buckets, i); if (equals(k, key)) { if (n == 1) { JS('', '#.delete(#)', keyMap, hash); } else { JS('', '#.splice(#, 1)', buckets, i); } JS('', '#.delete(#)', _map, k); _modifications = (_modifications + 1) & 0x3ffffff; return true; } } } return false; } void clear() { var map = _map; if (JS('!', '#.size', map) > 0) { JS('', '#.clear()', map); JS('', '#.clear()', _keyMap); _modifications = (_modifications + 1) & 0x3ffffff; } } } /// Base class for our internal [LinkedHashSet]/[HashSet] implementations. /// /// This implements the common functionality. abstract class _InternalSet extends _HashSetBase { @notNull get _map; @notNull int get _modifications; @notNull int get length => JS('!', '#.size', _map); @notNull bool get isEmpty => JS('bool', '#.size == 0', _map); @notNull bool get isNotEmpty => JS('bool', '#.size != 0', _map); Iterator get iterator => new DartIterator(_jsIterator()); @JSExportName('Symbol.iterator') _jsIterator() { var self = this; var iterator = JS('', '#.values()', self._map); int modifications = self._modifications; return JS( '', '''{ next() { if (# != #) { throw #; } return #.next(); } }''', modifications, self._modifications, new ConcurrentModificationError(self), iterator); } }