Index: lib/observe/set.dart |
diff --git a/lib/observe/set.dart b/lib/observe/set.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d4559d98abee89dca1f7e8f3be17b40fc5e9842e |
--- /dev/null |
+++ b/lib/observe/set.dart |
@@ -0,0 +1,190 @@ |
+// Copyright (c) 2012, 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. |
+ |
+library web_ui.observe.set; |
+ |
+import 'dart:collection'; |
+import 'dart:collection-dev'; |
+import 'package:web_ui/observe.dart'; |
+ |
+/** |
+ * Represents an observable map of model values. If any items are added, |
+ * removed, or replaced, then observers that are registered with |
+ * [observe] will be notified. |
+ */ |
+abstract class ObservableSet<E> implements Observable, Set<E> { |
+ /** |
+ * Creates a map with the default implementation. |
+ */ |
+ factory ObservableSet() => new ObservableHashSet<E>(); |
+ |
+ /** |
+ * Creates a [Map] that contains all key value pairs of [other]. |
+ */ |
+ factory ObservableSet.from(Iterable<E> other) => |
+ new ObservableHashSet<E>.from(other); |
+} |
+ |
+ |
+/** |
+ * Represents an observable [HashSet] of model values. If any items are added, |
+ * removed, or replaced, then observers that are registered with |
+ * [observe] will be notified. |
+ */ |
+// Note: this code is similar to HashSet, but adding notify calls. |
+// TODO(jmesserly): also this shold extend Collection<E>, so we can delete |
+// copy+paste code. Fix once we have mixins. |
+class ObservableHashSet<E> extends Observable implements ObservableSet<E> { |
+ // The map backing this set. The associations in this map are all |
+ // of the form element -> element. If a value is not in the map, |
+ // then it is not in the set. |
+ final HashMap<E, E> _backingMap; |
+ |
+ ObservableHashSet() : _backingMap = new HashMap<E, E>(); |
+ |
+ /** |
+ * Creates a [Set] that contains all elements of [other]. |
+ */ |
+ factory ObservableHashSet.from(Iterable<E> other) => |
+ new ObservableHashSet<E>()..addAll(other); |
+ |
+ void clear() { |
+ if (hasObservers) { |
+ for (var key in _backingMap.keys) { |
+ notifyChange(ChangeRecord.REMOVE, key, key, null); |
+ } |
+ notifyChange(ChangeRecord.FIELD, 'length', _backingMap.length, 0); |
+ } |
+ _backingMap.clear(); |
+ } |
+ |
+ void add(E value) { |
+ int oldLen = _backingMap.length; |
+ _backingMap[value] = value; |
+ int newLen = _backingMap.length; |
+ if (hasObservers && oldLen != newLen) { |
+ notifyChange(ChangeRecord.FIELD, 'length', oldLen, newLen); |
+ notifyChange(ChangeRecord.INSERT, value, null, value); |
+ } |
+ } |
+ |
+ bool remove(Object value) { |
+ if (observeReads) notifyRead(ChangeRecord.INDEX, value); |
+ if (!_backingMap.containsKey(value)) return false; |
+ _backingMap.remove(value); |
+ notifyChange(ChangeRecord.REMOVE, value, value, null); |
+ int len = _backingMap.length; |
+ notifyChange(ChangeRecord.FIELD, 'length', len + 1, len); |
+ return true; |
+ } |
+ |
+ bool contains(E value) { |
+ if (observeReads) notifyRead(ChangeRecord.INDEX, value); |
+ return _backingMap.containsKey(value); |
+ } |
+ |
+ Set<E> intersection(Collection<E> collection) { |
+ Set<E> result = new Set<E>(); |
+ collection.forEach((E value) { |
+ if (contains(value)) result.add(value); |
+ }); |
+ return result; |
+ } |
+ |
+ bool isSubsetOf(Collection<E> other) { |
+ return new Set<E>.from(other).containsAll(this); |
+ } |
+ |
+ bool containsAll(Collection<E> collection) { |
+ return collection.every((E value) { |
+ return contains(value); |
+ }); |
+ } |
+ |
+ bool get isEmpty => length == 0; |
+ |
+ int get length { |
+ if (observeReads) notifyRead(ChangeRecord.FIELD, 'length'); |
+ return _backingMap.length; |
+ } |
+ |
+ Iterator<E> get iterator => new _HashSetIterator<E>(this); |
+ |
+ String toString() { |
+ return Collections.collectionToString(this); |
+ } |
+ |
+ |
+ // --------------------------------------------------------------------------- |
+ // Note: below this comment, methods are copy+paste from Collection<E> and |
+ // Iterable<E>. Remove when we have mixins. |
+ // --------------------------------------------------------------------------- |
+ void addAll(Iterable<E> elements) { |
+ for (E element in elements) add(element); |
+ } |
+ |
+ // TODO(jmesserly): mappedByList, takeList, skipList are not right. |
+ |
+ void removeAll(Iterable elements) => |
+ IterableMixinWorkaround.removeAll(this, elements); |
+ void retainAll(Iterable elements) => |
+ IterableMixinWorkaround.retainAll(this, elements); |
+ void removeMatching(bool test(E element)) => |
+ IterableMixinWorkaround.removeMatching(this, test); |
+ void retainMatching(bool test(E element)) => |
+ IterableMixinWorkaround.retainMatching(this, test); |
+ Iterable mappedBy(f(E element)) => |
+ IterableMixinWorkaround.mappedByList(toList(), f); |
+ Iterable<E> where(bool f(E element)) => |
+ IterableMixinWorkaround.where(this, f); |
+ void forEach(void f(E element)) => IterableMixinWorkaround.forEach(this, f); |
+ dynamic reduce(var initialValue, |
+ dynamic combine(var previousValue, E element)) => |
+ IterableMixinWorkaround.reduce(this, initialValue, combine); |
+ bool every(bool f(E element)) => IterableMixinWorkaround.every(this, f); |
+ String join([String separator]) => |
+ IterableMixinWorkaround.join(this, separator); |
+ bool any(bool f(E element)) => IterableMixinWorkaround.any(this, f); |
+ List<E> toList() => new List<E>.from(this); |
+ Set<E> toSet() => new Set<E>.from(this); |
+ E min([int compare(E a, E b)]) => IterableMixinWorkaround.min(this, compare); |
+ E max([int compare(E a, E b)]) => IterableMixinWorkaround.max(this, compare); |
+ Iterable<E> take(int n) => IterableMixinWorkaround.takeList(toList(), n); |
+ Iterable<E> takeWhile(bool test(E value)) |
+ => IterableMixinWorkaround.takeWhile(this, test); |
+ Iterable<E> skip(int n) => IterableMixinWorkaround.skipList(toList(), n); |
+ Iterable<E> skipWhile(bool test(E value)) => |
+ IterableMixinWorkaround.skipWhile(this, test); |
+ E get single => IterableMixinWorkaround.single(this); |
+ E get first => IterableMixinWorkaround.first(this); |
+ E get last => IterableMixinWorkaround.last(this); |
+ E firstMatching(bool test(E value), { E orElse() }) => |
+ IterableMixinWorkaround.firstMatching(this, test, orElse); |
+ E lastMatching(bool test(E value), {E orElse()}) => |
+ IterableMixinWorkaround.lastMatching(this, test, orElse); |
+ E singleMatching(bool test(E value)) => |
+ IterableMixinWorkaround.singleMatching(this, test); |
+ E elementAt(int index) => IterableMixinWorkaround.elementAt(this, index); |
+} |
+ |
+class _HashSetIterator<E> implements Iterator<E> { |
+ final ObservableHashSet<E> _set; |
+ final Iterator _keysIterator; |
+ |
+ _HashSetIterator(ObservableHashSet<E> set) |
+ : _set = set, |
+ _keysIterator = set._backingMap.keys.iterator; |
+ |
+ E get current { |
+ E result = _keysIterator.current; |
+ if (observeReads) _set.notifyRead(ChangeRecord.INDEX, result); |
+ return result; |
+ } |
+ |
+ bool moveNext() { |
+ if (observeReads) _set.notifyRead(ChangeRecord.FIELD, 'length'); |
+ bool result = _keysIterator.moveNext(); |
+ return result; |
+ } |
+} |