Line data Source code
1 : // Copyright (c) 2014, 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 : import 'dart:collection';
6 :
7 : /// A map whose keys are converted to canonical values of type `C`.
8 : ///
9 : /// This is useful for using case-insensitive String keys, for example. It's
10 : /// more efficient than a [LinkedHashMap] with a custom equality operator
11 : /// because it only canonicalizes each key once, rather than doing so for each
12 : /// comparison.
13 : class CanonicalizedMap<C, K, V> implements Map<K, V> {
14 : final C Function(K) _canonicalize;
15 :
16 : final bool Function(K)? _isValidKeyFn;
17 :
18 : final _base = <C, MapEntry<K, V>>{};
19 :
20 : /// Creates an empty canonicalized map.
21 : ///
22 : /// The [canonicalize] function should return the canonical value for the
23 : /// given key. Keys with the same canonical value are considered equivalent.
24 : ///
25 : /// The [isValidKey] function is called before calling [canonicalize] for
26 : /// methods that take arbitrary objects. It can be used to filter out keys
27 : /// that can't be canonicalized.
28 0 : CanonicalizedMap(C Function(K key) canonicalize,
29 : {bool Function(K key)? isValidKey})
30 : : _canonicalize = canonicalize,
31 : _isValidKeyFn = isValidKey;
32 :
33 : /// Creates a canonicalized map that is initialized with the key/value pairs
34 : /// of [other].
35 : ///
36 : /// The [canonicalize] function should return the canonical value for the
37 : /// given key. Keys with the same canonical value are considered equivalent.
38 : ///
39 : /// The [isValidKey] function is called before calling [canonicalize] for
40 : /// methods that take arbitrary objects. It can be used to filter out keys
41 : /// that can't be canonicalized.
42 6 : CanonicalizedMap.from(Map<K, V> other, C Function(K key) canonicalize,
43 : {bool Function(K key)? isValidKey})
44 : : _canonicalize = canonicalize,
45 : _isValidKeyFn = isValidKey {
46 6 : addAll(other);
47 : }
48 :
49 0 : @override
50 : V? operator [](Object? key) {
51 0 : if (!_isValidKey(key)) return null;
52 0 : var pair = _base[_canonicalize(key as K)];
53 0 : return pair == null ? null : pair.value;
54 : }
55 :
56 6 : @override
57 : void operator []=(K key, V value) {
58 6 : if (!_isValidKey(key)) return;
59 24 : _base[_canonicalize(key)] = MapEntry(key, value);
60 : }
61 :
62 6 : @override
63 : void addAll(Map<K, V> other) {
64 18 : other.forEach((key, value) => this[key] = value);
65 : }
66 :
67 0 : @override
68 0 : void addEntries(Iterable<MapEntry<K, V>> entries) => _base.addEntries(entries
69 0 : .map((e) => MapEntry(_canonicalize(e.key), MapEntry(e.key, e.value))));
70 :
71 0 : @override
72 0 : Map<K2, V2> cast<K2, V2>() => _base.cast<K2, V2>();
73 :
74 0 : @override
75 : void clear() {
76 0 : _base.clear();
77 : }
78 :
79 0 : @override
80 : bool containsKey(Object? key) {
81 0 : if (!_isValidKey(key)) return false;
82 0 : return _base.containsKey(_canonicalize(key as K));
83 : }
84 :
85 0 : @override
86 : bool containsValue(Object? value) =>
87 0 : _base.values.any((pair) => pair.value == value);
88 :
89 0 : @override
90 : Iterable<MapEntry<K, V>> get entries =>
91 0 : _base.entries.map((e) => MapEntry(e.value.key, e.value.value));
92 :
93 1 : @override
94 : void forEach(void Function(K, V) f) {
95 5 : _base.forEach((key, pair) => f(pair.key, pair.value));
96 : }
97 :
98 0 : @override
99 0 : bool get isEmpty => _base.isEmpty;
100 :
101 0 : @override
102 0 : bool get isNotEmpty => _base.isNotEmpty;
103 :
104 0 : @override
105 0 : Iterable<K> get keys => _base.values.map((pair) => pair.key);
106 :
107 0 : @override
108 0 : int get length => _base.length;
109 :
110 0 : @override
111 : Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> Function(K, V) transform) =>
112 0 : _base.map((_, pair) => transform(pair.key, pair.value));
113 :
114 0 : @override
115 : V putIfAbsent(K key, V Function() ifAbsent) {
116 0 : return _base
117 0 : .putIfAbsent(_canonicalize(key), () => MapEntry(key, ifAbsent()))
118 0 : .value;
119 : }
120 :
121 0 : @override
122 : V? remove(Object? key) {
123 0 : if (!_isValidKey(key)) return null;
124 0 : var pair = _base.remove(_canonicalize(key as K));
125 0 : return pair?.value;
126 : }
127 :
128 0 : @override
129 : void removeWhere(bool Function(K key, V value) test) =>
130 0 : _base.removeWhere((_, pair) => test(pair.key, pair.value));
131 :
132 0 : @deprecated
133 0 : Map<K2, V2> retype<K2, V2>() => cast<K2, V2>();
134 :
135 0 : @override
136 : V update(K key, V Function(V) update, {V Function()? ifAbsent}) =>
137 0 : _base.update(_canonicalize(key), (pair) {
138 0 : var value = pair.value;
139 : var newValue = update(value);
140 : if (identical(newValue, value)) return pair;
141 0 : return MapEntry(key, newValue);
142 : },
143 : ifAbsent:
144 0 : ifAbsent == null ? null : () => MapEntry(key, ifAbsent())).value;
145 :
146 0 : @override
147 : void updateAll(V Function(K key, V value) update) =>
148 0 : _base.updateAll((_, pair) {
149 0 : var value = pair.value;
150 0 : var key = pair.key;
151 : var newValue = update(key, value);
152 : if (identical(value, newValue)) return pair;
153 0 : return MapEntry(key, newValue);
154 : });
155 :
156 0 : @override
157 0 : Iterable<V> get values => _base.values.map((pair) => pair.value);
158 :
159 0 : @override
160 0 : String toString() => MapBase.mapToString(this);
161 :
162 6 : bool _isValidKey(Object? key) =>
163 12 : (key is K) && (_isValidKeyFn == null || _isValidKeyFn!(key));
164 : }
|