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

Side by Side Diff: pkg/mdv_observe/lib/src/observable_list.dart

Issue 14908005: "Reverting 22561" (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 7 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 | Annotate | Revision Log
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 part of mdv_observe;
6
7 /**
8 * Represents an observable list of model values. If any items are added,
9 * removed, or replaced, then observers that are listening to [changes]
10 * will be notified.
11 */
12 // TODO(jmesserly): remove implements List<E> once we can extend ListBase<E>
13 class ObservableList<E> extends _ListBaseWorkaround with ObservableMixin
14 implements List<E> {
15 List<ListChangeRecord> _records;
16
17 static const _LENGTH = const Symbol('length');
18
19 /** The inner [List<E>] with the actual storage. */
20 final List<E> _list;
21
22 /**
23 * Creates an observable list of the given [length].
24 *
25 * If no [length] argument is supplied an extendable list of
26 * length 0 is created.
27 *
28 * If a [length] argument is supplied, a fixed size list of that
29 * length is created.
30 */
31 ObservableList([int length])
32 : _list = length != null ? new List<E>(length) : <E>[];
33
34 /**
35 * Creates an observable list with the elements of [other]. The order in
36 * the list will be the order provided by the iterator of [other].
37 */
38 factory ObservableList.from(Iterable<E> other) =>
39 new ObservableList<E>()..addAll(other);
40
41 // TODO(jmesserly): remove once we have mirrors
42 getValueWorkaround(key) => key == _LENGTH ? length : null;
43
44 setValueWorkaround(key, value) {
45 if (key == _LENGTH) length = value;
46 }
47
48 int get length => _list.length;
49
50 set length(int value) {
51 int len = _list.length;
52 if (len == value) return;
53
54 // Produce notifications if needed
55 if (hasObservers) {
56 if (value < len) {
57 // Remove items, then adjust length. Note the reverse order.
58 _recordChange(new ListChangeRecord(value, removedCount: len - value));
59 } else {
60 // Adjust length then add items
61 _recordChange(new ListChangeRecord(len, addedCount: value - len));
62 }
63 }
64
65 _list.length = value;
66 }
67
68 E operator [](int index) => _list[index];
69
70 void operator []=(int index, E value) {
71 var oldValue = _list[index];
72 if (hasObservers) {
73 _recordChange(new ListChangeRecord(index, addedCount: 1, removedCount: 1)) ;
74 }
75 _list[index] = value;
76 }
77
78 // The following methods are here so that we can provide nice change events.
79
80 void setAll(int index, Iterable<E> iterable) {
81 if (iterable is! List && iterable is! Set) {
82 iterable = iterable.toList();
83 }
84 var len = iterable.length;
85 _list.setAll(index, iterable);
86 if (hasObservers && len > 0) {
87 _recordChange(
88 new ListChangeRecord(index, addedCount: len, removedCount: len));
89 }
90 }
91
92 void add(E value) {
93 int len = _list.length;
94 if (hasObservers) {
95 _recordChange(new ListChangeRecord(len, addedCount: 1));
96 }
97
98 _list.add(value);
99 }
100
101 void addAll(Iterable<E> iterable) {
102 int len = _list.length;
103 _list.addAll(iterable);
104 int added = _list.length - len;
105 if (hasObservers && added > 0) {
106 _recordChange(new ListChangeRecord(len, addedCount: added));
107 }
108 }
109
110 bool remove(Object element) {
111 for (int i = 0; i < this.length; i++) {
112 if (this[i] == element) {
113 removeRange(i, 1);
114 return true;
115 }
116 }
117 return false;
118 }
119
120 void removeRange(int start, int end) {
121 _rangeCheck(start, end);
122 int length = end - start;
123 _list.setRange(start, this.length - length, this, end);
124
125 int len = _list.length;
126 _list.length -= length;
127 if (hasObservers && length > 0) {
128 _recordChange(new ListChangeRecord(start, removedCount: length));
129 }
130 }
131
132 void insertAll(int index, Iterable<E> iterable) {
133 if (index < 0 || index > length) {
134 throw new RangeError.range(index, 0, length);
135 }
136 // TODO(floitsch): we can probably detect more cases.
137 if (iterable is! List && iterable is! Set) {
138 iterable = iterable.toList();
139 }
140 int insertionLength = iterable.length;
141 // There might be errors after the length change, in which case the list
142 // will end up being modified but the operation not complete. Unless we
143 // always go through a "toList" we can't really avoid that.
144 int len = _list.length;
145 _list.length += insertionLength;
146
147 _list.setRange(index + insertionLength, this.length, this, index);
148 _list.setAll(index, iterable);
149
150 if (hasObservers && insertionLength > 0) {
151 _recordChange(new ListChangeRecord(index, addedCount: insertionLength));
152 }
153 }
154
155 void insert(int index, E element) {
156 if (index < 0 || index > length) {
157 throw new RangeError.range(index, 0, length);
158 }
159 if (index == length) {
160 add(element);
161 return;
162 }
163 // We are modifying the length just below the is-check. Without the check
164 // Array.copy could throw an exception, leaving the list in a bad state
165 // (with a length that has been increased, but without a new element).
166 if (index is! int) throw new ArgumentError(index);
167 _list.length++;
168 _list.setRange(index + 1, length, this, index);
169 if (hasObservers) {
170 _recordChange(new ListChangeRecord(index, addedCount: 1));
171 }
172 _list[index] = element;
173 }
174
175
176 E removeAt(int index) {
177 E result = this[index];
178 removeRange(index, index + 1);
179 return result;
180 }
181
182 void _rangeCheck(int start, int end) {
183 if (start < 0 || start > this.length) {
184 throw new RangeError.range(start, 0, this.length);
185 }
186 if (end < start || end > this.length) {
187 throw new RangeError.range(end, start, this.length);
188 }
189 }
190
191 void _recordChange(ListChangeRecord record) {
192 if (_records == null) {
193 _records = [];
194 queueChangeRecords(_summarizeRecords);
195 }
196 _records.add(record);
197 }
198
199 /**
200 * We need to summarize change records. Consumers of these records want to
201 * apply the batch sequentially, and ensure that they can find inserted
202 * items by looking at that position in the list. This property does not
203 * hold in our record-as-you-go records. Consider:
204 *
205 * var model = toObservable(['a', 'b']);
206 * model.removeAt(1);
207 * model.insertAll(0, ['c', 'd', 'e']);
208 * model.removeRange(1, 3);
209 * model.insert(1, 'f');
210 *
211 * Here, we inserted some records and then removed some of them.
212 * If someone processed these records naively, they would "play back" the
213 * insert incorrectly, because those items will be shifted.
214 *
215 * We summarize changes using a straightforward technique:
216 * Simulate the moves and use the final item positions to synthesize a
217 * new list of changes records. This has the advantage of not depending
218 * on the actual *values*, so we don't need to perform N^2 edit
219 */
220 // TODO(jmesserly): there's probably something smarter here, but this
221 // algorithm is pretty simple. It has complexity equivalent to the original
222 // list modifications.
223 // One simple idea: we can simply update the index map as we do the operations
224 // to the list, then produce the records at the end.
225 void _summarizeRecords() {
226 int oldLength = length;
227 for (var r in _records) {
228 oldLength += r.removedCount - r.addedCount;
229 }
230
231 if (length != oldLength) {
232 notifyPropertyChange(_LENGTH, oldLength, length);
233 }
234
235 if (_records.length == 1) {
236 notifyChange(_records[0]);
237 _records = null;
238 return;
239 }
240
241 var items = [];
242 for (int i = 0; i < oldLength; i++) items.add(i);
243 for (var r in _records) {
244 items.removeRange(r.index, r.index + r.removedCount);
245
246 // Represent inserts with -1.
247 items.insertAll(r.index, new List.filled(r.addedCount, -1));
248 }
249 assert(items.length == length);
250
251 _records = null;
252
253 int index = 0;
254 int offset = 0;
255 while (index < items.length) {
256 // Skip unchanged items.
257 while (index < items.length && items[index] == index + offset) {
258 index++;
259 }
260
261 // Find inserts
262 int startIndex = index;
263 while (index < items.length && items[index] == -1) {
264 index++;
265 }
266
267 int added = index - startIndex;
268
269 // Use the delta between our actual and expected position to determine
270 // how much was removed.
271 int actualItem = index < items.length ? items[index] : oldLength;
272 int expectedItem = startIndex + offset;
273
274 int removed = actualItem - expectedItem;
275
276 if (added > 0 || removed > 0) {
277 notifyChange(new ListChangeRecord(startIndex, addedCount: added,
278 removedCount: removed));
279 }
280
281 offset += removed - added;
282 }
283 }
284 }
285
286 // TODO(jmesserly): bogus type to workaround spurious VM bug with generic base
287 // class and mixins.
288 abstract class _ListBaseWorkaround extends ListBase<dynamic> {}
OLDNEW
« no previous file with comments | « pkg/mdv_observe/lib/src/observable_box.dart ('k') | pkg/mdv_observe/lib/src/observable_map.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698