Skip to content

Commit

Permalink
Add unmodifiable versions of each observable collection (#21)
Browse files Browse the repository at this point in the history
* Add an ObservableSet implementation

* Add tests, cleanup.

* Cleanup licensing.

* Add unmodifiable observable collections

* Change versions.
  • Loading branch information
matanlurey authored Nov 29, 2016
1 parent 8209344 commit f7521fb
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 1 deletion.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 0.20.1

* Add `Observable<List|Set|Map>.unmodifiable` for immutable collections
* Add `Observable<List|Set|Map>.EMPTY` for empty immutable collections
* This can be used as an optimization for libraries that always
need to return an observable collection, but don't want to
allocate a new instance to represent an empty immutable.

## 0.20.0

* Add `ObservableSet`, `SetChangeRecord`, and `SetDiffer`
Expand Down
76 changes: 76 additions & 0 deletions lib/src/collections/observable_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ import 'package:observable/src/differs.dart';
///
/// *See [ListDiffer] to manually diff two lists instead*
abstract class ObservableList<E> implements List<E>, Observable {
/// An empty observable list that never has changes.
static const ObservableList EMPTY = const _ObservableUnmodifiableList(
const [],
);

/// Applies [changes] to [previous] based on the [current] values.
///
/// ## Deprecated
Expand Down Expand Up @@ -83,6 +88,20 @@ abstract class ObservableList<E> implements List<E>, Observable {
return new ObservableList<E>(length);
}

/// Create new unmodifiable list from [list].
///
/// [ObservableList.changes] and [ObservableList.listChanges] both always
/// return an empty stream, and mutating or adding change records throws an
/// [UnsupportedError].
factory ObservableList.unmodifiable(
List<E> list,
) {
if (list is! UnmodifiableListView<E>) {
list = new List<E>.unmodifiable(list);
}
return new _ObservableUnmodifiableList<E>(list);
}

@Deprecated('No longer supported. Just use deliverChanges')
bool deliverListChanges();

Expand Down Expand Up @@ -442,3 +461,60 @@ class _ObservableDelegatingList<E> extends DelegatingList<E>
}
}
}

class _ObservableUnmodifiableList<E> extends DelegatingList<E>
implements ObservableList<E> {
const _ObservableUnmodifiableList(List<E> list) : super(list);

@override
Stream<List<ChangeRecord>> get changes => const Stream.empty();

@override
bool deliverChanges() => false;

@override
bool deliverListChanges() => false;

@override
void discardListChanges() {}

@override
final bool hasListObservers = false;

@override
final bool hasObservers = false;

@override
Stream<List<ListChangeRecord<E>>> get listChanges => const Stream.empty();

@override
void notifyChange([ChangeRecord change]) {
throw new UnsupportedError('Not modifiable');
}

@override
void notifyListChange(
int index, {
List<E> removed: const [],
int addedCount: 0,
}) {
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() {}
}
51 changes: 51 additions & 0 deletions lib/src/collections/observable_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import 'package:observable/observable.dart';
///
/// *See [MapDiffer] to manually diff two lists instead*
abstract class ObservableMap<K, V> implements Map<K, V>, Observable {
/// An empty observable map that never has changes.
static const ObservableMap EMPTY = const _ObservableUnmodifiableMap(const {});

/// Creates a new observable map.
factory ObservableMap() {
return new _ObservableDelegatingMap(new HashMap<K, V>());
Expand Down Expand Up @@ -70,6 +73,17 @@ abstract class ObservableMap<K, V> implements Map<K, V>, Observable {
/// Creates a new observable map wrapping [other].
@Deprecated('Use ObservableMap.delegate for API consistency')
factory ObservableMap.spy(Map<K, V> other) = ObservableMap<K, V>.delegate;

/// Create a new unmodifiable map from [map].
///
/// [ObservableMap.changes] always returns an empty stream, and mutating or
/// adding change records throws an [UnsupportedError].
factory ObservableMap.unmodifiable(Map<K, V> map) {
if (map is! UnmodifiableMapView<K, V>) {
map = new Map<K, V>.unmodifiable(map);
}
return new _ObservableUnmodifiableMap(map);
}
}

class _ObservableDelegatingMap<K, V> extends DelegatingMap<K, V>
Expand Down Expand Up @@ -183,3 +197,40 @@ class _ObservableDelegatingMap<K, V> extends DelegatingMap<K, V>
super.clear();
}
}

class _ObservableUnmodifiableMap<K, V> extends DelegatingMap<K, V>
implements ObservableMap<K, V> {
const _ObservableUnmodifiableMap(Map<K, V> map) : super(map);

@override
Stream<List<ChangeRecord>> 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() {}
}
100 changes: 100 additions & 0 deletions lib/src/collections/observable_set.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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';
Expand All @@ -25,6 +26,11 @@ import 'package:observable/observable.dart';
/// *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>());

Expand Down Expand Up @@ -62,6 +68,17 @@ abstract class ObservableSet<E>
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>
Expand Down Expand Up @@ -116,3 +133,86 @@ class _DelegatingObservableSet<E> extends DelegatingSet<E>
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 <E>[].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() {}
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: observable
version: 0.20.0
version: 0.20.1
author: Dart Team <[email protected]>
description: Support for marking objects as observable
homepage: https://github.com/dart-lang/observable
Expand Down

0 comments on commit f7521fb

Please sign in to comment.