Line data Source code
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
3 : // BSD-style license that can be found in the LICENSE file.
4 :
5 : import 'dart:collection';
6 :
7 : import 'comparators.dart';
8 :
9 : const int _HASH_MASK = 0x7fffffff;
10 :
11 : /// A generic equality relation on objects.
12 : abstract class Equality<E> {
13 : const factory Equality() = DefaultEquality<E>;
14 :
15 : /// Compare two elements for being equal.
16 : ///
17 : /// This should be a proper equality relation.
18 : bool equals(E e1, E e2);
19 :
20 : /// Get a hashcode of an element.
21 : ///
22 : /// The hashcode should be compatible with [equals], so that if
23 : /// `equals(a, b)` then `hash(a) == hash(b)`.
24 : int hash(E e);
25 :
26 : /// Test whether an object is a valid argument to [equals] and [hash].
27 : ///
28 : /// Some implementations may be restricted to only work on specific types
29 : /// of objects.
30 : bool isValidKey(Object? o);
31 : }
32 :
33 : /// Equality of objects based on derived values.
34 : ///
35 : /// For example, given the class:
36 : /// ```dart
37 : /// abstract class Employee {
38 : /// int get employmentId;
39 : /// }
40 : /// ```
41 : ///
42 : /// The following [Equality] considers employees with the same IDs to be equal:
43 : /// ```dart
44 : /// EqualityBy((Employee e) => e.employmentId);
45 : /// ```
46 : ///
47 : /// It's also possible to pass an additional equality instance that should be
48 : /// used to compare the value itself.
49 : class EqualityBy<E, F> implements Equality<E> {
50 : final F Function(E) _comparisonKey;
51 :
52 : final Equality<F> _inner;
53 :
54 0 : EqualityBy(F Function(E) comparisonKey,
55 : [Equality<F> inner = const DefaultEquality<Never>()])
56 : : _comparisonKey = comparisonKey,
57 : _inner = inner;
58 :
59 0 : @override
60 : bool equals(E e1, E e2) =>
61 0 : _inner.equals(_comparisonKey(e1), _comparisonKey(e2));
62 :
63 0 : @override
64 0 : int hash(E e) => _inner.hash(_comparisonKey(e));
65 :
66 0 : @override
67 : bool isValidKey(Object? o) {
68 0 : if (o is E) {
69 0 : final value = _comparisonKey(o);
70 0 : return value is F && _inner.isValidKey(value);
71 : }
72 : return false;
73 : }
74 : }
75 :
76 : /// Equality of objects that compares only the natural equality of the objects.
77 : ///
78 : /// This equality uses the objects' own [Object.==] and [Object.hashCode] for
79 : /// the equality.
80 : ///
81 : /// Note that [equals] and [hash] take `Object`s rather than `E`s. This allows
82 : /// `E` to be inferred as `Null` in const contexts where `E` wouldn't be a
83 : /// compile-time constant, while still allowing the class to be used at runtime.
84 : class DefaultEquality<E> implements Equality<E> {
85 22 : const DefaultEquality();
86 0 : @override
87 0 : bool equals(Object? e1, Object? e2) => e1 == e2;
88 0 : @override
89 0 : int hash(Object? e) => e.hashCode;
90 0 : @override
91 : bool isValidKey(Object? o) => true;
92 : }
93 :
94 : /// Equality of objects that compares only the identity of the objects.
95 : class IdentityEquality<E> implements Equality<E> {
96 0 : const IdentityEquality();
97 0 : @override
98 : bool equals(E e1, E e2) => identical(e1, e2);
99 0 : @override
100 0 : int hash(E e) => identityHashCode(e);
101 0 : @override
102 : bool isValidKey(Object? o) => true;
103 : }
104 :
105 : /// Equality on iterables.
106 : ///
107 : /// Two iterables are equal if they have the same elements in the same order.
108 : ///
109 : /// The [equals] and [hash] methods accepts `null` values,
110 : /// even if the [isValidKey] returns `false` for `null`.
111 : /// The [hash] of `null` is `null.hashCode`.
112 : class IterableEquality<E> implements Equality<Iterable<E>> {
113 : final Equality<E?> _elementEquality;
114 0 : const IterableEquality(
115 : [Equality<E> elementEquality = const DefaultEquality<Never>()])
116 : : _elementEquality = elementEquality;
117 :
118 0 : @override
119 : bool equals(Iterable<E>? elements1, Iterable<E>? elements2) {
120 : if (identical(elements1, elements2)) return true;
121 : if (elements1 == null || elements2 == null) return false;
122 0 : var it1 = elements1.iterator;
123 0 : var it2 = elements2.iterator;
124 : while (true) {
125 0 : var hasNext = it1.moveNext();
126 0 : if (hasNext != it2.moveNext()) return false;
127 : if (!hasNext) return true;
128 0 : if (!_elementEquality.equals(it1.current, it2.current)) return false;
129 : }
130 : }
131 :
132 0 : @override
133 : int hash(Iterable<E>? elements) {
134 0 : if (elements == null) return null.hashCode;
135 : // Jenkins's one-at-a-time hash function.
136 : var hash = 0;
137 0 : for (var element in elements) {
138 0 : var c = _elementEquality.hash(element);
139 0 : hash = (hash + c) & _HASH_MASK;
140 0 : hash = (hash + (hash << 10)) & _HASH_MASK;
141 0 : hash ^= (hash >> 6);
142 : }
143 0 : hash = (hash + (hash << 3)) & _HASH_MASK;
144 0 : hash ^= (hash >> 11);
145 0 : hash = (hash + (hash << 15)) & _HASH_MASK;
146 : return hash;
147 : }
148 :
149 0 : @override
150 0 : bool isValidKey(Object? o) => o is Iterable<E>;
151 : }
152 :
153 : /// Equality on lists.
154 : ///
155 : /// Two lists are equal if they have the same length and their elements
156 : /// at each index are equal.
157 : ///
158 : /// This is effectively the same as [IterableEquality] except that it
159 : /// accesses elements by index instead of through iteration.
160 : ///
161 : /// The [equals] and [hash] methods accepts `null` values,
162 : /// even if the [isValidKey] returns `false` for `null`.
163 : /// The [hash] of `null` is `null.hashCode`.
164 : class ListEquality<E> implements Equality<List<E>> {
165 : final Equality<E> _elementEquality;
166 0 : const ListEquality(
167 : [Equality<E> elementEquality = const DefaultEquality<Never>()])
168 : : _elementEquality = elementEquality;
169 :
170 0 : @override
171 : bool equals(List<E>? list1, List<E>? list2) {
172 : if (identical(list1, list2)) return true;
173 : if (list1 == null || list2 == null) return false;
174 0 : var length = list1.length;
175 0 : if (length != list2.length) return false;
176 0 : for (var i = 0; i < length; i++) {
177 0 : if (!_elementEquality.equals(list1[i], list2[i])) return false;
178 : }
179 : return true;
180 : }
181 :
182 0 : @override
183 : int hash(List<E>? list) {
184 0 : if (list == null) return null.hashCode;
185 : // Jenkins's one-at-a-time hash function.
186 : // This code is almost identical to the one in IterableEquality, except
187 : // that it uses indexing instead of iterating to get the elements.
188 : var hash = 0;
189 0 : for (var i = 0; i < list.length; i++) {
190 0 : var c = _elementEquality.hash(list[i]);
191 0 : hash = (hash + c) & _HASH_MASK;
192 0 : hash = (hash + (hash << 10)) & _HASH_MASK;
193 0 : hash ^= (hash >> 6);
194 : }
195 0 : hash = (hash + (hash << 3)) & _HASH_MASK;
196 0 : hash ^= (hash >> 11);
197 0 : hash = (hash + (hash << 15)) & _HASH_MASK;
198 : return hash;
199 : }
200 :
201 0 : @override
202 0 : bool isValidKey(Object? o) => o is List<E>;
203 : }
204 :
205 : abstract class _UnorderedEquality<E, T extends Iterable<E>?>
206 : implements Equality<T> {
207 : final Equality<E> _elementEquality;
208 :
209 0 : const _UnorderedEquality(this._elementEquality);
210 :
211 0 : @override
212 : bool equals(T elements1, T elements2) {
213 : if (identical(elements1, elements2)) return true;
214 : if (elements1 == null || elements2 == null) return false;
215 0 : var counts = HashMap(
216 0 : equals: _elementEquality.equals,
217 0 : hashCode: _elementEquality.hash,
218 0 : isValidKey: _elementEquality.isValidKey);
219 : var length = 0;
220 0 : for (var e in elements1) {
221 0 : var count = counts[e] ?? 0;
222 0 : counts[e] = count + 1;
223 0 : length++;
224 : }
225 0 : for (var e in elements2) {
226 0 : var count = counts[e];
227 0 : if (count == null || count == 0) return false;
228 0 : counts[e] = count - 1;
229 0 : length--;
230 : }
231 0 : return length == 0;
232 : }
233 :
234 0 : @override
235 : int hash(T elements) {
236 0 : if (elements == null) return null.hashCode;
237 : var hash = 0;
238 0 : for (E element in elements) {
239 0 : var c = _elementEquality.hash(element);
240 0 : hash = (hash + c) & _HASH_MASK;
241 : }
242 0 : hash = (hash + (hash << 3)) & _HASH_MASK;
243 0 : hash ^= (hash >> 11);
244 0 : hash = (hash + (hash << 15)) & _HASH_MASK;
245 : return hash;
246 : }
247 : }
248 :
249 : /// Equality of the elements of two iterables without considering order.
250 : ///
251 : /// Two iterables are considered equal if they have the same number of elements,
252 : /// and the elements of one set can be paired with the elements
253 : /// of the other iterable, so that each pair are equal.
254 : class UnorderedIterableEquality<E> extends _UnorderedEquality<E, Iterable<E>?> {
255 0 : const UnorderedIterableEquality(
256 : [Equality<E> elementEquality = const DefaultEquality<Never>()])
257 0 : : super(elementEquality);
258 :
259 0 : @override
260 0 : bool isValidKey(Object? o) => o is Iterable<E>;
261 : }
262 :
263 : /// Equality of sets.
264 : ///
265 : /// Two sets are considered equal if they have the same number of elements,
266 : /// and the elements of one set can be paired with the elements
267 : /// of the other set, so that each pair are equal.
268 : ///
269 : /// This equality behaves the same as [UnorderedIterableEquality] except that
270 : /// it expects sets instead of iterables as arguments.
271 : ///
272 : /// The [equals] and [hash] methods accepts `null` values,
273 : /// even if the [isValidKey] returns `false` for `null`.
274 : /// The [hash] of `null` is `null.hashCode`.
275 : class SetEquality<E> extends _UnorderedEquality<E, Set<E>?> {
276 0 : const SetEquality(
277 : [Equality<E> elementEquality = const DefaultEquality<Never>()])
278 0 : : super(elementEquality);
279 :
280 0 : @override
281 0 : bool isValidKey(Object? o) => o is Set<E>;
282 : }
283 :
284 : /// Internal class used by [MapEquality].
285 : ///
286 : /// The class represents a map entry as a single object,
287 : /// using a combined hashCode and equality of the key and value.
288 : class _MapEntry {
289 : final MapEquality equality;
290 : final key;
291 : final value;
292 0 : _MapEntry(this.equality, this.key, this.value);
293 :
294 0 : @override
295 : int get hashCode =>
296 0 : (3 * equality._keyEquality.hash(key) +
297 0 : 7 * equality._valueEquality.hash(value)) &
298 : _HASH_MASK;
299 :
300 0 : @override
301 : bool operator ==(Object other) =>
302 0 : other is _MapEntry &&
303 0 : equality._keyEquality.equals(key, other.key) &&
304 0 : equality._valueEquality.equals(value, other.value);
305 : }
306 :
307 : /// Equality on maps.
308 : ///
309 : /// Two maps are equal if they have the same number of entries, and if the
310 : /// entries of the two maps are pairwise equal on both key and value.
311 : ///
312 : /// The [equals] and [hash] methods accepts `null` values,
313 : /// even if the [isValidKey] returns `false` for `null`.
314 : /// The [hash] of `null` is `null.hashCode`.
315 : class MapEquality<K, V> implements Equality<Map<K, V>> {
316 : final Equality<K> _keyEquality;
317 : final Equality<V> _valueEquality;
318 0 : const MapEquality(
319 : {Equality<K> keys = const DefaultEquality<Never>(),
320 : Equality<V> values = const DefaultEquality<Never>()})
321 : : _keyEquality = keys,
322 : _valueEquality = values;
323 :
324 0 : @override
325 : bool equals(Map<K, V>? map1, Map<K, V>? map2) {
326 : if (identical(map1, map2)) return true;
327 : if (map1 == null || map2 == null) return false;
328 0 : var length = map1.length;
329 0 : if (length != map2.length) return false;
330 0 : Map<_MapEntry, int> equalElementCounts = HashMap();
331 0 : for (var key in map1.keys) {
332 0 : var entry = _MapEntry(this, key, map1[key]);
333 0 : var count = equalElementCounts[entry] ?? 0;
334 0 : equalElementCounts[entry] = count + 1;
335 : }
336 0 : for (var key in map2.keys) {
337 0 : var entry = _MapEntry(this, key, map2[key]);
338 0 : var count = equalElementCounts[entry];
339 0 : if (count == null || count == 0) return false;
340 0 : equalElementCounts[entry] = count - 1;
341 : }
342 : return true;
343 : }
344 :
345 0 : @override
346 : int hash(Map<K, V>? map) {
347 0 : if (map == null) return null.hashCode;
348 : var hash = 0;
349 0 : for (var key in map.keys) {
350 0 : var keyHash = _keyEquality.hash(key);
351 0 : var valueHash = _valueEquality.hash(map[key] as V);
352 0 : hash = (hash + 3 * keyHash + 7 * valueHash) & _HASH_MASK;
353 : }
354 0 : hash = (hash + (hash << 3)) & _HASH_MASK;
355 0 : hash ^= (hash >> 11);
356 0 : hash = (hash + (hash << 15)) & _HASH_MASK;
357 : return hash;
358 : }
359 :
360 0 : @override
361 0 : bool isValidKey(Object? o) => o is Map<K, V>;
362 : }
363 :
364 : /// Combines several equalities into a single equality.
365 : ///
366 : /// Tries each equality in order, using [Equality.isValidKey], and returns
367 : /// the result of the first equality that applies to the argument or arguments.
368 : ///
369 : /// For `equals`, the first equality that matches the first argument is used,
370 : /// and if the second argument of `equals` is not valid for that equality,
371 : /// it returns false.
372 : ///
373 : /// Because the equalities are tried in order, they should generally work on
374 : /// disjoint types. Otherwise the multi-equality may give inconsistent results
375 : /// for `equals(e1, e2)` and `equals(e2, e1)`. This can happen if one equality
376 : /// considers only `e1` a valid key, and not `e2`, but an equality which is
377 : /// checked later, allows both.
378 : class MultiEquality<E> implements Equality<E> {
379 : final Iterable<Equality<E>> _equalities;
380 :
381 0 : const MultiEquality(Iterable<Equality<E>> equalities)
382 : : _equalities = equalities;
383 :
384 0 : @override
385 : bool equals(E e1, E e2) {
386 0 : for (var eq in _equalities) {
387 0 : if (eq.isValidKey(e1)) return eq.isValidKey(e2) && eq.equals(e1, e2);
388 : }
389 : return false;
390 : }
391 :
392 0 : @override
393 : int hash(E e) {
394 0 : for (var eq in _equalities) {
395 0 : if (eq.isValidKey(e)) return eq.hash(e);
396 : }
397 : return 0;
398 : }
399 :
400 0 : @override
401 : bool isValidKey(Object? o) {
402 0 : for (var eq in _equalities) {
403 0 : if (eq.isValidKey(o)) return true;
404 : }
405 : return false;
406 : }
407 : }
408 :
409 : /// Deep equality on collections.
410 : ///
411 : /// Recognizes lists, sets, iterables and maps and compares their elements using
412 : /// deep equality as well.
413 : ///
414 : /// Non-iterable/map objects are compared using a configurable base equality.
415 : ///
416 : /// Works in one of two modes: ordered or unordered.
417 : ///
418 : /// In ordered mode, lists and iterables are required to have equal elements
419 : /// in the same order. In unordered mode, the order of elements in iterables
420 : /// and lists are not important.
421 : ///
422 : /// A list is only equal to another list, likewise for sets and maps. All other
423 : /// iterables are compared as iterables only.
424 : class DeepCollectionEquality implements Equality {
425 : final Equality _base;
426 : final bool _unordered;
427 0 : const DeepCollectionEquality([Equality base = const DefaultEquality<Never>()])
428 : : _base = base,
429 : _unordered = false;
430 :
431 : /// Creates a deep equality on collections where the order of lists and
432 : /// iterables are not considered important. That is, lists and iterables are
433 : /// treated as unordered iterables.
434 0 : const DeepCollectionEquality.unordered(
435 : [Equality base = const DefaultEquality<Never>()])
436 : : _base = base,
437 : _unordered = true;
438 :
439 0 : @override
440 : bool equals(e1, e2) {
441 0 : if (e1 is Set) {
442 0 : return e2 is Set && SetEquality(this).equals(e1, e2);
443 : }
444 0 : if (e1 is Map) {
445 0 : return e2 is Map && MapEquality(keys: this, values: this).equals(e1, e2);
446 : }
447 0 : if (!_unordered) {
448 0 : if (e1 is List) {
449 0 : return e2 is List && ListEquality(this).equals(e1, e2);
450 : }
451 0 : if (e1 is Iterable) {
452 0 : return e2 is Iterable && IterableEquality(this).equals(e1, e2);
453 : }
454 0 : } else if (e1 is Iterable) {
455 0 : if (e1 is List != e2 is List) return false;
456 0 : return e2 is Iterable && UnorderedIterableEquality(this).equals(e1, e2);
457 : }
458 0 : return _base.equals(e1, e2);
459 : }
460 :
461 0 : @override
462 : int hash(Object? o) {
463 0 : if (o is Set) return SetEquality(this).hash(o);
464 0 : if (o is Map) return MapEquality(keys: this, values: this).hash(o);
465 0 : if (!_unordered) {
466 0 : if (o is List) return ListEquality(this).hash(o);
467 0 : if (o is Iterable) return IterableEquality(this).hash(o);
468 0 : } else if (o is Iterable) {
469 0 : return UnorderedIterableEquality(this).hash(o);
470 : }
471 0 : return _base.hash(o);
472 : }
473 :
474 0 : @override
475 : bool isValidKey(Object? o) =>
476 0 : o is Iterable || o is Map || _base.isValidKey(o);
477 : }
478 :
479 : /// String equality that's insensitive to differences in ASCII case.
480 : ///
481 : /// Non-ASCII characters are compared as-is, with no conversion.
482 : class CaseInsensitiveEquality implements Equality<String> {
483 0 : const CaseInsensitiveEquality();
484 :
485 0 : @override
486 : bool equals(String string1, String string2) =>
487 0 : equalsIgnoreAsciiCase(string1, string2);
488 :
489 0 : @override
490 0 : int hash(String string) => hashIgnoreAsciiCase(string);
491 :
492 0 : @override
493 0 : bool isValidKey(Object? object) => object is String;
494 : }
|