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

Unified Diff: third_party/pkg/angular/lib/change_detection/dirty_checking_change_detector.dart

Issue 1058283006: Update pubspecs and dependencies to get pkgbuild tests working. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 5 years, 8 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 side-by-side diff with in-line comments
Download patch
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
deleted file mode 100644
index a51c74687733c77ce6bc0f283aa8c712c1eae877..0000000000000000000000000000000000000000
--- a/third_party/pkg/angular/lib/change_detection/dirty_checking_change_detector.dart
+++ /dev/null
@@ -1,1274 +0,0 @@
-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().
- * - SPEED this needs to be as fast as possible.
- * - No GC pressure. Since change detection runs often it should perform no
- * 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.
- *
- * [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> {
- /**
- * A group must have at least one record so that it can act as a placeholder.
- * This record has minimal cost and never detects change. Once actual records
- * get added the marker record gets removed, but it gets reinserted if all
- * other records are removed.
- */
- final DirtyCheckingRecord _marker = new DirtyCheckingRecord.marker();
-
- final GetterCache _getterCache;
-
- /**
- * All records for group are kept together and are denoted by head/tail.
- * The [_recordHead]-[_recordTail] only include our own records. If you want
- * to see our childGroup records as well use
- * [_head]-[_childInclRecordTail].
- */
- DirtyCheckingRecord _recordHead, _recordTail;
-
- /**
- * Same as [_tail] but includes child-group records as well.
- */
- DirtyCheckingRecord get _childInclRecordTail {
- DirtyCheckingChangeDetectorGroup tail = this, nextTail;
- while ((nextTail = tail._childTail) != null) {
- tail = nextTail;
- }
- return tail._recordTail;
- }
-
-
- DirtyCheckingChangeDetector get _root {
- var root = this;
- var next;
- while((next = root._parent) != null) {
- root = next;
- }
- return root is DirtyCheckingChangeDetector ? root : null;
- }
-
- /**
- * ChangeDetectorGroup is organized hierarchically, a root group can have
- * child groups and so on. We keep track of parent, children and next,
- * previous here.
- */
- DirtyCheckingChangeDetectorGroup _parent, _childHead, _childTail, _prev, _next;
-
- DirtyCheckingChangeDetectorGroup(this._parent, this._getterCache) {
- // we need to insert the marker record at the beginning.
- if (_parent == null) {
- _recordHead = _marker;
- _recordTail = _marker;
- } else {
- _recordTail = _parent._childInclRecordTail;
- // _recordAdd uses _recordTail from above.
- _recordHead = _recordTail = _recordAdd(_marker);
- }
- }
-
- /**
- * Returns the number of watches in this group (including child groups).
- */
- get count {
- int count = 0;
- DirtyCheckingRecord cursor = _recordHead;
- DirtyCheckingRecord end = _childInclRecordTail;
- while(cursor != null) {
- if (cursor._mode != DirtyCheckingRecord._MODE_MARKER_) {
- count++;
- }
- if (cursor == end) break;
- cursor = cursor._nextRecord;
- }
- return count;
- }
-
- 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));
- }
-
- /**
- * Create a child [ChangeDetector] group.
- */
- DirtyCheckingChangeDetectorGroup<H> newGroup() {
- assert(_root._assertRecordsOk());
- var child = new DirtyCheckingChangeDetectorGroup(this, _getterCache);
- if (_childHead == null) {
- _childHead = _childTail = child;
- } else {
- child._prev = _childTail;
- _childTail._next = child;
- _childTail = child;
- }
- assert(_root._assertRecordsOk());
- return child;
- }
-
- /**
- * Bulk remove all records.
- */
- void remove() {
- var root;
- assert((root = _root) != null);
- assert(root._assertRecordsOk());
- DirtyCheckingRecord prevRecord = _recordHead._prevRecord;
- 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;
-
- if (prevGroup == null) {
- _parent._childHead = nextGroup;
- } else {
- prevGroup._next = nextGroup;
- }
- if (nextGroup == null) {
- _parent._childTail = prevGroup;
- } else {
- nextGroup._prev = prevGroup;
- }
- _parent = null;
- assert(root._assertRecordsOk());
- }
-
- DirtyCheckingRecord _recordAdd(DirtyCheckingRecord record) {
- DirtyCheckingRecord previous = _recordTail;
- DirtyCheckingRecord next = previous == null ? null : previous._nextRecord;
-
- record._nextRecord = next;
- record._prevRecord = previous;
-
- if (previous != null) previous._nextRecord = record;
- if (next != null) next._prevRecord = record;
-
- _recordTail = record;
-
- if (previous == _marker) _recordRemove(_marker);
-
- return record;
- }
-
- void _recordRemove(DirtyCheckingRecord record) {
- DirtyCheckingRecord previous = record._prevRecord;
- DirtyCheckingRecord next = record._nextRecord;
-
- if (record == _recordHead && record == _recordTail) {
- // we are the last one, must leave marker behind.
- _recordHead = _recordTail = _marker;
- _marker._nextRecord = next;
- _marker._prevRecord = previous;
- if (previous != null) previous._nextRecord = _marker;
- if (next != null) next._prevRecord = _marker;
- } else {
- if (record == _recordTail) _recordTail = previous;
- if (record == _recordHead) _recordHead = next;
- if (previous != null) previous._nextRecord = next;
- if (next != null) next._prevRecord = previous;
- }
- }
-
- String toString() {
- var lines = [];
- if (_parent == null) {
- var allRecords = [];
- DirtyCheckingRecord record = _recordHead;
- var includeChildrenTail = _childInclRecordTail;
- do {
- allRecords.add(record.toString());
- record = record._nextRecord;
- }
- while (record != includeChildrenTail);
- lines.add('FIELDS: ${allRecords.join(', ')}');
- }
-
- var records = [];
- DirtyCheckingRecord record = _recordHead;
- while (record != _recordTail) {
- records.add(record.toString());
- record = record._nextRecord;
- }
- records.add(record.toString());
-
- lines.add('DirtyCheckingChangeDetectorGroup(fields: ${records.join(', ')})');
- var childGroup = _childHead;
- while (childGroup != null) {
- lines.add(' ' + childGroup.toString().split('\n').join('\n '));
- childGroup = childGroup._next;
- }
- return lines.join('\n');
- }
-}
-
-class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H>
- implements ChangeDetector<H> {
- DirtyCheckingChangeDetector(GetterCache getterCache): super(null, getterCache);
-
- DirtyCheckingChangeDetector get _root => this;
-
- _assertRecordsOk() {
- var record = this._recordHead;
- var groups = [this];
- DirtyCheckingChangeDetectorGroup group;
- while(groups.isNotEmpty) {
- group = groups.removeAt(0);
- DirtyCheckingChangeDetectorGroup childGroup = group._childTail;
- while(childGroup != null) {
- groups.insert(0, childGroup);
- childGroup = childGroup._prev;
- }
- var groupRecord = group._recordHead;
- var groupLast = group._recordTail;
- while(true) {
- if (groupRecord == record) {
- record = record._nextRecord;
- } else {
- throw 'lost: $record found $groupRecord\n$this';
- }
-
- if (groupRecord == groupLast) break;
- groupRecord = groupRecord._nextRecord;
- }
- }
- return true;
- }
-
- DirtyCheckingRecord<H> collectChanges({ EvalExceptionHandler exceptionHandler,
- AvgStopwatch stopwatch}) {
- if (stopwatch != null) stopwatch.start();
- DirtyCheckingRecord changeHead = null;
- DirtyCheckingRecord changeTail = null;
- 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++;
- } catch (e, s) {
- if (exceptionHandler == null) {
- rethrow;
- } else {
- exceptionHandler(e, s);
- }
- }
- current = current._nextRecord;
- }
- if (changeTail != null) changeTail.nextChange = null;
- if (stopwatch != null) stopwatch..stop()..increment(count);
- return changeHead;
- }
-
- void remove() {
- throw new StateError('Root ChangeDetector can not be removed');
- }
-}
-
-/**
- * [DirtyCheckingRecord] represents as single item to check. The heart of the
- * [DirtyCheckingRecord] is a the [check] method which can read the
- * [currentValue] and compare it to the [previousValue].
- *
- * [DirtyCheckingRecord]s form linked list. This makes traversal, adding, and
- * 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> {
- static const List<String> _MODE_NAMES =
- const ['MARKER', 'IDENT', 'REFLECT', '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;
-
- final DirtyCheckingChangeDetectorGroup _group;
- final String field;
- final Symbol _symbol;
- final FieldGetter _getter;
- final H handler;
-
- int _mode;
-
- var previousValue;
- var currentValue;
- DirtyCheckingRecord<H> _nextRecord;
- DirtyCheckingRecord<H> _prevRecord;
- ChangeRecord<H> nextChange;
- var _object;
- InstanceMirror _instanceMirror;
-
- DirtyCheckingRecord(this._group, object, fieldName, this._getter, this.handler)
- : field = fieldName,
- _symbol = fieldName == null ? null : new Symbol(fieldName)
- {
- this.object = object;
- }
-
- DirtyCheckingRecord.marker()
- : handler = null,
- field = null,
- _group = null,
- _symbol = null,
- _getter = null,
- _mode = _MODE_MARKER_;
-
- 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) {
- _object = obj;
- if (obj == null) {
- _mode = _MODE_IDENTITY_;
- return;
- }
-
- if (field == null) {
- _instanceMirror = 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();
- }
- } else if (obj is Iterable) {
- if (_mode != _MODE_ITERABLE_) {
- // Last one was collection as well, don't reset state.
- _mode = _MODE_ITERABLE_;
- currentValue = new _CollectionChangeRecord();
- }
- } else {
- _mode = _MODE_IDENTITY_;
- }
-
- return;
- }
-
- if (obj is Map) {
- _mode = _MODE_MAP_FIELD_;
- _instanceMirror = null;
- } else if (_getter != null) {
- _mode = _MODE_GETTER_;
- _instanceMirror = null;
- } else {
- _mode = _MODE_REFLECT_;
- _instanceMirror = reflect(obj);
- }
- }
-
- ChangeRecord<H> check() {
- assert(_mode != null);
- var current;
- switch (_mode) {
- case _MODE_MARKER_:
- return null;
- case _MODE_REFLECT_:
- current = _instanceMirror.getField(_symbol).reflectee;
- break;
- case _MODE_GETTER_:
- current = _getter(object);
- break;
- case _MODE_MAP_FIELD_:
- current = object[field];
- break;
- case _MODE_IDENTITY_:
- current = object;
- break;
- case _MODE_MAP_:
- return (currentValue as _MapChangeRecord)._check(object) ? this : null;
- case _MODE_ITERABLE_:
- return (currentValue as _CollectionChangeRecord)._check(object) ? this : null;
- default:
- assert(false);
- }
-
- var last = currentValue;
- if (!identical(last, current)) {
- if (last is String && current is String &&
- last == current) {
- // This is false change in strings we need to recover, and pretend it
- // is the same. We save the value so that next time identity will pass
- currentValue = current;
- } else if (last is num && last.isNaN && current is num && current.isNaN) {
- // we need this for the compiled JavaScript since in JS NaN !== NaN.
- } else {
- previousValue = last;
- currentValue = current;
- return this;
- }
- }
- return null;
- }
-
-
- void remove() {
- _group._recordRemove(this);
- }
-
- String toString() => '${_MODE_NAMES[_mode]}[$field]{$hashCode}';
-}
-
-final Object _INITIAL_ = new Object();
-
-class _MapChangeRecord<K, V> implements MapChangeRecord<K, V> {
- final Map<dynamic, KeyValueRecord> _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;
-
- 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;
- }
- }
-
- void forEachAddition(void f(AddedKeyValue<K, V> addition)){
- KeyValueRecord record = _additionsHead;
- while(record != null) {
- f(record);
- record = record._nextAddedKeyValue;
- }
- }
-
- void forEachRemoval(void f(RemovedKeyValue<K, V> removal)){
- KeyValueRecord record = _removalsHead;
- while(record != null) {
- f(record);
- record = record._nextRemovedKeyValue;
- }
- }
-
-
- bool _check(Map map) {
- _reset();
- _map = map;
- Map records = _records;
- KeyValueRecord oldSeqRecord = _mapHead;
- KeyValueRecord lastOldSeqRecord;
- KeyValueRecord lastNewSeqRecord;
- var seqChanged = false;
- map.forEach((key, value) {
- var newSeqRecord;
- if (oldSeqRecord != null && key == oldSeqRecord.key) {
- newSeqRecord = oldSeqRecord;
- if (!identical(value, oldSeqRecord._currentValue)) {
- var prev = oldSeqRecord._previousValue = oldSeqRecord._currentValue;
- oldSeqRecord._currentValue = value;
- if (!((value is String && prev is String && value == prev) ||
- (value is num && value.isNaN && prev is num && prev.isNaN))) {
- // Check string by value rather than reference
- _addToChanges(oldSeqRecord);
- }
- }
- } else {
- seqChanged = true;
- if (oldSeqRecord != null) {
- oldSeqRecord._nextKeyValue = null;
- _removeFromSeq(lastOldSeqRecord, oldSeqRecord);
- _addToRemovals(oldSeqRecord);
- }
- if (records.containsKey(key)) {
- newSeqRecord = records[key];
- } else {
- newSeqRecord = records[key] = new KeyValueRecord(key);
- newSeqRecord._currentValue = value;
- _addToAdditions(newSeqRecord);
- }
- }
-
- if (seqChanged) {
- if (_isInRemovals(newSeqRecord)) {
- _removeFromRemovals(newSeqRecord);
- }
- if (lastNewSeqRecord == null) {
- _mapHead = newSeqRecord;
- } else {
- lastNewSeqRecord._nextKeyValue = newSeqRecord;
- }
- }
- lastOldSeqRecord = oldSeqRecord;
- lastNewSeqRecord = newSeqRecord;
- oldSeqRecord = oldSeqRecord == null ? null : oldSeqRecord._nextKeyValue;
- });
- _truncate(lastOldSeqRecord, oldSeqRecord);
- return isDirty;
- }
-
- void _reset() {
- var record = _changesHead;
- while (record != null) {
- record._previousValue = record._currentValue;
- record = record._nextChangedKeyValue;
- }
-
- record = _additionsHead;
- while (record != null) {
- record._previousValue = record._currentValue;
- record = record._nextAddedKeyValue;
- }
-
- assert((() {
- var record = _changesHead;
- while (record != null) {
- var nextRecord = record._nextChangedKeyValue;
- record._nextChangedKeyValue = null;
- record = nextRecord;
- }
-
- record = _additionsHead;
- while (record != null) {
- var nextRecord = record._nextAddedKeyValue;
- record._nextAddedKeyValue = null;
- record = nextRecord;
- }
-
- record = _removalsHead;
- while (record != null) {
- var nextRecord = record._nextRemovedKeyValue;
- record._nextRemovedKeyValue = null;
- record = nextRecord;
- }
-
- return true;
- })());
- _changesHead = _changesTail = null;
- _additionsHead = _additionsTail = null;
- _removalsHead = _removalsTail = null;
- }
-
- void _truncate(KeyValueRecord lastRecord, KeyValueRecord record) {
- while(record != null) {
- if (lastRecord == null) {
- _mapHead = null;
- } else {
- lastRecord._nextKeyValue = null;
- }
- var nextRecord = record._nextKeyValue;
- assert((() {
- record._nextKeyValue = null;
- return true;
- })());
- _addToRemovals(record);
- lastRecord = record;
- record = nextRecord;
- }
-
- record = _removalsHead;
- while (record != null) {
- record._previousValue = record._currentValue;
- record._currentValue = null;
- _records.remove(record.key);
- record = record._nextRemovedKeyValue;
- }
- }
-
- bool _isInRemovals(KeyValueRecord record) =>
- record == _removalsHead ||
- record._nextRemovedKeyValue != null ||
- record._prevRemovedKeyValue != 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);
- if (_removalsHead == null) {
- _removalsHead = _removalsTail = record;
- } else {
- _removalsTail._nextRemovedKeyValue = record;
- record._prevRemovedKeyValue = _removalsTail;
- _removalsTail = record;
- }
- }
-
- void _removeFromSeq(KeyValueRecord prev, KeyValueRecord record) {
- KeyValueRecord next = record._nextKeyValue;
- if (prev == null) {
- _mapHead = next;
- } else {
- prev._nextKeyValue = next;
- }
- assert((() {
- record._nextKeyValue = null;
- return true;
- })());
- }
-
- void _removeFromRemovals(KeyValueRecord record) {
- assert(record._nextKeyValue == null);
- assert(record._nextAddedKeyValue == null);
- assert(record._nextChangedKeyValue == null);
-
- var prev = record._prevRemovedKeyValue;
- var next = record._nextRemovedKeyValue;
- if (prev == null) {
- _removalsHead = next;
- } else {
- prev._nextRemovedKeyValue = next;
- }
- if (next == null) {
- _removalsTail = prev;
- } else {
- next._prevRemovedKeyValue = prev;
- }
- record._prevRemovedKeyValue = record._nextRemovedKeyValue = 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);
- if (_additionsHead == null) {
- _additionsHead = _additionsTail = record;
- } else {
- _additionsTail._nextAddedKeyValue = record;
- _additionsTail = record;
- }
- }
-
- void _addToChanges(KeyValueRecord record) {
- assert(record._nextAddedKeyValue == null);
- assert(record._nextChangedKeyValue == null);
- assert(record._nextRemovedKeyValue == null);
- assert(record._prevRemovedKeyValue == null);
- if (_changesHead == null) {
- _changesHead = _changesTail = record;
- } else {
- _changesTail._nextChangedKeyValue = record;
- _changesTail = record;
- }
- }
-}
-
-class KeyValueRecord<K, V> implements KeyValue<K, V>, AddedKeyValue<K, V>,
- RemovedKeyValue<K, V>, ChangedKeyValue<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;
-
- String toString() => _previousValue == _currentValue
- ? key
- : '$key[$_previousValue -> $_currentValue]';
-}
-
-
-class _CollectionChangeRecord<V> implements CollectionChangeRecord<V> {
- Iterable _iterable;
- /** Used to keep track of items during moves. */
- DuplicateMap _items = new DuplicateMap();
-
- /** Used to keep track of removed items. */
- DuplicateMap _removedItems = new DuplicateMap();
-
- ItemRecord<V> _collectionHead, _collectionTail;
- 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 forEachAddition(void f(AddedItem<V> addition)){
- ItemRecord record = _additionsHead;
- while(record != null) {
- f(record);
- record = record._nextAddedRec;
- }
- }
-
- void forEachMove(void f(MovedItem<V> change)) {
- ItemRecord record = _movesHead;
- while(record != null) {
- f(record);
- record = record._nextMovedRec;
- }
- }
-
- void forEachRemoval(void f(RemovedItem<V> removal)){
- ItemRecord record = _removalsHead;
- while(record != null) {
- f(record);
- record = record._nextRemovedRec;
- }
- }
-
- Iterable get iterable => _iterable;
-
- bool _check(Iterable collection) {
- _reset();
- ItemRecord record = _collectionHead;
- bool maybeDirty = false;
- if ((collection is UnmodifiableListView) &&
- identical(_iterable, collection)) {
- // Short circuit and assume that the list has not been modified.
- return false;
- }
-
- if (collection is List) {
- List list = collection;
- for (int index = 0; index < list.length; index++) {
- var item = list[index];
- if (record == null || !identical(item, record.item)) {
- record = mismatch(record, item, index);
- maybeDirty = true;
- } else if (maybeDirty) {
- // TODO(misko): can we limit this to duplicates only?
- record = verifyReinsertion(record, item, index);
- }
- record = record._nextRec;
- }
- } else {
- int index = 0;
- for (var item in collection) {
- if (record == null || !identical(item, record.item)) {
- record = mismatch(record, item, index);
- maybeDirty = true;
- } else if (maybeDirty) {
- // TODO(misko): can we limit this to duplicates only?
- record = verifyReinsertion(record, item, index);
- }
- record = record._nextRec;
- index++;
- }
- }
-
- _truncate(record);
- _iterable = collection;
- return isDirty;
- }
-
- /**
- * Reset the state of the change objects to show no changes. This means set
- * previousKey to currentKey, and clear all of the queues (additions, moves,
- * removals).
- */
- void _reset() {
- ItemRecord record;
-
- record = _additionsHead;
- while(record != null) {
- record.previousIndex = record.currentIndex;
- record = record._nextAddedRec;
- }
- _additionsHead = _additionsTail = null;
-
- record = _movesHead;
- while(record != null) {
- record.previousIndex = record.currentIndex;
- var nextRecord = record._nextMovedRec;
- assert((record._nextMovedRec = null) == null);
- record = nextRecord;
- }
- _movesHead = _movesTail = null;
- _removalsHead = _removalsTail = null;
- assert(isDirty == false);
- }
-
- /**
- * A [_CollectionChangeRecord] is considered dirty if it has additions, moves
- * or removals.
- */
- 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.
- * - [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
- 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
- // the same. We save the value so that next time identity can pass
- return record..item = item;
- }
-
- if (item is num && item.isNaN && record.item is num && record.item.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;
-
- // 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);
- if (record != null) {
- // We have seen this before, we need to move it forward in the collection.
- _collection_moveAfter(record, prev, index);
- } else {
- // Never seen it, check evicted list.
- record = _removedItems.get(item);
- if (record != null) {
- // It is an item which we have earlier evict it, reinsert it back into
- // the list.
- _collection_reinsertAfter(record, prev, index);
- } else {
- // It is a new item add it.
- record = _collection_addAfter(new ItemRecord(item), prev, index);
- }
- }
- return record;
- }
-
- /**
- * This check is only needed if an array contains duplicates. (Short circuit
- * of nothing dirty)
- *
- * Use case: `[a, a]` => `[b, a, a]`
- *
- * If we did not have this check then the insertion of `b` would:
- * 1) evict first `a`
- * 2) insert `b` at `0` index.
- * 3) leave `a` at index `1` as is. <-- this is wrong!
- * 3) reinsert `a` at index 2. <-- this is wrong!
- *
- * The correct behavior is:
- * 1) evict first `a`
- * 2) insert `b` at `0` index.
- * 3) reinsert `a` at index 1.
- * 3) move `a` at from `1` to `2`.
- *
- *
- * Double check that we have not evicted a duplicate item. We need to check if
- * the item type may have already been removed:
- * The insertion of b will evict the first 'a'. If we don't reinsert it now it
- * will be reinserted at the end. Which will show up as the two 'a's switching
- * 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,
- int index) {
- ItemRecord reinsertRecord = _removedItems.get(item);
- if (reinsertRecord != null) {
- record = _collection_reinsertAfter(reinsertRecord, record._prevRec, index);
- } else if (record.currentIndex != index) {
- record.currentIndex = index;
- _moves_add(record);
- }
- return record;
- }
-
- /**
- * Get rid of any excess [ItemRecord]s from the previous collection
- *
- * - [record] The first excess [ItemRecord].
- */
- void _truncate(ItemRecord record) {
- // Anything after that needs to be removed;
- while(record != null) {
- ItemRecord nextRecord = record._nextRec;
- _removals_add(_collection_unlink(record));
- record = nextRecord;
- }
- _removedItems.clear();
- }
-
- ItemRecord _collection_reinsertAfter(ItemRecord record, ItemRecord insertPrev,
- int index) {
- _removedItems.remove(record);
- var prev = record._prevRemovedRec;
- var next = record._nextRemovedRec;
-
- assert((record._prevRemovedRec = null) == null);
- assert((record._nextRemovedRec = null) == null);
-
- if (prev == null) {
- _removalsHead = next;
- } else {
- prev._nextRemovedRec = next;
- }
- if (next == null) {
- _removalsTail = prev;
- } else {
- next._prevRemovedRec = prev;
- }
-
- _collection_insertAfter(record, insertPrev, index);
- _moves_add(record);
- return record;
- }
-
- ItemRecord _collection_moveAfter(ItemRecord record, ItemRecord 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) {
- _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;
- }
- return record;
- }
-
- ItemRecord _collection_insertAfter(ItemRecord record, ItemRecord prev,
- int index) {
- assert(record != prev);
- assert(record._nextRec == null);
- assert(record._prevRec == null);
-
- ItemRecord next = prev == null ? _collectionHead : prev._nextRec;
- assert(next != record);
- assert(prev != record);
- record._nextRec = next;
- record._prevRec = prev;
- if (next == null) {
- _collectionTail = record;
- } else {
- next._prevRec = record;
- }
- if (prev == null) {
- _collectionHead = record;
- } else {
- prev._nextRec = record;
- }
-
- _items.put(record);
- record.currentIndex = index;
- return record;
- }
-
- ItemRecord _collection_remove(ItemRecord record) =>
- _removals_add(_collection_unlink(record));
-
- ItemRecord _collection_unlink(ItemRecord record) {
- _items.remove(record);
-
- var prev = record._prevRec;
- var next = record._nextRec;
-
- assert((record._prevRec = null) == null);
- assert((record._nextRec = null) == null);
-
- if (prev == null) {
- _collectionHead = next;
- } else {
- prev._nextRec = next;
- }
- if (next == null) {
- _collectionTail = prev;
- } else {
- next._prevRec = prev;
- }
-
- return record;
- }
-
- ItemRecord _moves_add(ItemRecord record) {
- assert(record._nextMovedRec == null);
- if (_movesTail == null) {
- assert(_movesHead == null);
- _movesTail = _movesHead = record;
- } else {
- assert(_movesTail._nextMovedRec == null);
- _movesTail = _movesTail._nextMovedRec = record;
- }
-
- return record;
- }
-
- ItemRecord _removals_add(ItemRecord record) {
- record.currentIndex = null;
- _removedItems.put(record);
-
- if (_removalsTail == null) {
- assert(_removalsHead == null);
- _removalsTail = _removalsHead = record;
- } else {
- assert(_removalsTail._nextRemovedRec == null);
- assert(record._nextRemovedRec == null);
- record._prevRemovedRec = _removalsTail;
- _removalsTail = _removalsTail._nextRemovedRec = record;
- }
- return record;
- }
-
- String toString() {
- ItemRecord record;
-
- var list = [];
- record = _collectionHead;
- while(record != null) {
- list.add(record);
- record = record._nextRec;
- }
-
- var additions = [];
- record = _additionsHead;
- while(record != null) {
- additions.add(record);
- record = record._nextAddedRec;
- }
-
- var moves = [];
- record = _movesHead;
- while(record != null) {
- moves.add(record);
- record = record._nextMovedRec;
- }
-
- var removals = [];
- record = _removalsHead;
- while(record != null) {
- removals.add(record);
- record = record._nextRemovedRec;
- }
-
- return """
-collection: ${list.join(", ")}
-additions: ${additions.join(", ")}
-moves: ${moves.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;
-
- CollectionItem<V> get nextCollectionItem => _nextRec;
- RemovedItem<V> get nextRemovedItem => _nextRemovedRec;
- AddedItem<V> get nextAddedItem => _nextAddedRec;
- MovedItem<V> get nextMovedItem => _nextMovedRec;
-
- ItemRecord(this.item);
-
- String toString() => previousIndex == currentIndex
- ? '$item'
- : '$item[$previousIndex -> $currentIndex]';
-}
-
-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);
- if (head == null) {
- assert(beforeRecord == null);
- head = tail = record;
- } else {
- assert(record.item == head.item);
- if (beforeRecord == null) {
- tail._nextDupRec = record;
- record._prevDupRec = tail;
- tail = record;
- } else {
- var prev = beforeRecord._prevDupRec;
- var next = beforeRecord;
- record._prevDupRec = prev;
- record._nextDupRec = next;
- if (prev == null) {
- head = record;
- } else {
- prev._nextDupRec = record;
- }
- next._prevDupRec = record;
- }
- }
- }
-
- ItemRecord get(key, int hideIndex) {
- ItemRecord record = head;
- while(record != null) {
- if (hideIndex == null ||
- hideIndex < record.currentIndex && identical(record.item, key)) {
- return record;
- }
- record = record._nextDupRec;
- }
- return record;
- }
-
- bool remove(ItemRecord record) {
- assert(() {
- // verify that the record being removed is someplace in the list.
- ItemRecord cursor = head;
- while(cursor != null) {
- if (identical(cursor, record)) return true;
- cursor = cursor._nextDupRec;
- }
- return false;
- });
-
- var prev = record._prevDupRec;
- var next = record._nextDupRec;
- if (prev == null) {
- head = next;
- } else {
- prev._nextDupRec = next;
- }
- if (next == null) {
- tail = prev;
- } else {
- next._prevDupRec = 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.
- */
-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);
- }
-
- /**
- * Retrieve the `value` using [key]. Because the [ItemRecord] value maybe one
- * which we have already iterated over, we use the [hideIndex] to pretend it
- * is not there.
- *
- * Use case: `[a, b, c, a, a]` if we are at index `3` which is the second `a`
- * then asking if we have any more `a`s needs to return the last `a` not the
- * first or second.
- */
- ItemRecord get(key, [int hideIndex]) {
- _DuplicateItemRecordList recordList = map[key];
- return recordList == null ? null : recordList.get(key, hideIndex);
- }
-
- ItemRecord remove(ItemRecord record) {
- _DuplicateItemRecordList recordList = map[record.item];
- assert(recordList != null);
- if (recordList.remove(record)) map.remove(record.item);
- return record;
- }
-
- void clear() {
- map.clear();
- }
-}

Powered by Google App Engine
This is Rietveld 408576698