|  | // Copyright (c) 2018, 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. | 
|  |  | 
|  | import "package:expect/expect.dart"; | 
|  | import 'dart:collection'; | 
|  | import 'dart:convert' show json; | 
|  |  | 
|  | Map<String, dynamic> newJsonMap() => json.decode('{}'); | 
|  | Map<String, dynamic> newJsonMapCustomReviver() => | 
|  | json.decode('{}', reviver: (key, value) => value); | 
|  |  | 
|  | void main() { | 
|  | test({}); | 
|  | test(new LinkedHashMap()); | 
|  | test(new HashMap()); | 
|  | test(new LinkedHashMap.identity()); | 
|  | test(new HashMap.identity()); | 
|  | test(new MapView(new HashMap())); | 
|  | test(new MapBaseMap()); | 
|  | test(new MapMixinMap()); | 
|  | test(newJsonMap()); | 
|  | test(newJsonMapCustomReviver()); | 
|  | testNonNull(new SplayTreeMap()); | 
|  | testNonNull(new SplayTreeMap(Comparable.compare)); | 
|  | testNonNull(new MapView(new SplayTreeMap())); | 
|  | } | 
|  |  | 
|  | void test(Map<Comparable, Object> map) { | 
|  | testNonNull(map); | 
|  |  | 
|  | // Also works with null keys and values (omitted for splay-tree based maps) | 
|  | map.clear(); | 
|  | map.update(null, unreachable, ifAbsent: () => null); | 
|  | Expect.mapEquals({null: null}, map); | 
|  | map.update(null, (v) => "$v", ifAbsent: unreachable); | 
|  | Expect.mapEquals({null: "null"}, map); | 
|  | map.update(null, (v) => null, ifAbsent: unreachable); | 
|  | Expect.mapEquals({null: null}, map); | 
|  | } | 
|  |  | 
|  | void testNonNull(Map<Comparable, Object> map) { | 
|  | // Only use literal String keys since JSON maps only accept strings, | 
|  | // and literals works with identity-maps, and it's comparable for SplayTreeMap | 
|  | // maps. | 
|  | Expect.mapEquals({}, map); | 
|  | map.update("key1", unreachable, ifAbsent: () => 42); | 
|  | Expect.mapEquals({"key1": 42}, map); | 
|  | map.clear(); | 
|  | map["key1"] = 42; | 
|  | map.update("key1", (v) => 1 + v, ifAbsent: unreachable); | 
|  | Expect.mapEquals({"key1": 43}, map); | 
|  | map.clear(); | 
|  |  | 
|  | // Operations on maps with multiple elements. | 
|  | var multi = { | 
|  | "k1": 1, | 
|  | "k2": 2, | 
|  | "k3": 3, | 
|  | "k4": 4, | 
|  | "k5": 5, | 
|  | "k6": 6, | 
|  | "k7": 7, | 
|  | "k8": 8, | 
|  | "k9": 9, | 
|  | "k10": 10, | 
|  | }; | 
|  | map.addAll(multi); | 
|  | Expect.mapEquals(multi, map); | 
|  | map.update("k3", (v) => 13); | 
|  | map.update("k6", (v) => 16); | 
|  | map.update("k11", unreachable, ifAbsent: () => 21); | 
|  | Expect.mapEquals({ | 
|  | "k1": 1, | 
|  | "k2": 2, | 
|  | "k3": 13, | 
|  | "k4": 4, | 
|  | "k5": 5, | 
|  | "k6": 16, | 
|  | "k7": 7, | 
|  | "k8": 8, | 
|  | "k9": 9, | 
|  | "k10": 10, | 
|  | "k11": 21, | 
|  | }, map); | 
|  |  | 
|  | map.clear(); | 
|  | map.updateAll((k, v) => throw "unreachable"); | 
|  | Expect.mapEquals({}, map); | 
|  |  | 
|  | map.addAll(multi); | 
|  | map.updateAll((k, v) => "$k:$v"); | 
|  | Expect.mapEquals({ | 
|  | "k1": "k1:1", | 
|  | "k2": "k2:2", | 
|  | "k3": "k3:3", | 
|  | "k4": "k4:4", | 
|  | "k5": "k5:5", | 
|  | "k6": "k6:6", | 
|  | "k7": "k7:7", | 
|  | "k8": "k8:8", | 
|  | "k9": "k9:9", | 
|  | "k10": "k10:10", | 
|  | }, map); | 
|  |  | 
|  | map.clear(); | 
|  | Expect.throws( | 
|  | () => map.update("key1", unreachable, ifAbsent: () => throw "expected"), | 
|  | (t) => t == "expected"); | 
|  |  | 
|  | map["key1"] = 42; | 
|  | Expect.throws(() => map.update("key1", (_) => throw "expected"), | 
|  | (t) => t == "expected"); | 
|  |  | 
|  | // No ifAbsent means throw if key not there. | 
|  | Expect.throws(() => map.update("key-not", unreachable), (e) => e is Error); | 
|  |  | 
|  | Expect.throws(() => map.update("key1", null), (e) => e is Error); | 
|  |  | 
|  | // Works with null values. | 
|  | map.clear(); | 
|  | map.update("key1", unreachable, ifAbsent: () => null); | 
|  | Expect.mapEquals({"key1": null}, map); | 
|  | map.update("key1", (v) => "$v", ifAbsent: unreachable); | 
|  | Expect.mapEquals({"key1": "null"}, map); | 
|  | map.update("key1", (v) => null, ifAbsent: unreachable); | 
|  | Expect.mapEquals({"key1": null}, map); | 
|  | } | 
|  |  | 
|  | // Slow implementation of Map based on MapBase. | 
|  | abstract class MapBaseOperations<K, V> { | 
|  | final List _keys = <K>[]; | 
|  | final List _values = <V>[]; | 
|  | int _modCount = 0; | 
|  |  | 
|  | V operator [](Object key) { | 
|  | int index = _keys.indexOf(key); | 
|  | if (index < 0) return null; | 
|  | return _values[index]; | 
|  | } | 
|  |  | 
|  | Iterable<K> get keys => new TestKeyIterable<K>(this); | 
|  |  | 
|  | void operator []=(K key, V value) { | 
|  | int index = _keys.indexOf(key); | 
|  | if (index >= 0) { | 
|  | _values[index] = value; | 
|  | } else { | 
|  | _modCount++; | 
|  | _keys.add(key); | 
|  | _values.add(value); | 
|  | } | 
|  | } | 
|  |  | 
|  | V remove(Object key) { | 
|  | int index = _keys.indexOf(key); | 
|  | if (index >= 0) { | 
|  | var result = _values[index]; | 
|  | key = _keys.removeLast(); | 
|  | var value = _values.removeLast(); | 
|  | if (index != _keys.length) { | 
|  | _keys[index] = key; | 
|  | _values[index] = value; | 
|  | } | 
|  | _modCount++; | 
|  | return result; | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | void clear() { | 
|  | // Clear cannot be based on remove, since remove won't remove keys that | 
|  | // are not equal to themselves. It will fail the testNaNKeys test. | 
|  | _keys.clear(); | 
|  | _values.clear(); | 
|  | _modCount++; | 
|  | } | 
|  | } | 
|  |  | 
|  | class MapBaseMap<K, V> = MapBase<K, V> with MapBaseOperations<K, V>; | 
|  | class MapMixinMap<K, V> = MapBaseOperations<K, V> with MapMixin<K, V>; | 
|  |  | 
|  | class TestKeyIterable<K> extends IterableBase<K> { | 
|  | final _map; | 
|  | TestKeyIterable(this._map); | 
|  | int get length => _map._keys.length; | 
|  | Iterator<K> get iterator => new TestKeyIterator<K>(_map); | 
|  | } | 
|  |  | 
|  | class TestKeyIterator<K> implements Iterator<K> { | 
|  | final _map; | 
|  | final int _modCount; | 
|  | int _index = 0; | 
|  | var _current; | 
|  | TestKeyIterator(map) | 
|  | : _map = map, | 
|  | _modCount = map._modCount; | 
|  |  | 
|  | bool moveNext() { | 
|  | if (_modCount != _map._modCount) { | 
|  | throw new ConcurrentModificationError(_map); | 
|  | } | 
|  | if (_index == _map._keys.length) { | 
|  | _current = null; | 
|  | return false; | 
|  | } | 
|  | _current = _map._keys[_index++]; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | K get current => _current; | 
|  | } | 
|  |  | 
|  | Null unreachable([_, __]) => throw "unreachable"; |