Index: third_party/pkg/angular/lib/change_detection/dirty_checking_change_detector.dart |
diff --git a/third_party/pkg/angular/lib/change_detection/dirty_checking_change_detector.dart b/third_party/pkg/angular/lib/change_detection/dirty_checking_change_detector.dart |
index a51c74687733c77ce6bc0f283aa8c712c1eae877..5eddbe83e30b15bb763208de544e3d53a7c745ed 100644 |
--- a/third_party/pkg/angular/lib/change_detection/dirty_checking_change_detector.dart |
+++ b/third_party/pkg/angular/lib/change_detection/dirty_checking_change_detector.dart |
@@ -1,49 +1,31 @@ |
library dirty_checking_change_detector; |
-import 'dart:mirrors'; |
import 'dart:collection'; |
import 'package:angular/change_detection/change_detection.dart'; |
-typedef FieldGetter(object); |
- |
-class GetterCache { |
- final Map<String, FieldGetter> _map; |
- |
- GetterCache(this._map); |
- |
- FieldGetter call(String name) => _map[name]; |
-} |
- |
/** |
* [DirtyCheckingChangeDetector] determines which object properties have changed |
* by comparing them to the their previous value. |
* |
* GOALS: |
* - Plugable implementation, replaceable with other technologies, such as |
- * Object.observe(). |
+ * Object.observe(). |
* - SPEED this needs to be as fast as possible. |
* - No GC pressure. Since change detection runs often it should perform no |
- * memory allocations. |
+ * memory allocations. |
* - The changes need to be delivered in a single data-structure at once. |
- * There are two reasons for this: |
- * 1. It should be easy to measure the cost of change detection vs |
- * processing. |
- * 2. The feature may move to VM for performance reason. The VM should be |
- * free to implement it in any way. The only requirement is that the list of |
- * changes need to be delivered. |
+ * There are two reasons for this: |
+ * 1. It should be easy to measure the cost of change detection vs |
+ * processing. |
+ * 2. The feature may move to VM for performance reason. The VM should be |
+ * free to implement it in any way. The only requirement is that the |
+ * list of changes need to be delivered. |
* |
* [DirtyCheckingRecord] |
* |
* Each property to be watched is recorded as a [DirtyCheckingRecord] and kept |
* in a linked list. Linked list are faster than Arrays for iteration. They also |
* allow removal of large blocks of watches in an efficient manner. |
- * |
- * [ChangeRecord] |
- * |
- * When the results are delivered they are a linked list of [ChangeRecord]s. For |
- * efficiency reasons the [DirtyCheckingRecord] and [ChangeRecord] are two |
- * different interfaces for the same underlying object this makes reporting |
- * efficient since no additional memory allocation is performed. |
*/ |
class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> { |
/** |
@@ -54,7 +36,7 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> { |
*/ |
final DirtyCheckingRecord _marker = new DirtyCheckingRecord.marker(); |
- final GetterCache _getterCache; |
+ final FieldGetterFactory _fieldGetterFactory; |
/** |
* All records for group are kept together and are denoted by head/tail. |
@@ -75,12 +57,23 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> { |
return tail._recordTail; |
} |
+ bool get isAttached { |
+ DirtyCheckingChangeDetectorGroup current = this; |
+ DirtyCheckingChangeDetectorGroup parent; |
+ while ((parent = current._parent) != null) { |
+ current = parent; |
+ } |
+ return current is DirtyCheckingChangeDetector |
+ ? true |
+ : current._prev != null && current._next != null; |
+ } |
+ |
DirtyCheckingChangeDetector get _root { |
var root = this; |
- var next; |
- while((next = root._parent) != null) { |
- root = next; |
+ var parent; |
+ while ((parent = root._parent) != null) { |
+ root = parent; |
} |
return root is DirtyCheckingChangeDetector ? root : null; |
} |
@@ -92,7 +85,7 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> { |
*/ |
DirtyCheckingChangeDetectorGroup _parent, _childHead, _childTail, _prev, _next; |
- DirtyCheckingChangeDetectorGroup(this._parent, this._getterCache) { |
+ DirtyCheckingChangeDetectorGroup(this._parent, this._fieldGetterFactory) { |
// we need to insert the marker record at the beginning. |
if (_parent == null) { |
_recordHead = _marker; |
@@ -111,7 +104,7 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> { |
int count = 0; |
DirtyCheckingRecord cursor = _recordHead; |
DirtyCheckingRecord end = _childInclRecordTail; |
- while(cursor != null) { |
+ while (cursor != null) { |
if (cursor._mode != DirtyCheckingRecord._MODE_MARKER_) { |
count++; |
} |
@@ -123,17 +116,17 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> { |
WatchRecord<H> watch(Object object, String field, H handler) { |
assert(_root != null); // prove that we are not deleted connected; |
- var getter = field == null ? null : _getterCache(field); |
- return _recordAdd(new DirtyCheckingRecord(this, object, field, getter, |
- handler)); |
+ return _recordAdd(new DirtyCheckingRecord(this, _fieldGetterFactory, |
+ handler, field, object)); |
} |
/** |
* Create a child [ChangeDetector] group. |
*/ |
DirtyCheckingChangeDetectorGroup<H> newGroup() { |
- assert(_root._assertRecordsOk()); |
- var child = new DirtyCheckingChangeDetectorGroup(this, _getterCache); |
+ // Disabled due to issue https://github.com/angular/angular.dart/issues/812 |
+ // assert(_root._assertRecordsOk()); |
+ var child = new DirtyCheckingChangeDetectorGroup(this, _fieldGetterFactory); |
if (_childHead == null) { |
_childHead = _childTail = child; |
} else { |
@@ -141,7 +134,8 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> { |
_childTail._next = child; |
_childTail = child; |
} |
- assert(_root._assertRecordsOk()); |
+ // Disabled due to issue https://github.com/angular/angular.dart/issues/812 |
+ // assert(_root._assertRecordsOk()); |
return child; |
} |
@@ -153,16 +147,12 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> { |
assert((root = _root) != null); |
assert(root._assertRecordsOk()); |
DirtyCheckingRecord prevRecord = _recordHead._prevRecord; |
- DirtyCheckingRecord nextRecord = _childInclRecordTail._nextRecord; |
+ var childInclRecordTail = _childInclRecordTail; |
+ DirtyCheckingRecord nextRecord = childInclRecordTail._nextRecord; |
if (prevRecord != null) prevRecord._nextRecord = nextRecord; |
if (nextRecord != null) nextRecord._prevRecord = prevRecord; |
- var cursor = _recordHead; |
- while(cursor != nextRecord) { |
- cursor = cursor._nextRecord; |
- } |
- |
var prevGroup = _prev; |
var nextGroup = _next; |
@@ -177,6 +167,9 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> { |
nextGroup._prev = prevGroup; |
} |
_parent = null; |
+ _prev = _next = null; |
+ _recordHead._prevRecord = null; |
+ childInclRecordTail._nextRecord = null; |
assert(root._assertRecordsOk()); |
} |
@@ -225,8 +218,8 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> { |
do { |
allRecords.add(record.toString()); |
record = record._nextRecord; |
- } |
- while (record != includeChildrenTail); |
+ } while (record != includeChildrenTail); |
+ allRecords.add(includeChildrenTail); |
lines.add('FIELDS: ${allRecords.join(', ')}'); |
} |
@@ -250,7 +243,11 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> { |
class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H> |
implements ChangeDetector<H> { |
- DirtyCheckingChangeDetector(GetterCache getterCache): super(null, getterCache); |
+ |
+ final DirtyCheckingRecord _fakeHead = new DirtyCheckingRecord.marker(); |
+ |
+ DirtyCheckingChangeDetector(FieldGetterFactory fieldGetterFactory) |
+ : super(null, fieldGetterFactory); |
DirtyCheckingChangeDetector get _root => this; |
@@ -258,47 +255,65 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H> |
var record = this._recordHead; |
var groups = [this]; |
DirtyCheckingChangeDetectorGroup group; |
- while(groups.isNotEmpty) { |
+ while (groups.isNotEmpty) { |
group = groups.removeAt(0); |
DirtyCheckingChangeDetectorGroup childGroup = group._childTail; |
- while(childGroup != null) { |
+ while (childGroup != null) { |
groups.insert(0, childGroup); |
childGroup = childGroup._prev; |
} |
var groupRecord = group._recordHead; |
var groupLast = group._recordTail; |
- while(true) { |
+ if (record != groupRecord) { |
+ throw "Next record is $record expecting $groupRecord"; |
+ } |
+ var done = false; |
+ while (!done && groupRecord != null) { |
if (groupRecord == record) { |
+ if (record._group != null && record._group != group) { |
+ throw "Wrong group: $record " |
+ "Got ${record._group} Expecting: $group"; |
+ } |
record = record._nextRecord; |
} else { |
throw 'lost: $record found $groupRecord\n$this'; |
} |
- if (groupRecord == groupLast) break; |
+ if (groupRecord._nextRecord != null && |
+ groupRecord._nextRecord._prevRecord != groupRecord) { |
+ throw "prev/next pointer missmatch on " |
+ "$groupRecord -> ${groupRecord._nextRecord} " |
+ "<= ${groupRecord._nextRecord._prevRecord} in $this"; |
+ } |
+ if (groupRecord._prevRecord != null && |
+ groupRecord._prevRecord._nextRecord != groupRecord) { |
+ throw "prev/next pointer missmatch on " |
+ "$groupRecord -> ${groupRecord._prevRecord} " |
+ "<= ${groupRecord._prevRecord._nextChange} in $this"; |
+ } |
+ if (groupRecord == groupLast) { |
+ done = true; |
+ } |
groupRecord = groupRecord._nextRecord; |
} |
} |
+ if(record != null) { |
+ throw "Extra records at tail: $record on $this"; |
+ } |
return true; |
} |
- DirtyCheckingRecord<H> collectChanges({ EvalExceptionHandler exceptionHandler, |
- AvgStopwatch stopwatch}) { |
+ Iterator<Record<H>> collectChanges({EvalExceptionHandler exceptionHandler, |
+ AvgStopwatch stopwatch}) { |
if (stopwatch != null) stopwatch.start(); |
- DirtyCheckingRecord changeHead = null; |
- DirtyCheckingRecord changeTail = null; |
+ DirtyCheckingRecord changeTail = _fakeHead; |
DirtyCheckingRecord current = _recordHead; // current index |
int count = 0; |
while (current != null) { |
try { |
- if (current.check() != null) { |
- if (changeHead == null) { |
- changeHead = changeTail = current; |
- } else { |
- changeTail = changeTail.nextChange = current; |
- } |
- } |
- if (stopwatch != null) count++; |
+ if (current.check()) changeTail = changeTail._nextChange = current; |
+ count++; |
} catch (e, s) { |
if (exceptionHandler == null) { |
rethrow; |
@@ -308,9 +323,13 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H> |
} |
current = current._nextRecord; |
} |
- if (changeTail != null) changeTail.nextChange = null; |
+ |
+ changeTail._nextChange = null; |
if (stopwatch != null) stopwatch..stop()..increment(count); |
- return changeHead; |
+ DirtyCheckingRecord changeHead = _fakeHead._nextChange; |
+ _fakeHead._nextChange = null; |
+ |
+ return new _ChangeIterator(changeHead); |
} |
void remove() { |
@@ -318,6 +337,29 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H> |
} |
} |
+class _ChangeIterator<H> implements Iterator<Record<H>>{ |
+ DirtyCheckingRecord _current; |
+ DirtyCheckingRecord _next; |
+ DirtyCheckingRecord get current => _current; |
+ |
+ _ChangeIterator(this._next); |
+ |
+ bool moveNext() { |
+ _current = _next; |
+ if (_next != null) { |
+ _next = _current._nextChange; |
+ /* |
+ * This is important to prevent memory leaks. If we don't reset then |
+ * a record maybe pointing to a deleted change detector group and it |
+ * will not release the reference until it fires again. So we have |
+ * to be eager about releasing references. |
+ */ |
+ _current._nextChange = null; |
+ } |
+ return _current != null; |
+ } |
+} |
+ |
/** |
* [DirtyCheckingRecord] represents as single item to check. The heart of the |
* [DirtyCheckingRecord] is a the [check] method which can read the |
@@ -327,21 +369,19 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H> |
* removing efficient. [DirtyCheckingRecord] also has a [nextChange] field which |
* creates a single linked list of all of the changes for efficient traversal. |
*/ |
-class DirtyCheckingRecord<H> implements ChangeRecord<H>, WatchRecord<H> { |
+class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> { |
static const List<String> _MODE_NAMES = |
- const ['MARKER', 'IDENT', 'REFLECT', 'GETTER', 'MAP[]', 'ITERABLE', 'MAP']; |
+ const ['MARKER', 'IDENT', 'GETTER', 'MAP[]', 'ITERABLE', 'MAP']; |
static const int _MODE_MARKER_ = 0; |
static const int _MODE_IDENTITY_ = 1; |
- static const int _MODE_REFLECT_ = 2; |
- static const int _MODE_GETTER_ = 3; |
- static const int _MODE_MAP_FIELD_ = 4; |
- static const int _MODE_ITERABLE_ = 5; |
- static const int _MODE_MAP_ = 6; |
+ static const int _MODE_GETTER_ = 2; |
+ static const int _MODE_MAP_FIELD_ = 3; |
+ static const int _MODE_ITERABLE_ = 4; |
+ static const int _MODE_MAP_ = 5; |
final DirtyCheckingChangeDetectorGroup _group; |
+ final FieldGetterFactory _fieldGetterFactory; |
final String field; |
- final Symbol _symbol; |
- final FieldGetter _getter; |
final H handler; |
int _mode; |
@@ -350,53 +390,65 @@ class DirtyCheckingRecord<H> implements ChangeRecord<H>, WatchRecord<H> { |
var currentValue; |
DirtyCheckingRecord<H> _nextRecord; |
DirtyCheckingRecord<H> _prevRecord; |
- ChangeRecord<H> nextChange; |
+ Record<H> _nextChange; |
var _object; |
- InstanceMirror _instanceMirror; |
+ FieldGetter _getter; |
- DirtyCheckingRecord(this._group, object, fieldName, this._getter, this.handler) |
- : field = fieldName, |
- _symbol = fieldName == null ? null : new Symbol(fieldName) |
- { |
- this.object = object; |
+ DirtyCheckingRecord(this._group, this._fieldGetterFactory, this.handler, |
+ this.field, _object) { |
+ object = _object; |
} |
DirtyCheckingRecord.marker() |
- : handler = null, |
+ : _group = null, |
+ _fieldGetterFactory = null, |
+ handler = null, |
field = null, |
- _group = null, |
- _symbol = null, |
_getter = null, |
_mode = _MODE_MARKER_; |
- get object => _object; |
+ dynamic get object => _object; |
/** |
* Setting an [object] will cause the setter to introspect it and place |
* [DirtyCheckingRecord] into different access modes. If Object it sets up |
* reflection. If [Map] then it sets up map accessor. |
*/ |
- set object(obj) { |
+ void set object(obj) { |
_object = obj; |
if (obj == null) { |
_mode = _MODE_IDENTITY_; |
+ _getter = null; |
return; |
} |
if (field == null) { |
- _instanceMirror = null; |
+ _getter = null; |
if (obj is Map) { |
if (_mode != _MODE_MAP_) { |
- // Last one was collection as well, don't reset state. |
_mode = _MODE_MAP_; |
currentValue = new _MapChangeRecord(); |
} |
+ if (currentValue.isDirty) { |
+ // We're dirty because the mapping we tracked by reference mutated. |
+ // In addition, our reference has now changed. We should compare the |
+ // previous reported value of that mapping with the one from the |
+ // new reference. |
+ currentValue._revertToPreviousState(); |
+ } |
+ |
} else if (obj is Iterable) { |
if (_mode != _MODE_ITERABLE_) { |
- // Last one was collection as well, don't reset state. |
- _mode = _MODE_ITERABLE_; |
+ _mode = _MODE_ITERABLE_; |
currentValue = new _CollectionChangeRecord(); |
} |
+ if (currentValue.isDirty) { |
+ // We're dirty because the collection we tracked by reference mutated. |
+ // In addition, our reference has now changed. We should compare the |
+ // previous reported value of that collection with the one from the |
+ // new reference. |
+ currentValue._revertToPreviousState(); |
+ } |
} else { |
_mode = _MODE_IDENTITY_; |
} |
@@ -406,25 +458,26 @@ class DirtyCheckingRecord<H> implements ChangeRecord<H>, WatchRecord<H> { |
if (obj is Map) { |
_mode = _MODE_MAP_FIELD_; |
- _instanceMirror = null; |
- } else if (_getter != null) { |
- _mode = _MODE_GETTER_; |
- _instanceMirror = null; |
+ _getter = null; |
} else { |
- _mode = _MODE_REFLECT_; |
- _instanceMirror = reflect(obj); |
+ if (_fieldGetterFactory.isMethod(obj, field)) { |
+ _mode = _MODE_IDENTITY_; |
+ previousValue = currentValue = _fieldGetterFactory.method(obj, field)(obj); |
+ assert(previousValue is Function); |
+ } else { |
+ _mode = _MODE_GETTER_; |
+ _getter = _fieldGetterFactory.getter(obj, field); |
+ currentValue = _getter(obj); |
+ } |
} |
} |
- ChangeRecord<H> check() { |
+ bool check() { |
assert(_mode != null); |
var current; |
switch (_mode) { |
case _MODE_MARKER_: |
- return null; |
- case _MODE_REFLECT_: |
- current = _instanceMirror.getField(_symbol).reflectee; |
- break; |
+ return false; |
case _MODE_GETTER_: |
current = _getter(object); |
break; |
@@ -435,9 +488,9 @@ class DirtyCheckingRecord<H> implements ChangeRecord<H>, WatchRecord<H> { |
current = object; |
break; |
case _MODE_MAP_: |
- return (currentValue as _MapChangeRecord)._check(object) ? this : null; |
+ return (currentValue as _MapChangeRecord)._check(object); |
case _MODE_ITERABLE_: |
- return (currentValue as _CollectionChangeRecord)._check(object) ? this : null; |
+ return (currentValue as _CollectionChangeRecord)._check(object); |
default: |
assert(false); |
} |
@@ -454,10 +507,10 @@ class DirtyCheckingRecord<H> implements ChangeRecord<H>, WatchRecord<H> { |
} else { |
previousValue = last; |
currentValue = current; |
- return this; |
+ return true; |
} |
} |
- return null; |
+ return false; |
} |
@@ -471,47 +524,70 @@ class DirtyCheckingRecord<H> implements ChangeRecord<H>, WatchRecord<H> { |
final Object _INITIAL_ = new Object(); |
class _MapChangeRecord<K, V> implements MapChangeRecord<K, V> { |
- final Map<dynamic, KeyValueRecord> _records = new Map<dynamic, KeyValueRecord>(); |
+ final _records = new Map<dynamic, KeyValueRecord>(); |
Map _map; |
- KeyValueRecord _mapHead; |
- KeyValueRecord _changesHead, _changesTail; |
- KeyValueRecord _additionsHead, _additionsTail; |
- KeyValueRecord _removalsHead, _removalsTail; |
Map get map => _map; |
- KeyValue<K, V> get mapHead => _mapHead; |
- ChangedKeyValue<K, V> get changesHead => _changesHead; |
- AddedKeyValue<K, V> get additionsHead => _additionsHead; |
- RemovedKeyValue<K, V> get removalsHead => _removalsHead; |
+ |
+ KeyValueRecord<K, V> _mapHead; |
+ KeyValueRecord<K, V> _previousMapHead; |
+ KeyValueRecord<K, V> _changesHead, _changesTail; |
+ KeyValueRecord<K, V> _additionsHead, _additionsTail; |
+ KeyValueRecord<K, V> _removalsHead, _removalsTail; |
get isDirty => _additionsHead != null || |
_changesHead != null || |
_removalsHead != null; |
- void forEachChange(void f(ChangedKeyValue<K, V> change)) { |
- KeyValueRecord record = _changesHead; |
- while(record != null) { |
- f(record); |
- record = record._nextChangedKeyValue; |
+ _revertToPreviousState() { |
+ if (!isDirty) { |
+ return; |
+ } |
+ KeyValueRecord record, prev; |
+ int i = 0; |
+ for (record = _mapHead = _previousMapHead; |
+ record != null; |
+ prev = record, record = record._nextPrevious, ++i) { |
+ record._currentValue = record._previousValue; |
+ if (prev != null) { |
+ prev._next = prev._nextPrevious = record; |
+ } |
+ } |
+ prev._next = null; |
+ _undoDeltas(); |
+ } |
+ |
+ KeyValueRecord<K, V> r; |
+ |
+ void forEachItem(void f(MapKeyValue<K, V> change)) { |
+ for (r = _mapHead; r != null; r = r._next) { |
+ f(r); |
+ } |
+ } |
+ |
+ void forEachPreviousItem(void f(MapKeyValue<K, V> change)) { |
+ for (r = _previousMapHead; r != null; r = r._nextPrevious) { |
+ f(r); |
} |
} |
- void forEachAddition(void f(AddedKeyValue<K, V> addition)){ |
- KeyValueRecord record = _additionsHead; |
- while(record != null) { |
- f(record); |
- record = record._nextAddedKeyValue; |
+ void forEachChange(void f(MapKeyValue<K, V> change)) { |
+ for (r = _changesHead; r != null; r = r._nextChanged) { |
+ f(r); |
} |
} |
- void forEachRemoval(void f(RemovedKeyValue<K, V> removal)){ |
- KeyValueRecord record = _removalsHead; |
- while(record != null) { |
- f(record); |
- record = record._nextRemovedKeyValue; |
+ void forEachAddition(void f(MapKeyValue<K, V> addition)){ |
+ for (r = _additionsHead; r != null; r = r._nextAdded) { |
+ f(r); |
} |
} |
+ void forEachRemoval(void f(MapKeyValue<K, V> removal)){ |
+ for (r = _removalsHead; r != null; r = r._nextRemoved) { |
+ f(r); |
+ } |
+ } |
bool _check(Map map) { |
_reset(); |
@@ -537,7 +613,7 @@ class _MapChangeRecord<K, V> implements MapChangeRecord<K, V> { |
} else { |
seqChanged = true; |
if (oldSeqRecord != null) { |
- oldSeqRecord._nextKeyValue = null; |
+ oldSeqRecord._next = null; |
_removeFromSeq(lastOldSeqRecord, oldSeqRecord); |
_addToRemovals(oldSeqRecord); |
} |
@@ -557,50 +633,60 @@ class _MapChangeRecord<K, V> implements MapChangeRecord<K, V> { |
if (lastNewSeqRecord == null) { |
_mapHead = newSeqRecord; |
} else { |
- lastNewSeqRecord._nextKeyValue = newSeqRecord; |
+ lastNewSeqRecord._next = newSeqRecord; |
} |
} |
lastOldSeqRecord = oldSeqRecord; |
lastNewSeqRecord = newSeqRecord; |
- oldSeqRecord = oldSeqRecord == null ? null : oldSeqRecord._nextKeyValue; |
+ oldSeqRecord = oldSeqRecord == null ? null : oldSeqRecord._next; |
}); |
_truncate(lastOldSeqRecord, oldSeqRecord); |
return isDirty; |
} |
void _reset() { |
- var record = _changesHead; |
- while (record != null) { |
- record._previousValue = record._currentValue; |
- record = record._nextChangedKeyValue; |
+ if (isDirty) { |
+ // Record the state of the mapping for a possible _revertToPreviousState() |
+ for (KeyValueRecord record = _previousMapHead = _mapHead; |
+ record != null; |
+ record = record._next) { |
+ record._nextPrevious = record._next; |
+ } |
+ _undoDeltas(); |
} |
+ } |
- record = _additionsHead; |
- while (record != null) { |
- record._previousValue = record._currentValue; |
- record = record._nextAddedKeyValue; |
+ void _undoDeltas() { |
+ KeyValueRecord<K, V> r; |
+ |
+ for (r = _changesHead; r != null; r = r._nextChanged) { |
+ r._previousValue = r._currentValue; |
+ } |
+ |
+ for (r = _additionsHead; r != null; r = r._nextAdded) { |
+ r._previousValue = r._currentValue; |
} |
assert((() { |
- var record = _changesHead; |
- while (record != null) { |
- var nextRecord = record._nextChangedKeyValue; |
- record._nextChangedKeyValue = null; |
- record = nextRecord; |
+ var r = _changesHead; |
+ while (r != null) { |
+ var nextRecord = r._nextChanged; |
+ r._nextChanged = null; |
+ r = nextRecord; |
} |
- record = _additionsHead; |
- while (record != null) { |
- var nextRecord = record._nextAddedKeyValue; |
- record._nextAddedKeyValue = null; |
- record = nextRecord; |
+ r = _additionsHead; |
+ while (r != null) { |
+ var nextRecord = r._nextAdded; |
+ r._nextAdded = null; |
+ r = nextRecord; |
} |
- record = _removalsHead; |
- while (record != null) { |
- var nextRecord = record._nextRemovedKeyValue; |
- record._nextRemovedKeyValue = null; |
- record = nextRecord; |
+ r = _removalsHead; |
+ while (r != null) { |
+ var nextRecord = r._nextRemoved; |
+ r._nextRemoved = null; |
+ r = nextRecord; |
} |
return true; |
@@ -611,15 +697,15 @@ class _MapChangeRecord<K, V> implements MapChangeRecord<K, V> { |
} |
void _truncate(KeyValueRecord lastRecord, KeyValueRecord record) { |
- while(record != null) { |
+ while (record != null) { |
if (lastRecord == null) { |
_mapHead = null; |
} else { |
- lastRecord._nextKeyValue = null; |
+ lastRecord._next = null; |
} |
- var nextRecord = record._nextKeyValue; |
+ var nextRecord = record._next; |
assert((() { |
- record._nextKeyValue = null; |
+ record._next = null; |
return true; |
})()); |
_addToRemovals(record); |
@@ -627,178 +713,225 @@ class _MapChangeRecord<K, V> implements MapChangeRecord<K, V> { |
record = nextRecord; |
} |
- record = _removalsHead; |
- while (record != null) { |
- record._previousValue = record._currentValue; |
- record._currentValue = null; |
- _records.remove(record.key); |
- record = record._nextRemovedKeyValue; |
+ for (var r = _removalsHead; r != null; r = r._nextRemoved) { |
+ r._previousValue = r._currentValue; |
+ r._currentValue = null; |
+ _records.remove(r.key); |
} |
} |
bool _isInRemovals(KeyValueRecord record) => |
record == _removalsHead || |
- record._nextRemovedKeyValue != null || |
- record._prevRemovedKeyValue != null; |
+ record._nextRemoved != null || |
+ record._prevRemoved != null; |
void _addToRemovals(KeyValueRecord record) { |
- assert(record._nextKeyValue == null); |
- assert(record._nextAddedKeyValue == null); |
- assert(record._nextChangedKeyValue == null); |
- assert(record._nextRemovedKeyValue == null); |
- assert(record._prevRemovedKeyValue == null); |
+ assert(record._next == null); |
+ assert(record._nextAdded == null); |
+ assert(record._nextChanged == null); |
+ assert(record._nextRemoved == null); |
+ assert(record._prevRemoved == null); |
if (_removalsHead == null) { |
_removalsHead = _removalsTail = record; |
} else { |
- _removalsTail._nextRemovedKeyValue = record; |
- record._prevRemovedKeyValue = _removalsTail; |
+ _removalsTail._nextRemoved = record; |
+ record._prevRemoved = _removalsTail; |
_removalsTail = record; |
} |
} |
void _removeFromSeq(KeyValueRecord prev, KeyValueRecord record) { |
- KeyValueRecord next = record._nextKeyValue; |
+ KeyValueRecord next = record._next; |
if (prev == null) { |
_mapHead = next; |
} else { |
- prev._nextKeyValue = next; |
+ prev._next = next; |
} |
assert((() { |
- record._nextKeyValue = null; |
+ record._next = null; |
return true; |
})()); |
} |
void _removeFromRemovals(KeyValueRecord record) { |
- assert(record._nextKeyValue == null); |
- assert(record._nextAddedKeyValue == null); |
- assert(record._nextChangedKeyValue == null); |
+ assert(record._next == null); |
+ assert(record._nextAdded == null); |
+ assert(record._nextChanged == null); |
- var prev = record._prevRemovedKeyValue; |
- var next = record._nextRemovedKeyValue; |
+ var prev = record._prevRemoved; |
+ var next = record._nextRemoved; |
if (prev == null) { |
_removalsHead = next; |
} else { |
- prev._nextRemovedKeyValue = next; |
+ prev._nextRemoved = next; |
} |
if (next == null) { |
_removalsTail = prev; |
} else { |
- next._prevRemovedKeyValue = prev; |
+ next._prevRemoved = prev; |
} |
- record._prevRemovedKeyValue = record._nextRemovedKeyValue = null; |
+ record._prevRemoved = record._nextRemoved = null; |
} |
void _addToAdditions(KeyValueRecord record) { |
- assert(record._nextKeyValue == null); |
- assert(record._nextAddedKeyValue == null); |
- assert(record._nextChangedKeyValue == null); |
- assert(record._nextRemovedKeyValue == null); |
- assert(record._prevRemovedKeyValue == null); |
+ assert(record._next == null); |
+ assert(record._nextAdded == null); |
+ assert(record._nextChanged == null); |
+ assert(record._nextRemoved == null); |
+ assert(record._prevRemoved == null); |
if (_additionsHead == null) { |
_additionsHead = _additionsTail = record; |
} else { |
- _additionsTail._nextAddedKeyValue = record; |
+ _additionsTail._nextAdded = record; |
_additionsTail = record; |
} |
} |
void _addToChanges(KeyValueRecord record) { |
- assert(record._nextAddedKeyValue == null); |
- assert(record._nextChangedKeyValue == null); |
- assert(record._nextRemovedKeyValue == null); |
- assert(record._prevRemovedKeyValue == null); |
+ assert(record._nextAdded == null); |
+ assert(record._nextChanged == null); |
+ assert(record._nextRemoved == null); |
+ assert(record._prevRemoved == null); |
if (_changesHead == null) { |
_changesHead = _changesTail = record; |
} else { |
- _changesTail._nextChangedKeyValue = record; |
+ _changesTail._nextChanged = record; |
_changesTail = record; |
} |
} |
+ |
+ String toString() { |
+ List itemsList = [], previousList = [], changesList = [], additionsList = [], removalsList = []; |
+ KeyValueRecord<K, V> r; |
+ for (r = _mapHead; r != null; r = r._next) { |
+ itemsList.add("$r"); |
+ } |
+ for (r = _previousMapHead; r != null; r = r._nextPrevious) { |
+ previousList.add("$r"); |
+ } |
+ for (r = _changesHead; r != null; r = r._nextChanged) { |
+ changesList.add("$r"); |
+ } |
+ for (r = _additionsHead; r != null; r = r._nextAdded) { |
+ additionsList.add("$r"); |
+ } |
+ for (r = _removalsHead; r != null; r = r._nextRemoved) { |
+ removalsList.add("$r"); |
+ } |
+ return """ |
+map: ${itemsList.join(", ")} |
+previous: ${previousList.join(", ")} |
+changes: ${changesList.join(", ")} |
+additions: ${additionsList.join(", ")} |
+removals: ${removalsList.join(", ")} |
+"""; |
+ } |
} |
-class KeyValueRecord<K, V> implements KeyValue<K, V>, AddedKeyValue<K, V>, |
- RemovedKeyValue<K, V>, ChangedKeyValue<K, V> { |
+class KeyValueRecord<K, V> implements MapKeyValue<K, V> { |
final K key; |
V _previousValue, _currentValue; |
- KeyValueRecord<K, V> _nextKeyValue; |
- KeyValueRecord<K, V> _nextAddedKeyValue; |
- KeyValueRecord<K, V> _nextRemovedKeyValue, _prevRemovedKeyValue; |
- KeyValueRecord<K, V> _nextChangedKeyValue; |
- |
- KeyValueRecord(this.key); |
- |
V get previousValue => _previousValue; |
V get currentValue => _currentValue; |
- KeyValue<K, V> get nextKeyValue => _nextKeyValue; |
- AddedKeyValue<K, V> get nextAddedKeyValue => _nextAddedKeyValue; |
- RemovedKeyValue<K, V> get nextRemovedKeyValue => _nextRemovedKeyValue; |
- ChangedKeyValue<K, V> get nextChangedKeyValue => _nextChangedKeyValue; |
+ |
+ KeyValueRecord<K, V> _nextPrevious; |
+ KeyValueRecord<K, V> _next; |
+ KeyValueRecord<K, V> _nextAdded; |
+ KeyValueRecord<K, V> _nextRemoved, _prevRemoved; |
+ KeyValueRecord<K, V> _nextChanged; |
+ |
+ KeyValueRecord(this.key); |
String toString() => _previousValue == _currentValue |
- ? key |
+ ? "$key" |
: '$key[$_previousValue -> $_currentValue]'; |
} |
- |
class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> { |
Iterable _iterable; |
- /** Used to keep track of items during moves. */ |
- DuplicateMap _items = new DuplicateMap(); |
+ int _length; |
+ |
+ /// Keeps track of moved items. |
+ DuplicateMap _movedItems = new DuplicateMap(); |
- /** Used to keep track of removed items. */ |
+ /// Keeps track of removed items. |
DuplicateMap _removedItems = new DuplicateMap(); |
- ItemRecord<V> _collectionHead, _collectionTail; |
+ ItemRecord<V> _previousItHead; |
+ ItemRecord<V> _itHead, _itTail; |
ItemRecord<V> _additionsHead, _additionsTail; |
ItemRecord<V> _movesHead, _movesTail; |
ItemRecord<V> _removalsHead, _removalsTail; |
- CollectionChangeItem<V> get collectionHead => _collectionHead; |
- CollectionChangeItem<V> get additionsHead => _additionsHead; |
- CollectionChangeItem<V> get movesHead => _movesHead; |
- CollectionChangeItem<V> get removalsHead => _removalsHead; |
+ void _revertToPreviousState() { |
+ if (!isDirty) return; |
+ |
+ _movedItems.clear(); |
+ ItemRecord<V> prev; |
+ int i = 0; |
+ |
+ for (ItemRecord<V> record = _itHead = _previousItHead; |
+ record != null; |
+ prev = record, record = record._nextPrevious, i++) { |
+ record.currentIndex = record.previousIndex = i; |
+ record._prev = prev; |
+ if (prev != null) prev._next = prev._nextPrevious = record; |
+ _movedItems.put(record); |
+ } |
+ |
+ prev._next = null; |
+ _itTail = prev; |
+ _undoDeltas(); |
+ } |
+ |
+ void forEachItem(void f(CollectionChangeItem<V> item)) { |
+ for (var r = _itHead; r != null; r = r._next) { |
+ f(r); |
+ } |
+ } |
+ |
+ void forEachPreviousItem(void f(CollectionChangeItem<V> previousItem)) { |
+ for (var r = _previousItHead; r != null; r = r._nextPrevious) { |
+ f(r); |
+ } |
+ } |
- void forEachAddition(void f(AddedItem<V> addition)){ |
- ItemRecord record = _additionsHead; |
- while(record != null) { |
- f(record); |
- record = record._nextAddedRec; |
+ void forEachAddition(void f(CollectionChangeItem<V> addition)){ |
+ for (var r = _additionsHead; r != null; r = r._nextAdded) { |
+ f(r); |
} |
} |
- void forEachMove(void f(MovedItem<V> change)) { |
- ItemRecord record = _movesHead; |
- while(record != null) { |
- f(record); |
- record = record._nextMovedRec; |
+ void forEachMove(void f(CollectionChangeItem<V> change)) { |
+ for (var r = _movesHead; r != null; r = r._nextMoved) { |
+ f(r); |
} |
} |
- void forEachRemoval(void f(RemovedItem<V> removal)){ |
- ItemRecord record = _removalsHead; |
- while(record != null) { |
- f(record); |
- record = record._nextRemovedRec; |
+ void forEachRemoval(void f(CollectionChangeItem<V> removal)){ |
+ for (var r = _removalsHead; r != null; r = r._nextRemoved) { |
+ f(r); |
} |
} |
Iterable get iterable => _iterable; |
+ int get length => _length; |
bool _check(Iterable collection) { |
_reset(); |
- ItemRecord record = _collectionHead; |
- bool maybeDirty = false; |
- if ((collection is UnmodifiableListView) && |
- identical(_iterable, collection)) { |
+ if (collection is UnmodifiableListView && identical(_iterable, collection)) { |
// Short circuit and assume that the list has not been modified. |
return false; |
} |
+ ItemRecord<V> record = _itHead; |
+ bool maybeDirty = false; |
+ |
if (collection is List) { |
List list = collection; |
- for (int index = 0; index < list.length; index++) { |
+ _length = list.length; |
+ for (int index = 0; index < _length; index++) { |
var item = list[index]; |
if (record == null || !identical(item, record.item)) { |
record = mismatch(record, item, index); |
@@ -807,7 +940,7 @@ class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> { |
// TODO(misko): can we limit this to duplicates only? |
record = verifyReinsertion(record, item, index); |
} |
- record = record._nextRec; |
+ record = record._next; |
} |
} else { |
int index = 0; |
@@ -819,9 +952,10 @@ class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> { |
// TODO(misko): can we limit this to duplicates only? |
record = verifyReinsertion(record, item, index); |
} |
- record = record._nextRec; |
+ record = record._next; |
index++; |
} |
+ _length = index; |
} |
_truncate(record); |
@@ -835,20 +969,32 @@ class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> { |
* removals). |
*/ |
void _reset() { |
- ItemRecord record; |
+ if (isDirty) { |
+ // Record the state of the collection for a possible _revertToPreviousState() |
+ for (ItemRecord<V> record = _previousItHead = _itHead; |
+ record != null; |
+ record = record._next) { |
+ record._nextPrevious = record._next; |
+ } |
+ _undoDeltas(); |
+ } |
+ } |
+ |
+ void _undoDeltas() { |
+ ItemRecord<V> record; |
record = _additionsHead; |
- while(record != null) { |
+ while (record != null) { |
record.previousIndex = record.currentIndex; |
- record = record._nextAddedRec; |
+ record = record._nextAdded; |
} |
_additionsHead = _additionsTail = null; |
record = _movesHead; |
- while(record != null) { |
+ while (record != null) { |
record.previousIndex = record.currentIndex; |
- var nextRecord = record._nextMovedRec; |
- assert((record._nextMovedRec = null) == null); |
+ var nextRecord = record._nextMoved; |
+ assert((record._nextMoved = null) == null); |
record = nextRecord; |
} |
_movesHead = _movesTail = null; |
@@ -860,20 +1006,19 @@ class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> { |
* A [_CollectionChangeRecord] is considered dirty if it has additions, moves |
* or removals. |
*/ |
- get isDirty => _additionsHead != null || |
- _movesHead != null || |
- _removalsHead != null; |
+ bool get isDirty => _additionsHead != null || |
+ _movesHead != null || |
+ _removalsHead != null; |
/** |
* This is the core function which handles differences between collections. |
* |
- * - [record] is the record which we saw at this position last time. If `null` |
- * then it is a new item. |
+ * - [record] is the record which we saw at this position last time. If |
+ * [:null:] then it is a new item. |
* - [item] is the current item in the collection |
* - [index] is the position of the item in the collection |
*/ |
- ItemRecord mismatch(ItemRecord record, item, int index) { |
- // Guard against bogus String changes |
+ ItemRecord<V> mismatch(ItemRecord<V> record, item, int index) { |
if (record != null) { |
if (item is String && record.item is String && record.item == item) { |
// this is false change in strings we need to recover, and pretend it is |
@@ -881,20 +1026,20 @@ class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> { |
return record..item = item; |
} |
- if (item is num && item.isNaN && record.item is num && record.item.isNaN){ |
+ if (item is num && (item as num).isNaN && record.item is num && (record.item as num).isNaN){ |
// we need this for JavaScript since in JS NaN !== NaN. |
return record; |
} |
} |
// find the previous record so that we know where to insert after. |
- ItemRecord prev = record == null ? _collectionTail : record._prevRec; |
+ ItemRecord<V> prev = record == null ? _itTail : record._prev; |
// Remove the record from the collection since we know it does not match the |
// item. |
if (record != null) _collection_remove(record); |
// Attempt to see if we have seen the item before. |
- record = _items.get(item, index); |
+ record = _movedItems.get(item, index); |
if (record != null) { |
// We have seen this before, we need to move it forward in the collection. |
_collection_moveAfter(record, prev, index); |
@@ -907,7 +1052,7 @@ class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> { |
_collection_reinsertAfter(record, prev, index); |
} else { |
// It is a new item add it. |
- record = _collection_addAfter(new ItemRecord(item), prev, index); |
+ record = _collection_addAfter(new ItemRecord<V>(item), prev, index); |
} |
} |
return record; |
@@ -939,11 +1084,11 @@ class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> { |
* position. This is incorrect, since a better way to think of it is as insert |
* of 'b' rather then switch 'a' with 'b' and then add 'a' at the end. |
*/ |
- ItemRecord verifyReinsertion(ItemRecord record, dynamic item, |
+ ItemRecord<V> verifyReinsertion(ItemRecord record, dynamic item, |
int index) { |
- ItemRecord reinsertRecord = _removedItems.get(item); |
+ ItemRecord<V> reinsertRecord = _removedItems.get(item); |
if (reinsertRecord != null) { |
- record = _collection_reinsertAfter(reinsertRecord, record._prevRec, index); |
+ record = _collection_reinsertAfter(reinsertRecord, record._prev, index); |
} else if (record.currentIndex != index) { |
record.currentIndex = index; |
_moves_add(record); |
@@ -956,34 +1101,40 @@ class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> { |
* |
* - [record] The first excess [ItemRecord]. |
*/ |
- void _truncate(ItemRecord record) { |
+ void _truncate(ItemRecord<V> record) { |
// Anything after that needs to be removed; |
- while(record != null) { |
- ItemRecord nextRecord = record._nextRec; |
+ while (record != null) { |
+ ItemRecord<V> nextRecord = record._next; |
_removals_add(_collection_unlink(record)); |
record = nextRecord; |
} |
_removedItems.clear(); |
+ |
+ if (_additionsTail != null) _additionsTail._nextAdded = null; |
+ if (_movesTail != null) _movesTail._nextMoved = null; |
+ if (_itTail != null) _itTail._next = null; |
+ if (_removalsTail != null) _removalsTail._nextRemoved = null; |
} |
- ItemRecord _collection_reinsertAfter(ItemRecord record, ItemRecord insertPrev, |
- int index) { |
+ ItemRecord<V> _collection_reinsertAfter(ItemRecord<V> record, |
+ ItemRecord<V> insertPrev, |
+ int index) { |
_removedItems.remove(record); |
- var prev = record._prevRemovedRec; |
- var next = record._nextRemovedRec; |
+ var prev = record._prevRemoved; |
+ var next = record._nextRemoved; |
- assert((record._prevRemovedRec = null) == null); |
- assert((record._nextRemovedRec = null) == null); |
+ assert((record._prevRemoved = null) == null); |
+ assert((record._nextRemoved = null) == null); |
if (prev == null) { |
_removalsHead = next; |
} else { |
- prev._nextRemovedRec = next; |
+ prev._nextRemoved = next; |
} |
if (next == null) { |
_removalsTail = prev; |
} else { |
- next._prevRemovedRec = prev; |
+ next._prevRemoved = prev; |
} |
_collection_insertAfter(record, insertPrev, index); |
@@ -991,96 +1142,99 @@ class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> { |
return record; |
} |
- ItemRecord _collection_moveAfter(ItemRecord record, ItemRecord prev, |
- int index) { |
+ ItemRecord<V> _collection_moveAfter(ItemRecord<V> record, |
+ ItemRecord<V> prev, |
+ int index) { |
_collection_unlink(record); |
_collection_insertAfter(record, prev, index); |
_moves_add(record); |
return record; |
} |
- ItemRecord _collection_addAfter(ItemRecord record, ItemRecord prev, |
- int index) { |
+ ItemRecord<V> _collection_addAfter(ItemRecord<V> record, |
+ ItemRecord<V> prev, |
+ int index) { |
_collection_insertAfter(record, prev, index); |
if (_additionsTail == null) { |
assert(_additionsHead == null); |
_additionsTail = _additionsHead = record; |
} else { |
- assert(_additionsTail._nextAddedRec == null); |
- assert(record._nextAddedRec == null); |
- _additionsTail = _additionsTail._nextAddedRec = record; |
+ assert(_additionsTail._nextAdded == null); |
+ assert(record._nextAdded == null); |
+ _additionsTail = _additionsTail._nextAdded = record; |
} |
return record; |
} |
- ItemRecord _collection_insertAfter(ItemRecord record, ItemRecord prev, |
- int index) { |
+ ItemRecord<V> _collection_insertAfter(ItemRecord<V> record, |
+ ItemRecord<V> prev, |
+ int index) { |
assert(record != prev); |
- assert(record._nextRec == null); |
- assert(record._prevRec == null); |
+ assert(record._next == null); |
+ assert(record._prev == null); |
- ItemRecord next = prev == null ? _collectionHead : prev._nextRec; |
+ ItemRecord<V> next = prev == null ? _itHead : prev._next; |
assert(next != record); |
assert(prev != record); |
- record._nextRec = next; |
- record._prevRec = prev; |
+ record._next = next; |
+ record._prev = prev; |
if (next == null) { |
- _collectionTail = record; |
+ _itTail = record; |
} else { |
- next._prevRec = record; |
+ next._prev = record; |
} |
if (prev == null) { |
- _collectionHead = record; |
+ _itHead = record; |
} else { |
- prev._nextRec = record; |
+ prev._next = record; |
} |
- _items.put(record); |
+ _movedItems.put(record); |
record.currentIndex = index; |
return record; |
} |
- ItemRecord _collection_remove(ItemRecord record) => |
+ ItemRecord<V> _collection_remove(ItemRecord record) => |
_removals_add(_collection_unlink(record)); |
- ItemRecord _collection_unlink(ItemRecord record) { |
- _items.remove(record); |
+ ItemRecord<V> _collection_unlink(ItemRecord record) { |
+ _movedItems.remove(record); |
- var prev = record._prevRec; |
- var next = record._nextRec; |
+ var prev = record._prev; |
+ var next = record._next; |
- assert((record._prevRec = null) == null); |
- assert((record._nextRec = null) == null); |
+ assert((record._prev = null) == null); |
+ assert((record._next = null) == null); |
if (prev == null) { |
- _collectionHead = next; |
+ _itHead = next; |
} else { |
- prev._nextRec = next; |
+ prev._next = next; |
} |
if (next == null) { |
- _collectionTail = prev; |
+ _itTail = prev; |
} else { |
- next._prevRec = prev; |
+ next._prev = prev; |
} |
return record; |
} |
- ItemRecord _moves_add(ItemRecord record) { |
- assert(record._nextMovedRec == null); |
+ ItemRecord<V> _moves_add(ItemRecord<V> record) { |
+ assert(record._nextMoved == null); |
if (_movesTail == null) { |
assert(_movesHead == null); |
_movesTail = _movesHead = record; |
} else { |
- assert(_movesTail._nextMovedRec == null); |
- _movesTail = _movesTail._nextMovedRec = record; |
+ assert(_movesTail._nextMoved == null); |
+ _movesTail = _movesTail._nextMoved = record; |
} |
return record; |
} |
- ItemRecord _removals_add(ItemRecord record) { |
+ ItemRecord<V> _removals_add(ItemRecord<V> record) { |
record.currentIndex = null; |
_removedItems.put(record); |
@@ -1088,69 +1242,62 @@ class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> { |
assert(_removalsHead == null); |
_removalsTail = _removalsHead = record; |
} else { |
- assert(_removalsTail._nextRemovedRec == null); |
- assert(record._nextRemovedRec == null); |
- record._prevRemovedRec = _removalsTail; |
- _removalsTail = _removalsTail._nextRemovedRec = record; |
+ assert(_removalsTail._nextRemoved == null); |
+ assert(record._nextRemoved == null); |
+ record._prevRemoved = _removalsTail; |
+ _removalsTail = _removalsTail._nextRemoved = record; |
} |
return record; |
} |
String toString() { |
- ItemRecord record; |
+ ItemRecord<V> r; |
var list = []; |
- record = _collectionHead; |
- while(record != null) { |
- list.add(record); |
- record = record._nextRec; |
+ for (r = _itHead; r != null; r = r._next) { |
+ list.add(r); |
} |
- var additions = []; |
- record = _additionsHead; |
- while(record != null) { |
- additions.add(record); |
- record = record._nextAddedRec; |
+ var previous = []; |
+ for (r = _previousItHead; r != null; r = r._nextPrevious) { |
+ previous.add(r); |
} |
+ var additions = []; |
+ for (r = _additionsHead; r != null; r = r._nextAdded) { |
+ additions.add(r); |
+ } |
var moves = []; |
- record = _movesHead; |
- while(record != null) { |
- moves.add(record); |
- record = record._nextMovedRec; |
+ for (r = _movesHead; r != null; r = r._nextMoved) { |
+ moves.add(r); |
} |
var removals = []; |
- record = _removalsHead; |
- while(record != null) { |
- removals.add(record); |
- record = record._nextRemovedRec; |
+ for (r = _removalsHead; r != null; r = r._nextRemoved) { |
+ removals.add(r); |
} |
return """ |
collection: ${list.join(", ")} |
+previous: ${previous.join(", ")} |
additions: ${additions.join(", ")} |
moves: ${moves.join(", ")} |
-removals: ${removals.join(", ")}' |
- """; |
+removals: ${removals.join(", ")} |
+"""; |
} |
} |
-class ItemRecord<V> implements CollectionItem<V>, AddedItem<V>, MovedItem<V>, |
- RemovedItem<V> { |
- int previousIndex = null; |
- int currentIndex = null; |
- V item = _INITIAL_; |
- |
- ItemRecord<V> _prevRec, _nextRec; |
- ItemRecord<V> _prevDupRec, _nextDupRec; |
- ItemRecord<V> _prevRemovedRec, _nextRemovedRec; |
- ItemRecord<V> _nextAddedRec, _nextMovedRec; |
+class ItemRecord<V> extends CollectionChangeItem<V> { |
+ int currentIndex; |
+ int previousIndex; |
+ V item; |
- CollectionItem<V> get nextCollectionItem => _nextRec; |
- RemovedItem<V> get nextRemovedItem => _nextRemovedRec; |
- AddedItem<V> get nextAddedItem => _nextAddedRec; |
- MovedItem<V> get nextMovedItem => _nextMovedRec; |
+ ItemRecord<V> _nextPrevious; |
+ ItemRecord<V> _prev, _next; |
+ ItemRecord<V> _prevDup, _nextDup; |
+ ItemRecord<V> _prevRemoved, _nextRemoved; |
+ ItemRecord<V> _nextAdded; |
+ ItemRecord<V> _nextMoved; |
ItemRecord(this.item); |
@@ -1162,87 +1309,94 @@ class ItemRecord<V> implements CollectionItem<V>, AddedItem<V>, MovedItem<V>, |
class _DuplicateItemRecordList { |
ItemRecord head, tail; |
- void add(ItemRecord record, ItemRecord beforeRecord) { |
- assert(record._prevDupRec == null); |
- assert(record._nextDupRec == null); |
- assert(beforeRecord == null ? true : beforeRecord.item == record.item); |
+ /** |
+ * Add the [record] before the [previousRecord] in the list of duplicates or |
+ * at the end of the list when no [previousRecord] is specified. |
+ * |
+ * Note: by design all records in the list of duplicates hold the save value |
+ * in [record.item]. |
+ */ |
+ void add(ItemRecord record, ItemRecord previousRecord) { |
+ assert(previousRecord == null || previousRecord.item == record.item); |
if (head == null) { |
- assert(beforeRecord == null); |
+ assert(previousRecord == null); |
head = tail = record; |
+ record._nextDup = null; |
+ record._prevDup = null; |
} else { |
assert(record.item == head.item); |
- if (beforeRecord == null) { |
- tail._nextDupRec = record; |
- record._prevDupRec = tail; |
+ if (previousRecord == null) { |
+ tail._nextDup = record; |
+ record._prevDup = tail; |
+ record._nextDup = null; |
tail = record; |
} else { |
- var prev = beforeRecord._prevDupRec; |
- var next = beforeRecord; |
- record._prevDupRec = prev; |
- record._nextDupRec = next; |
+ var prev = previousRecord._prevDup; |
+ var next = previousRecord; |
+ record._prevDup = prev; |
+ record._nextDup = next; |
if (prev == null) { |
head = record; |
} else { |
- prev._nextDupRec = record; |
+ prev._nextDup = record; |
} |
- next._prevDupRec = record; |
+ next._prevDup = record; |
} |
} |
} |
ItemRecord get(key, int hideIndex) { |
- ItemRecord record = head; |
- while(record != null) { |
- if (hideIndex == null || |
- hideIndex < record.currentIndex && identical(record.item, key)) { |
+ ItemRecord record; |
+ for (record = head; record != null; record = record._nextDup) { |
+ if ((hideIndex == null || hideIndex < record.currentIndex) && |
+ identical(record.item, key)) { |
return record; |
} |
- record = record._nextDupRec; |
} |
return record; |
} |
+ /** |
+ * Remove one [ItemRecord] from the list of duplicates. |
+ * |
+ * Returns whether when the list of duplicates is empty. |
+ */ |
bool remove(ItemRecord record) { |
assert(() { |
// verify that the record being removed is someplace in the list. |
- ItemRecord cursor = head; |
- while(cursor != null) { |
+ for (ItemRecord cursor = head; cursor != null; cursor = cursor._nextDup) { |
if (identical(cursor, record)) return true; |
- cursor = cursor._nextDupRec; |
} |
return false; |
}); |
- var prev = record._prevDupRec; |
- var next = record._nextDupRec; |
+ var prev = record._prevDup; |
+ var next = record._nextDup; |
if (prev == null) { |
head = next; |
} else { |
- prev._nextDupRec = next; |
+ prev._nextDup = next; |
} |
if (next == null) { |
tail = prev; |
} else { |
- next._prevDupRec = prev; |
+ next._prevDup = prev; |
} |
- |
- assert((record._prevDupRec = null) == null); |
- assert((record._nextDupRec = null) == null); |
- |
return head == null; |
} |
} |
/** |
- * This is a custom map which supports duplicate [ItemRecord] values for each |
- * key. |
+ * [DuplicateMap] maps [ItemRecord.value] to a list of [ItemRecord] having the |
+ * same value (duplicates). |
+ * |
+ * The list of duplicates is implemented by [_DuplicateItemRecordList]. |
+ * |
*/ |
class DuplicateMap { |
final map = <dynamic, _DuplicateItemRecordList>{}; |
void put(ItemRecord record, [ItemRecord beforeRecord = null]) { |
- assert(record._nextDupRec == null); |
- assert(record._prevDupRec == null); |
map.putIfAbsent(record.item, () => new _DuplicateItemRecordList()) |
.add(record, beforeRecord); |
} |
@@ -1261,6 +1415,11 @@ class DuplicateMap { |
return recordList == null ? null : recordList.get(key, hideIndex); |
} |
+ /** |
+ * Removes an [ItemRecord] from the list of duplicates. |
+ * |
+ * The list of duplicates also is removed from the map if it gets empty. |
+ */ |
ItemRecord remove(ItemRecord record) { |
_DuplicateItemRecordList recordList = map[record.item]; |
assert(recordList != null); |
@@ -1268,7 +1427,11 @@ class DuplicateMap { |
return record; |
} |
+ bool get isEmpty => map.isEmpty; |
+ |
void clear() { |
map.clear(); |
} |
+ |
+ String toString() => "$runtimeType($map)"; |
} |