Chromium Code Reviews| 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. |
|
Siggi Cherem (dart-lang)
2013/02/13 19:28:54
yeah, this one is more obvious, because if it is n
Jennifer Messerly
2013/02/14 00:38:09
Done.
|
| + 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; |
| + } |
| +} |