blob: 1117a29b056ea31e4edd42dd4fab255486d400b6 [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:async';
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> {
/// An empty observable set that never has changes.
static const ObservableSet EMPTY = const _UnmodifiableObservableSet(
const _UnmodifiableEmptySet(),
);
/// 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>());
}
/// Create a new unmodifiable set from [set].
///
/// [ObservableSet.changes] always returns an empty stream, and mutating or
/// adding change records throws an [UnsupportedError].
factory ObservableSet.unmodifiable(Set<E> set) {
if (set is! UnmodifiableSetView<E>) {
set = new UnmodifiableSetView<E>(set);
}
return new _UnmodifiableObservableSet(set);
}
}
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));
}
}
class _UnmodifiableEmptySet<E> extends IterableBase<E> implements Set<E> {
const _UnmodifiableEmptySet();
@override
bool add(E value) => false;
@override
void addAll(Iterable<E> elements) {}
@override
void clear() {}
@override
bool containsAll(Iterable<Object> other) => other.isEmpty;
@override
Set<E> difference(Set<Object> other) => other.toSet();
@override
Set<E> intersection(Set<Object> other) => this;
@override
Iterator<E> get iterator => const <Null>[].iterator;
@override
E lookup(Object object) => null;
@override
bool remove(Object value) => false;
@override
void removeAll(Iterable<Object> elements) {}
@override
void removeWhere(bool test(E element)) {}
@override
void retainAll(Iterable<Object> elements) {}
@override
void retainWhere(bool test(E element)) {}
@override
Set<E> union(Set<E> other) => other.toSet();
}
class _UnmodifiableObservableSet<E> extends DelegatingSet<E>
implements ObservableSet<E> {
const _UnmodifiableObservableSet(Set<E> set) : super(set);
@override
Stream<List<SetChangeRecord<E>>> get changes => const Stream.empty();
@override
bool deliverChanges() => false;
// TODO: implement hasObservers
@override
final bool hasObservers = false;
@override
void notifyChange([ChangeRecord change]) {
throw new UnsupportedError('Not modifiable');
}
@override
T notifyPropertyChange<T>(
Symbol field,
T oldValue,
T newValue,
) {
throw new UnsupportedError('Not modifiable');
}
@override
void observed() {}
@override
void unobserved() {}
}