blob: 762a782d85b658f42da7be3a1ce782692e4e463a [file] [log] [blame]
// Copyright (c) 2016, 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 'dart:collection';
import 'package:collection/collection.dart';
import 'package:observable/observable.dart';
/// A [Set] that broadcasts [changes] to subscribers for efficient mutations.
///
/// When client code expects a read heavy/write light workload, it is often more
/// efficient to notify _when_ something has changed, instead of constantly
/// diffing lists to find a single change (like an inserted element). You may
/// accept an observable set to be notified of mutations:
/// ```
/// set emails(Set<String> emails) {
/// emailUsers(emails);
/// if (names is ObservableSet<String>) {
/// emails.changes.listen(updateEmailList);
/// }
/// }
/// ```
///
/// *See [SetDiffer] to manually diff two lists instead*
abstract class ObservableSet<E>
implements Observable<SetChangeRecord<E>>, Set<E> {
/// Create a new empty observable set.
factory ObservableSet() => new _DelegatingObservableSet<E>(new HashSet<E>());
/// Like [ObservableSet.from], but creates a new empty set.
factory ObservableSet.createFromType(Iterable<E> other) {
ObservableSet<E> result;
if (other is LinkedHashSet) {
result = new _DelegatingObservableSet<E>(new LinkedHashSet<E>());
} else if (result is SplayTreeSet) {
result = new _DelegatingObservableSet<E>(new SplayTreeSet<E>());
} else {
result = new _DelegatingObservableSet<E>(new HashSet<E>());
}
return result;
}
/// Create a new observable set using [set] as a backing store.
factory ObservableSet.delegate(Set<E> set) = _DelegatingObservableSet<E>;
/// Creates a new observable set that contains all elements in [other].
///
/// It will attempt to use the same backing set type if the other set is
/// either a [LinkedHashSet], [SplayTreeSet], or [HashSet]. Otherwise it will
/// fall back to using a [HashSet].
factory ObservableSet.from(Iterable<E> other) {
return new ObservableSet<E>.createFromType(other)..addAll(other);
}
/// Creates a new observable map using a [LinkedHashSet].
factory ObservableSet.linked() {
return new _DelegatingObservableSet<E>(new LinkedHashSet<E>());
}
/// Creates a new observable map using a [SplayTreeSet].
factory ObservableSet.sorted() {
return new _DelegatingObservableSet<E>(new SplayTreeSet<E>());
}
}
class _DelegatingObservableSet<E> extends DelegatingSet<E>
with ChangeNotifier<SetChangeRecord<E>>
implements ObservableSet<E> {
_DelegatingObservableSet(Set<E> set) : super(set);
@override
bool add(E value) {
if (super.add(value)) {
if (hasObservers) {
notifyChange(new SetChangeRecord<E>.add(value));
}
return true;
}
return false;
}
@override
void addAll(Iterable<E> values) {
values.forEach(add);
}
@override
bool remove(Object value) {
if (super.remove(value)) {
if (hasObservers) {
notifyChange(new SetChangeRecord<E>.remove(value as E));
}
return true;
}
return false;
}
@override
void removeAll(Iterable<Object> values) {
values.toList().forEach(remove);
}
@override
void removeWhere(bool test(E value)) {
removeAll(super.where(test));
}
@override
void retainAll(Iterable<Object> elements) {
retainWhere(elements.toSet().contains);
}
@override
void retainWhere(bool test(E element)) {
removeWhere((e) => !test(e));
}
}