Create associated packages for the dart:collection and dart:async libs.
R=sgjesse@google.com
Review URL: https://codereview.chromium.org//113883002
git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/collection@31260 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bb392fd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,55 @@
+Helper libraries for working with collections.
+
+The `collection` package contains a number of separate libraries
+with utility functions and classes that makes working with collections easier.
+
+## Using
+
+The `collection` package can be imported as separate libraries, or
+in totality:
+
+ import 'package:collection/equality.dart';
+ import 'package:collection/algorithms.dart';
+ import 'package:collection/wrappers.dart';
+
+or
+
+ import 'package:collection/collection.dart';
+
+## Equality
+
+The equality library gives a way to specify equality of elements and
+collections.
+
+Collections in Dart have no inherent equality. Two sets are not equal, even
+if they contain exactly the same objects as elements.
+
+The equality library provides a way to say define such an equality. In this
+case, for example, `const SetEquality(const IdentityEquality())` is an equality
+that considers two sets equal exactly if they contain identical elements.
+
+The library provides ways to define equalities on `Iterable`s, `List`s, `Set`s, and
+`Map`s, as well as combinations of these, such as:
+
+ const MapEquality(const IdentityEquality(), const ListEquality());
+
+This equality considers maps equal if they have identical keys, and the corresponding values are lists with equal (`operator==`) values.
+
+## Algorithms
+
+The algorithms library contains functions that operate on lists.
+
+It contains ways to shuffle a `List`, do binary search on a sorted `List`, and
+some different sorting algorithms.
+
+
+## Wrappers
+
+The wrappers library contains classes that "wrap" a collection.
+
+A wrapper class contains an object of the same type, and it forwards all
+methods to the wrapped object.
+
+Wrapper classes can be used in various ways, for example to restrict the type
+of an object to that of a supertype, or to change the behavior of selected
+functions on an existing object.
diff --git a/lib/algorithms.dart b/lib/algorithms.dart
new file mode 100644
index 0000000..5ff0bb3
--- /dev/null
+++ b/lib/algorithms.dart
@@ -0,0 +1,301 @@
+// Copyright (c) 2013, 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.
+
+/**
+ * Operations on collections.
+ */
+library dart.pkg.collection.algorithms;
+
+import "dart:math" show Random;
+
+/** Version of [binarySearch] optimized for comparable keys */
+int _comparableBinarySearch(List<Comparable> list, Comparable key) {
+ int min = 0;
+ int max = list.length;
+ while (min < max) {
+ int mid = min + ((max - min) >> 1);
+ var element = list[mid];
+ int comp = element.compareTo(key);
+ if (comp == 0) return mid;
+ if (comp < 0) {
+ min = mid + 1;
+ } else {
+ max = mid;
+ }
+ }
+ return -1;
+}
+
+/**
+ * Returns a position of the [key] in [sortedList], if it is there.
+ *
+ * If the list isn't sorted according to the [compare] function, the result
+ * is unpredictable.
+ *
+ * If [compare] is omitted, it defaults to calling [Comparable.compareTo] on
+ * the objects.
+ *
+ * Returns -1 if [key] is not in the list by default.
+ */
+int binarySearch(List sortedList, var key,
+ { int compare(var a, var b) }) {
+ if (compare == null) {
+ return _comparableBinarySearch(sortedList, key);
+ }
+ int min = 0;
+ int max = sortedList.length;
+ while (min < max) {
+ int mid = min + ((max - min) >> 1);
+ var element = sortedList[mid];
+ int comp = compare(element, key);
+ if (comp == 0) return mid;
+ if (comp < 0) {
+ min = mid + 1;
+ } else {
+ max = mid;
+ }
+ }
+ return -1;
+}
+
+
+/**
+ * Shuffles a list randomly.
+ *
+ * A sub-range of a list can be shuffled by providing [start] and [end].
+ */
+void shuffle(List list, [int start = 0, int end = null]) {
+ Random random = new Random();
+ if (end == null) end = list.length;
+ int length = end - start;
+ while (length > 1) {
+ int pos = random.nextInt(length);
+ length--;
+ var tmp1 = list[start + pos];
+ list[start + pos] = list[start + length];
+ list[start + length] = tmp1;
+ }
+}
+
+
+/**
+ * Reverses a list, or a part of a list, in-place.
+ */
+void reverse(List list, [int start = 0, int end = null]) {
+ if (end == null) end = list.length;
+ _reverse(list, start, end);
+}
+
+// Internal helper function that assumes valid arguments.
+void _reverse(List list, int start, int end) {
+ for (int i = start, j = end - 1; i < j; i++, j--) {
+ var tmp = list[i];
+ list[i] = list[j];
+ list[j] = tmp;
+ }
+}
+
+/**
+ * Sort a list using insertion sort.
+ *
+ * Insertion sort is a simple sorting algorithm. For `n` elements it does on
+ * the order of `n * log(n)` comparisons but up to `n` squared moves. The
+ * sorting is performed in-place, without using extra memory.
+ *
+ * For short lists the many moves have less impact than the simple algorithm,
+ * and it is often the favored sorting algorithm for short lists.
+ *
+ * This insertion sort is stable: Equal elements end up in the same order
+ * as they started in.
+ */
+void insertionSort(List list,
+ { int compare(a, b),
+ int start: 0,
+ int end: null }) {
+ // If the same method could have both positional and named optional
+ // parameters, this should be (list, [start, end], {compare}).
+ if (end == null) end = list.length;
+ if (compare == null) compare = Comparable.compare;
+ _insertionSort(list, compare, start, end, start + 1);
+}
+
+/**
+ * Internal helper function that assumes arguments correct.
+ *
+ * Assumes that the elements up to [sortedUntil] (not inclusive) are
+ * already sorted. The [sortedUntil] values should always be at least
+ * `start + 1`.
+ */
+void _insertionSort(List list, int compare(a, b), int start, int end,
+ int sortedUntil) {
+ for (int pos = sortedUntil; pos < end; pos++) {
+ int min = start;
+ int max = pos;
+ var element = list[pos];
+ while (min < max) {
+ int mid = min + ((max - min) >> 1);
+ int comparison = compare(element, list[mid]);
+ if (comparison < 0) {
+ max = mid;
+ } else {
+ min = mid + 1;
+ }
+ }
+ list.setRange(min + 1, pos + 1, list, min);
+ list[min] = element;
+ }
+}
+
+/** Limit below which merge sort defaults to insertion sort. */
+const int _MERGE_SORT_LIMIT = 32;
+
+/**
+ * Sorts a list, or a range of a list, using the merge sort algorithm.
+ *
+ * Merge-sorting works by splitting the job into two parts, sorting each
+ * recursively, and then merging the two sorted parts.
+ *
+ * This takes on the order of `n * log(n)` comparisons and moves to sort
+ * `n` elements, but requires extra space of about the same size as the list
+ * being sorted.
+ *
+ * This merge sort is stable: Equal elements end up in the same order
+ * as they started in.
+ */
+void mergeSort(List list, {int start: 0, int end: null, int compare(a, b)}) {
+ if (end == null) end = list.length;
+ if (compare == null) compare = Comparable.compare;
+ int length = end - start;
+ if (length < 2) return;
+ if (length < _MERGE_SORT_LIMIT) {
+ _insertionSort(list, compare, start, end, start + 1);
+ return;
+ }
+ // Special case the first split instead of directly calling
+ // _mergeSort, because the _mergeSort requires its target to
+ // be different from its source, and it requires extra space
+ // of the same size as the list to sort.
+ // This split allows us to have only half as much extra space,
+ // and it ends up in the original place.
+ int middle = start + ((end - start) >> 1);
+ int firstLength = middle - start;
+ int secondLength = end - middle;
+ // secondLength is always the same as firstLength, or one greater.
+ List scratchSpace = new List(secondLength);
+ _mergeSort(list, compare, middle, end, scratchSpace, 0);
+ int firstTarget = end - firstLength;
+ _mergeSort(list, compare, start, middle, list, firstTarget);
+ _merge(compare,
+ list, firstTarget, end,
+ scratchSpace, 0, secondLength,
+ list, start);
+}
+
+/**
+ * Performs an insertion sort into a potentially different list than the
+ * one containing the original values.
+ *
+ * It will work in-place as well.
+ */
+void _movingInsertionSort(List list, int compare(a, b), int start, int end,
+ List target, int targetOffset) {
+ int length = end - start;
+ if (length == 0) return;
+ target[targetOffset] = list[start];
+ for (int i = 1; i < length; i++) {
+ var element = list[start + i];
+ int min = targetOffset;
+ int max = targetOffset + i;
+ while (min < max) {
+ int mid = min + ((max - min) >> 1);
+ if (compare(element, target[mid]) < 0) {
+ max = mid;
+ } else {
+ min = mid + 1;
+ }
+ }
+ target.setRange(min + 1, targetOffset + i + 1,
+ target, min);
+ target[min] = element;
+ }
+}
+
+/**
+ * Sorts [list] from [start] to [end] into [target] at [targetOffset].
+ *
+ * The `target` list must be able to contain the range from `start` to `end`
+ * after `targetOffset`.
+ *
+ * Allows target to be the same list as [list], as long as it's not
+ * overlapping the `start..end` range.
+ */
+void _mergeSort(List list, int compare(a, b), int start, int end,
+ List target, int targetOffset) {
+ int length = end - start;
+ if (length < _MERGE_SORT_LIMIT) {
+ _movingInsertionSort(list, compare, start, end, target, targetOffset);
+ return;
+ }
+ int middle = start + (length >> 1);
+ int firstLength = middle - start;
+ int secondLength = end - middle;
+ // Here secondLength >= firstLength (differs by at most one).
+ int targetMiddle = targetOffset + firstLength;
+ // Sort the second half into the end of the target area.
+ _mergeSort(list, compare, middle, end,
+ target, targetMiddle);
+ // Sort the first half into the end of the source area.
+ _mergeSort(list, compare, start, middle,
+ list, middle);
+ // Merge the two parts into the target area.
+ _merge(compare,
+ list, middle, middle + firstLength,
+ target, targetMiddle, targetMiddle + secondLength,
+ target, targetOffset);
+}
+
+/**
+ * Merges two lists into a target list.
+ *
+ * One of the input lists may be positioned at the end of the target
+ * list.
+ *
+ * For equal object, elements from [firstList] are always preferred.
+ * This allows the merge to be stable if the first list contains elements
+ * that started out earlier than the ones in [secondList]
+ */
+void _merge(int compare(a, b),
+ List firstList, int firstStart, int firstEnd,
+ List secondList, int secondStart, int secondEnd,
+ List target, int targetOffset) {
+ // No empty lists reaches here.
+ assert(firstStart < firstEnd);
+ assert(secondStart < secondEnd);
+ int cursor1 = firstStart;
+ int cursor2 = secondStart;
+ var firstElement = firstList[cursor1++];
+ var secondElement = secondList[cursor2++];
+ while (true) {
+ if (compare(firstElement, secondElement) <= 0) {
+ target[targetOffset++] = firstElement;
+ if (cursor1 == firstEnd) break; // Flushing second list after loop.
+ firstElement = firstList[cursor1++];
+ } else {
+ target[targetOffset++] = secondElement;
+ if (cursor2 != secondEnd) {
+ secondElement = secondList[cursor2++];
+ continue;
+ }
+ // Second list empties first. Flushing first list here.
+ target[targetOffset++] = firstElement;
+ target.setRange(targetOffset, targetOffset + (firstEnd - cursor1),
+ firstList, cursor1);
+ return;
+ }
+ }
+ // First list empties first. Reached by break above.
+ target[targetOffset++] = secondElement;
+ target.setRange(targetOffset, targetOffset + (secondEnd - cursor2),
+ secondList, cursor2);
+}
diff --git a/lib/collection.dart b/lib/collection.dart
new file mode 100644
index 0000000..a6f192d
--- /dev/null
+++ b/lib/collection.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2013, 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.
+
+/**
+ * Exports all the individual parts of the collection-helper library.
+ *
+ * The sub-libraries of this package are:
+ *
+ * - `algorithms.dart`: Algorithms that work on lists (shuffle, binary search
+ * and various sorting algorithms).
+ * - `equality.dart`: Different notions of equality of collections.
+ * - `iterable_zip.dart`: Combining multiple iterables into one.
+ * - `wrappers.dart`: Wrapper classes that delegate to a collection object.
+ * Includes unmodifiable views of collections.
+ */
+library dart.pkg.collection;
+
+export "algorithms.dart";
+export "equality.dart";
+export "iterable_zip.dart";
+export "wrappers.dart";
diff --git a/lib/equality.dart b/lib/equality.dart
new file mode 100644
index 0000000..c6fdafa
--- /dev/null
+++ b/lib/equality.dart
@@ -0,0 +1,419 @@
+// Copyright (c) 2013, 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.
+
+/**
+ * Defines equality relations on collections.
+ */
+library dart.pkg.collection.equality;
+
+import "dart:collection";
+
+const int _HASH_MASK = 0x7fffffff;
+
+/**
+ * A generic equality relation on objects.
+ */
+abstract class Equality<E> {
+ const factory Equality() = DefaultEquality;
+
+ /**
+ * Compare two elements for being equal.
+ *
+ * This should be a proper equality relation.
+ */
+ bool equals(E e1, E e2);
+
+ /**
+ * Get a hashcode of an element.
+ *
+ * The hashcode should be compatible with [equals], so that if
+ * `equals(a, b)` then `hash(a) == hash(b)`.
+ */
+ int hash(E e);
+
+ /**
+ * Test whether an object is a valid argument to [equals] and [hash].
+ *
+ * Some implementations may be restricted to only work on specific types
+ * of objects.
+ */
+ bool isValidKey(Object o);
+}
+
+/**
+ * Equality of objects that compares only the natural equality of the objects.
+ *
+ * This equality uses the objects' own [Object.==] and [Object.hashCode] for
+ * the equality.
+ */
+class DefaultEquality implements Equality {
+ const DefaultEquality();
+ bool equals(Object e1, Object e2) => e1 == e2;
+ int hash(Object e) => e.hashCode;
+ bool isValidKey(Object o) => true;
+}
+
+/**
+ * Equality of objects that compares only the identity of the objects.
+ */
+class IdentityEquality implements Equality {
+ const IdentityEquality();
+ bool equals(Object e1, Object e2) => identical(e1, e2);
+ int hash(Object e) => identityHashCode(e);
+ bool isValidKey(Object o) => true;
+}
+
+/**
+ * Equality on iterables.
+ *
+ * Two iterables are equal if they have the same elements in the same order.
+ */
+class IterableEquality<E> implements Equality<Iterable<E>> {
+ final Equality<E> _elementEquality;
+ const IterableEquality([Equality<E> elementEquality =
+ const DefaultEquality()])
+ : _elementEquality = elementEquality;
+
+ bool equals(Iterable<E> elements1, Iterable<E> elements2) {
+ if (identical(elements1, elements2)) return true;
+ if (elements1 == null || elements2 == null) return false;
+ Iterator it1 = elements1.iterator;
+ Iterator it2 = elements2.iterator;
+ while (true) {
+ bool hasNext = it1.moveNext();
+ if (hasNext != it2.moveNext()) return false;
+ if (!hasNext) return true;
+ if (!_elementEquality.equals(it1.current, it2.current)) return false;
+ }
+ }
+
+ int hash(Iterable<E> elements) {
+ // Jenkins's one-at-a-time hash function.
+ int hash = 0;
+ for (E element in elements) {
+ int c = _elementEquality.hash(element);
+ hash = (hash + c) & _HASH_MASK;
+ hash = (hash + (hash << 10)) & _HASH_MASK;
+ hash ^= (hash >> 6);
+ }
+ hash = (hash + (hash << 3)) & _HASH_MASK;
+ hash ^= (hash >> 11);
+ hash = (hash + (hash << 15)) & _HASH_MASK;
+ return hash;
+ }
+
+ bool isValidKey(Object o) => o is Iterable<E>;
+}
+
+/**
+ * Equality on lists.
+ *
+ * Two lists are equal if they have the same length and their elements
+ * at each index are equal.
+ *
+ * This is effectively the same as [IterableEquality] except that it
+ * accesses elements by index instead of through iteration.
+ */
+class ListEquality<E> implements Equality<List<E>> {
+ final Equality<E> _elementEquality;
+ const ListEquality([Equality<E> elementEquality = const DefaultEquality()])
+ : _elementEquality = elementEquality;
+
+ bool equals(List<E> e1, List<E> e2) {
+ if (identical(e1, e2)) return true;
+ if (e1 == null || e2 == null) return false;
+ int length = e1.length;
+ if (length != e2.length) return false;
+ for (int i = 0; i < length; i++) {
+ if (!_elementEquality.equals(e1[i], e2[i])) return false;
+ }
+ return true;
+ }
+
+ int hash(List<E> e) {
+ // Jenkins's one-at-a-time hash function.
+ // This code is almost identical to the one in IterableEquality, except
+ // that it uses indexing instead of iterating to get the elements.
+ int hash = 0;
+ for (int i = 0; i < e.length; i++) {
+ int c = _elementEquality.hash(e[i]);
+ hash = (hash + c) & _HASH_MASK;
+ hash = (hash + (hash << 10)) & _HASH_MASK;
+ hash ^= (hash >> 6);
+ }
+ hash = (hash + (hash << 3)) & _HASH_MASK;
+ hash ^= (hash >> 11);
+ hash = (hash + (hash << 15)) & _HASH_MASK;
+ return hash;
+ }
+
+ bool isValidKey(Object o) => o is List<E>;
+}
+
+abstract class _UnorderedEquality<E, T extends Iterable<E>>
+ implements Equality<T> {
+ final Equality<E> _elementEquality;
+
+ const _UnorderedEquality(this._elementEquality);
+
+ bool equals(T e1, T e2) {
+ if (identical(e1, e2)) return true;
+ if (e1 == null || e2 == null) return false;
+ HashMap<E, int> counts = new HashMap(
+ equals: _elementEquality.equals,
+ hashCode: _elementEquality.hash,
+ isValidKey: _elementEquality.isValidKey);
+ int length = 0;
+ for (var e in e1) {
+ int count = counts[e];
+ if (count == null) count = 0;
+ counts[e] = count + 1;
+ length++;
+ }
+ for (var e in e2) {
+ int count = counts[e];
+ if (count == null || count == 0) return false;
+ counts[e] = count - 1;
+ length--;
+ }
+ return length == 0;
+ }
+
+ int hash(T e) {
+ int hash = 0;
+ for (E element in e) {
+ int c = _elementEquality.hash(element);
+ hash = (hash + c) & _HASH_MASK;
+ }
+ hash = (hash + (hash << 3)) & _HASH_MASK;
+ hash ^= (hash >> 11);
+ hash = (hash + (hash << 15)) & _HASH_MASK;
+ return hash;
+ }
+}
+
+/**
+ * Equality of the elements of two iterables without considering order.
+ *
+ * Two iterables are considered equal if they have the same number of elements,
+ * and the elements of one set can be paired with the elements
+ * of the other iterable, so that each pair are equal.
+ */
+class UnorderedIterableEquality<E> extends _UnorderedEquality<E, Iterable<E>> {
+ const UnorderedIterableEquality(
+ [Equality<E> elementEquality = const DefaultEquality()])
+ : super(elementEquality);
+
+ bool isValidKey(Object o) => o is Iterable<E>;
+}
+
+/**
+ * Equality of sets.
+ *
+ * Two sets are considered equal if they have the same number of elements,
+ * and the elements of one set can be paired with the elements
+ * of the other set, so that each pair are equal.
+ *
+ * This equality behaves the same as [UnorderedIterableEquality] except that
+ * it expects sets instead of iterables as arguments.
+ */
+class SetEquality<E> extends _UnorderedEquality<E, Set<E>> {
+ const SetEquality(
+ [Equality<E> elementEquality = const DefaultEquality()])
+ : super(elementEquality);
+
+ bool isValidKey(Object o) => o is Set<E>;
+}
+
+/**
+ * Internal class used by [MapEquality].
+ *
+ * The class represents a map entry as a single object,
+ * using a combined hashCode and equality of the key and value.
+ */
+class _MapEntry {
+ final MapEquality equality;
+ final key;
+ final value;
+ _MapEntry(this.equality, this.key, this.value);
+
+ int get hashCode =>
+ (3 * equality._keyEquality.hash(key) +
+ 7 * equality._valueEquality.hash(value)) & _HASH_MASK;
+
+ bool operator==(Object other) {
+ if (other is! _MapEntry) return false;
+ _MapEntry otherEntry = other;
+ return equality._keyEquality.equals(key, otherEntry.key) &&
+ equality._valueEquality.equals(value, otherEntry.value);
+
+ }
+}
+
+/**
+ * Equality on maps.
+ *
+ * Two maps are equal if they have the same number of entries, and if the
+ * entries of the two maps are pairwise equal on both key and value.
+ */
+class MapEquality<K, V> implements Equality<Map<K, V>> {
+ final Equality<K> _keyEquality;
+ final Equality<V> _valueEquality;
+ const MapEquality({ Equality<K> keys : const DefaultEquality(),
+ Equality<V> values : const DefaultEquality() })
+ : _keyEquality = keys, _valueEquality = values;
+
+ bool equals(Map<K, V> e1, Map<K, V> e2) {
+ if (identical(e1, e2)) return true;
+ if (e1 == null || e2 == null) return false;
+ int length = e1.length;
+ if (length != e2.length) return false;
+ Map<_MapEntry, int> equalElementCounts = new HashMap();
+ for (K key in e1.keys) {
+ _MapEntry entry = new _MapEntry(this, key, e1[key]);
+ int count = equalElementCounts[entry];
+ if (count == null) count = 0;
+ equalElementCounts[entry] = count + 1;
+ }
+ for (K key in e2.keys) {
+ _MapEntry entry = new _MapEntry(this, key, e2[key]);
+ int count = equalElementCounts[entry];
+ if (count == null || count == 0) return false;
+ equalElementCounts[entry] = count - 1;
+ }
+ return true;
+ }
+
+ int hash(Map<K, V> map) {
+ int hash = 0;
+ for (K key in map.keys) {
+ int keyHash = _keyEquality.hash(key);
+ int valueHash = _valueEquality.hash(map[key]);
+ hash = (hash + 3 * keyHash + 7 * valueHash) & _HASH_MASK;
+ }
+ hash = (hash + (hash << 3)) & _HASH_MASK;
+ hash ^= (hash >> 11);
+ hash = (hash + (hash << 15)) & _HASH_MASK;
+ return hash;
+ }
+
+ bool isValidKey(Object o) => o is Map<K, V>;
+}
+
+/**
+ * Combines several equalities into a single equality.
+ *
+ * Tries each equality in order, using [Equality.isValidKey], and returns
+ * the result of the first equality that applies to the argument or arguments.
+ *
+ * For `equals`, the first equality that matches the first argument is used,
+ * and if the second argument of `equals` is not valid for that equality,
+ * it returns false.
+ *
+ * Because the equalities are tried in order, they should generally work on
+ * disjoint types. Otherwise the multi-equality may give inconsistent results
+ * for `equals(e1, e2)` and `equals(e2, e1)`. This can happen if one equality
+ * considers only `e1` a valid key, and not `e2`, but an equality which is
+ * checked later, allows both.
+ */
+class MultiEquality<E> implements Equality<E> {
+ final Iterable<Equality<E>> _equalities;
+
+ const MultiEquality(Iterable<Equality<E>> equalities)
+ : _equalities = equalities;
+
+ bool equals(E e1, E e2) {
+ for (Equality<E> eq in _equalities) {
+ if (eq.isValidKey(e1)) return eq.isValidKey(e2) && eq.equals(e1, e2);
+ }
+ return false;
+ }
+
+ int hash(E e) {
+ for (Equality<E> eq in _equalities) {
+ if (eq.isValidKey(e)) return eq.hash(e);
+ }
+ return -1;
+ }
+
+ bool isValidKey(Object o) {
+ for (Equality<E> eq in _equalities) {
+ if (eq.isValidKey(o)) return true;
+ }
+ return false;
+ }
+}
+
+/**
+ * Deep equality on collections.
+ *
+ * Recognizes lists, sets, iterables and maps and compares their elements using
+ * deep equality as well.
+ *
+ * Non-iterable/map objects are compared using a configurable base equality.
+ *
+ * Works in one of two modes: ordered or unordered.
+ *
+ * In ordered mode, lists and iterables are required to have equal elements
+ * in the same order. In unordered mode, the order of elements in iterables
+ * and lists are not importan.
+ *
+ * A list is only equal to another list, likewise for sets and maps. All other
+ * iterables are compared as iterables only.
+ */
+class DeepCollectionEquality implements Equality {
+ final Equality _base;
+ final bool _unordered;
+ const DeepCollectionEquality([Equality base = const DefaultEquality()])
+ : _base = base, _unordered = false;
+
+ /**
+ * Creates a deep equality on collections where the order of lists and
+ * iterables are not considered important. That is, lists and iterables are
+ * treated as unordered iterables.
+ */
+ const DeepCollectionEquality.unordered(
+ [Equality base = const DefaultEquality()])
+ : _base = base, _unordered = true;
+
+ bool equals(e1, e2) {
+ if (e1 is Set) {
+ if (e2 is! Set) return false;
+ return new SetEquality(this).equals(e1, e2);
+ }
+ if (e1 is Map) {
+ if (e2 is! Map) return false;
+ return new MapEquality(keys: this, values: this).equals(e1, e2);
+ }
+ if (!_unordered) {
+ if (e1 is List) {
+ if (e2 is! List) return false;
+ return new ListEquality(this).equals(e1, e2);
+ }
+ if (e1 is Iterable) {
+ if (e2 is! Iterable) return false;
+ return new IterableEquality(this).equals(e1, e2);
+ }
+ } else if (e1 is Iterable) {
+ if (e2 is! Iterable) return false;
+ if (e1 is List != e2 is List) return false;
+ return new UnorderedIterableEquality(this).equals(e1, e2);
+ }
+ return _base.equals(e1, e2);
+ }
+
+ int hash(Object o) {
+ if (o is Set) return new SetEquality(this).hash(o);
+ if (o is Map) return new MapEquality(keys: this, values: this).hash(o);
+ if (!_unordered) {
+ if (o is List) return new ListEquality(this).hash(o);
+ if (o is Iterable) return new IterableEquality(this).hash(o);
+ } else if (o is Iterable) {
+ return new UnorderedIterableEquality(this).hash(o);
+ }
+ return _base.hash(o);
+ }
+
+ bool isValidKey(Object o) => o is Iterable || o is Map || _base.isValidKey(o);
+}
diff --git a/lib/src/unmodifiable_wrappers.dart b/lib/src/unmodifiable_wrappers.dart
new file mode 100644
index 0000000..022bbfe
--- /dev/null
+++ b/lib/src/unmodifiable_wrappers.dart
@@ -0,0 +1,251 @@
+// Copyright (c) 2013, 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.
+
+/**
+ * Wrappers that prevent a List, Set, or Map object from being modified.
+ *
+ * The [Set] and [Map] wrappers allow reading from the wrapped collection,
+ * but prohibit writing.
+ *
+ * The [List] wrapper prevents changes to the length of the wrapped list,
+ * but allows changes to the contents.
+ */
+part of dart.pkg.collection.wrappers;
+
+/**
+ * A fixed-length list.
+ *
+ * A `NonGrowableListView` contains a [List] object and ensures that
+ * its length does not change.
+ * Methods that would change the length of the list,
+ * such as [add] and [remove], throw an [UnsupportedError].
+ * All other methods work directly on the underlying list.
+ *
+ * This class _does_ allow changes to the contents of the wrapped list.
+ * You can, for example, [sort] the list.
+ * Permitted operations defer to the wrapped list.
+ */
+class NonGrowableListView<E> extends DelegatingList<E>
+ with NonGrowableListMixin<E> {
+ NonGrowableListView(List<E> listBase) : super(listBase);
+}
+
+/**
+ * Mixin class that implements a throwing version of all list operations that
+ * change the List's length.
+ */
+abstract class NonGrowableListMixin<E> implements List<E> {
+ static void _throw() {
+ throw new UnsupportedError(
+ "Cannot change the length of a fixed-length list");
+ }
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the length of the list are disallowed.
+ */
+ void set length(int newLength) => _throw();
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the length of the list are disallowed.
+ */
+ bool add(E value) {
+ _throw();
+ }
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the length of the list are disallowed.
+ */
+ void addAll(Iterable<E> iterable) => _throw();
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the length of the list are disallowed.
+ */
+ void insert(int index, E element) => _throw();
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the length of the list are disallowed.
+ */
+ void insertAll(int index, Iterable<E> iterable) => _throw();
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the length of the list are disallowed.
+ */
+ bool remove(Object value) { _throw(); }
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the length of the list are disallowed.
+ */
+ E removeAt(int index) { _throw(); }
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the length of the list are disallowed.
+ */
+ E removeLast() { _throw(); }
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the length of the list are disallowed.
+ */
+ void removeWhere(bool test(E element)) => _throw();
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the length of the list are disallowed.
+ */
+ void retainWhere(bool test(E element)) => _throw();
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the length of the list are disallowed.
+ */
+ void removeRange(int start, int end) => _throw();
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the length of the list are disallowed.
+ */
+ void replaceRange(int start, int end, Iterable<E> iterable) => _throw();
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the length of the list are disallowed.
+ */
+ void clear() => _throw();
+}
+
+/**
+ * An unmodifiable set.
+ *
+ * An UnmodifiableSetView contains a [Set] object and ensures
+ * that it does not change.
+ * Methods that would change the set,
+ * such as [add] and [remove], throw an [UnsupportedError].
+ * Permitted operations defer to the wrapped set.
+ */
+class UnmodifiableSetView<E> extends DelegatingSet<E>
+ with UnmodifiableSetMixin<E> {
+ UnmodifiableSetView(Set<E> setBase) : super(setBase);
+}
+
+/**
+ * Mixin class that implements a throwing version of all set operations that
+ * change the Set.
+ */
+abstract class UnmodifiableSetMixin<E> implements Set<E> {
+ void _throw() {
+ throw new UnsupportedError("Cannot modify an unmodifiable Set");
+ }
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the set are disallowed.
+ */
+ bool add(E value) {
+ _throw();
+ }
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the set are disallowed.
+ */
+ void addAll(Iterable<E> elements) => _throw();
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the set are disallowed.
+ */
+ bool remove(Object value) { _throw(); }
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the set are disallowed.
+ */
+ void removeAll(Iterable elements) => _throw();
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the set are disallowed.
+ */
+ void retainAll(Iterable elements) => _throw();
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the set are disallowed.
+ */
+ void removeWhere(bool test(E element)) => _throw();
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the set are disallowed.
+ */
+ void retainWhere(bool test(E element)) => _throw();
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the set are disallowed.
+ */
+ void clear() => _throw();
+}
+
+/**
+ * An unmodifiable map.
+ *
+ * An UnmodifiableMapView contains a [Map] object and ensures
+ * that it does not change.
+ * Methods that would change the map,
+ * such as [addAll] and [remove], throw an [UnsupportedError].
+ * Permitted operations defer to the wrapped map.
+ */
+class UnmodifiableMapView<K, V> extends DelegatingMap<K, V>
+ with UnmodifiableMapMixin<K, V> {
+ UnmodifiableMapView(Map<K, V> baseMap) : super(baseMap);
+}
+
+/**
+ * Mixin class that implements a throwing version of all map operations that
+ * change the Map.
+ */
+abstract class UnmodifiableMapMixin<K, V> implements Map<K, V> {
+ static void _throw() {
+ throw new UnsupportedError("Cannot modify an unmodifiable Map");
+ }
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the map are disallowed.
+ */
+ void operator []=(K key, V value) => _throw();
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the map are disallowed.
+ */
+ V putIfAbsent(K key, V ifAbsent()) { _throw(); }
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the map are disallowed.
+ */
+ void addAll(Map<K, V> other) => _throw();
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the map are disallowed.
+ */
+ V remove(K key) { _throw(); }
+
+ /**
+ * Throws an [UnsupportedError];
+ * operations that change the map are disallowed.
+ */
+ void clear() => _throw();
+}
diff --git a/lib/wrappers.dart b/lib/wrappers.dart
new file mode 100644
index 0000000..ec78f5c
--- /dev/null
+++ b/lib/wrappers.dart
@@ -0,0 +1,334 @@
+// Copyright (c) 2013, 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.
+
+/**
+ * Delegating wrappers for [Iterable], [List], [Set], [Queue] and [Map].
+ *
+ * Also adds unmodifiable views for `Set` and `Map`, and a fixed length
+ * view for `List`. The unmodifable list view from `dart:collection` is exported
+ * as well, just for completeness.
+ */
+library dart.pkg.collection.wrappers;
+
+import "dart:collection";
+import "dart:math" show Random;
+
+export "dart:collection" show UnmodifiableListView;
+
+part "src/unmodifiable_wrappers.dart";
+
+/**
+ * Creates an [Iterable] that delegates all operations to a base iterable.
+ *
+ * This class can be used hide non-`Iterable` methods of an iterable object,
+ * or it can be extended to add extra functionality on top of an existing
+ * iterable object.
+ */
+class DelegatingIterable<E> implements Iterable<E> {
+ Iterable<E> _base;
+
+ /**
+ * Create a wrapper that forwards operations to [base].
+ */
+ DelegatingIterable(Iterable<E> base) : _base = base;
+
+ bool any(bool test(E element)) => _base.any(test);
+
+ bool contains(Object element) => _base.contains(element);
+
+ E elementAt(int index) => _base.elementAt(index);
+
+ bool every(bool test(E element)) => _base.every(test);
+
+ Iterable expand(Iterable f(E element)) => _base.expand(f);
+
+ E get first => _base.first;
+
+ E firstWhere(bool test(E element), {E orElse()}) =>
+ _base.firstWhere(test, orElse: orElse);
+
+ fold(initialValue, combine(previousValue, E element)) =>
+ _base.fold(initialValue, combine);
+
+ void forEach(void f(E element)) => _base.forEach(f);
+
+ bool get isEmpty => _base.isEmpty;
+
+ bool get isNotEmpty => _base.isNotEmpty;
+
+ Iterator<E> get iterator => _base.iterator;
+
+ String join([String separator = ""]) => _base.join(separator);
+
+ E get last => _base.last;
+
+ E lastWhere(bool test(E element), {E orElse()}) =>
+ _base.lastWhere(test, orElse: orElse);
+
+ int get length => _base.length;
+
+ Iterable map(f(E element)) => _base.map(f);
+
+ E reduce(E combine(E value, E element)) => _base.reduce(combine);
+
+ E get single => _base.single;
+
+ E singleWhere(bool test(E element)) => _base.singleWhere(test);
+
+ Iterable<E> skip(int n) => _base.skip(n);
+
+ Iterable<E> skipWhile(bool test(E value)) => _base.skipWhile(test);
+
+ Iterable<E> take(int n) => _base.take(n);
+
+ Iterable<E> takeWhile(bool test(E value)) => _base.takeWhile(test);
+
+ List<E> toList({bool growable: true}) => _base.toList(growable: growable);
+
+ Set<E> toSet() => _base.toSet();
+
+ Iterable<E> where(bool test(E element)) => _base.where(test);
+}
+
+
+/**
+ * Creates a [List] that delegates all operations to a base list.
+ *
+ * This class can be used hide non-`List` methods of a list object,
+ * or it can be extended to add extra functionality on top of an existing
+ * list object.
+ */
+class DelegatingList<E> extends DelegatingIterable<E> implements List<E> {
+ DelegatingList(List<E> base) : super(base);
+
+ List<E> get _listBase => _base;
+
+ E operator [](int index) => _listBase[index];
+
+ void operator []=(int index, E value) {
+ _listBase[index] = value;
+ }
+
+ void add(E value) {
+ _listBase.add(value);
+ }
+
+ void addAll(Iterable<E> iterable) {
+ _listBase.addAll(iterable);
+ }
+
+ Map<int, E> asMap() => _listBase.asMap();
+
+ void clear() {
+ _listBase.clear();
+ }
+
+ void fillRange(int start, int end, [E fillValue]) {
+ _listBase.fillRange(start, end, fillValue);
+ }
+
+ Iterable<E> getRange(int start, int end) => _listBase.getRange(start, end);
+
+ int indexOf(E element, [int start = 0]) => _listBase.indexOf(element, start);
+
+ void insert(int index, E element) {
+ _listBase.insert(index, element);
+ }
+
+ void insertAll(int index, Iterable<E> iterable) {
+ _listBase.insertAll(index, iterable);
+ }
+
+ int lastIndexOf(E element, [int start]) =>
+ _listBase.lastIndexOf(element, start);
+
+ void set length(int newLength) {
+ _listBase.length = newLength;
+ }
+
+ bool remove(Object value) => _listBase.remove(value);
+
+ E removeAt(int index) => _listBase.removeAt(index);
+
+ E removeLast() => _listBase.removeLast();
+
+ void removeRange(int start, int end) {
+ _listBase.removeRange(start, end);
+ }
+
+ void removeWhere(bool test(E element)) {
+ _listBase.removeWhere(test);
+ }
+
+ void replaceRange(int start, int end, Iterable<E> iterable) {
+ _listBase.replaceRange(start, end, iterable);
+ }
+
+ void retainWhere(bool test(E element)) {
+ _listBase.retainWhere(test);
+ }
+
+ Iterable<E> get reversed => _listBase.reversed;
+
+ void setAll(int index, Iterable<E> iterable) {
+ _listBase.setAll(index, iterable);
+ }
+
+ void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) {
+ _listBase.setRange(start, end, iterable, skipCount);
+ }
+
+ void shuffle([Random random]) {
+ _listBase.shuffle(random);
+ }
+
+ void sort([int compare(E a, E b)]) {
+ _listBase.sort(compare);
+ }
+
+ List<E> sublist(int start, [int end]) => _listBase.sublist(start, end);
+}
+
+
+/**
+ * Creates a [Set] that delegates all operations to a base set.
+ *
+ * This class can be used hide non-`Set` methods of a set object,
+ * or it can be extended to add extra functionality on top of an existing
+ * set object.
+ */
+class DelegatingSet<E> extends DelegatingIterable<E> implements Set<E> {
+ DelegatingSet(Set<E> base) : super(base);
+
+ Set<E> get _setBase => _base;
+
+ bool add(E value) => _setBase.add(value);
+
+ void addAll(Iterable<E> elements) {
+ _setBase.addAll(elements);
+ }
+
+ void clear() {
+ _setBase.clear();
+ }
+
+ bool containsAll(Iterable<Object> other) => _setBase.containsAll(other);
+
+ Set<E> difference(Set<E> other) => _setBase.difference(other);
+
+ Set<E> intersection(Set<Object> other) => _setBase.intersection(other);
+
+ E lookup(E element) => _setBase.lookup(element);
+
+ bool remove(Object value) => _setBase.remove(value);
+
+ void removeAll(Iterable<Object> elements) {
+ _setBase.removeAll(elements);
+ }
+
+ void removeWhere(bool test(E element)) {
+ _setBase.removeWhere(test);
+ }
+
+ void retainAll(Iterable<Object> elements) {
+ _setBase.retainAll(elements);
+ }
+
+ void retainWhere(bool test(E element)) {
+ _setBase.retainWhere(test);
+ }
+
+ Set<E> union(Set<E> other) => _setBase.union(other);
+}
+
+/**
+ * Creates a [Queue] that delegates all operations to a base queue.
+ *
+ * This class can be used hide non-`Queue` methods of a queue object,
+ * or it can be extended to add extra functionality on top of an existing
+ * queue object.
+ */
+class DelegatingQueue<E> extends DelegatingIterable<E> implements Queue<E> {
+ DelegatingQueue(Queue<E> queue) : super(queue);
+
+ Queue<E> get _baseQueue => _base;
+
+ void add(E value) {
+ _baseQueue.add(value);
+ }
+
+ void addAll(Iterable<E> iterable) {
+ _baseQueue.addAll(iterable);
+ }
+
+ void addFirst(E value) {
+ _baseQueue.addFirst(value);
+ }
+
+ void addLast(E value) {
+ _baseQueue.addLast(value);
+ }
+
+ void clear() {
+ _baseQueue.clear();
+ }
+
+ bool remove(Object object) => _baseQueue.remove(object);
+
+ void removeWhere(bool test(E element)) { _baseQueue.removeWhere(test); }
+
+ void retainWhere(bool test(E element)) { _baseQueue.retainWhere(test); }
+
+ E removeFirst() => _baseQueue.removeFirst();
+
+ E removeLast() => _baseQueue.removeLast();
+}
+
+/**
+ * Creates a [Map] that delegates all operations to a base map.
+ *
+ * This class can be used hide non-`Map` methods of an object that extends
+ * `Map`, or it can be extended to add extra functionality on top of an existing
+ * map object.
+ */
+class DelegatingMap<K, V> implements Map<K, V> {
+ Map<K, V> _base;
+ DelegatingMap(Map<K, V> base) : _base = base;
+
+ V operator [](Object key) => _base[key];
+
+ void operator []=(K key, V value) {
+ _base[key] = value;
+ }
+
+ void addAll(Map<K, V> other) {
+ _base.addAll(other);
+ }
+
+ void clear() {
+ _base.clear();
+ }
+
+ bool containsKey(Object key) => _base.containsKey(key);
+
+ bool containsValue(Object value) => _base.containsValue(value);
+
+ void forEach(void f(K key, V value)) {
+ _base.forEach(f);
+ }
+
+ bool get isEmpty => _base.isEmpty;
+
+ bool get isNotEmpty => _base.isNotEmpty;
+
+ Iterable<K> get keys => _base.keys;
+
+ int get length => _base.length;
+
+ V putIfAbsent(K key, V ifAbsent()) => _base.putIfAbsent(key, ifAbsent);
+
+ V remove(Object key) => _base.remove(key);
+
+ Iterable<V> get values => _base.values;
+}
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 0000000..cdcbe88
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,9 @@
+name: collection
+version: 0.9.0
+author: '"Dart Team <misc@dartlang.org>"'
+description: Collections and utilities functions and classes related to collections.
+homepage: http://www.dartlang.org
+dev_dependencies:
+ unittest: ">=0.9.0 <0.10.0"
+environment:
+ sdk: ">=1.0.0 <2.0.0"
diff --git a/test/algorithms_test.dart b/test/algorithms_test.dart
new file mode 100644
index 0000000..933e268
--- /dev/null
+++ b/test/algorithms_test.dart
@@ -0,0 +1,271 @@
+// Copyright (c) 2013, 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.
+
+/// Tests algorithm utilities.
+
+import "package:collection/collection.dart";
+import "package:unittest/unittest.dart";
+import 'dart:math';
+
+void main() {
+ void testShuffle(List list) {
+ List copy = list.toList();
+ shuffle(list);
+ expect(new UnorderedIterableEquality().equals(list, copy), isTrue);
+ }
+
+ test("Shuffle 0", () {
+ testShuffle([]);
+ });
+ test("Shuffle 1", () {
+ testShuffle([1]);
+ });
+ test("Shuffle 3", () {
+ testShuffle([1, 2, 3]);
+ });
+ test("Shuffle 10", () {
+ testShuffle([1, 2, 3, 4, 5, 1, 3, 5, 7, 9]);
+ });
+ test("Shuffle shuffles", () {
+ List l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
+ List c = l.toList();
+ int count = 0;
+ do {
+ shuffle(l);
+ if (!const ListEquality().equals(c, l)) return;
+ // Odds of not changing the order should be one in ~ 16! ~= 2e+13.
+ // Repeat this 10 times, and the odds of accidentally shuffling to the
+ // same result every time is disappearingly tiny.
+ count++;
+ // If this happens even once, it's ok to report it.
+ print("Failed shuffle $count times");
+ if (count == 10) fail("Shuffle didn't change order.");
+ } while (true);
+ });
+ test("Shuffle sublist", (){
+ List l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
+ List c = l.toList();
+ shuffle(l, 4, 12);
+ expect(const IterableEquality().equals(l.getRange(0, 4),
+ c.getRange(0, 4)), isTrue);
+ expect(const IterableEquality().equals(l.getRange(12, 16),
+ c.getRange(12, 16)), isTrue);
+ expect(const UnorderedIterableEquality().equals(l.getRange(4, 12),
+ c.getRange(4, 12)),
+ isTrue);
+
+ });
+
+ test("binsearch0", () {
+ expect(binarySearch([], 2), equals(-1));
+ });
+
+ test("binsearch1", () {
+ expect(binarySearch([5], 2), equals(-1));
+ expect(binarySearch([5], 5), equals(0));
+ expect(binarySearch([5], 7), equals(-1));
+ });
+
+ test("binsearch3", () {
+ expect(binarySearch([0, 5, 10], -1), equals(-1));
+ expect(binarySearch([0, 5, 10], 0), equals(0));
+ expect(binarySearch([0, 5, 10], 2), equals(-1));
+ expect(binarySearch([0, 5, 10], 5), equals(1));
+ expect(binarySearch([0, 5, 10], 7), equals(-1));
+ expect(binarySearch([0, 5, 10], 10), equals(2));
+ expect(binarySearch([0, 5, 10], 12), equals(-1));
+ });
+
+ test("binsearchCompare0", () {
+ expect(binarySearch([], new C(2), compare: compareC), equals(-1));
+ });
+
+ test("binsearchCompare1", () {
+ var l1 = [new C(5)];
+ expect(binarySearch(l1, new C(2), compare: compareC), equals(-1));
+ expect(binarySearch(l1, new C(5), compare: compareC), equals(0));
+ expect(binarySearch(l1, new C(7), compare: compareC), equals(-1));
+ });
+
+ test("binsearchCompare3", () {
+ var l3 = [new C(0), new C(5), new C(10)];
+ expect(binarySearch(l3, new C(-1), compare: compareC), equals(-1));
+ expect(binarySearch(l3, new C(0), compare: compareC), equals(0));
+ expect(binarySearch(l3, new C(2), compare: compareC), equals(-1));
+ expect(binarySearch(l3, new C(5), compare: compareC), equals(1));
+ expect(binarySearch(l3, new C(7), compare: compareC), equals(-1));
+ expect(binarySearch(l3, new C(10), compare: compareC), equals(2));
+ expect(binarySearch(l3, new C(12), compare: compareC), equals(-1));
+ });
+
+ test("insertionSortRandom", () {
+ Random random = new Random();
+ for (int i = 0; i < 25; i++) {
+ List list = new List(i);
+ for (int j = 0; j < i; j++) {
+ list[j] = random.nextInt(25); // Expect some equal elements.
+ }
+ insertionSort(list);
+ for (int j = 1; j < i; j++) {
+ expect(list[j - 1], lessThanOrEqualTo(list[j]));
+ }
+ }
+ });
+
+ test("insertionSortSubRanges", () {
+ List l = [6, 5, 4, 3, 2, 1];
+ insertionSort(l, start: 2, end: 4);
+ expect(l, equals([6, 5, 3, 4, 2, 1]));
+ insertionSort(l, start: 1, end: 1);
+ expect(l, equals([6, 5, 3, 4, 2, 1]));
+ insertionSort(l, start: 4, end: 6);
+ expect(l, equals([6, 5, 3, 4, 1, 2]));
+ insertionSort(l, start: 0, end: 2);
+ expect(l, equals([5, 6, 3, 4, 1, 2]));
+ insertionSort(l, start: 0, end: 6);
+ expect(l, equals([1, 2, 3, 4, 5, 6]));
+ });
+
+ test("insertionSortSpecialCases", () {
+ List l = [6, 6, 6, 6, 6, 6];
+ insertionSort(l);
+ expect(l, equals([6, 6, 6, 6, 6, 6]));
+
+ l = [6, 6, 3, 3, 0, 0];
+ insertionSort(l);
+ expect(l, equals([0, 0, 3, 3, 6, 6]));
+ });
+
+ test("MergeSortRandom", () {
+ Random random = new Random();
+ for (int i = 0; i < 250; i += 1) {
+ List list = new List(i);
+ for (int j = 0; j < i; j++) {
+ list[j] = random.nextInt(i); // Expect some equal elements.
+ }
+ mergeSort(list);
+ for (int j = 1; j < i; j++) {
+ expect(list[j - 1], lessThanOrEqualTo(list[j]));
+ }
+ }
+ });
+
+ test("MergeSortPreservesOrder", () {
+ Random random = new Random();
+ // Small case where only insertion call is called,
+ // larger case where the internal moving insertion sort is used
+ // larger cases with multiple splittings, numbers just around a power of 2.
+ for (int size in [8, 50, 511, 512, 513]) {
+ List list = new List(size);
+ // Class OC compares using id.
+ // With size elements with id's in the range 0..size/4, a number of
+ // collisions are guaranteed. These should be sorted so that the "order"
+ // part of the objects are still in order.
+ for (int i = 0; i < size; i++) {
+ list[i] = new OC(random.nextInt(size >> 2), i);
+ }
+ mergeSort(list);
+ OC prev = list[0];
+ for (int i = 1; i < size; i++) {
+ OC next = list[i];
+ expect(prev.id, lessThanOrEqualTo(next.id));
+ if (next.id == prev.id) {
+ expect(prev.order, lessThanOrEqualTo(next.order));
+ }
+ prev = next;
+ }
+ // Reverse compare on part of list.
+ List copy = list.toList();
+ int min = size >> 2;
+ int max = size - min;
+ mergeSort(list, start: min, end: max, compare: (a, b) => b.compareTo(a));
+ prev = list[min];
+ for (int i = min + 1; i < max; i++) {
+ OC next = list[i];
+ expect(prev.id, greaterThanOrEqualTo(next.id));
+ if (next.id == prev.id) {
+ expect(prev.order, lessThanOrEqualTo(next.order));
+ }
+ prev = next;
+ }
+ // Equals on OC objects is identity, so this means the parts before min,
+ // and the parts after max, didn't change at all.
+ expect(list.sublist(0, min), equals(copy.sublist(0, min)));
+ expect(list.sublist(max), equals(copy.sublist(max)));
+ }
+ });
+
+ test("MergeSortSpecialCases", () {
+ for (int size in [511, 512, 513]) {
+ // All equal.
+ List list = new List(size);
+ for (int i = 0; i < size; i++) {
+ list[i] = new OC(0, i);
+ }
+ mergeSort(list);
+ for (int i = 0; i < size; i++) {
+ expect(list[i].order, equals(i));
+ }
+ // All but one equal, first.
+ list[0] = new OC(1, 0);
+ for (int i = 1; i < size; i++) {
+ list[i] = new OC(0, i);
+ }
+ mergeSort(list);
+ for (int i = 0; i < size - 1; i++) {
+ expect(list[i].order, equals(i + 1));
+ }
+ expect(list[size - 1].order, equals(0));
+
+ // All but one equal, last.
+ for (int i = 0; i < size - 1; i++) {
+ list[i] = new OC(0, i);
+ }
+ list[size - 1] = new OC(-1, size - 1);
+ mergeSort(list);
+ expect(list[0].order, equals(size - 1));
+ for (int i = 1; i < size; i++) {
+ expect(list[i].order, equals(i - 1));
+ }
+
+ // Reversed.
+ for (int i = 0; i < size; i++) {
+ list[i] = new OC(size - 1 - i, i);
+ }
+ mergeSort(list);
+ for (int i = 0; i < size; i++) {
+ expect(list[i].id, equals(i));
+ expect(list[i].order, equals(size - 1 - i));
+ }
+ }
+ });
+
+ test("Reverse", () {
+ List l = [6, 5, 4, 3, 2, 1];
+ reverse(l, 2, 4);
+ expect(l, equals([6, 5, 3, 4, 2, 1]));
+ reverse(l, 1, 1);
+ expect(l, equals([6, 5, 3, 4, 2, 1]));
+ reverse(l, 4, 6);
+ expect(l, equals([6, 5, 3, 4, 1, 2]));
+ reverse(l, 0, 2);
+ expect(l, equals([5, 6, 3, 4, 1, 2]));
+ reverse(l, 0, 6);
+ expect(l, equals([2, 1, 4, 3, 6, 5]));
+ });
+}
+
+class C {
+ final int id;
+ C(this.id);
+}
+int compareC(C one, C other) => one.id - other.id;
+
+class OC implements Comparable<OC> {
+ final int id;
+ final int order;
+ OC(this.id, this.order);
+ int compareTo(OC other) => id - other.id;
+ String toString() => "OC[$id,$order]";
+}
diff --git a/test/equality_test.dart b/test/equality_test.dart
new file mode 100644
index 0000000..edabfd5
--- /dev/null
+++ b/test/equality_test.dart
@@ -0,0 +1,164 @@
+// Copyright (c) 2013, 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.
+
+// Tests equality utilities.
+
+import "dart:collection";
+import "package:collection/collection.dart";
+import "package:unittest/unittest.dart";
+
+main() {
+ test("IterableEquality - List", () {
+ var l1 = [1, 2, 3, 4, 5];
+ var l2 = [1.0, 2.0, 3.0, 4.0, 5.0];
+ expect(const IterableEquality().equals(l1, l2), isTrue);
+ Equality iterId = const IterableEquality(const IdentityEquality());
+ expect(iterId.equals(l1, l2), isFalse); /// 01: ok
+ });
+
+ test("IterableEquality - LinkedSet", () {
+ var l1 = new LinkedHashSet.from([1, 2, 3, 4, 5]);
+ var l2 = new LinkedHashSet.from([1.0, 2.0, 3.0, 4.0, 5.0]);
+ expect(const IterableEquality().equals(l1, l2), isTrue);
+ Equality iterId = const IterableEquality(const IdentityEquality());
+ expect(iterId.equals(l1, l2), isFalse); /// 02: ok
+ });
+
+ test("ListEquality", () {
+ var l1 = [1, 2, 3, 4, 5];
+ var l2 = [1.0, 2.0, 3.0, 4.0, 5.0];
+ expect(const ListEquality().equals(l1, l2),
+ isTrue);
+ Equality listId = const ListEquality(const IdentityEquality());
+ expect(listId.equals(l1, l2), isFalse); /// 03: ok
+ });
+
+ test("ListInequality length", () {
+ var l1 = [1, 2, 3, 4, 5];
+ var l2 = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
+ expect(const ListEquality().equals(l1, l2),
+ isFalse);
+ expect(const ListEquality(const IdentityEquality()).equals(l1, l2),
+ isFalse);
+ });
+
+ test("ListInequality value", () {
+ var l1 = [1, 2, 3, 4, 5];
+ var l2 = [1.0, 2.0, 3.0, 4.0, 6.0];
+ expect(const ListEquality().equals(l1, l2),
+ isFalse);
+ expect(const ListEquality(const IdentityEquality()).equals(l1, l2),
+ isFalse);
+ });
+
+ test("UnorderedIterableEquality", () {
+ var l1 = [1, 2, 3, 4, 5];
+ var l2 = [1.0, 3.0, 5.0, 4.0, 2.0];
+ expect(const UnorderedIterableEquality().equals(l1, l2),
+ isTrue);
+ Equality uniterId =
+ const UnorderedIterableEquality(const IdentityEquality());
+ expect(uniterId.equals(l1, l2), isFalse); /// 04: ok
+ });
+
+ test("UnorderedIterableInequality length", () {
+ var l1 = [1, 2, 3, 4, 5];
+ var l2 = [1.0, 3.0, 5.0, 4.0, 2.0, 1.0];
+ expect(const UnorderedIterableEquality().equals(l1, l2),
+ isFalse);
+ expect(const UnorderedIterableEquality(const IdentityEquality())
+ .equals(l1, l2),
+ isFalse);
+ });
+
+ test("UnorderedIterableInequality values", () {
+ var l1 = [1, 2, 3, 4, 5];
+ var l2 = [1.0, 3.0, 5.0, 4.0, 6.0];
+ expect(const UnorderedIterableEquality().equals(l1, l2),
+ isFalse);
+ expect(const UnorderedIterableEquality(const IdentityEquality())
+ .equals(l1, l2),
+ isFalse);
+ });
+
+ test("SetEquality", () {
+ var l1 = new HashSet.from([1, 2, 3, 4, 5]);
+ var l2 = new LinkedHashSet.from([1.0, 3.0, 5.0, 4.0, 2.0]);
+ expect(const SetEquality().equals(l1, l2),
+ isTrue);
+ Equality setId = const SetEquality(const IdentityEquality());
+ expect(setId.equals(l1, l2), isFalse); /// 05: ok
+ });
+
+ test("SetInequality length", () {
+ var l1 = new HashSet.from([1, 2, 3, 4, 5]);
+ var l2 = new LinkedHashSet.from([1.0, 3.0, 5.0, 4.0, 2.0, 6.0]);
+ expect(const SetEquality().equals(l1, l2),
+ isFalse);
+ expect(const SetEquality(const IdentityEquality()).equals(l1, l2),
+ isFalse);
+ });
+
+ test("SetInequality value", () {
+ var l1 = new HashSet.from([1, 2, 3, 4, 5]);
+ var l2 = new LinkedHashSet.from([1.0, 3.0, 5.0, 4.0, 6.0]);
+ expect(const SetEquality().equals(l1, l2),
+ isFalse);
+ expect(const SetEquality(const IdentityEquality()).equals(l1, l2),
+ isFalse);
+ });
+
+ var map1a = {"x": [1, 2, 3], "y": [true, false, null]};
+ var map1b = {"x": [4.0, 5.0, 6.0], "y": [false, true, null]};
+ var map2a = {"x": [3.0, 2.0, 1.0], "y": [false, true, null]};
+ var map2b = {"x": [6, 5, 4], "y": [null, false, true]};
+ var l1 = [map1a, map1b];
+ var l2 = [map2a, map2b];
+ var s1 = new Set.from(l1);
+ var s2 = new Set.from([map2b, map2a]);
+
+ test("RecursiveEquality", () {
+ const unordered = const UnorderedIterableEquality();
+ expect(unordered.equals(map1a["x"], map2a["x"]),
+ isTrue);
+ expect(unordered.equals(map1a["y"], map2a["y"]),
+ isTrue);
+ expect(unordered.equals(map1b["x"], map2b["x"]),
+ isTrue);
+ expect(unordered.equals(map1b["y"], map2b["y"]),
+ isTrue);
+ const mapval = const MapEquality(values: unordered);
+ expect(
+ mapval.equals(map1a, map2a),
+ isTrue);
+ expect(mapval.equals(map1b, map2b),
+ isTrue);
+ const listmapval = const ListEquality(mapval);
+ expect(listmapval.equals(l1, l2),
+ isTrue);
+ const setmapval = const SetEquality(mapval);
+ expect(setmapval.equals(s1, s2),
+ isTrue);
+ });
+
+ test("DeepEquality", () {
+ var colleq = const DeepCollectionEquality.unordered();
+ expect(colleq.equals(map1a["x"], map2a["x"]),
+ isTrue);
+ expect(colleq.equals(map1a["y"], map2a["y"]),
+ isTrue);
+ expect(colleq.equals(map1b["x"], map2b["x"]),
+ isTrue);
+ expect(colleq.equals(map1b["y"], map2b["y"]),
+ isTrue);
+ expect(colleq.equals(map1a, map2a),
+ isTrue);
+ expect(colleq.equals(map1b, map2b),
+ isTrue);
+ expect(colleq.equals(l1, l2),
+ isTrue);
+ expect(colleq.equals(s1, s2),
+ isTrue);
+ });
+}
diff --git a/test/unmodifiable_collection_test.dart b/test/unmodifiable_collection_test.dart
new file mode 100644
index 0000000..d810b75
--- /dev/null
+++ b/test/unmodifiable_collection_test.dart
@@ -0,0 +1,629 @@
+// Copyright (c) 2013, 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:collection/wrappers.dart";
+import "package:unittest/unittest.dart";
+
+// Test unmodifiable collection views.
+// The collections should pass through the operations that are allowed,
+// an throw on the ones that aren't without affecting the original.
+
+main() {
+ List list = [];
+ testUnmodifiableList(list, new UnmodifiableListView(list), "empty");
+ list = [42];
+ testUnmodifiableList(list, new UnmodifiableListView(list), "single-42");
+ list = [7];
+ testUnmodifiableList(list, new UnmodifiableListView(list), "single!42");
+ list = [1, 42, 10];
+ testUnmodifiableList(list, new UnmodifiableListView(list), "three-42");
+ list = [1, 7, 10];
+ testUnmodifiableList(list, new UnmodifiableListView(list), "three!42");
+
+ list = [];
+ testNonGrowableList(list, new NonGrowableListView(list), "empty");
+ list = [42];
+ testNonGrowableList(list, new NonGrowableListView(list), "single-42");
+ list = [7];
+ testNonGrowableList(list, new NonGrowableListView(list), "single!42");
+ list = [1, 42, 10];
+ testNonGrowableList(list, new NonGrowableListView(list), "three-42");
+ list = [1, 7, 10];
+ testNonGrowableList(list, new NonGrowableListView(list), "three!42");
+
+ Set aSet = new Set();
+ testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "empty");
+ aSet = new Set.from([42]);
+ testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "single-42");
+ aSet = new Set.from([7]);
+ testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "single!42");
+ aSet = new Set.from([1, 42, 10]);
+ testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "three-42");
+ aSet = new Set.from([1, 7, 10]);
+ testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "three!42");
+
+ Map map = new Map();
+ testUnmodifiableMap(map, new UnmodifiableMapView(map), "empty");
+ map = new Map()..[0] = 2;
+ testUnmodifiableMap(map, new UnmodifiableMapView(map), "single-0");
+ map = new Map()..[3] = 2;
+ testUnmodifiableMap(map, new UnmodifiableMapView(map), "single!0");
+ map = new Map()..[0] = 2
+ ..[1] = 1
+ ..[2] = 0;
+ testUnmodifiableMap(map, new UnmodifiableMapView(map), "three-0");
+ map = new Map()..[3] = 2
+ ..[1] = 1
+ ..[2] = 3;
+ testUnmodifiableMap(map, new UnmodifiableMapView(map), "three!0");
+}
+
+void testUnmodifiableList(List original, List wrapped, String name) {
+ name = "unmodifiable-list-$name";
+ testIterable(original, wrapped, name);
+ testReadList(original, wrapped, name);
+ testNoWriteList(original, wrapped, name);
+ testNoChangeLengthList(original, wrapped, name);
+}
+
+void testNonGrowableList(List original, List wrapped, String name) {
+ name = "nongrowable-list-$name";
+ testIterable(original, wrapped, name);
+ testReadList(original, wrapped, name);
+ testWriteList(original, wrapped, name);
+ testNoChangeLengthList(original, wrapped, name);
+}
+
+void testUnmodifiableSet(Set original, Set wrapped, String name) {
+ name = "unmodifiable-set-$name";
+ testIterable(original, wrapped, name);
+ testReadSet(original, wrapped, name);
+ testNoChangeSet(original, wrapped, name);
+}
+
+void testUnmodifiableMap(Map original, Map wrapped, name) {
+ name = "unmodifiable-map-$name";
+ testReadMap(original, wrapped, name);
+ testNoChangeMap(original, wrapped, name);
+}
+
+void testIterable(Iterable original, Iterable wrapped, String name) {
+ test("$name - any", () {
+ expect(wrapped.any((x) => true), equals(original.any((x) => true)));
+ expect(wrapped.any((x) => false), equals(original.any((x) => false)));
+ });
+
+ test("$name - contains", () {
+ expect(wrapped.contains(0), equals(original.contains(0)));
+ });
+
+ test("$name - elementAt", () {
+ if (original.isEmpty) {
+ expect(() => wrapped.elementAt(0), throws);
+ } else {
+ expect(wrapped.elementAt(0), equals(original.elementAt(0)));
+ }
+ });
+
+ test("$name - every", () {
+ expect(wrapped.every((x) => true), equals(original.every((x) => true)));
+ expect(wrapped.every((x) => false), equals(original.every((x) => false)));
+ });
+
+ test("$name - expand", () {
+ expect(wrapped.expand((x) => [x, x]),
+ equals(original.expand((x) => [x, x])));
+ });
+
+ test("$name - first", () {
+ if (original.isEmpty) {
+ expect(() => wrapped.first, throws);
+ } else {
+ expect(wrapped.first, equals(original.first));
+ }
+ });
+
+ test("$name - firstWhere", () {
+ if (original.isEmpty) {
+ expect(() => wrapped.firstWhere((_) => true), throws);
+ } else {
+ expect(wrapped.firstWhere((_) => true),
+ equals(original.firstWhere((_) => true)));
+ }
+ expect(() => wrapped.firstWhere((_) => false), throws);
+ });
+
+ test("$name - fold", () {
+ expect(wrapped.fold(0, (x, y) => x + y),
+ equals(original.fold(0, (x, y) => x + y)));
+ });
+
+ test("$name - forEach", () {
+ int wrapCtr = 0;
+ int origCtr = 0;
+ wrapped.forEach((x) { wrapCtr += x; });
+ original.forEach((x) { origCtr += x; });
+ expect(wrapCtr, equals(origCtr));
+ });
+
+ test("$name - isEmpty", () {
+ expect(wrapped.isEmpty, equals(original.isEmpty));
+ });
+
+ test("$name - isNotEmpty", () {
+ expect(wrapped.isNotEmpty, equals(original.isNotEmpty));
+ });
+
+ test("$name - iterator", () {
+ Iterator wrapIter = wrapped.iterator;
+ Iterator origIter = original.iterator;
+ while (origIter.moveNext()) {
+ expect(wrapIter.moveNext(), equals(true));
+ expect(wrapIter.current, equals(origIter.current));
+ }
+ expect(wrapIter.moveNext(), equals(false));
+ });
+
+ test("$name - join", () {
+ expect(wrapped.join(""), equals(original.join("")));
+ expect(wrapped.join("-"), equals(original.join("-")));
+ });
+
+ test("$name - last", () {
+ if (original.isEmpty) {
+ expect(() => wrapped.last, throws);
+ } else {
+ expect(wrapped.last, equals(original.last));
+ }
+ });
+
+ test("$name - lastWhere", () {
+ if (original.isEmpty) {
+ expect(() => wrapped.lastWhere((_) => true), throws);
+ } else {
+ expect(wrapped.lastWhere((_) => true),
+ equals(original.lastWhere((_) => true)));
+ }
+ expect(() => wrapped.lastWhere((_) => false), throws);
+ });
+
+ test("$name - length", () {
+ expect(wrapped.length, equals(original.length));
+ });
+
+ test("$name - map", () {
+ expect(wrapped.map((x) => "[$x]"),
+ equals(original.map((x) => "[$x]")));
+ });
+
+ test("$name - reduce", () {
+ if (original.isEmpty) {
+ expect(() => wrapped.reduce((x, y) => x + y), throws);
+ } else {
+ expect(wrapped.reduce((x, y) => x + y),
+ equals(original.reduce((x, y) => x + y)));
+ }
+ });
+
+ test("$name - single", () {
+ if (original.length != 1) {
+ expect(() => wrapped.single, throws);
+ } else {
+ expect(wrapped.single, equals(original.single));
+ }
+ });
+
+ test("$name - singleWhere", () {
+ if (original.length != 1) {
+ expect(() => wrapped.singleWhere((_) => true), throws);
+ } else {
+ expect(wrapped.singleWhere((_) => true),
+ equals(original.singleWhere((_) => true)));
+ }
+ expect(() => wrapped.singleWhere((_) => false), throws);
+ });
+
+ test("$name - skip", () {
+ expect(wrapped.skip(0), orderedEquals(original.skip(0)));
+ expect(wrapped.skip(1), orderedEquals(original.skip(1)));
+ expect(wrapped.skip(5), orderedEquals(original.skip(5)));
+ });
+
+ test("$name - skipWhile", () {
+ expect(wrapped.skipWhile((x) => true),
+ orderedEquals(original.skipWhile((x) => true)));
+ expect(wrapped.skipWhile((x) => false),
+ orderedEquals(original.skipWhile((x) => false)));
+ expect(wrapped.skipWhile((x) => x != 42),
+ orderedEquals(original.skipWhile((x) => x != 42)));
+ });
+
+ test("$name - take", () {
+ expect(wrapped.take(0), orderedEquals(original.take(0)));
+ expect(wrapped.take(1), orderedEquals(original.take(1)));
+ expect(wrapped.take(5), orderedEquals(original.take(5)));
+ });
+
+ test("$name - takeWhile", () {
+ expect(wrapped.takeWhile((x) => true),
+ orderedEquals(original.takeWhile((x) => true)));
+ expect(wrapped.takeWhile((x) => false),
+ orderedEquals(original.takeWhile((x) => false)));
+ expect(wrapped.takeWhile((x) => x != 42),
+ orderedEquals(original.takeWhile((x) => x != 42)));
+ });
+
+ test("$name - toList", () {
+ expect(wrapped.toList(), orderedEquals(original.toList()));
+ expect(wrapped.toList(growable: false),
+ orderedEquals(original.toList(growable: false)));
+ });
+
+ test("$name - toSet", () {
+ expect(wrapped.toSet(), unorderedEquals(original.toSet()));
+ });
+
+ test("$name - where", () {
+ expect(wrapped.where((x) => true),
+ orderedEquals(original.where((x) => true)));
+ expect(wrapped.where((x) => false),
+ orderedEquals(original.where((x) => false)));
+ expect(wrapped.where((x) => x != 42),
+ orderedEquals(original.where((x) => x != 42)));
+ });
+}
+
+void testReadList(List original, List wrapped, String name) {
+ test("$name - length", () {
+ expect(wrapped.length, equals(original.length));
+ });
+
+ test("$name - isEmpty", () {
+ expect(wrapped.isEmpty, equals(original.isEmpty));
+ });
+
+ test("$name - isNotEmpty", () {
+ expect(wrapped.isNotEmpty, equals(original.isNotEmpty));
+ });
+
+ test("$name - []", () {
+ if (original.isEmpty) {
+ expect(() { wrapped[0]; }, throwsRangeError);
+ } else {
+ expect(wrapped[0], equals(original[0]));
+ }
+ });
+
+ test("$name - indexOf", () {
+ expect(wrapped.indexOf(42), equals(original.indexOf(42)));
+ });
+
+ test("$name - lastIndexOf", () {
+ expect(wrapped.lastIndexOf(42), equals(original.lastIndexOf(42)));
+ });
+
+ test("$name - getRange", () {
+ int len = original.length;
+ expect(wrapped.getRange(0, len), equals(original.getRange(0, len)));
+ expect(wrapped.getRange(len ~/ 2, len),
+ equals(original.getRange(len ~/ 2, len)));
+ expect(wrapped.getRange(0, len ~/ 2),
+ equals(original.getRange(0, len ~/ 2)));
+ });
+
+ test("$name - sublist", () {
+ int len = original.length;
+ expect(wrapped.sublist(0), equals(original.sublist(0)));
+ expect(wrapped.sublist(len ~/ 2), equals(original.sublist(len ~/ 2)));
+ expect(wrapped.sublist(0, len ~/ 2),
+ equals(original.sublist(0, len ~/ 2)));
+ });
+
+ test("$name - asMap", () {
+ expect(wrapped.asMap(), equals(original.asMap()));
+ });
+}
+
+void testNoWriteList(List original, List wrapped, String name) {
+ List copy = new List.from(original);
+
+ testThrows(name, thunk) {
+ test(name, () {
+ expect(thunk, throwsUnsupportedError);
+ // No modifications happened.
+ expect(original, equals(copy));
+ });
+ }
+
+ testThrows("$name - []= throws", () { wrapped[0] = 42; });
+
+ testThrows("$name - sort throws", () { wrapped.sort(); });
+
+ testThrows("$name - fillRange throws", () {
+ wrapped.fillRange(0, wrapped.length, 42);
+ });
+
+ testThrows("$name - setRange throws", () {
+ wrapped.setRange(0, wrapped.length,
+ new Iterable.generate(wrapped.length, (i) => i));
+ });
+
+ testThrows("$name - setAll throws", () {
+ wrapped.setAll(0, new Iterable.generate(wrapped.length, (i) => i));
+ });
+}
+
+void testWriteList(List original, List wrapped, String name) {
+ List copy = new List.from(original);
+
+ test("$name - []=", () {
+ if (original.isNotEmpty) {
+ int originalFirst = original[0];
+ wrapped[0] = originalFirst + 1;
+ expect(original[0], equals(originalFirst + 1));
+ original[0] = originalFirst;
+ } else {
+ expect(() { wrapped[0] = 42; }, throws);
+ }
+ });
+
+ test("$name - sort", () {
+ List sortCopy = new List.from(original);
+ sortCopy.sort();
+ wrapped.sort();
+ expect(original, orderedEquals(sortCopy));
+ original.setAll(0, copy);
+ });
+
+ test("$name - fillRange", () {
+ wrapped.fillRange(0, wrapped.length, 37);
+ for (int i = 0; i < original.length; i++) {
+ expect(original[i], equals(37));
+ }
+ original.setAll(0, copy);
+ });
+
+ test("$name - setRange", () {
+ List reverseList = original.reversed.toList();
+ wrapped.setRange(0, wrapped.length, reverseList);
+ expect(original, equals(reverseList));
+ original.setAll(0, copy);
+ });
+
+ test("$name - setAll", () {
+ List reverseList = original.reversed.toList();
+ wrapped.setAll(0, reverseList);
+ expect(original, equals(reverseList));
+ original.setAll(0, copy);
+ });
+}
+
+void testNoChangeLengthList(List original, List wrapped, String name) {
+ List copy = new List.from(original);
+
+ testThrows(name, thunk) {
+ test(name, () {
+ expect(thunk, throwsUnsupportedError);
+ // No modifications happened.
+ expect(original, equals(copy));
+ });
+ }
+
+ testThrows("$name - length= throws", () {
+ wrapped.length = 100;
+ });
+
+ testThrows("$name - add throws", () {
+ wrapped.add(42);
+ });
+
+ testThrows("$name - addAll throws", () {
+ wrapped.addAll([42]);
+ });
+
+ testThrows("$name - insert throws", () {
+ wrapped.insert(0, 42);
+ });
+
+ testThrows("$name - insertAll throws", () {
+ wrapped.insertAll(0, [42]);
+ });
+
+ testThrows("$name - remove throws", () {
+ wrapped.remove(42);
+ });
+
+ testThrows("$name - removeAt throws", () {
+ wrapped.removeAt(0);
+ });
+
+ testThrows("$name - removeLast throws", () {
+ wrapped.removeLast();
+ });
+
+ testThrows("$name - removeWhere throws", () {
+ wrapped.removeWhere((element) => false);
+ });
+
+ testThrows("$name - retainWhere throws", () {
+ wrapped.retainWhere((element) => true);
+ });
+
+ testThrows("$name - removeRange throws", () {
+ wrapped.removeRange(0, wrapped.length);
+ });
+
+ testThrows("$name - replaceRange throws", () {
+ wrapped.replaceRange(0, wrapped.length, [42]);
+ });
+
+ testThrows("$name - clear throws", () {
+ wrapped.clear();
+ });
+}
+
+void testReadSet(Set original, Set wrapped, String name) {
+ Set copy = new Set.from(original);
+
+ test("$name - containsAll", () {
+ expect(wrapped.containsAll(copy), isTrue);
+ expect(wrapped.containsAll(copy.toList()), isTrue);
+ expect(wrapped.containsAll([]), isTrue);
+ expect(wrapped.containsAll([42]), equals(original.containsAll([42])));
+ });
+
+ test("$name - intersection", () {
+ expect(wrapped.intersection(new Set()), isEmpty);
+ expect(wrapped.intersection(copy), unorderedEquals(original));
+ expect(wrapped.intersection(new Set.from([42])),
+ new Set.from(original.contains(42) ? [42] : []));
+ });
+
+ test("$name - union", () {
+ expect(wrapped.union(new Set()), unorderedEquals(original));
+ expect(wrapped.union(copy), unorderedEquals(original));
+ expect(wrapped.union(new Set.from([42])),
+ equals(original.union(new Set.from([42]))));
+ });
+
+ test("$name - difference", () {
+ expect(wrapped.difference(new Set()), unorderedEquals(original));
+ expect(wrapped.difference(copy), isEmpty);
+ expect(wrapped.difference(new Set.from([42])),
+ equals(original.difference(new Set.from([42]))));
+ });
+}
+
+void testNoChangeSet(Set original, Set wrapped, String name) {
+ List originalElements = original.toList();
+
+ testThrows(name, thunk) {
+ test(name, () {
+ expect(thunk, throwsUnsupportedError);
+ // No modifications happened.
+ expect(original.toList(), equals(originalElements));
+ });
+ }
+
+ testThrows("$name - add throws", () {
+ wrapped.add(42);
+ });
+
+ testThrows("$name - addAll throws", () {
+ wrapped.addAll([42]);
+ });
+
+ testThrows("$name - addAll empty throws", () {
+ wrapped.addAll([]);
+ });
+
+ testThrows("$name - remove throws", () {
+ wrapped.remove(42);
+ });
+
+ testThrows("$name - removeAll throws", () {
+ wrapped.removeAll([42]);
+ });
+
+ testThrows("$name - removeAll empty throws", () {
+ wrapped.removeAll([]);
+ });
+
+ testThrows("$name - retainAll throws", () {
+ wrapped.retainAll([42]);
+ });
+
+ testThrows("$name - removeWhere throws", () {
+ wrapped.removeWhere((_) => false);
+ });
+
+ testThrows("$name - retainWhere throws", () {
+ wrapped.retainWhere((_) => true);
+ });
+
+ testThrows("$name - clear throws", () {
+ wrapped.clear();
+ });
+}
+
+void testReadMap(Map original, Map wrapped, String name) {
+ test("$name length", () {
+ expect(wrapped.length, equals(original.length));
+ });
+
+ test("$name isEmpty", () {
+ expect(wrapped.isEmpty, equals(original.isEmpty));
+ });
+
+ test("$name isNotEmpty", () {
+ expect(wrapped.isNotEmpty, equals(original.isNotEmpty));
+ });
+
+ test("$name operator[]", () {
+ expect(wrapped[0], equals(original[0]));
+ expect(wrapped[999], equals(original[999]));
+ });
+
+ test("$name containsKey", () {
+ expect(wrapped.containsKey(0), equals(original.containsKey(0)));
+ expect(wrapped.containsKey(999), equals(original.containsKey(999)));
+ });
+
+ test("$name containsValue", () {
+ expect(wrapped.containsValue(0), equals(original.containsValue(0)));
+ expect(wrapped.containsValue(999), equals(original.containsValue(999)));
+ });
+
+ test("$name forEach", () {
+ int origCnt = 0;
+ int wrapCnt = 0;
+ wrapped.forEach((k, v) { wrapCnt += 1 << k + 3 * v; });
+ original.forEach((k, v) { origCnt += 1 << k + 3 * v; });
+ expect(wrapCnt, equals(origCnt));
+ });
+
+ test("$name keys", () {
+ expect(wrapped.keys, orderedEquals(original.keys));
+ });
+
+ test("$name values", () {
+ expect(wrapped.values, orderedEquals(original.values));
+ });
+}
+
+testNoChangeMap(Map original, Map wrapped, String name) {
+ Map copy = new Map.from(original);
+
+ testThrows(name, thunk) {
+ test(name, () {
+ expect(thunk, throwsUnsupportedError);
+ // No modifications happened.
+ expect(original, equals(copy));
+ });
+ }
+
+ testThrows("$name operator[]= throws", () {
+ wrapped[0] = 42;
+ });
+
+ testThrows("$name putIfAbsent throws", () {
+ wrapped.putIfAbsent(0, () => 42);
+ });
+
+ testThrows("$name addAll throws", () {
+ wrapped.addAll(new Map()..[42] = 42);
+ });
+
+ testThrows("$name addAll empty throws", () {
+ wrapped.addAll(new Map());
+ });
+
+ testThrows("$name remove throws", () {
+ wrapped.remove(0);
+ });
+
+ testThrows("$name clear throws", () {
+ wrapped.clear();
+ });
+}
diff --git a/test/wrapper_test.dart b/test/wrapper_test.dart
new file mode 100644
index 0000000..875a70a
--- /dev/null
+++ b/test/wrapper_test.dart
@@ -0,0 +1,255 @@
+// Copyright (c) 2013, 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.
+
+/// Tests wrapper utilities.
+
+import "dart:collection";
+import "package:collection/collection.dart";
+import "package:unittest/unittest.dart";
+
+// Test that any member access/call on the wrapper object is equal to
+// an expected access on the wrapped object.
+// This is implemented by capturing accesses using noSuchMethod and comparing
+// them to expected accesses captured previously.
+
+// Compare two Invocations for having equal type and arguments.
+void testInvocations(Invocation i1, Invocation i2) {
+ String name = "${i1.memberName}";
+ expect(i1.isGetter, equals(i2.isGetter), reason: name);
+ expect(i1.isSetter, equals(i2.isSetter), reason: name);
+ expect(i1.memberName, equals(i2.memberName), reason: name);
+ expect(i1.positionalArguments, equals(i2.positionalArguments), reason: name);
+ expect(i1.namedArguments, equals(i2.namedArguments), reason: name);
+}
+
+/**
+ * Utility class to record a member access and a member access on a wrapped
+ * object, and compare them for equality.
+ */
+abstract class Expector {
+ getWrappedObject(action(Invocation i));
+ // Hack to test assignment ([]=) because it doesn't return the result
+ // of the member call. Instead use (expect..[4]=5).equal[4]=5 where
+ // you would normally use expect[4].equals[4] for non-assignments.
+ var equals;
+
+ noSuchMethod(Invocation m) => new _Equals(equals = getWrappedObject((m2) {
+ testInvocations(m, m2);
+ }));
+}
+
+// An object with a field called "equals", only introduced into the
+// flow to allow writing expect.xxx.equals.xxx.
+class _Equals {
+ final equals;
+ _Equals(this.equals);
+}
+
+// Parameterization of noSuchMethod.
+class NSM {
+ Function _action;
+ NSM(this._action);
+ noSuchMethod(Invocation i) => _action(i);
+}
+
+// LikeNSM, but has types Iterable, Set and List to allow it as
+// argument to DelegatingIterable/Set/List.
+class IterableNSM extends NSM implements Iterable, Set, List, Queue {
+ IterableNSM(action(Invocation i)) : super(action);
+ noSuchMethod(Invocation i) => super.noSuchMethod(i); // Silence warnings
+}
+
+// Expector that wraps in DelegatingIterable.
+class IterableExpector extends Expector {
+ getWrappedObject(void action(Invocation i)) {
+ return new DelegatingIterable(new IterableNSM(action));
+ }
+}
+
+// Expector that wraps in DelegatingList.
+class ListExpector extends Expector {
+ getWrappedObject(void action(Invocation i)) {
+ return new DelegatingList(new IterableNSM(action));
+ }
+}
+
+// Expector that wraps in DelegatingSet.
+class SetExpector extends Expector {
+ getWrappedObject(void action(Invocation i)) {
+ return new DelegatingSet(new IterableNSM(action));
+ }
+}
+
+// Expector that wraps in DelegatingSet.
+class QueueExpector extends Expector {
+ getWrappedObject(void action(Invocation i)) {
+ return new DelegatingQueue(new IterableNSM(action));
+ }
+}
+
+// Like NSM but implements Map to allow as argument for DelegatingMap.
+class MapNSM extends NSM implements Map {
+ MapNSM(action(Invocation i)) : super(action);
+ noSuchMethod(Invocation i) => super.noSuchMethod(i);
+}
+
+// Expector that wraps in DelegatingMap.
+class MapExpector extends Expector {
+ getWrappedObject(void action(Invocation i)) {
+ return new DelegatingMap(new MapNSM(action));
+ }
+}
+
+// Utility values to use as arguments in calls.
+func0() {}
+func1(x) {}
+func2(x, y) {}
+var val = new Object();
+
+void main() {
+ testIterable(var expect) {
+ expect.any(func1).equals.any(func1);
+ expect.contains(val).equals.contains(val);
+ expect.elementAt(0).equals.elementAt(0);
+ expect.every(func1).equals.every(func1);
+ expect.expand(func1).equals.expand(func1);
+ expect.first.equals.first;
+ // Default values of the Iterable interface will be added in the
+ // second call to firstWhere, so we must record them in our
+ // expectation (which doesn't have the interface implementat or
+ // its default values).
+ expect.firstWhere(func1, orElse: null).equals.firstWhere(func1);
+ expect.firstWhere(func1, orElse: func0).equals.
+ firstWhere(func1, orElse: func0);
+ expect.fold(null, func2).equals.fold(null, func2);
+ expect.forEach(func1).equals.forEach(func1);
+ expect.isEmpty.equals.isEmpty;
+ expect.isNotEmpty.equals.isNotEmpty;
+ expect.iterator.equals.iterator;
+ expect.join('').equals.join();
+ expect.join("X").equals.join("X");
+ expect.last.equals.last;
+ expect.lastWhere(func1, orElse: null).equals.lastWhere(func1);
+ expect.lastWhere(func1, orElse: func0).equals.
+ lastWhere(func1, orElse: func0);
+ expect.length.equals.length;
+ expect.map(func1).equals.map(func1);
+ expect.reduce(func2).equals.reduce(func2);
+ expect.single.equals.single;
+ expect.singleWhere(func1).equals.singleWhere(func1);
+ expect.skip(5).equals.skip(5);
+ expect.skipWhile(func1).equals.skipWhile(func1);
+ expect.take(5).equals.take(5);
+ expect.takeWhile(func1).equals.takeWhile(func1);
+ expect.toList(growable: true).equals.toList();
+ expect.toList(growable: true).equals.toList(growable: true);
+ expect.toList(growable: false).equals.toList(growable: false);
+ expect.toSet().equals.toSet();
+ expect.where(func1).equals.where(func1);
+ }
+
+ void testList(var expect) {
+ testIterable(expect);
+
+ expect[4].equals[4];
+ (expect..[4] = 5).equals[4] = 5;
+
+ expect.add(val).equals.add(val);
+ expect.addAll([val]).equals.addAll([val]);
+ expect.asMap().equals.asMap();
+ expect.clear().equals.clear();
+ expect.fillRange(4, 5, null).equals.fillRange(4, 5);
+ expect.fillRange(4, 5, val).equals.fillRange(4, 5, val);
+ expect.getRange(4, 5).equals.getRange(4, 5);
+ expect.indexOf(val, 0).equals.indexOf(val);
+ expect.indexOf(val, 4).equals.indexOf(val, 4);
+ expect.insert(4, val).equals.insert(4, val);
+ expect.insertAll(4, [val]).equals.insertAll(4, [val]);
+ expect.lastIndexOf(val, null).equals.lastIndexOf(val);
+ expect.lastIndexOf(val, 4).equals.lastIndexOf(val, 4);
+ (expect..length = 4).equals.length = 4;
+ expect.remove(val).equals.remove(val);
+ expect.removeAt(4).equals.removeAt(4);
+ expect.removeLast().equals.removeLast();
+ expect.removeRange(4, 5).equals.removeRange(4, 5);
+ expect.removeWhere(func1).equals.removeWhere(func1);
+ expect.replaceRange(4, 5, [val]).equals.replaceRange(4, 5, [val]);
+ expect.retainWhere(func1).equals.retainWhere(func1);
+ expect.reversed.equals.reversed;
+ expect.setAll(4, [val]).equals.setAll(4, [val]);
+ expect.setRange(4, 5, [val], 0).equals.setRange(4, 5, [val]);
+ expect.setRange(4, 5, [val], 3).equals.setRange(4, 5, [val], 3);
+ expect.sort(null).equals.sort();
+ expect.sort(func2).equals.sort(func2);
+ expect.sublist(4, null).equals.sublist(4);
+ expect.sublist(4, 5).equals.sublist(4, 5);
+ }
+
+ void testSet(var expect) {
+ testIterable(expect);
+ Set set = new Set();
+ expect.add(val).equals.add(val);
+ expect.addAll([val]).equals.addAll([val]);
+ expect.clear().equals.clear();
+ expect.containsAll([val]).equals.containsAll([val]);
+ expect.difference(set).equals.difference(set);
+ expect.intersection(set).equals.intersection(set);
+ expect.remove(val).equals.remove(val);
+ expect.removeAll([val]).equals.removeAll([val]);
+ expect.removeWhere(func1).equals.removeWhere(func1);
+ expect.retainAll([val]).equals.retainAll([val]);
+ expect.retainWhere(func1).equals.retainWhere(func1);
+ expect.union(set).equals.union(set);
+ }
+
+ void testQueue(var expect) {
+ testIterable(expect);
+ expect.add(val).equals.add(val);
+ expect.addAll([val]).equals.addAll([val]);
+ expect.addFirst(val).equals.addFirst(val);
+ expect.addLast(val).equals.addLast(val);
+ expect.clear().equals.clear();
+ expect.remove(val).equals.remove(val);
+ expect.removeFirst().equals.removeFirst();
+ expect.removeLast().equals.removeLast();
+ }
+
+ void testMap(var expect) {
+ Map map = new Map();
+ expect[val].equals[val];
+ (expect..[val] = val).equals[val] = val;
+ expect.addAll(map).equals.addAll(map);
+ expect.clear().equals.clear();
+ expect.containsKey(val).equals.containsKey(val);
+ expect.containsValue(val).equals.containsValue(val);
+ expect.forEach(func2).equals.forEach(func2);
+ expect.isEmpty.equals.isEmpty;
+ expect.isNotEmpty.equals.isNotEmpty;
+ expect.keys.equals.keys;
+ expect.length.equals.length;
+ expect.putIfAbsent(val, func0).equals.putIfAbsent(val, func0);
+ expect.remove(val).equals.remove(val);
+ expect.values.equals.values;
+ }
+
+ test("Iterable", () {
+ testIterable(new IterableExpector());
+ });
+
+ test("List", () {
+ testList(new ListExpector());
+ });
+
+ test("Set", () {
+ testSet(new SetExpector());
+ });
+
+ test("Queue", () {
+ testQueue(new QueueExpector());
+ });
+
+ test("Map", () {
+ testMap(new MapExpector());
+ });
+}