| 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();
 | 
| -  }
 | 
| -}
 | 
| 
 |