diffusion
Version:
Diffusion JavaScript client
411 lines (325 loc) • 12 kB
JavaScript
var _implements = require('util/interface')._implements;
var identity = require('util/function').identity;
var BufferOutputStream = require('io/buffer-output-stream');
var BinaryDeltaTypeImpl = require('data/binary/binary-delta-type-impl');
var BytesImpl = require('data/bytes-impl');
var RecordV2Type = require('../../../data/record/record');
var BINARY_DELTA_TYPE = new BinaryDeltaTypeImpl(RecordV2Impl, from, identity);
var RECORD_PARSER = require('data/record/recordv2-parser');
var RecordModelImpl = require('data/record/model/record-model-impl');
var RecordV2Delta = require('data/record/recordv2-delta-impl');
var utils = require('data/record/recordv2-utils');
function from(value) {
if (value instanceof RecordV2Impl) {
return value;
} else {
return new RecordV2Impl(value);
}
}
function RecordV2Impl(buffer, offset, length) {
// eslint-disable-next-line no-underscore-dangle
BytesImpl.__constructor.call(this, buffer, offset, length);
var self = this;
this.apply = function(delta) {
return BINARY_DELTA_TYPE.apply(self, delta);
};
this.toString = function() {
return JSON.stringify(self.asRecords());
};
this.asRecords = function() {
return RECORD_PARSER.parse(self.$buffer, self.$offset, self.$length);
};
this.asFields = function() {
return self.asRecords().reduce(function(fields, record) {
return fields.concat(record);
}, []);
};
this.asModel = function(schema) {
return new RecordModelImpl(RecordV2Impl, schema, self.asRecords(), false);
};
this.asValidatedModel = function(schema) {
return new RecordModelImpl(RecordV2Impl, schema, self.asRecords(), true);
};
this.binaryDiff = function(original) {
return BINARY_DELTA_TYPE.diff(original, self);
};
this.diff = function(original) {
var oldBytes = original.$buffer;
var oldOffset = original.$offset;
var oldLength = original.$length;
var newBytes = self.$buffer;
var newOffset = self.$offset;
var newLength = self.$length;
if (newLength === 0) {
if (oldLength === 0) {
return RecordV2Delta.NO_CHANGE;
}
return deltaForEmptyNewValue();
} else if (utils.isSingleEmptyRecord(newBytes, newOffset, newLength)) {
if (utils.isSingleEmptyRecord(oldBytes, oldOffset, oldLength)) {
return RecordV2Delta.NO_CHANGE;
}
return deltaForSingleEmptyRecordNewValue(utils.recordCount(oldBytes, oldOffset, oldLength));
}
if (oldLength === 0) {
return deltaForAllNewRecords(newBytes, newOffset, newLength);
} else if (utils.isSingleEmptyRecord(oldBytes, oldOffset, oldLength)) {
return deltaForOldSingleEmptyRecord(newBytes, newOffset, newLength);
}
var result = diffRecords(
oldBytes, oldOffset, oldLength,
newBytes, newOffset, newLength);
if (result.hasChanges()) {
var delta = result.getDelta();
return new RecordV2Delta(
delta,
0,
delta.length,
result.getRecordChanges(),
result.getFieldChanges());
} else {
return RecordV2Delta.NO_CHANGE;
}
};
}
function diffRecords(
oldBytes, oldOffset, oldLength,
newBytes, newOffset, newLength) {
var result = new DiffResult();
var oldCount = utils.recordCount(oldBytes, oldOffset, oldLength);
var newCount = utils.recordCount(newBytes, newOffset, newLength);
var same = Math.min(oldCount, newCount);
var oldStart = oldOffset;
var oldEnd = oldOffset;
var newStart = newOffset;
var newEnd = newOffset;
var delimiter = false;
for (var i = 0; i < same; ++i) {
oldEnd = utils.findDelimiter(oldBytes, oldEnd, oldOffset + oldLength, utils.RECORD_DELIMITER);
newEnd = utils.findDelimiter(newBytes, newEnd, newOffset + newLength, utils.RECORD_DELIMITER);
if (delimiter) {
result.write(utils.RECORD_DELIMITER);
} else {
delimiter = true;
}
diffRecord(
i,
oldBytes, oldStart, oldEnd - oldStart,
newBytes, newStart, newEnd - newStart, result);
oldStart = ++oldEnd;
newStart = ++newEnd;
}
if (newCount !== oldCount) {
if (newCount > oldCount) {
result.write(newBytes, newStart - 1, newOffset + newLength - newStart + 1);
}
result.setRecordsChanged(newCount > oldCount, same);
}
return result;
}
function diffRecord(
recordIndex,
oldBytes, oldOffset, oldLength,
newBytes, newOffset, newLength,
result) {
if (newLength === 0) {
if (oldLength !== 0) {
result.setFieldsRemoved(recordIndex, 0);
}
return;
}
if (utils.recordIsSingleEmptyField(newBytes, newOffset, newLength)) {
diffWhenNewIsSingleEmptyField(recordIndex, oldBytes, oldOffset, oldLength, result);
return;
}
if (oldLength === 0) {
result.write(newBytes, newOffset, newLength);
result.setFieldsAdded(recordIndex, 0);
return;
}
if (utils.recordIsSingleEmptyField(oldBytes, oldOffset, oldLength)) {
diffWhenOldIsSingleEmptyField(recordIndex, newBytes, newOffset, newLength, result);
return;
}
diffRecordFields(recordIndex, oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength, result);
}
function diffWhenNewIsSingleEmptyField(recordIndex, oldBytes, oldOffset, oldLength, result) {
if (utils.recordIsSingleEmptyField(oldBytes, oldOffset, oldLength)) {
result.write(utils.FIELD_MU);
} else {
var oldCount = utils.fieldCount(oldBytes, oldOffset, oldLength);
if (oldCount > 1) {
result.setFieldsRemoved(recordIndex, 1);
}
result.write(utils.EMPTY_FIELD);
result.setChanged();
}
}
function diffWhenOldIsSingleEmptyField(recordIndex, newBytes, newOffset, newLength, result) {
var b = newBytes[newOffset];
if (b === utils.EMPTY_FIELD) {
result.write(newBytes, newOffset + 1, newLength - 1);
} else {
result.write(newBytes, newOffset, newLength);
if (b !== utils.FIELD_DELIMITER) {
result.setChanged();
}
}
if (utils.fieldCount(newBytes, newOffset, newLength) > 1) {
result.setFieldsAdded(recordIndex, 1);
}
}
function diffRecordFields(recordIndex, oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength, result) {
var oldCount = utils.fieldCount(oldBytes, oldOffset, oldLength);
var newCount = utils.fieldCount(newBytes, newOffset, newLength);
var same = Math.min(oldCount, newCount);
var oldStart = oldOffset;
var oldEnd = oldOffset;
var newStart = newOffset;
var newEnd = newOffset;
var startSize = result.size();
var delimiter = false;
for (var i = 0; i < same; ++i) {
oldEnd = utils.findDelimiter(oldBytes, oldEnd, oldOffset + oldLength, utils.FIELD_DELIMITER);
newEnd = utils.findDelimiter(newBytes, newEnd, newOffset + newLength, utils.FIELD_DELIMITER);
if (delimiter) {
result.write(utils.FIELD_DELIMITER);
} else {
delimiter = true;
}
diffField(
oldBytes, oldStart, oldEnd - oldStart,
newBytes, newStart, newEnd - newStart,
result);
oldStart = ++oldEnd;
newStart = ++newEnd;
}
if (newCount !== oldCount) {
if (newCount > oldCount) {
result.write(newBytes, newStart - 1, newOffset + newLength - newStart + 1);
result.setFieldsAdded(recordIndex, same);
} else {
result.setFieldsRemoved(recordIndex, same);
}
}
if (newCount === 1 && startSize === result.size()) {
result.write(utils.FIELD_MU);
}
}
function diffField(oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength, result) {
if (fieldsAreEqual(oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength)) {
return;
}
if (newLength === 0) {
result.write(utils.EMPTY_FIELD);
} else {
result.write(newBytes, newOffset, newLength);
}
result.setChanged();
}
function fieldsAreEqual(oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength) {
if (oldLength === newLength) {
if (oldLength === 0 || fieldEquals(oldBytes, oldOffset, newBytes, newOffset, oldLength)) {
return true;
}
} else if (oldLength === 1 && oldBytes[oldOffset] === utils.EMPTY_FIELD && newLength === 0) {
return true;
} else if (newLength === 1 && newBytes[newOffset] === utils.EMPTY_FIELD && oldLength === 0) {
return true;
}
return false;
}
function fieldEquals(oldBytes, oldOffset, newBytes, newOffset, length) {
for (var i = 0; i < length; ++i) {
if (oldBytes[oldOffset + i] !== newBytes[newOffset + i]) {
return false;
}
}
return true;
}
function deltaForEmptyNewValue() {
return new RecordV2Delta(new Buffer([]), 0, 0, [false, 0], {});
}
function deltaForSingleEmptyRecordNewValue(oldRecordCount) {
switch (oldRecordCount) {
case 0 :
return new RecordV2Delta(new Buffer([utils.RECORD_MU]), 0, 1, [true, 0], {});
case 1 :
return new RecordV2Delta(new Buffer([utils.RECORD_MU]), 0, 1, null, { 0 : [false, 0] });
default :
return new RecordV2Delta(new Buffer([utils.RECORD_MU]), 0, 1, [false, 1], { 0 : [false, 0] });
}
}
function deltaForAllNewRecords(newBytes, newOffset, newLength) {
return new RecordV2Delta(newBytes, newOffset, newLength, [true, 0], {});
}
/*
* Return a delta for when the old value is a single empty record.
* This assumes that the new record is neither empty nor a single empty
* record.
*/
function deltaForOldSingleEmptyRecord(newBytes, newOffset, newLength) {
var newRecordCount = utils.recordCount(newBytes, newOffset, newLength);
if (newRecordCount === 1) {
// Single record so fields added to first
return new RecordV2Delta(
newBytes, newOffset, newLength,
null,
{ 0: [true, 0] });
}
else {
// Records added
return new RecordV2Delta(
newBytes, newOffset, newLength,
[true, 1],
newBytes[0] === utils.RECORD_DELIMITER ? {} : { 0: [true, 0] });
}
}
function DiffResult() {
var delta = new BufferOutputStream();
var hasChanges = false;
var recordChanges = null;
var fieldChanges = {};
this.write = function(b, o, l) {
if (o !== undefined && l !== undefined) {
delta.writeMany(b, o, l);
} else {
delta.write(b);
}
};
this.getDelta = function() {
return delta.getBuffer();
};
this.size = function() {
return delta.count;
};
this.hasChanges = function() {
return hasChanges;
};
this.setChanged = function() {
hasChanges = true;
};
this.setRecordsChanged = function(added, index) {
recordChanges = [added, index];
hasChanges = true;
};
this.getRecordChanges = function() {
return recordChanges;
};
this.setFieldsAdded = function(recordIndex, fieldIndex) {
fieldChanges[recordIndex] = [true, fieldIndex];
hasChanges = true;
};
this.setFieldsRemoved = function(recordIndex, fieldIndex) {
fieldChanges[recordIndex] = [false, fieldIndex];
hasChanges = true;
};
this.getFieldChanges = function() {
return fieldChanges;
};
}
module.exports = _implements(RecordV2Type, RecordV2Impl);
module.exports.from = from;
module.exports.toString = function() {
return "RecordV2Impl";
};