Add unmodifiable versions of each observable collection (#21)

* Add an ObservableSet implementation

* Add tests, cleanup.

* Cleanup licensing.

* Add unmodifiable observable collections

* Change versions.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dc5e443..99089b9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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`
diff --git a/lib/src/collections/observable_list.dart b/lib/src/collections/observable_list.dart
index c8266c7..9b86089 100644
--- a/lib/src/collections/observable_list.dart
+++ b/lib/src/collections/observable_list.dart
@@ -25,6 +25,11 @@
 ///
 /// *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
@@ -83,6 +88,20 @@
     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();
 
@@ -442,3 +461,60 @@
     }
   }
 }
+
+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() {}
+}
diff --git a/lib/src/collections/observable_map.dart b/lib/src/collections/observable_map.dart
index 3c2ed48..e83664d 100644
--- a/lib/src/collections/observable_map.dart
+++ b/lib/src/collections/observable_map.dart
@@ -25,6 +25,9 @@
 ///
 /// *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>());
@@ -70,6 +73,17 @@
   /// 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>
@@ -183,3 +197,40 @@
     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() {}
+}
diff --git a/lib/src/collections/observable_set.dart b/lib/src/collections/observable_set.dart
index 762a782..b8e347f 100644
--- a/lib/src/collections/observable_set.dart
+++ b/lib/src/collections/observable_set.dart
@@ -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';
@@ -25,6 +26,11 @@
 /// *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>());
 
@@ -62,6 +68,17 @@
   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>
@@ -116,3 +133,86 @@
     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() {}
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 84d9382..35b5a79 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: observable
-version: 0.20.0
+version: 0.20.1
 author: Dart Team <misc@dartlang.org>
 description: Support for marking objects as observable
 homepage: https://github.com/dart-lang/observable