Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(677)

Side by Side Diff: lib/observe/map.dart

Issue 12096106: work in progress: observable implementation using detailed change records (Closed) Base URL: https://github.com/dart-lang/web-ui.git@master
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « lib/observe/list.dart ('k') | lib/observe/observable.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 library web_ui.observe.map;
6
7 import 'dart:collection';
8 import 'package:web_ui/observe.dart';
9
10 // TODO(jmesserly): also need versions of SplayTreeMap, LinkedHashMap.
11
12 /**
13 * Represents an observable map of model values. If any items are added,
14 * removed, or replaced, then observers that are registered with
15 * [observe] will be notified.
16 */
17 abstract class ObservableMap<K, V> implements Observable, Map<K, V> {
18 /**
19 * Creates a map with the default implementation.
20 */
21 factory ObservableMap() => new ObservableHashMap<K, V>();
22
23 /**
24 * Creates a [Map] that contains all key value pairs of [other].
25 */
26 factory ObservableMap.from(Map<K, V> other) =>
27 new ObservableHashMap<K, V>.from(other);
28 }
29
30
31 /**
32 * Hash map version of the [ObservableMap] interface. A [ObservableHashMap] does
33 * not provide any guarantees on the order of keys and values in [keys]
34 * and [values].
35 */
36 class ObservableHashMap<K, V> extends Observable
37 implements ObservableMap<K, V>, HashMap<K, V> {
38
39 // The [_keys] list contains the keys inserted in the map.
40 // The [_keys] list must be a raw list because it
41 // will contain both elements of type K, and the [_DELETED_KEY] of type
42 // [_DeletedKeySentinel].
43 // The alternative of declaring the [_keys] list as of type Object
44 // does not work, because the HashSetIterator constructor would fail:
45 // HashSetIterator(HashSet<E> set)
46 // : _nextValidIndex = -1,
47 // _entries = set_._backingMap._keys {
48 // _advance();
49 // }
50 // With K being type int, for example, it would fail because
51 // List<Object> is not assignable to type List<int> of entries.
52 List _keys;
53
54 // The values inserted in the map. For a filled entry index in this
55 // list, there is always the corresponding key in the [keys_] list
56 // at the same entry index.
57 List<V> _values;
58
59 // The load limit is the number of entries we allow until we double
60 // the size of the lists.
61 int _loadLimit;
62
63 // The current number of entries in the map. Will never be greater
64 // than [_loadLimit].
65 int _numberOfEntriesRaw;
66
67 // The current number of deleted entries in the map.
68 int _numberOfDeleted;
69
70 // The sentinel when a key is deleted from the map.
71 static const _DeletedKeySentinel _DELETED_KEY = const _DeletedKeySentinel();
72
73 // The initial capacity of a hash map.
74 static const int _INITIAL_CAPACITY = 8; // must be power of 2
75
76 ObservableHashMap() {
77 _numberOfEntries = 0;
78 _numberOfDeleted = 0;
79 _loadLimit = _computeLoadLimit(_INITIAL_CAPACITY);
80 _keys = new List.fixedLength(_INITIAL_CAPACITY);
81 _values = new List<V>.fixedLength(_INITIAL_CAPACITY);
82 }
83
84 factory ObservableHashMap.from(Map<K, V> other) {
85 Map<K, V> result = new ObservableHashMap<K, V>();
86 other.forEach((K key, V value) { result[key] = value; });
87 return result;
88 }
89
90 int get _numberOfEntries {
91 if (observeReads) notifyRead(ChangeRecord.FIELD, 'length');
92 return _numberOfEntriesRaw;
93 }
94
95 void set _numberOfEntries(int value) {
96 if (hasObservers) {
97 notifyChange(ChangeRecord.FIELD, 'length', _numberOfEntriesRaw, value);
98 }
99 _numberOfEntriesRaw = value;
100 }
101
102 static int _computeLoadLimit(int capacity) {
103 return (capacity * 3) ~/ 4;
104 }
105
106 static int _firstProbe(int hashCode, int length) {
107 return hashCode & (length - 1);
108 }
109
110 static int _nextProbe(int currentProbe, int numberOfProbes, int length) {
111 return (currentProbe + numberOfProbes) & (length - 1);
112 }
113
114 int _probeForAdding(K key) {
115 if (key == null) throw new ArgumentError(null);
116 int hash = _firstProbe(key.hashCode, _keys.length);
117 int numberOfProbes = 1;
118 int initialHash = hash;
119 // insertionIndex points to a slot where a key was deleted.
120 int insertionIndex = -1;
121 while (true) {
122 // [existingKey] can be either of type [K] or [_DeletedKeySentinel].
123 Object existingKey = _keys[hash];
124 if (existingKey == null) {
125 // We are sure the key is not already in the set.
126 // If the current slot is empty and we didn't find any
127 // insertion slot before, return this slot.
128 if (insertionIndex < 0) return hash;
129 // If we did find an insertion slot before, return it.
130 return insertionIndex;
131 } else if (existingKey == key) {
132 // The key is already in the map. Return its slot.
133 return hash;
134 } else if ((insertionIndex < 0) &&
135 (identical(existingKey, _DELETED_KEY))) {
136 // The slot contains a deleted element. Because previous calls to this
137 // method may not have had this slot deleted, we must continue iterate
138 // to find if there is a slot with the given key.
139 insertionIndex = hash;
140 }
141
142 // We did not find an insertion slot. Look at the next one.
143 hash = _nextProbe(hash, numberOfProbes++, _keys.length);
144 // _ensureCapacity has guaranteed the following cannot happen.
145 // assert(hash != initialHash);
146 }
147 }
148
149 int _probeForLookup(K key) {
150 if (key == null) throw new ArgumentError(null);
151 if (observeReads) notifyRead(ChangeRecord.INDEX, key);
152 int hash = _firstProbe(key.hashCode, _keys.length);
153 int numberOfProbes = 1;
154 int initialHash = hash;
155 while (true) {
156 // [existingKey] can be either of type [K] or [_DeletedKeySentinel].
157 Object existingKey = _keys[hash];
158 // If the slot does not contain anything (in particular, it does not
159 // contain a deleted key), we know the key is not in the map.
160 if (existingKey == null) return -1;
161 // The key is in the map, return its index.
162 if (existingKey == key) return hash;
163 // Go to the next probe.
164 hash = _nextProbe(hash, numberOfProbes++, _keys.length);
165 // _ensureCapacity has guaranteed the following cannot happen.
166 // assert(hash != initialHash);
167 }
168 }
169
170 void _ensureCapacity() {
171 int newNumberOfEntries = _numberOfEntries + 1;
172 // Test if adding an element will reach the load limit.
173 if (newNumberOfEntries >= _loadLimit) {
174 _grow(_keys.length * 2);
175 return;
176 }
177
178 // Make sure that we don't have poor performance when a map
179 // contains lots of deleted entries: we _grow if
180 // there are more deleted entried than free entries.
181 int capacity = _keys.length;
182 int numberOfFreeOrDeleted = capacity - newNumberOfEntries;
183 int numberOfFree = numberOfFreeOrDeleted - _numberOfDeleted;
184 // assert(numberOfFree > 0);
185 if (_numberOfDeleted > numberOfFree) {
186 _grow(_keys.length);
187 }
188 }
189
190 static bool _isPowerOfTwo(int x) {
191 return ((x & (x - 1)) == 0);
192 }
193
194 void _grow(int newCapacity) {
195 assert(_isPowerOfTwo(newCapacity));
196 int capacity = _keys.length;
197 _loadLimit = _computeLoadLimit(newCapacity);
198 List oldKeys = _keys;
199 List<V> oldValues = _values;
200 _keys = new List.fixedLength(newCapacity);
201 _values = new List<V>.fixedLength(newCapacity);
202 for (int i = 0; i < capacity; i++) {
203 // [key] can be either of type [K] or [_DeletedKeySentinel].
204 Object key = oldKeys[i];
205 // If there is no key, we don't need to deal with the current slot.
206 if (key == null || identical(key, _DELETED_KEY)) {
207 continue;
208 }
209 V value = oldValues[i];
210 // Insert the {key, value} pair in their new slot.
211 int newIndex = _probeForAdding(key);
212 _keys[newIndex] = key;
213 _values[newIndex] = value;
214 }
215 _numberOfDeleted = 0;
216 }
217
218 void clear() {
219 _numberOfEntries = 0;
220 _numberOfDeleted = 0;
221 int length = _keys.length;
222 for (int i = 0; i < length; i++) {
223 var key = _keys[i];
224 if (hasObservers && key != null && !identical(key, _DELETED_KEY)) {
225 notifyChange(ChangeRecord.REMOVE, key, _values[i], null);
226 }
227 _keys[i] = null;
228 _values[i] = null;
229 }
230 }
231
232 void operator []=(K key, V value) {
233 _ensureCapacity();
234 int index = _probeForAdding(key);
235 if ((_keys[index] == null) || (identical(_keys[index], _DELETED_KEY))) {
236 _numberOfEntries++;
237 if (hasObservers) notifyChange(ChangeRecord.INSERT, key, null, value);
238 } else if (hasObservers) {
239 notifyChange(ChangeRecord.INDEX, key, _values[index], value);
240 }
241 _keys[index] = key;
242 _values[index] = value;
243 }
244
245 V operator [](K key) {
246 int index = _probeForLookup(key);
247 if (index < 0) return null;
248 return _values[index];
249 }
250
251 V putIfAbsent(K key, V ifAbsent()) {
252 int index = _probeForLookup(key);
253 if (index >= 0) return _values[index];
254
255 V value = ifAbsent();
256 this[key] = value;
257 return value;
258 }
259
260 V remove(K key) {
261 int index = _probeForLookup(key);
262 if (index >= 0) {
263 _numberOfEntries--;
264 V value = _values[index];
265 _values[index] = null;
266 if (hasObservers) notifyChange(ChangeRecord.REMOVE, key, value, null);
267 // Set the key to the sentinel to not break the probing chain.
268 _keys[index] = _DELETED_KEY;
269 _numberOfDeleted++;
270 return value;
271 }
272 return null;
273 }
274
275 bool get isEmpty {
276 return _numberOfEntries == 0;
277 }
278
279 int get length {
280 return _numberOfEntries;
281 }
282
283 void forEach(void f(K key, V value)) {
284 Iterator<int> it = new _HashMapImplIndexIterator(this);
285 while (it.moveNext()) {
286 f(_keys[it.current], _values[it.current]);
287 }
288 }
289
290 Iterable<K> get keys => new _HashMapImplKeyIterable<K>(this);
291
292 Iterable<V> get values => new _HashMapImplValueIterable<V>(this);
293
294 bool containsKey(K key) {
295 return (_probeForLookup(key) != -1);
296 }
297
298 bool containsValue(V value) => values.contains(value);
299
300 String toString() => Maps.mapToString(this);
301 }
302
303 class _HashMapImplKeyIterable<E> extends Iterable<E> {
304 final ObservableHashMap _map;
305 _HashMapImplKeyIterable(this._map);
306
307 Iterator<E> get iterator => new _HashMapImplKeyIterator<E>(_map);
308 }
309
310 class _HashMapImplValueIterable<E> extends Iterable<E> {
311 final ObservableHashMap _map;
312 _HashMapImplValueIterable(this._map);
313
314 Iterator<E> get iterator => new _HashMapImplValueIterator<E>(_map);
315 }
316
317 abstract class _HashMapImplIterator<E> implements Iterator<E> {
318 final ObservableHashMap _map;
319 int _index = -1;
320 E _current;
321
322 _HashMapImplIterator(this._map);
323
324 E _computeCurrentFromIndex(int index, List keys, List values);
325
326 bool moveNext() {
327 // Conceptually iteration depends on the ObservableMap size
328 if (observeReads) _map.notifyRead(ChangeRecord.FIELD, 'length');
329
330 int length = _map._keys.length;
331 int newIndex = _index + 1;
332 while (newIndex < length) {
333 var key = _map._keys[newIndex];
334 if (key != null && !identical(key, ObservableHashMap._DELETED_KEY)) {
335 if (observeReads) {
336 _map.notifyRead(ChangeRecord.INDEX, key);
337 }
338 _current = _computeCurrentFromIndex(newIndex, _map._keys, _map._values);
339 _index = newIndex;
340 return true;
341 }
342 newIndex++;
343 }
344 _index = length;
345 _current = null;
346 return false;
347 }
348
349 E get current => _current;
350 }
351
352 class _HashMapImplKeyIterator<E> extends _HashMapImplIterator<E> {
353 _HashMapImplKeyIterator(ObservableHashMap map) : super(map);
354
355 E _computeCurrentFromIndex(int index, List keys, List values) {
356 return keys[index];
357 }
358 }
359
360 class _HashMapImplValueIterator<E> extends _HashMapImplIterator<E> {
361 _HashMapImplValueIterator(ObservableHashMap map) : super(map);
362
363 E _computeCurrentFromIndex(int index, List keys, List values) {
364 return values[index];
365 }
366 }
367
368 class _HashMapImplIndexIterator extends _HashMapImplIterator<int> {
369 _HashMapImplIndexIterator(ObservableHashMap map) : super(map);
370
371 int _computeCurrentFromIndex(int index, List keys, List values) {
372 return index;
373 }
374 }
375
376 /**
377 * A singleton sentinel used to represent when a key is deleted from the map.
378 * We can't use [: const Object() :] as a sentinel because it would end up
379 * canonicalized and then we cannot distinguish the deleted key from the
380 * canonicalized [: Object() :].
381 */
382 class _DeletedKeySentinel {
383 const _DeletedKeySentinel();
384 }
385
386
387 /**
388 * This class represents a pair of two objects, used by LinkedHashMap
389 * to store a {key, value} in a list.
390 */
391 class _KeyValuePair<K, V> {
392 _KeyValuePair(this.key, this.value) {}
393
394 final K key;
395 V value;
396 }
OLDNEW
« no previous file with comments | « lib/observe/list.dart ('k') | lib/observe/observable.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698