| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library observe.src.observable_list; | 5 library observe.src.observable_list; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection' show ListBase, UnmodifiableListView; | 8 import 'dart:collection' show ListBase, UnmodifiableListView; |
| 9 import 'package:observe/observe.dart'; | 9 import 'package:observe/observe.dart'; |
| 10 import 'list_diff.dart' show projectListSplices, calcSplices; | 10 import 'list_diff.dart' show projectListSplices, calcSplices; |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 75 bool get _hasListObservers => | 75 bool get _hasListObservers => |
| 76 _listChanges != null && _listChanges.hasListener; | 76 _listChanges != null && _listChanges.hasListener; |
| 77 | 77 |
| 78 @reflectable int get length => _list.length; | 78 @reflectable int get length => _list.length; |
| 79 | 79 |
| 80 @reflectable set length(int value) { | 80 @reflectable set length(int value) { |
| 81 int len = _list.length; | 81 int len = _list.length; |
| 82 if (len == value) return; | 82 if (len == value) return; |
| 83 | 83 |
| 84 // Produce notifications if needed | 84 // Produce notifications if needed |
| 85 notifyPropertyChange(#length, len, value); | 85 _notifyChangeLength(len, value); |
| 86 if (_hasListObservers) { | 86 if (_hasListObservers) { |
| 87 if (value < len) { | 87 if (value < len) { |
| 88 _recordChange(new ListChangeRecord(this, value, | 88 _recordChange(new ListChangeRecord(this, value, |
| 89 removed: _list.getRange(value, len).toList())); | 89 removed: _list.getRange(value, len).toList())); |
| 90 } else { | 90 } else { |
| 91 _recordChange(new ListChangeRecord(this, len, addedCount: value - len)); | 91 _recordChange(new ListChangeRecord(this, len, addedCount: value - len)); |
| 92 } | 92 } |
| 93 } | 93 } |
| 94 | 94 |
| 95 _list.length = value; | 95 _list.length = value; |
| 96 } | 96 } |
| 97 | 97 |
| 98 @reflectable E operator [](int index) => _list[index]; | 98 @reflectable E operator [](int index) => _list[index]; |
| 99 | 99 |
| 100 @reflectable void operator []=(int index, E value) { | 100 @reflectable void operator []=(int index, E value) { |
| 101 var oldValue = _list[index]; | 101 var oldValue = _list[index]; |
| 102 if (_hasListObservers) { | 102 if (_hasListObservers) { |
| 103 _recordChange(new ListChangeRecord(this, index, addedCount: 1, | 103 _recordChange(new ListChangeRecord(this, index, addedCount: 1, |
| 104 removed: [oldValue])); | 104 removed: [oldValue])); |
| 105 } | 105 } |
| 106 _list[index] = value; | 106 _list[index] = value; |
| 107 } | 107 } |
| 108 | 108 |
| 109 // Forwarders so we can reflect on the properties. |
| 110 @reflectable bool get isEmpty => super.isEmpty; |
| 111 @reflectable bool get isNotEmpty => super.isNotEmpty; |
| 112 |
| 113 // TODO(jmesserly): should we support first/last/single? They're kind of |
| 114 // dangerous to use in a path because they throw exceptions. Also we'd need |
| 115 // to produce property change notifications which seems to conflict with our |
| 116 // existing list notifications. |
| 117 |
| 109 // The following methods are here so that we can provide nice change events. | 118 // The following methods are here so that we can provide nice change events. |
| 110 | 119 |
| 111 void setAll(int index, Iterable<E> iterable) { | 120 void setAll(int index, Iterable<E> iterable) { |
| 112 if (iterable is! List && iterable is! Set) { | 121 if (iterable is! List && iterable is! Set) { |
| 113 iterable = iterable.toList(); | 122 iterable = iterable.toList(); |
| 114 } | 123 } |
| 115 var len = iterable.length; | 124 var len = iterable.length; |
| 116 if (_hasListObservers && len > 0) { | 125 if (_hasListObservers && len > 0) { |
| 117 _recordChange(new ListChangeRecord(this, index, addedCount: len, | 126 _recordChange(new ListChangeRecord(this, index, addedCount: len, |
| 118 removed: _list.getRange(index, len).toList())); | 127 removed: _list.getRange(index, len).toList())); |
| 119 } | 128 } |
| 120 _list.setAll(index, iterable); | 129 _list.setAll(index, iterable); |
| 121 } | 130 } |
| 122 | 131 |
| 123 void add(E value) { | 132 void add(E value) { |
| 124 int len = _list.length; | 133 int len = _list.length; |
| 125 notifyPropertyChange(#length, len, len + 1); | 134 _notifyChangeLength(len, len + 1); |
| 126 if (_hasListObservers) { | 135 if (_hasListObservers) { |
| 127 _recordChange(new ListChangeRecord(this, len, addedCount: 1)); | 136 _recordChange(new ListChangeRecord(this, len, addedCount: 1)); |
| 128 } | 137 } |
| 129 | 138 |
| 130 _list.add(value); | 139 _list.add(value); |
| 131 } | 140 } |
| 132 | 141 |
| 133 void addAll(Iterable<E> iterable) { | 142 void addAll(Iterable<E> iterable) { |
| 134 int len = _list.length; | 143 int len = _list.length; |
| 135 _list.addAll(iterable); | 144 _list.addAll(iterable); |
| 136 | 145 |
| 137 notifyPropertyChange(#length, len, _list.length); | 146 _notifyChangeLength(len, _list.length); |
| 138 | 147 |
| 139 int added = _list.length - len; | 148 int added = _list.length - len; |
| 140 if (_hasListObservers && added > 0) { | 149 if (_hasListObservers && added > 0) { |
| 141 _recordChange(new ListChangeRecord(this, len, addedCount: added)); | 150 _recordChange(new ListChangeRecord(this, len, addedCount: added)); |
| 142 } | 151 } |
| 143 } | 152 } |
| 144 | 153 |
| 145 bool remove(Object element) { | 154 bool remove(Object element) { |
| 146 for (int i = 0; i < this.length; i++) { | 155 for (int i = 0; i < this.length; i++) { |
| 147 if (this[i] == element) { | 156 if (this[i] == element) { |
| 148 removeRange(i, i + 1); | 157 removeRange(i, i + 1); |
| 149 return true; | 158 return true; |
| 150 } | 159 } |
| 151 } | 160 } |
| 152 return false; | 161 return false; |
| 153 } | 162 } |
| 154 | 163 |
| 155 void removeRange(int start, int end) { | 164 void removeRange(int start, int end) { |
| 156 _rangeCheck(start, end); | 165 _rangeCheck(start, end); |
| 157 int rangeLength = end - start; | 166 int rangeLength = end - start; |
| 158 int len = _list.length; | 167 int len = _list.length; |
| 159 | 168 |
| 160 notifyPropertyChange(#length, len, len - rangeLength); | 169 _notifyChangeLength(len, len - rangeLength); |
| 161 if (_hasListObservers && rangeLength > 0) { | 170 if (_hasListObservers && rangeLength > 0) { |
| 162 _recordChange(new ListChangeRecord(this, start, | 171 _recordChange(new ListChangeRecord(this, start, |
| 163 removed: _list.getRange(start, end).toList())); | 172 removed: _list.getRange(start, end).toList())); |
| 164 } | 173 } |
| 165 | 174 |
| 166 _list.removeRange(start, end); | 175 _list.removeRange(start, end); |
| 167 } | 176 } |
| 168 | 177 |
| 169 void insertAll(int index, Iterable<E> iterable) { | 178 void insertAll(int index, Iterable<E> iterable) { |
| 170 if (index < 0 || index > length) { | 179 if (index < 0 || index > length) { |
| 171 throw new RangeError.range(index, 0, length); | 180 throw new RangeError.range(index, 0, length); |
| 172 } | 181 } |
| 173 // TODO(floitsch): we can probably detect more cases. | 182 // TODO(floitsch): we can probably detect more cases. |
| 174 if (iterable is! List && iterable is! Set) { | 183 if (iterable is! List && iterable is! Set) { |
| 175 iterable = iterable.toList(); | 184 iterable = iterable.toList(); |
| 176 } | 185 } |
| 177 int insertionLength = iterable.length; | 186 int insertionLength = iterable.length; |
| 178 // There might be errors after the length change, in which case the list | 187 // There might be errors after the length change, in which case the list |
| 179 // will end up being modified but the operation not complete. Unless we | 188 // will end up being modified but the operation not complete. Unless we |
| 180 // always go through a "toList" we can't really avoid that. | 189 // always go through a "toList" we can't really avoid that. |
| 181 int len = _list.length; | 190 int len = _list.length; |
| 182 _list.length += insertionLength; | 191 _list.length += insertionLength; |
| 183 | 192 |
| 184 _list.setRange(index + insertionLength, this.length, this, index); | 193 _list.setRange(index + insertionLength, this.length, this, index); |
| 185 _list.setAll(index, iterable); | 194 _list.setAll(index, iterable); |
| 186 | 195 |
| 187 notifyPropertyChange(#length, len, _list.length); | 196 _notifyChangeLength(len, _list.length); |
| 188 | 197 |
| 189 if (_hasListObservers && insertionLength > 0) { | 198 if (_hasListObservers && insertionLength > 0) { |
| 190 _recordChange(new ListChangeRecord(this, index, | 199 _recordChange(new ListChangeRecord(this, index, |
| 191 addedCount: insertionLength)); | 200 addedCount: insertionLength)); |
| 192 } | 201 } |
| 193 } | 202 } |
| 194 | 203 |
| 195 void insert(int index, E element) { | 204 void insert(int index, E element) { |
| 196 if (index < 0 || index > length) { | 205 if (index < 0 || index > length) { |
| 197 throw new RangeError.range(index, 0, length); | 206 throw new RangeError.range(index, 0, length); |
| 198 } | 207 } |
| 199 if (index == length) { | 208 if (index == length) { |
| 200 add(element); | 209 add(element); |
| 201 return; | 210 return; |
| 202 } | 211 } |
| 203 // We are modifying the length just below the is-check. Without the check | 212 // We are modifying the length just below the is-check. Without the check |
| 204 // Array.copy could throw an exception, leaving the list in a bad state | 213 // Array.copy could throw an exception, leaving the list in a bad state |
| 205 // (with a length that has been increased, but without a new element). | 214 // (with a length that has been increased, but without a new element). |
| 206 if (index is! int) throw new ArgumentError(index); | 215 if (index is! int) throw new ArgumentError(index); |
| 207 _list.length++; | 216 _list.length++; |
| 208 _list.setRange(index + 1, length, this, index); | 217 _list.setRange(index + 1, length, this, index); |
| 209 | 218 |
| 210 notifyPropertyChange(#length, _list.length - 1, _list.length); | 219 _notifyChangeLength(_list.length - 1, _list.length); |
| 211 if (_hasListObservers) { | 220 if (_hasListObservers) { |
| 212 _recordChange(new ListChangeRecord(this, index, addedCount: 1)); | 221 _recordChange(new ListChangeRecord(this, index, addedCount: 1)); |
| 213 } | 222 } |
| 214 _list[index] = element; | 223 _list[index] = element; |
| 215 } | 224 } |
| 216 | 225 |
| 217 | 226 |
| 218 E removeAt(int index) { | 227 E removeAt(int index) { |
| 219 E result = this[index]; | 228 E result = this[index]; |
| 220 removeRange(index, index + 1); | 229 removeRange(index, index + 1); |
| (...skipping 12 matching lines...) Expand all Loading... |
| 233 void _recordChange(ListChangeRecord record) { | 242 void _recordChange(ListChangeRecord record) { |
| 234 if (!_hasListObservers) return; | 243 if (!_hasListObservers) return; |
| 235 | 244 |
| 236 if (_listRecords == null) { | 245 if (_listRecords == null) { |
| 237 _listRecords = []; | 246 _listRecords = []; |
| 238 scheduleMicrotask(deliverListChanges); | 247 scheduleMicrotask(deliverListChanges); |
| 239 } | 248 } |
| 240 _listRecords.add(record); | 249 _listRecords.add(record); |
| 241 } | 250 } |
| 242 | 251 |
| 252 void _notifyChangeLength(int oldValue, int newValue) { |
| 253 notifyPropertyChange(#length, oldValue, newValue); |
| 254 notifyPropertyChange(#isEmpty, oldValue == 0, newValue == 0); |
| 255 notifyPropertyChange(#isNotEmpty, oldValue != 0, newValue != 0); |
| 256 } |
| 257 |
| 243 bool deliverListChanges() { | 258 bool deliverListChanges() { |
| 244 if (_listRecords == null) return false; | 259 if (_listRecords == null) return false; |
| 245 var records = projectListSplices(this, _listRecords); | 260 var records = projectListSplices(this, _listRecords); |
| 246 _listRecords = null; | 261 _listRecords = null; |
| 247 | 262 |
| 248 if (_hasListObservers) { | 263 if (_hasListObservers) { |
| 249 _listChanges.add(new UnmodifiableListView<ListChangeRecord>(records)); | 264 _listChanges.add(new UnmodifiableListView<ListChangeRecord>(records)); |
| 250 return true; | 265 return true; |
| 251 } | 266 } |
| 252 return false; | 267 return false; |
| (...skipping 10 matching lines...) Expand all Loading... |
| 263 * final state of a list. The basic approach is to calculate the edit distance | 278 * final state of a list. The basic approach is to calculate the edit distance |
| 264 * matrix and choose the shortest path through it. | 279 * matrix and choose the shortest path through it. |
| 265 * | 280 * |
| 266 * Complexity is `O(l * p)` where `l` is the length of the current list and | 281 * Complexity is `O(l * p)` where `l` is the length of the current list and |
| 267 * `p` is the length of the old list. | 282 * `p` is the length of the old list. |
| 268 */ | 283 */ |
| 269 static List<ListChangeRecord> calculateChangeRecords( | 284 static List<ListChangeRecord> calculateChangeRecords( |
| 270 List<Object> oldValue, List<Object> newValue) => | 285 List<Object> oldValue, List<Object> newValue) => |
| 271 calcSplices(newValue, 0, newValue.length, oldValue, 0, oldValue.length); | 286 calcSplices(newValue, 0, newValue.length, oldValue, 0, oldValue.length); |
| 272 } | 287 } |
| OLD | NEW |