| 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;
|
| + }
|
| +}
|
|
|