/*!
* UI development toolkit for HTML5 (OpenUI5)
* (c) Copyright 2009-2018 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
sap.ui.define(['sap/base/util/deepEqual', 'sap/base/strings/hash'], function(deepEqual, hash) {
"use strict";
/**
* Calculate delta of old list and new list.
*
* This function implements the algorithm described in "A Technique for Isolating Differences Between Files"
* (Commun. ACM, April 1978, Volume 21, Number 4, Pages 264-268).
*
* Items in the arrays are not compared directly. Instead, a substitute symbol is determined for each item
* by applying the provided function fnSymbol
to it. Items with strictly equal symbols are
* assumed to represent the same logical item:
*
* fnSymbol(a) === fnSymbol(b) <=> a 'is logically the same as' b
*
* As an additional constraint, casting the symbols to string should not modify the comparison result.
* If this second constraint is not met, this method might report more diffs than necessary.
*
* If no symbol function is provided, a default implementation is used which applies JSON.stringify
* to non-string items and reduces the strings to a hash code. It is not guaranteed that this default
* implementation fulfills the above constraint in all cases, but it is a compromise between implementation
* effort, generality and performance. If items are known to be non-stringifiable (e.g. because they may
* contain cyclic references) or when hash collisions are likely, an own fnSymbol
function
* must be provided.
*
* The result of the diff is a sequence of update operations, each consisting of a type
* (either "insert"
or "delete"
) and an index
.
* By applying the operations one after the other to the old array, it can be transformed to an
* array whose items are equal to the new array.
*
* @example Sample implementation of the update
* function update(aOldArray, aNewArray) {
*
* // calculate the diff
* var aDiff = diff(aOldArray, aNewArray, __provide_your_symbol_function_here__);
*
* // apply update operations
* aDiff.forEach( function(op) {
*
* // invariant: aOldArray and aNewArray now are equal up to (excluding) op.index
*
* switch ( op.type ) {
* case 'insert':
* // new array contains a new (or otherwise unmapped) item, add it here
* aOldArray.splice(op.index, 0, aNewArray[op.index]);
* break;
* case 'delete':
* // an item is no longer part of the array (or has been moved to another position), remove it
* aOldArray.splice(op.index, 1);
* break;
* default:
* throw new Error('unexpected diff operation type');
* }
*
* });
* }
*
*
* @function
* @since 1.58
* @param {Array} aOld Old Array
* @param {Array} aNew New Array
* @param {function} [fnSymbol] Function to calculate substitute symbols for array items
* @alias module:sap/base/util/array/diff
* @return {Array.<{type:string,index:int}>} List of update operations
* @public
*/
var fnDiff = function(aOld, aNew, fnSymbol){
var mSymbols = {},
aOldRefs = [],
aNewRefs = [],
iOldLine,
vSymbol, oSymbol,
iOld = 0,
iNew = 0,
iOldRefLine,
iNewRefLine,
iOldDistance,
iNewDistance,
aDiff = [];
// If arrays are equal, don't try to diff them
if (aOld === aNew || deepEqual(aOld, aNew)) {
return aDiff;
}
// If no symbol function is provided, we stringify, if it is not type string, and create a hash from it
fnSymbol = fnSymbol || function(vValue) {
if (typeof vValue !== "string") {
vValue = JSON.stringify(vValue) || "";
}
return hash(vValue);
};
// Pass 1
for (var i = 0; i < aNew.length; i++) {
vSymbol = fnSymbol(aNew[i]);
oSymbol = mSymbols[vSymbol];
if (!oSymbol) {
oSymbol = mSymbols[vSymbol] = {
iNewCount: 0,
iOldCount: 0
};
}
oSymbol.iNewCount++;
aNewRefs[i] = {
symbol: oSymbol
};
}
// Pass 2
for (var i = 0; i < aOld.length; i++) {
vSymbol = fnSymbol(aOld[i]);
oSymbol = mSymbols[vSymbol];
if (!oSymbol) {
oSymbol = mSymbols[vSymbol] = {
iNewCount: 0,
iOldCount: 0
};
}
oSymbol.iOldCount++;
oSymbol.iOldLine = i;
aOldRefs[i] = {
symbol: oSymbol
};
}
// Pass 3
for (var i = 0; i < aNewRefs.length; i++) {
oSymbol = aNewRefs[i].symbol;
if (oSymbol.iNewCount === 1 && oSymbol.iOldCount === 1) {
aNewRefs[i].line = oSymbol.iOldLine;
aOldRefs[oSymbol.iOldLine].line = i;
}
}
// Pass 4
for (var i = 0; i < aNewRefs.length - 1; i++) {
iOldLine = aNewRefs[i].line;
if (iOldLine !== undefined && iOldLine < aOldRefs.length - 1) {
if (aOldRefs[iOldLine + 1].symbol === aNewRefs[i + 1].symbol) {
aOldRefs[iOldLine + 1].line = i + 1;
aNewRefs[i + 1].line = iOldLine + 1;
}
}
}
// Pass 5
for (var i = aNewRefs.length - 1; i > 0; i--) {
iOldLine = aNewRefs[i].line;
if (iOldLine !== undefined && iOldLine > 0) {
if (aOldRefs[iOldLine - 1].symbol === aNewRefs[i - 1].symbol) {
aOldRefs[iOldLine - 1].line = i - 1;
aNewRefs[i - 1].line = iOldLine - 1;
}
}
}
// Create diff
while (iOld < aOld.length || iNew < aNew.length) {
iNewRefLine = aOldRefs[iOld] && aOldRefs[iOld].line;
iOldRefLine = aNewRefs[iNew] && aNewRefs[iNew].line;
if (iOld < aOld.length && (iNewRefLine === undefined || iNewRefLine < iNew)) {
aDiff.push({
index: iNew,
type: "delete"
});
iOld++;
} else if (iNew < aNew.length && (iOldRefLine === undefined || iOldRefLine < iOld)) {
aDiff.push({
index: iNew,
type: "insert"
});
iNew++;
} else if (iNew === iNewRefLine) {
iNew++;
iOld++;
} else {
iNewDistance = iNewRefLine - iNew;
iOldDistance = iOldRefLine - iOld;
if (iNewDistance <= iOldDistance) {
aDiff.push({
index: iNew,
type: "insert"
});
iNew++;
} else {
aDiff.push({
index: iNew,
type: "delete"
});
iOld++;
}
}
}
return aDiff;
};
return fnDiff;
});