blob: bcbf5b6b141fe681c6c48165b9ef61f53ca2d3a2 [file] [log] [blame]
// Copyright (c) 2017, 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.
part of dart._js_helper;
class CustomKeyHashMap<K, V> extends CustomHashMap<K, V> {
final _Predicate<Object?> _validKey;
CustomKeyHashMap(_Equality<K> equals, _Hasher<K> hashCode, this._validKey)
: super(equals, hashCode);
@override
@notNull
bool containsKey(Object? key) {
if (!_validKey(key)) return false;
return super.containsKey(key);
}
@override
V? operator [](Object? key) {
if (!_validKey(key)) return null;
return super[key];
}
@override
V? remove(Object? key) {
if (!_validKey(key)) return null;
return super.remove(key);
}
}
class CustomHashMap<K, V> extends InternalMap<K, V> {
/// The backing store for this map.
@notNull
final _map = JS('', 'new Map()');
/// Our map used to map keys onto the canonical key that is stored in [_map].
@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;
final _Equality<K> _equals;
final _Hasher<K> _hashCode;
CustomHashMap(this._equals, this._hashCode);
@notNull
int get length => JS<int>('!', '#.size', _map);
@notNull
bool get isEmpty => JS<bool>('!', '#.size == 0', _map);
@notNull
bool get isNotEmpty => JS<bool>('!', '#.size != 0', _map);
Iterable<K> get keys => _JSMapIterable<K>(this, true);
Iterable<V> get values => _JSMapIterable<V>(this, false);
@notNull
bool containsKey(Object? key) {
if (key is K) {
var buckets = JS('', '#.get(# & 0x3fffffff)', _keyMap, _hashCode(key));
if (buckets != null) {
var equals = _equals;
for (int i = 0, n = JS<int>('!', '#.length', buckets); i < n; i++) {
K k = JS('', '#[#]', buckets, i);
if (equals(k, key)) return true;
}
}
}
return false;
}
bool containsValue(Object? value) {
for (var v in JS('', '#.values()', _map)) {
if (value == v) return true;
}
return false;
}
void addAll(Map<K, V> other) {
other.forEach((K key, V value) {
this[key] = value;
});
}
V? operator [](Object? key) {
if (key is K) {
var buckets = JS('', '#.get(# & 0x3fffffff)', _keyMap, _hashCode(key));
if (buckets != null) {
var equals = _equals;
for (int i = 0, n = JS<int>('!', '#.length', buckets); i < n; i++) {
K k = JS('', '#[#]', buckets, i);
if (equals(k, key)) {
V value = JS('', '#.get(#)', _map, k);
return value == null ? null : value; // coerce undefined to null.
}
}
}
}
return null;
}
void operator []=(K key, V value) {
var keyMap = _keyMap;
int hash = JS('!', '# & 0x3fffffff', _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<int>('!', '#.length', buckets);;) {
K k = JS('', '#[#]', buckets, i);
if (equals(k, key)) {
key = k;
break;
}
if (++i >= n) {
JS('', '#.push(#)', buckets, key);
break;
}
}
}
JS('', '#.set(#, #)', _map, key, value);
_modifications = (_modifications + 1) & 0x3fffffff;
}
V putIfAbsent(K key, V ifAbsent()) {
var keyMap = _keyMap;
int hash = JS('!', '# & 0x3fffffff', _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<int>('!', '#.length', buckets); i < n; i++) {
K k = JS('', '#[#]', buckets, i);
if (equals(k, key)) return JS('', '#.get(#)', _map, k);
}
JS('', '#.push(#)', buckets, key);
}
V value = ifAbsent();
if (value == null) JS('', '# = null', value); // coerce undefined to null.
JS('', '#.set(#, #)', _map, key, value);
_modifications = (_modifications + 1) & 0x3fffffff;
return value;
}
V? remove(Object? key) {
if (key is K) {
int hash = JS('!', '# & 0x3fffffff', _hashCode(key));
var keyMap = _keyMap;
var buckets = JS('', '#.get(#)', keyMap, hash);
if (buckets == null) return null; // not found
var equals = _equals;
for (int i = 0, n = JS<int>('!', '#.length', buckets); i < n; i++) {
K k = JS('', '#[#]', buckets, i);
if (equals(k, key)) {
if (n == 1) {
JS('', '#.delete(#)', keyMap, hash);
} else {
JS('', '#.splice(#, 1)', buckets, i);
}
var map = _map;
V value = JS('', '#.get(#)', map, k);
JS('', '#.delete(#)', map, k);
_modifications = (_modifications + 1) & 0x3fffffff;
return value == null ? null : value; // coerce undefined to null.
}
}
}
return null;
}
void clear() {
var map = _map;
if (JS<int>('!', '#.size', map) > 0) {
JS('', '#.clear()', map);
JS('', '#.clear()', _keyMap);
_modifications = (_modifications + 1) & 0x3fffffff;
}
}
}
typedef bool _Equality<K>(K a, K b);
typedef int _Hasher<K>(K object);
typedef bool _Predicate<T>(T value);