LCOV - code coverage report
Current view: top level - collection-1.15.0/lib/src - equality.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 1 166 0.6 %
Date: 2021-11-28 14:37:50 Functions: 0 0 -

          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             : }

Generated by: LCOV version 1.14