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