Index: lib/observe/map.dart |
diff --git a/lib/observe/map.dart b/lib/observe/map.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ede08fc0abaa7cdc0ca6ffe94a2306f28cbb928f |
--- /dev/null |
+++ b/lib/observe/map.dart |
@@ -0,0 +1,221 @@ |
+// Copyright (c) 2013, 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.map; |
+ |
+import 'dart:collection'; |
+import 'package:web_ui/observe.dart'; |
+import 'list.dart'; |
+ |
+typedef Map<K, dynamic> MapFactory<K>(); |
+ |
+// TODO(jmesserly): this needs to be faster. We currently require multiple |
+// lookups per key to get the old value. Most likely this needs to be based on |
+// a modified HashMap source code. |
+/** |
+ * 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. |
+ */ |
+class ObservableMap<K, V> implements Map<K, V> { |
+ final Map<K, V> _map; |
+ final Map<K, Object> _observeKey; |
+ Object _observeLength; |
+ _ObservableMapKeyIterable<K, V> _keys; |
+ _ObservableMapValueIterable<K, V> _values; |
+ |
+ /** |
+ * Creates an observable map, optionally using the provided factory |
+ * [createMap] to construct a custom map type. |
+ */ |
+ ObservableMap({MapFactory<K> createMap}) |
+ : _map = createMap != null ? createMap() : new Map<K, V>(), |
+ _observeKey = createMap != null ? createMap() : new Map<K, Object>() { |
+ _keys = new _ObservableMapKeyIterable<K, V>(this); |
+ _values = new _ObservableMapValueIterable<K, V>(this); |
+ } |
+ |
+ /** Creates a new observable map using a [LinkedHashMap]. */ |
+ ObservableMap.linked() : this(createMap: () => new LinkedHashMap()); |
+ |
+ /** |
+ * Creates an observable map that contains all key value pairs of [other]. |
+ */ |
+ factory ObservableMap.from(Map<K, V> other, {MapFactory<K> createMap}) { |
+ var result = new ObservableMap<K, V>(createMap: createMap); |
+ other.forEach((K key, V value) { result[key] = value; }); |
+ return result; |
+ } |
+ |
+ |
+ Iterable<K> get keys => _keys; |
+ |
+ Iterable<V> get values => _values; |
+ |
+ int get length { |
+ _notifyReadLength(); |
+ return _map.length; |
+ } |
+ |
+ bool get isEmpty => length == 0; |
+ |
+ void _notifyReadKey(K key) { |
+ if (observeReads) _observeKey[key] = notifyRead(_observeKey[key]); |
+ } |
+ |
+ void _notifyReadLength() { |
+ if (observeReads) _observeLength = notifyRead(_observeLength); |
+ } |
+ |
+ void _notifyReadAll() { |
+ if (!observeReads) return; |
+ _observeLength = notifyRead(_observeLength); |
+ for (K key in _map.keys) { |
+ _observeKey[key] = notifyRead(_observeKey[key]); |
+ } |
+ } |
+ |
+ void _notifyWriteLength(int originalLength) { |
+ if (_observeLength != null && originalLength != _map.length) { |
+ _observeLength = notifyWrite(_observeLength); |
+ } |
+ } |
+ |
+ void _notifyWriteKey(K key) { |
+ var observer = _observeKey.remove(key); |
+ if (observer != null) notifyWrite(observer); |
+ } |
+ |
+ bool containsValue(V value) { |
+ _notifyReadAll(); |
+ return _map.containsValue(value); |
+ } |
+ |
+ bool containsKey(K key) { |
+ _notifyReadKey(key); |
+ return _map.containsKey(key); |
+ } |
+ |
+ V operator [](K key) { |
+ _notifyReadKey(key); |
+ return _map[key]; |
+ } |
+ |
+ void operator []=(K key, V value) { |
+ int len = _map.length; |
+ V oldValue = _map[key]; |
+ _map[key] = value; |
+ // Note: if length changed, it means the key was added, so we need to |
+ // _notifyWriteKey. Also _notifyWriteLength will check if length changed. |
+ if (len != _map.length || oldValue != value) { |
+ _notifyWriteKey(key); |
+ _notifyWriteLength(len); |
+ } |
+ } |
+ |
+ V putIfAbsent(K key, V ifAbsent()) { |
+ // notifyRead because result depends on if the key already exists |
+ _notifyReadKey(key); |
+ |
+ int len = _map.length; |
+ V result = _map.putIfAbsent(key, ifAbsent); |
+ // Note: if length changed, it means the key was added, so we need to |
+ // _notifyWriteKey. Also _notifyWriteLength will check if length changed. |
+ if (len != _map.length) { |
+ _notifyWriteKey(key); |
+ _notifyWriteLength(len); |
+ } |
+ return result; |
+ } |
+ |
+ V remove(K key) { |
+ // notifyRead because result depends on if the key already exists |
+ _notifyReadKey(key); |
+ |
+ int len = _map.length; |
+ V result = _map.remove(key); |
+ if (len != _map.length) { |
+ _notifyWriteKey(key); |
+ _notifyWriteLength(len); |
+ } |
+ return result; |
+ } |
+ |
+ void clear() { |
+ int len = _map.length; |
+ _map.clear(); |
+ _notifyWriteLength(len); |
+ _observeKey.values.forEach(notifyWrite); |
+ _observeKey.clear(); |
+ } |
+ |
+ void forEach(void f(K key, V value)) { |
+ _notifyReadAll(); |
+ _map.forEach(f); |
+ } |
+ |
+ String toString() => Maps.mapToString(this); |
+} |
+ |
+class _ObservableMapKeyIterable<K, V> extends Iterable<K> { |
+ final ObservableMap<K, V> _map; |
+ _ObservableMapKeyIterable(this._map); |
+ |
+ Iterator<K> get iterator => new _ObservableMapKeyIterator<K, V>(_map); |
+} |
+ |
+class _ObservableMapKeyIterator<K, V> implements Iterator<K> { |
+ final ObservableMap<K, V> _map; |
+ final Iterator<K> _keys; |
+ |
+ _ObservableMapKeyIterator(ObservableMap<K, V> map) |
+ : _map = map, |
+ _keys = map._map.keys.iterator; |
+ |
+ bool moveNext() { |
+ _map._notifyReadLength(); |
+ return _keys.moveNext(); |
+ } |
+ |
+ K get current { |
+ var key = _keys.current; |
+ if (key != null) _map._notifyReadKey(key); |
+ return key; |
+ } |
+} |
+ |
+ |
+class _ObservableMapValueIterable<K, V> extends Iterable<V> { |
+ final ObservableMap<K, V> _map; |
+ _ObservableMapValueIterable(this._map); |
+ |
+ Iterator<V> get iterator => new _ObservableMapValueIterator<K, V>(_map); |
+} |
+ |
+class _ObservableMapValueIterator<K, V> implements Iterator<V> { |
+ final ObservableMap<K, V> _map; |
+ final Iterator<K> _keys; |
+ final Iterator<V> _values; |
+ |
+ _ObservableMapValueIterator(ObservableMap<K, V> map) |
+ : _map = map, |
+ _keys = map._map.keys.iterator, |
+ _values = map._map.values.iterator; |
+ |
+ bool moveNext() { |
+ _map._notifyReadLength(); |
+ bool moreKeys = _keys.moveNext(); |
+ bool moreValues = _values.moveNext(); |
+ if (moreKeys != moreValues) { |
+ throw new StateError('keys and values should be the same length'); |
+ } |
+ return moreValues; |
+ } |
+ |
+ V get current { |
+ var key = _keys.current; |
+ if (key != null) _map._notifyReadKey(key); |
+ return _values.current; |
+ } |
+} |