2015-02-02 16:25:34 -08:00
|
|
|
import {ListWrapper, MapWrapper, StringMapWrapper} from 'facade/src/collection';
|
2014-10-29 21:56:31 +01:00
|
|
|
|
2015-02-02 16:25:34 -08:00
|
|
|
import {stringify, looseIdentical, isJsObject} from 'facade/src/lang';
|
2014-10-29 21:56:31 +01:00
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
export class KeyValueChanges {
|
2014-11-21 21:19:23 -08:00
|
|
|
_records:Map;
|
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
_mapHead:KVChangeRecord;
|
|
|
|
_previousMapHead:KVChangeRecord;
|
|
|
|
_changesHead:KVChangeRecord;
|
|
|
|
_changesTail:KVChangeRecord;
|
|
|
|
_additionsHead:KVChangeRecord;
|
|
|
|
_additionsTail:KVChangeRecord;
|
|
|
|
_removalsHead:KVChangeRecord;
|
|
|
|
_removalsTail:KVChangeRecord;
|
2014-10-29 21:56:31 +01:00
|
|
|
|
|
|
|
constructor() {
|
|
|
|
this._records = MapWrapper.create();
|
|
|
|
this._mapHead = null;
|
|
|
|
this._previousMapHead = null;
|
|
|
|
this._changesHead = null;
|
|
|
|
this._changesTail = null;
|
|
|
|
this._additionsHead = null;
|
|
|
|
this._additionsTail = null;
|
|
|
|
this._removalsHead = null;
|
|
|
|
this._removalsTail = null;
|
|
|
|
}
|
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
static supports(obj):boolean {
|
|
|
|
return obj instanceof Map || isJsObject(obj);
|
|
|
|
}
|
|
|
|
|
2014-10-29 21:56:31 +01:00
|
|
|
get isDirty():boolean {
|
|
|
|
return this._additionsHead !== null ||
|
|
|
|
this._changesHead !== null ||
|
|
|
|
this._removalsHead !== null;
|
|
|
|
}
|
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
forEachItem(fn:Function) {
|
|
|
|
var record:KVChangeRecord;
|
|
|
|
for (record = this._mapHead; record !== null; record = record._next) {
|
|
|
|
fn(record);
|
2014-10-29 21:56:31 +01:00
|
|
|
}
|
2014-11-24 18:42:53 +01:00
|
|
|
}
|
2014-10-29 21:56:31 +01:00
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
forEachPreviousItem(fn:Function) {
|
|
|
|
var record:KVChangeRecord;
|
|
|
|
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
|
|
|
|
fn(record);
|
2014-10-29 21:56:31 +01:00
|
|
|
}
|
2014-11-24 18:42:53 +01:00
|
|
|
}
|
2014-10-29 21:56:31 +01:00
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
forEachChangedItem(fn:Function) {
|
|
|
|
var record:KVChangeRecord;
|
|
|
|
for (record = this._changesHead; record !== null; record = record._nextChanged) {
|
|
|
|
fn(record);
|
2014-10-29 21:56:31 +01:00
|
|
|
}
|
2014-11-24 18:42:53 +01:00
|
|
|
}
|
2014-10-29 21:56:31 +01:00
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
forEachAddedItem(fn:Function){
|
|
|
|
var record:KVChangeRecord;
|
|
|
|
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
|
|
|
|
fn(record);
|
2014-10-29 21:56:31 +01:00
|
|
|
}
|
2014-11-24 18:42:53 +01:00
|
|
|
}
|
2014-10-29 21:56:31 +01:00
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
forEachRemovedItem(fn:Function){
|
|
|
|
var record:KVChangeRecord;
|
|
|
|
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
|
|
|
|
fn(record);
|
2014-10-29 21:56:31 +01:00
|
|
|
}
|
2014-11-24 18:42:53 +01:00
|
|
|
}
|
2014-10-29 21:56:31 +01:00
|
|
|
|
|
|
|
check(map):boolean {
|
|
|
|
this._reset();
|
|
|
|
var records = this._records;
|
2014-11-24 18:42:53 +01:00
|
|
|
var oldSeqRecord:KVChangeRecord = this._mapHead;
|
|
|
|
var lastOldSeqRecord:KVChangeRecord = null;
|
|
|
|
var lastNewSeqRecord:KVChangeRecord = null;
|
2014-10-29 21:56:31 +01:00
|
|
|
var seqChanged:boolean = false;
|
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
this._forEach(map, (value, key) => {
|
2014-10-29 21:56:31 +01:00
|
|
|
var newSeqRecord;
|
|
|
|
if (oldSeqRecord !== null && key === oldSeqRecord.key) {
|
|
|
|
newSeqRecord = oldSeqRecord;
|
|
|
|
if (!looseIdentical(value, oldSeqRecord._currentValue)) {
|
|
|
|
oldSeqRecord._previousValue = oldSeqRecord._currentValue;
|
|
|
|
oldSeqRecord._currentValue = value;
|
|
|
|
this._addToChanges(oldSeqRecord);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
seqChanged = true;
|
|
|
|
if (oldSeqRecord !== null) {
|
|
|
|
oldSeqRecord._next = null;
|
|
|
|
this._removeFromSeq(lastOldSeqRecord, oldSeqRecord);
|
|
|
|
this._addToRemovals(oldSeqRecord);
|
|
|
|
}
|
|
|
|
if (MapWrapper.contains(records, key)) {
|
|
|
|
newSeqRecord = MapWrapper.get(records, key);
|
|
|
|
} else {
|
2014-11-24 18:42:53 +01:00
|
|
|
newSeqRecord = new KVChangeRecord(key);
|
2014-10-29 21:56:31 +01:00
|
|
|
MapWrapper.set(records, key, newSeqRecord);
|
|
|
|
newSeqRecord._currentValue = value;
|
|
|
|
this._addToAdditions(newSeqRecord);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (seqChanged) {
|
|
|
|
if (this._isInRemovals(newSeqRecord)) {
|
|
|
|
this._removeFromRemovals(newSeqRecord);
|
|
|
|
}
|
|
|
|
if (lastNewSeqRecord == null) {
|
|
|
|
this._mapHead = newSeqRecord;
|
|
|
|
} else {
|
|
|
|
lastNewSeqRecord._next = newSeqRecord;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lastOldSeqRecord = oldSeqRecord;
|
|
|
|
lastNewSeqRecord = newSeqRecord;
|
|
|
|
oldSeqRecord = oldSeqRecord === null ? null : oldSeqRecord._next;
|
|
|
|
});
|
|
|
|
this._truncate(lastOldSeqRecord, oldSeqRecord);
|
|
|
|
return this.isDirty;
|
|
|
|
}
|
|
|
|
|
|
|
|
_reset() {
|
|
|
|
if (this.isDirty) {
|
2014-11-24 18:42:53 +01:00
|
|
|
var record:KVChangeRecord;
|
2014-10-29 21:56:31 +01:00
|
|
|
// Record the state of the mapping
|
|
|
|
for (record = this._previousMapHead = this._mapHead;
|
|
|
|
record !== null;
|
|
|
|
record = record._next) {
|
|
|
|
record._nextPrevious = record._next;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (record = this._changesHead; record !== null; record = record._nextChanged) {
|
|
|
|
record._previousValue = record._currentValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (record = this._additionsHead; record != null; record = record._nextAdded) {
|
|
|
|
record._previousValue = record._currentValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// todo(vicb) once assert is supported
|
|
|
|
//assert(() {
|
|
|
|
// var r = _changesHead;
|
|
|
|
// while (r != null) {
|
|
|
|
// var nextRecord = r._nextChanged;
|
|
|
|
// r._nextChanged = null;
|
|
|
|
// r = nextRecord;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// r = _additionsHead;
|
|
|
|
// while (r != null) {
|
|
|
|
// var nextRecord = r._nextAdded;
|
|
|
|
// r._nextAdded = null;
|
|
|
|
// r = nextRecord;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// r = _removalsHead;
|
|
|
|
// while (r != null) {
|
|
|
|
// var nextRecord = r._nextRemoved;
|
|
|
|
// r._nextRemoved = null;
|
|
|
|
// r = nextRecord;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return true;
|
|
|
|
//});
|
|
|
|
this._changesHead = this._changesTail = null;
|
|
|
|
this._additionsHead = this._additionsTail = null;
|
|
|
|
this._removalsHead = this._removalsTail = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
_truncate(lastRecord:KVChangeRecord, record:KVChangeRecord) {
|
2014-10-29 21:56:31 +01:00
|
|
|
while (record !== null) {
|
|
|
|
if (lastRecord === null) {
|
|
|
|
this._mapHead = null;
|
|
|
|
} else {
|
|
|
|
lastRecord._next = null;
|
|
|
|
}
|
|
|
|
var nextRecord = record._next;
|
|
|
|
// todo(vicb) assert
|
|
|
|
//assert((() {
|
|
|
|
// record._next = null;
|
|
|
|
// return true;
|
|
|
|
//}));
|
|
|
|
this._addToRemovals(record);
|
|
|
|
lastRecord = record;
|
|
|
|
record = nextRecord;
|
|
|
|
}
|
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
for (var rec:KVChangeRecord = this._removalsHead; rec !== null; rec = rec._nextRemoved) {
|
2014-10-29 21:56:31 +01:00
|
|
|
rec._previousValue = rec._currentValue;
|
|
|
|
rec._currentValue = null;
|
|
|
|
MapWrapper.delete(this._records, rec.key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
_isInRemovals(record:KVChangeRecord) {
|
2014-10-29 21:56:31 +01:00
|
|
|
return record === this._removalsHead ||
|
|
|
|
record._nextRemoved !== null ||
|
|
|
|
record._prevRemoved !== null;
|
|
|
|
}
|
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
_addToRemovals(record:KVChangeRecord) {
|
2014-10-29 21:56:31 +01:00
|
|
|
// todo(vicb) assert
|
|
|
|
//assert(record._next == null);
|
|
|
|
//assert(record._nextAdded == null);
|
|
|
|
//assert(record._nextChanged == null);
|
|
|
|
//assert(record._nextRemoved == null);
|
|
|
|
//assert(record._prevRemoved == null);
|
|
|
|
if (this._removalsHead === null) {
|
|
|
|
this._removalsHead = this._removalsTail = record;
|
|
|
|
} else {
|
|
|
|
this._removalsTail._nextRemoved = record;
|
|
|
|
record._prevRemoved = this._removalsTail;
|
|
|
|
this._removalsTail = record;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
_removeFromSeq(prev:KVChangeRecord, record:KVChangeRecord) {
|
2014-10-29 21:56:31 +01:00
|
|
|
var next = record._next;
|
|
|
|
if (prev === null) {
|
|
|
|
this._mapHead = next;
|
|
|
|
} else {
|
|
|
|
prev._next = next;
|
|
|
|
}
|
|
|
|
// todo(vicb) assert
|
|
|
|
//assert((() {
|
|
|
|
// record._next = null;
|
|
|
|
// return true;
|
|
|
|
//})());
|
|
|
|
}
|
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
_removeFromRemovals(record:KVChangeRecord) {
|
2014-10-29 21:56:31 +01:00
|
|
|
// todo(vicb) assert
|
|
|
|
//assert(record._next == null);
|
|
|
|
//assert(record._nextAdded == null);
|
|
|
|
//assert(record._nextChanged == null);
|
|
|
|
|
|
|
|
var prev = record._prevRemoved;
|
|
|
|
var next = record._nextRemoved;
|
|
|
|
if (prev === null) {
|
|
|
|
this._removalsHead = next;
|
|
|
|
} else {
|
|
|
|
prev._nextRemoved = next;
|
|
|
|
}
|
|
|
|
if (next === null) {
|
|
|
|
this._removalsTail = prev;
|
|
|
|
} else {
|
|
|
|
next._prevRemoved = prev;
|
|
|
|
}
|
|
|
|
record._prevRemoved = record._nextRemoved = null;
|
|
|
|
}
|
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
_addToAdditions(record:KVChangeRecord) {
|
2014-10-29 21:56:31 +01:00
|
|
|
// todo(vicb): assert
|
|
|
|
//assert(record._next == null);
|
|
|
|
//assert(record._nextAdded == null);
|
|
|
|
//assert(record._nextChanged == null);
|
|
|
|
//assert(record._nextRemoved == null);
|
|
|
|
//assert(record._prevRemoved == null);
|
|
|
|
if (this._additionsHead === null) {
|
|
|
|
this._additionsHead = this._additionsTail = record;
|
|
|
|
} else {
|
|
|
|
this._additionsTail._nextAdded = record;
|
|
|
|
this._additionsTail = record;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
_addToChanges(record:KVChangeRecord) {
|
2014-10-29 21:56:31 +01:00
|
|
|
// todo(vicb) assert
|
|
|
|
//assert(record._nextAdded == null);
|
|
|
|
//assert(record._nextChanged == null);
|
|
|
|
//assert(record._nextRemoved == null);
|
|
|
|
//assert(record._prevRemoved == null);
|
|
|
|
if (this._changesHead === null) {
|
|
|
|
this._changesHead = this._changesTail = record;
|
|
|
|
} else {
|
|
|
|
this._changesTail._nextChanged = record;
|
|
|
|
this._changesTail = record;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
toString():string {
|
|
|
|
var items = [];
|
|
|
|
var previous = [];
|
|
|
|
var changes = [];
|
|
|
|
var additions = [];
|
|
|
|
var removals = [];
|
2014-11-24 18:42:53 +01:00
|
|
|
var record:KVChangeRecord;
|
2014-10-29 21:56:31 +01:00
|
|
|
|
|
|
|
for (record = this._mapHead; record !== null; record = record._next) {
|
|
|
|
ListWrapper.push(items, stringify(record));
|
|
|
|
}
|
|
|
|
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
|
|
|
|
ListWrapper.push(previous, stringify(record));
|
|
|
|
}
|
|
|
|
for (record = this._changesHead; record !== null; record = record._nextChanged) {
|
|
|
|
ListWrapper.push(changes, stringify(record));
|
|
|
|
}
|
|
|
|
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
|
|
|
|
ListWrapper.push(additions, stringify(record));
|
|
|
|
}
|
|
|
|
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
|
|
|
|
ListWrapper.push(removals, stringify(record));
|
|
|
|
}
|
|
|
|
|
|
|
|
return "map: " + items.join(', ') + "\n" +
|
|
|
|
"previous: " + previous.join(', ') + "\n" +
|
|
|
|
"additions: " + additions.join(', ') + "\n" +
|
|
|
|
"changes: " + changes.join(', ') + "\n" +
|
|
|
|
"removals: " + removals.join(', ') + "\n";
|
|
|
|
}
|
2014-11-24 18:42:53 +01:00
|
|
|
|
|
|
|
_forEach(obj, fn:Function) {
|
|
|
|
if (obj instanceof Map) {
|
|
|
|
MapWrapper.forEach(obj, fn);
|
|
|
|
} else {
|
|
|
|
StringMapWrapper.forEach(obj, fn);
|
|
|
|
}
|
|
|
|
}
|
2014-10-29 21:56:31 +01:00
|
|
|
}
|
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
|
|
|
|
|
|
|
|
export class KVChangeRecord {
|
2014-11-21 21:19:23 -08:00
|
|
|
key;
|
|
|
|
_previousValue;
|
|
|
|
_currentValue;
|
|
|
|
|
2014-11-24 18:42:53 +01:00
|
|
|
_nextPrevious:KVChangeRecord;
|
|
|
|
_next:KVChangeRecord;
|
|
|
|
_nextAdded:KVChangeRecord;
|
|
|
|
_nextRemoved:KVChangeRecord;
|
|
|
|
_prevRemoved:KVChangeRecord;
|
|
|
|
_nextChanged:KVChangeRecord;
|
2014-10-29 21:56:31 +01:00
|
|
|
|
|
|
|
constructor(key) {
|
|
|
|
this.key = key;
|
|
|
|
this._previousValue = null;
|
|
|
|
this._currentValue = null;
|
|
|
|
|
|
|
|
this._nextPrevious = null;
|
|
|
|
this._next = null;
|
|
|
|
this._nextAdded = null;
|
|
|
|
this._nextRemoved = null;
|
|
|
|
this._prevRemoved = null;
|
|
|
|
this._nextChanged = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
toString():string {
|
|
|
|
return looseIdentical(this._previousValue, this._currentValue) ?
|
|
|
|
stringify(this.key) :
|
|
|
|
(stringify(this.key) + '[' + stringify(this._previousValue) + '->' +
|
|
|
|
stringify(this._currentValue) + ']');
|
|
|
|
}
|
|
|
|
}
|