webgme-engine
Version:
WebGME server and Client API without a GUI
1,247 lines (1,108 loc) • 128 kB
JavaScript
/*globals define*/
/*eslint-env node, browser*/
/**
* @author kecso / https://github.com/kecso
*/
define([
'common/util/canon',
'common/core/tasync',
'common/core/CoreAssert',
'common/regexp',
'common/util/random',
'common/core/constants',
'common/util/diff'
], function (CANON, TASYNC, ASSERT, REGEXP, RANDOM, CONSTANTS, DIFF) {
'use strict';
function DiffCore(innerCore, options) {
ASSERT(typeof options === 'object');
ASSERT(typeof options.globConf === 'object');
ASSERT(typeof options.logger !== 'undefined');
var logger = options.logger,
self = this,
key,
_conflictMine,
_conflictTheirs,
_concatBase,
_concatExtension,
_concatBaseRemovals,
_concatMoves;
for (key in innerCore) {
this[key] = innerCore[key];
}
logger.debug('initialized DiffCore');
//<editor-fold=Helper Functions>
function compareRelids(a, b) {
var aRel = self.getRelid(a),
bRel = self.getRelid(b);
if (aRel < bRel) {
return -1;
} else if (aRel > bRel) {
return 1;
} else {
return 0;
}
}
function normalize(obj) {
// TODO: Does this really need to be called as many times as it is?
if (!obj) {
return obj;
}
var keys = Object.keys(obj),
i;
if (JSON.stringify(obj.set) === JSON.stringify({})) {
delete obj.set;
}
for (i = 0; i < keys.length; i++) {
/*if (Array.isArray(obj[keys[i]])) {
if (obj[keys[i]].length === 0) {
delete obj[keys[i]];
}*/
if (Array.isArray(obj[keys[i]])) {
//do nothing, leave the array as is
} else if (obj[keys[i]] === undefined) {
delete obj[keys[i]]; //there cannot be undefined in the object
} else if (keys[i] === 'set') {
//do nothing with set as it can include empty set's as well
} else if (typeof obj[keys[i]] === 'object') {
normalize(obj[keys[i]]);
if (obj[keys[i]] && Object.keys(obj[keys[i]]).length === 0) {
delete obj[keys[i]];
}
}
}
keys = JSON.parse(JSON.stringify(obj));
delete keys.guid;
delete keys.oGuids;
delete keys.ooGuids;
delete keys.oBaseGuids;
delete keys.ooBaseGuids;
if (Object.keys(keys).length === 0) {
// it did not have additional information
delete obj.guid;
delete obj.oGuids;
delete obj.ooGuids;
delete obj.oBaseGuids;
delete obj.ooBaseGuids;
}
}
function attrDiff(source, target) {
var sNames = self.getOwnAttributeNames(source),
tNames = self.getOwnAttributeNames(target),
diff = {},
sAttr,
tAttr,
i;
for (i = 0; i < sNames.length; i++) {
if (tNames.indexOf(sNames[i]) === -1) {
diff[sNames[i]] = CONSTANTS.TO_DELETE_STRING;
}
}
for (i = 0; i < tNames.length; i++) {
sAttr = self.getOwnAttribute(source, tNames[i]);
tAttr = self.getOwnAttribute(target, tNames[i]);
if (CANON.stringify(sAttr) !== CANON.stringify(tAttr)) {
diff[tNames[i]] = tAttr;
}
}
return diff;
}
function regDiff(source, target) {
var sNames = self.getOwnRegistryNames(source),
tNames = self.getOwnRegistryNames(target),
diff = {},
sReg,
tReg,
i;
for (i = 0; i < sNames.length; i++) {
if (tNames.indexOf(sNames[i]) === -1) {
diff[sNames[i]] = CONSTANTS.TO_DELETE_STRING;
}
}
for (i = 0; i < tNames.length; i++) {
sReg = self.getOwnRegistry(source, tNames[i]);
tReg = self.getOwnRegistry(target, tNames[i]);
if (CANON.stringify(sReg) !== CANON.stringify(tReg)) {
diff[tNames[i]] = tReg;
}
}
return diff;
}
function childrenDiff(source, target) {
var sRelids = self.getChildrenRelids(source, true),
tRelids = self.getChildrenRelids(target, true),
tHashes = self.getChildrenHashes(target),
sHashes = self.getChildrenHashes(source),
relid,
diff = {added: [], removed: []};
for (relid in sRelids) {
if (Object.hasOwn(tRelids, relid) === false) {
diff.removed.push({relid: relid, hash: sHashes[relid]});
}
}
for (relid in tRelids) {
if (Object.hasOwn(sRelids, relid) === false) {
diff.added.push({relid: relid, hash: tHashes[relid]});
}
}
return diff;
}
function pointerDiff(source, target) {
// FIXME: Shouldn't these be ownPointerNames?
var getPointerData = function (node) {
var data = {},
names = self.getPointerNames(node),
i;
for (i = 0; i < names.length; i++) {
data[names[i]] = self.getPointerPath(node, names[i]);
}
return data;
},
sPointer = getPointerData(source),
tPointer = getPointerData(target);
if (CANON.stringify(sPointer) !== CANON.stringify(tPointer)) {
return {source: sPointer, target: tPointer};
}
return {};
}
function setDiff(source, target) {
var getSetData = function (node) {
var data = {},
names, targets, keys, i, j, k;
names = self.getSetNames(node);
for (i = 0; i < names.length; i++) {
data[names[i]] = {attr: {}, reg: {}};
keys = self.getOwnSetAttributeNames(node, names[i]);
for (j = 0; j < keys.length; j += 1) {
data[names[i]].attr[keys[j]] = self.getOwnSetAttribute(node, names[i], keys[j]);
}
keys = self.getOwnSetRegistryNames(node, names[i]);
for (j = 0; j < keys.length; j += 1) {
data[names[i]].reg[keys[j]] = self.getOwnSetRegistry(node, names[i], keys[j]);
}
targets = self.getMemberPaths(node, names[i]);
for (j = 0; j < targets.length; j++) {
data[names[i]][targets[j]] = {attr: {}, reg: {}};
keys = self.getMemberOwnAttributeNames(node, names[i], targets[j]);
for (k = 0; k < keys.length; k++) {
data[names[i]][targets[j]].attr[keys[k]] = self.getMemberAttribute(node,
names[i], targets[j], keys[k]);
}
keys = self.getMemberRegistryNames(node, names[i], targets[j]);
for (k = 0; k < keys.length; k++) {
data[names[i]][targets[j]].reg[keys[k]] = self.getMemberRegistry(node,
names[i], targets[j], keys[k]);
}
}
}
return data;
},
sSet = getSetData(source),
tSet = getSetData(target);
if (CANON.stringify(sSet) !== CANON.stringify(tSet)) {
return {source: sSet, target: tSet};
}
return {};
}
function ovrDiff(source, target) {
var sOvr = self.getRawOverlayInformation(source),
tOvr = self.getRawOverlayInformation(target);
if (CANON.stringify(sOvr) !== CANON.stringify(tOvr)) {
return {source: sOvr, target: tOvr};
}
return {};
}
function metaDiff(source, target) {
var convertJsonMeta = function (jsonMeta) {
var i, j, names, itemsObject;
//children
if (jsonMeta.children) {
itemsObject = jsonMeta.children;
for (i = 0; i < itemsObject.items.length; i += 1) {
itemsObject[itemsObject.items[i]] = {
min: itemsObject.minItems[i],
max: itemsObject.maxItems[i]
};
}
delete itemsObject.items;
delete itemsObject.minItems;
delete itemsObject.maxItems;
}
//ptr
if (jsonMeta.pointers) {
names = Object.keys(jsonMeta.pointers);
for (j = 0; j < names.length; j += 1) {
itemsObject = jsonMeta.pointers[names[j]];
for (i = 0; i < itemsObject.items.length; i += 1) {
itemsObject[itemsObject.items[i]] = {
min: itemsObject.minItems[i],
max: itemsObject.maxItems[i]
};
}
delete itemsObject.items;
delete itemsObject.minItems;
delete itemsObject.maxItems;
}
}
return jsonMeta;
},
sMeta = convertJsonMeta(self.getOwnJsonMeta(source)),
tMeta = convertJsonMeta(self.getOwnJsonMeta(target));
if (CANON.stringify(sMeta) !== CANON.stringify(tMeta)) {
return {source: sMeta, target: tMeta};
}
return {};
}
function combineMoveIntoMetaDiff(diff, diffMoves) {
var keys = Object.keys(diff),
i;
for (i = 0; i < keys.length; i++) {
if (diffMoves[keys[i]]) {
diff[diffMoves[keys[i]]] = diff[keys[i]];
delete diff[keys[i]];
} else if (typeof diff[keys[i]] === 'object') {
combineMoveIntoMetaDiff(diff[keys[i]], diffMoves);
}
}
}
function combineMoveIntoPointerDiff(diff, diffMoves) {
var keys = Object.keys(diff),
i;
for (i = 0; i < keys.length; i++) {
if (diffMoves[diff[keys[i]]]) {
diff[keys[i]] = diffMoves[diff[keys[i]]];
}
}
}
function getDiffChildrenRelids(diff) {
var keys = Object.keys(diff || {}),
i,
filteredKeys = [];
for (i = 0; i < keys.length; i++) {
if (DIFF.FORBIDDEN_WORDS[keys[i]] !== true) {
filteredKeys.push(keys[i]);
}
}
return filteredKeys;
}
function arrayDiff(source, target) {
var i,
diff = {};
for (i = 0; i < source.length; i += 1) {
if (target.indexOf(source[i]) === -1) {
diff[source[i]] = CONSTANTS.TO_DELETE_STRING;
}
}
for (i = 0; i < target.length; i += 1) {
if (source.indexOf(target[i])) {
diff[target[i]] = true;
}
}
return diff;
}
function diffObjects(source, target) {
var diff = {},
sKeys = Object.keys(source),
tKeys = Object.keys(target),
tDiff, i;
for (i = 0; i < sKeys.length; i++) {
if (tKeys.indexOf(sKeys[i]) === -1) {
diff[sKeys[i]] = CONSTANTS.TO_DELETE_STRING;
}
}
for (i = 0; i < tKeys.length; i++) {
if (sKeys.indexOf(tKeys[i]) === -1) {
diff[tKeys[i]] = target[tKeys[i]];
} else {
if (typeof target[tKeys[i]] === typeof source[tKeys[i]]) {
tDiff = {};
if (source[tKeys[i]] instanceof Array && target[tKeys[i]] instanceof Array) {
tDiff = arrayDiff(source[tKeys[i]], target[tKeys[i]]);
} else if (typeof target[tKeys[i]] === 'object' &&
target[tKeys[i]] !== null && source[tKeys[i]] !== null) {
tDiff = diffObjects(source[tKeys[i]], target[tKeys[i]]);
} else if (source[tKeys[i]] !== target[tKeys[i]]) {
diff[tKeys[i]] = target[tKeys[i]];
}
if (Object.keys(tDiff).length > 0) {
diff[tKeys[i]] = tDiff;
}
}
}
}
return diff;
}
function finalizeMetaDiff(diff, diffMoves) {
// At this point diff is ready and the diffMoves are complete.
var relids = getDiffChildrenRelids(diff),
i, sMeta, tMeta;
if (diff.meta) {
sMeta = diff.meta.source || {};
tMeta = diff.meta.target || {};
combineMoveIntoMetaDiff(sMeta, diffMoves);
diff.meta = diffObjects(sMeta, tMeta);
}
for (i = 0; i < relids.length; i++) {
finalizeMetaDiff(diff[relids[i]], diffMoves);
}
}
function finalizePointerDiff(diff, diffMoves) {
var relids = getDiffChildrenRelids(diff),
i, sPointer, tPointer;
if (diff.pointer) {
sPointer = diff.pointer.source || {};
tPointer = diff.pointer.target || {};
/*if(diff.movedFrom && !sPointer.base && tPointer.base){
delete tPointer.base;
}*/
combineMoveIntoPointerDiff(sPointer, diffMoves);
diff.pointer = diffObjects(sPointer, tPointer);
}
for (i = 0; i < relids.length; i++) {
finalizePointerDiff(diff[relids[i]], diffMoves);
}
}
function finalizeSetDiff(diff, diffMoves) {
var relids = getDiffChildrenRelids(diff),
i, sSet, tSet;
if (diff.set) {
sSet = diff.set.source || {};
tSet = diff.set.target || {};
combineMoveIntoMetaDiff(sSet, diffMoves);
diff.set = diffObjects(sSet, tSet);
}
for (i = 0; i < relids.length; i++) {
finalizeSetDiff(diff[relids[i]], diffMoves);
}
}
function finalizeDiff(diff, diffMoves) {
finalizeMetaDiff(diff, diffMoves);
finalizePointerDiff(diff, diffMoves);
finalizeSetDiff(diff, diffMoves);
normalize(diff);
}
function isEmptyNodeDiff(diff) {
// TODO: This could probably be reversed and optimized.
if (
Object.keys(diff.children || {}).length > 0 ||
Object.keys(diff.attr || {}).length > 0 ||
Object.keys(diff.reg || {}).length > 0 ||
Object.keys(diff.pointer || {}).length > 0 ||
Object.keys(diff.set || {}).length > 0 ||
diff.meta
) {
return false;
}
return true;
}
function getPathOfDiff(diff, path) {
var pathArray = path.split('/'),
i;
pathArray.shift();
for (i = 0; i < pathArray.length; i++) {
diff[pathArray[i]] = diff[pathArray[i]] || {};
diff = diff[pathArray[i]];
}
return diff;
}
function extendDiffWithOvr(diff, oDiff) {
var i, paths, names, j, tDiff,
onlyBaseRemoved = function (path) {
var sCopy = JSON.parse(JSON.stringify(oDiff.source[path] || {})),
tCopy = JSON.parse(JSON.stringify(oDiff.target[path] || {}));
if (tCopy.base) {
return false;
}
delete sCopy.base;
return CANON.stringify(sCopy) === CANON.stringify(tCopy);
};
//first extend sources
paths = Object.keys(oDiff.source || {});
for (i = 0; i < paths.length; i++) {
tDiff = getPathOfDiff(diff, paths[i]);
if (tDiff.removed !== true && !onlyBaseRemoved(paths[i])) {
tDiff.pointer = tDiff.pointer || {source: {}, target: {}};
tDiff.pointer.source = tDiff.pointer.source || {};
tDiff.pointer.target = tDiff.pointer.target || {};
names = Object.keys(oDiff.source[paths[i]]);
for (j = 0; j < names.length; j++) {
tDiff.pointer.source[names[j]] = oDiff.source[paths[i]][names[j]];
}
}
}
//then targets
paths = Object.keys(oDiff.target || {});
for (i = 0; i < paths.length; i++) {
tDiff = getPathOfDiff(diff, paths[i]);
if (tDiff.removed !== true && !onlyBaseRemoved(paths[i])) {
tDiff.pointer = tDiff.pointer || {source: {}, target: {}};
names = Object.keys(oDiff.target[paths[i]]);
for (j = 0; j < names.length; j++) {
tDiff.pointer.target[names[j]] = oDiff.target[paths[i]][names[j]];
}
}
}
}
function gatherObstructiveGuids(node) {
var result = {all: {}, bases: {}},
putParents = function (n) {
result.bases[self.getGuid(n)] = true;
while (n) {
result.all[self.getGuid(n)] = true;
n = self.getParent(n);
}
};
while (node) {
putParents(node);
node = self.getBase(node);
}
return result;
}
function fillMissingGuid(root, sRoot, path, diff) {
var relids = getDiffChildrenRelids(diff),
i,
done,
subComputationFinished = function (cDiff, relid) {
diff[relid] = cDiff;
return null;
};
for (i = 0; i < relids.length; i++) {
done = TASYNC.call(subComputationFinished,
fillMissingGuid(root, sRoot, path + '/' + relids[i], diff[relids[i]]), relids[i], done);
}
return TASYNC.call(function () {
return TASYNC.call(function (child, sChild) {
if (!child) {
child = sChild;
}
diff.guid = self.getGuid(child);
diff.hash = self.getHash(child);
diff.oGuids = gatherObstructiveGuids(child);
diff.oBaseGuids = diff.oGuids.bases;
diff.oGuids = diff.oGuids.all;
return diff;
}, self.loadByPath(root, path), self.loadByPath(sRoot, path));
}, done);
}
function mergeObjects(source, target) {
var merged = {},
sKeys = Object.keys(source),
tKeys = Object.keys(target),
i;
for (i = 0; i < sKeys.length; i++) {
merged[sKeys[i]] = source[sKeys[i]];
}
for (i = 0; i < tKeys.length; i++) {
if (sKeys.indexOf(tKeys[i]) === -1) {
merged[tKeys[i]] = target[tKeys[i]];
} else {
if (typeof target[tKeys[i]] === typeof source[tKeys[i]] &&
typeof target[tKeys[i]] === 'object' && !(target instanceof Array)) {
merged[tKeys[i]] = mergeObjects(source[tKeys[i]], target[tKeys[i]]);
} else {
merged[tKeys[i]] = target[tKeys[i]];
}
}
}
return merged;
}
function updateDiff(sourceRoot, targetRoot, yetToCompute) {
var diff = self.nodeDiff(sourceRoot, targetRoot) || {},
oDiff = ovrDiff(sourceRoot, targetRoot),
getChild = function (childArray, relid) {
// TODO: This seems computational expensive - maybe core.loadChild is faster?
// TODO: Alt. created maps for sChildren and tChildren
for (var i = 0; i < childArray.length; i++) {
if (self.getRelid(childArray[i]) === relid) {
return childArray[i];
}
}
return null;
};
return TASYNC.call(function (sChildren, tChildren) {
ASSERT(sChildren.length >= 0 && tChildren.length >= 0);
sChildren.sort(compareRelids);
tChildren.sort(compareRelids);
var i, child, done, tDiff, guid, base,
childComputationFinished = function (cDiff, relid/*, d*/) {
diff[relid] = cDiff;
return null;
};
tDiff = diff.children ? diff.children.removed || [] : [];
for (i = 0; i < tDiff.length; i++) {
diff.childrenListChanged = true;
child = getChild(sChildren, tDiff[i].relid);
if (child) {
guid = self.getGuid(child);
// FIXME: Isn't the hash already given at childrenDiff?
diff[tDiff[i].relid] = {guid: guid, removed: true, hash: self.getHash(child)};
yetToCompute[guid] = yetToCompute[guid] || {};
yetToCompute[guid].from = child;
yetToCompute[guid].fromExpanded = false;
}
}
tDiff = diff.children ? diff.children.added || [] : [];
for (i = 0; i < tDiff.length; i++) {
diff.childrenListChanged = true;
child = getChild(tChildren, tDiff[i].relid);
if (child) {
guid = self.getGuid(child);
base = self.getBase(child);
diff[tDiff[i].relid] = {
guid: guid,
removed: false,
hash: self.getHash(child),
pointer: {source: {}, target: {base: base === null ? null : self.getPath(base)}}
};
yetToCompute[guid] = yetToCompute[guid] || {};
yetToCompute[guid].to = child;
yetToCompute[guid].toExpanded = false;
}
}
for (i = 0; i < tChildren.length; i++) {
child = getChild(sChildren, self.getRelid(tChildren[i]));
if (child && self.getHash(tChildren[i]) !== self.getHash(child)) {
done = TASYNC.call(childComputationFinished,
updateDiff(child, tChildren[i], yetToCompute), self.getRelid(child), done);
}
}
return TASYNC.call(function () {
delete diff.children;
extendDiffWithOvr(diff, oDiff);
normalize(diff);
if (Object.keys(diff).length > 0) {
diff.guid = self.getGuid(targetRoot);
diff.hash = self.getHash(targetRoot);
diff.oGuids = gatherObstructiveGuids(targetRoot);
diff.oBaseGuids = diff.oGuids.bases;
diff.oGuids = diff.oGuids.all;
return TASYNC.call(function (finalDiff) {
return finalDiff;
}, fillMissingGuid(targetRoot, sourceRoot, '', diff));
} else {
return diff;
}
}, done);
}, self.loadChildren(sourceRoot), self.loadChildren(targetRoot));
}
function expandDiff(root, isDeleted, yetToCompute) {
var diff = {
guid: self.getGuid(root),
hash: self.getHash(root),
removed: isDeleted === true
};
return TASYNC.call(function (children) {
var guid;
for (var i = 0; i < children.length; i++) {
guid = self.getGuid(children[i]);
diff[self.getRelid(children[i])] = {
guid: guid,
hash: self.getHash(children[i]),
removed: isDeleted === true
};
if (isDeleted) {
yetToCompute[guid] = yetToCompute[guid] || {};
yetToCompute[guid].from = children[i];
yetToCompute[guid].fromExpanded = false;
} else {
yetToCompute[guid] = yetToCompute[guid] || {};
yetToCompute[guid].to = children[i];
yetToCompute[guid].toExpanded = false;
}
}
return diff;
}, self.loadChildren(root));
}
function insertIntoDiff(path, diff, sDiff) {
var pathObject = DIFF.pathToObject(path),
pathArray = pathObject.pathArray,
relid = pathArray.pop(),
i;
for (i = 0; i < pathArray.length; i++) {
sDiff = sDiff[pathArray[i]];
}
//sDiff[relid] = diff;
sDiff[relid] = mergeObjects(sDiff[relid], diff);
}
function removePathFromDiff(diff, path) {
var relId, i,
pathObject = DIFF.pathToObject(path),
pathArray = pathObject.pathArray;
relId = pathArray.pop();
for (i = 0; i < pathArray.length; i++) {
diff = diff[pathArray[i]];
}
delete diff[relId];
}
function shrinkDiff(rootDiff) {
var _shrink = function (diff) {
if (diff) {
var keys = getDiffChildrenRelids(diff),
i;
if (typeof diff.movedFrom === 'string') {
removePathFromDiff(rootDiff, diff.movedFrom);
}
if (diff.removed !== false || typeof diff.movedFrom === 'string') {
delete diff.hash;
}
if (diff.removed === true) {
for (i = 0; i < keys.length; i++) {
delete diff[keys[i]];
}
} else {
for (i = 0; i < keys.length; i++) {
_shrink(diff[keys[i]]);
}
}
}
};
_shrink(rootDiff);
}
function insertAtPath(diff, path, object) {
ASSERT(typeof path === 'string');
var i, base,
pathObject = DIFF.pathToObject(path),
relid = pathObject.pathArray.pop();
base = diff;
for (i = 0; i < pathObject.pathArray.length; i += 1) {
base[pathObject.pathArray[i]] = base[pathObject.pathArray[i]] || {};
base = base[pathObject.pathArray[i]];
}
base[relid] = JSON.parse(JSON.stringify(object));
return;
}
function checkRound(yetToCompute, diff, diffMoves, needChecking) {
var guids = Object.keys(yetToCompute),
done,
ytc,
i,
computingMove = function (mDiff, info) {
mDiff.guid = self.getGuid(info.from);
mDiff.movedFrom = self.getPath(info.from);
mDiff.ooGuids = gatherObstructiveGuids(info.from);
mDiff.ooBaseGuids = mDiff.ooGuids.bases;
mDiff.ooGuids = mDiff.ooGuids.all;
diffMoves[self.getPath(info.from)] = self.getPath(info.to);
insertAtPath(diff, self.getPath(info.to), mDiff);
return null;
},
expandFrom = function (mDiff, info) {
mDiff.hash = self.getHash(info.from);
mDiff.removed = true;
insertIntoDiff(self.getPath(info.from), mDiff, diff);
return null;
},
expandTo = function (mDiff, info) {
if (!mDiff.hash) {
mDiff.hash = self.getHash(info.to);
}
mDiff.removed = false;
insertIntoDiff(self.getPath(info.to), mDiff, diff);
return null;
};
if (needChecking !== true || guids.length < 1) {
shrinkDiff(diff);
finalizeDiff(diff, diffMoves);
return JSON.parse(JSON.stringify(diff));
}
needChecking = false;
for (i = 0; i < guids.length; i++) {
ytc = yetToCompute[guids[i]];
if (ytc.from && ytc.to) {
//move
needChecking = true;
delete yetToCompute[guids[i]];
done = TASYNC.call(computingMove, updateDiff(ytc.from, ytc.to, yetToCompute), ytc, done);
} else {
if (ytc.from && ytc.fromExpanded === false) {
//expand from
ytc.fromExpanded = true;
needChecking = true;
done = TASYNC.call(expandFrom, expandDiff(ytc.from, true, yetToCompute), ytc, done);
} else if (ytc.to && ytc.toExpanded === false) {
//expand to
ytc.toExpanded = true;
needChecking = true;
done = TASYNC.call(expandTo, expandDiff(ytc.to, false, yetToCompute), ytc, done);
}
}
}
return TASYNC.call(checkRound, yetToCompute, diff, diffMoves, needChecking, done);
}
function hasRealChange(diffNode) {
var keys = Object.keys(diffNode || {}),
searchedKeywords = {
hash: true,
attr: true,
reg: true,
pointer: true,
set: true,
meta: true,
movedFrom: true,
removed: true,
childrenListChanged: true
},
i;
for (i = 0; i < keys.length; i += 1) {
if (searchedKeywords[keys[i]]) {
return true;
}
}
return false;
}
function getMoveSources(diff, path, toFrom, fromTo) {
var relids = getDiffChildrenRelids(diff),
i;
for (i = 0; i < relids.length; i++) {
getMoveSources(diff[relids[i]], path + '/' + relids[i], toFrom, fromTo);
}
if (typeof diff.movedFrom === 'string') {
toFrom[path] = diff.movedFrom;
fromTo[diff.movedFrom] = path;
}
}
function getParentPath(path) {
path = path.split(CONSTANTS.PATH_SEP);
path.splice(-1, 1);
return path.join(CONSTANTS.PATH_SEP);
}
function getNodeByGuid(diff, guid) {
var relids, i, node;
if (REGEXP.GUID.test(guid) !== true) {
return null;
}
if (diff.guid === guid) {
return diff;
}
relids = getDiffChildrenRelids(diff);
for (i = 0; i < relids.length; i++) {
node = getNodeByGuid(diff[relids[i]], guid);
if (node) {
return node;
}
}
return null;
}
function _getPathOfGuidR(diff, guid, path) {
var relids, i, result;
if (diff.guid === guid) {
return path;
}
relids = getDiffChildrenRelids(diff);
for (i = 0; i < relids.length; i++) {
result = _getPathOfGuidR(diff[relids[i]], guid, path + CONSTANTS.PATH_SEP + relids[i]);
if (result !== null) {
return result;
}
}
return null;
}
function getPathOfGuid(diff, guid) {
if (REGEXP.GUID.test(guid) !== true) {
return null;
}
return _getPathOfGuidR(diff, guid, '');
}
function getRelidFromPath(path) {
path = path.split(CONSTANTS.PATH_SEP);
return path.splice(-1, 1)[0];
}
function getParentGuid(diff, path) {
return getPathOfDiff(diff, getParentPath(path)).guid || null;
}
function fixInheritanceCollision(path, diffBase, diffExtension, moveBase) {
// a generic approach to check for complex collisions, when the same
// path is being created by changes in the base of some container and
// inside the container by either move or creation
// also it moves new nodes whenever any of its container changed base -
// not necessarily able to figure out, so it is safer to reallocate relid in this rare case
var i,
diff = getPathOfDiff(diffBase, path),
keys = getDiffChildrenRelids(diff),
newRelid,
newPath,
parent,
src2dst,
dst2src,
checkContainer = function (containerGuid, relativePath) {
var usedDiff, path, containerDiff, baseGuids, i, baseDiff, dataKnownInExtension;
containerDiff = getNodeByGuid(diffExtension, containerGuid);
if (containerDiff === null) {
containerDiff = getNodeByGuid(diffBase, containerGuid);
usedDiff = diffBase;
path = getPathOfGuid(usedDiff, containerGuid);
} else {
dataKnownInExtension = true;
usedDiff = diffExtension;
path = getPathOfGuid(usedDiff, containerGuid);
}
baseGuids = Object.keys(containerDiff.oBaseGuids || {})
.concat(Object.keys(containerDiff.ooBaseGuids || {}));
for (i = 0; i < baseGuids.length; i += 1) {
baseDiff = getPathOfDiff(getNodeByGuid(diffExtension, baseGuids[i]) || {}, relativePath);
if (baseDiff.removed === false || typeof baseDiff.movedFrom === 'string') {
//the base exists / changed and new at the given path
return true;
}
}
if (dataKnownInExtension &&
containerDiff.pointer &&
typeof containerDiff.pointer.base === 'string') {
// the container changed its base
return true;
}
//this parent was fine, so let's go to the next one - except the root, that we do not have to check
relativePath = CONSTANTS.PATH_SEP + getRelidFromPath(path) + relativePath;
if (getParentPath(path)) {
// we should stop before the ROOT
return checkContainer(
getParentGuid(diffExtension, path) || getParentGuid(diffBase, path),
relativePath
);
}
return false;
};
if (diff.removed === false || typeof diff.movedFrom === 'string') {
// this is a new node at the given place, so let's check for base collisions
if (checkContainer(getParentGuid(diffBase, path), CONSTANTS.PATH_SEP + getRelidFromPath(path))) {
// we have to move the node
if (moveBase === true) {
dst2src = _concatMoves.getBaseSourceFromDestination;
src2dst = _concatMoves.getBaseDestinationFromSource;
} else {
dst2src = _concatMoves.getExtensionSourceFromDestination;
src2dst = _concatMoves.getExtensionDestinationFromSource;
}
//TODO is there a safer way to ensure no collision with the new relid
newRelid = RANDOM.generateRelid({}, CONSTANTS.MAXIMUM_STARTING_RELID_LENGTH);
newPath = getParentPath(path) + '/' + newRelid;
//now the actual place switching
parent = getPathOfDiff(diffBase, getParentPath(path));
parent[newRelid] = diff;
parent[newRelid].collidingRelid = getRelidFromPath(path);
delete parent[getRelidFromPath(path)];
dst2src[newPath] = dst2src[path];
delete dst2src[path];
src2dst[dst2src[newPath]] = newPath;
}
}
for (i = 0; i < keys.length; i += 1) {
fixInheritanceCollision(path + CONSTANTS.PATH_SEP + keys[i], diffBase, diffExtension, moveBase);
}
}
function fixCollision(path, relid, diffBase, diffExtension) {
//a generic approach, to check if both diff has the same path
// but for a different node
//there is three types of path equality:
//1. same guids -> same node
//2. both was moved -> different nodes
//3. one was moved and the other is created ->different nodes (here we always have to generate
// new relid to the moved one)
//4. both was created (we have to generate relid to one of them)
var i,
keys = getDiffChildrenRelids(diffBase),
globalDiff,
newRelid,
newPath,
nodeDiff,
relids,
dst2src,
src2dst,
relidObj = {},
parent;
if (diffBase.guid !== diffExtension.guid &&
(typeof diffBase.guid === 'string' && typeof diffExtension.guid === 'string')) {
if (diffBase.movedFrom && diffExtension.movedFrom) {
//relocate the extension
globalDiff = _concatExtension;
nodeDiff = diffExtension;
dst2src = _concatMoves.getExtensionSourceFromDestination;
src2dst = _concatMoves.getExtensionDestinationFromSource;
} else if (diffBase.movedFrom && diffExtension.removed === false) {
globalDiff = _concatBase;
nodeDiff = diffBase;
dst2src = _concatMoves.getBaseSourceFromDestination;
src2dst = _concatMoves.getBaseDestinationFromSource;
} else if (diffExtension.movedFrom && diffBase.removed === false) {
globalDiff = _concatExtension;
nodeDiff = diffExtension;
dst2src = _concatMoves.getExtensionSourceFromDestination;
src2dst = _concatMoves.getExtensionDestinationFromSource;
} else if (diffBase.removed === false && diffExtension.removed === false) {
globalDiff = _concatExtension;
nodeDiff = diffExtension;
dst2src = _concatMoves.getExtensionSourceFromDestination;
src2dst = _concatMoves.getExtensionDestinationFromSource;
} else {
throw new Error('there is a guid mismatch among the two diffs: ' +
diffBase.guid + ' vs ' + diffExtension.guid);
}
relids = getDiffChildrenRelids(getPathOfDiff(_concatBase, getParentPath(path)))
.concat(getDiffChildrenRelids(getPathOfDiff(_concatExtension, getParentPath(path))));
relidObj = {};
for (i = 0; i < relids.length; i += 1) {
relidObj[relids[i]] = {};
}
// TODO: Could this lead to collisions on bases/instances?
newRelid = RANDOM.generateRelid(relidObj);
newPath = getParentPath(path) + '/' + newRelid;
//now the actual place switching
parent = getPathOfDiff(globalDiff, getParentPath(path));
parent[newRelid] = nodeDiff;
parent[newRelid].collidingRelid = relid;
delete parent[relid];
dst2src[newPath] = dst2src[path];
delete dst2src[path];
src2dst[dst2src[newPath]] = newPath;
}
//recursive calls - only if there were no replacement due to collision
for (i = 0; i < keys.length; i += 1) {
if (diffExtension[keys[i]]) {
fixCollision(path + '/' + keys[i], keys[i], diffBase[keys[i]], diffExtension[keys[i]]);
}
}
}
function getAncestorPath(onePath, otherPath) {
var ancestorPath = '',
onePathArray = onePath.split('/'),
otherPathArray = otherPath.split('/'),
i = 0;
onePathArray.shift();
otherPathArray.shift();
if (onePathArray.length > 0 && otherPathArray.length > 0) {
while (i < onePathArray.length && onePathArray[i] === otherPathArray[i]) {
ancestorPath += '/' + onePathArray[i];
i += 1;
}
}
return ancestorPath;
}
function setBaseOfNewNode(root, nodePath, basePath) {
var ancestorPath = getAncestorPath(nodePath, basePath);
return TASYNC.call(function (node) {
var sourcePath = nodePath.substr(ancestorPath.length),
targetPath = basePath.substr(ancestorPath.length);
innerCore.overlayInsert(node, sourcePath, 'base', targetPath);
}, self.loadByPath(root, ancestorPath));
}
function getOrderedRelids(diffObject) {
//those nodes that were changing relid as a result of move should be handled last
var keys = getDiffChildrenRelids(diffObject),
i,
ordered = [],
sourceRelid;
for (i = 0; i < keys.length; i += 1) {
if (diffObject[keys[i]].movedFrom) {
sourceRelid = diffObject[keys[i]].movedFrom;
sourceRelid = sourceRelid.split('/');
sourceRelid = sourceRelid[sourceRelid.length - 1];
if (sourceRelid !== keys[i]) {
ordered.push(keys[i]);
} else {
ordered.unshift(keys[i]);
}
} else {
ordered.unshift(keys[i]);
}
}
return ordered;
}
function makeInitialContainmentChanges(node, diff) {
var relids = getOrderedRelids(diff),
i, done, child, moved,
moving = function (n, di, r, p, m, a/*, d*/) {
var nRelid;
if (m === true) {
n = self.moveNode(n, p);
nRelid = self.getRelid(n);
if (r !== nRelid) {
//we have to make additional changes to our move table
diff[nRelid] = JSON.parse(JSON.stringify(diff[r]));
delete diff[r];
}
}
return makeInitialContainmentChanges(n, di, a);
};
for (i = 0; i < relids.length; i++) {
moved = false;
if (diff[relids[i]].movedFrom) {
//moved node
moved = true;
child = self.loadByPath(self.getRoot(node), diff[relids[i]].movedFrom);
done = TASYNC.call(moving, child, diff[relids[i]], relids[i], node, moved, done);
} else if (diff[relids[i]].removed === false) {
//added node
if (diff[relids[i]].hash) {
self.setProperty(node, relids[i], diff[relids[i]].hash);
node.childrenRelids = null;
}
} else {
//simple node
child = self.loadChild(node, relids[i]);
done = TASYNC.call(moving, child, diff[relids[i]], relids[i], node, moved, done);
}
}
return TASYNC.call(function (/*d*/) {
return null;
}, done);
}
function setBaseRelationsOfNewNodes(root, path, diff, added) {
var relids = getOrderedRelids(diff),
i,
children = [],
newNode = false;
for (i = 0; i < relids.length; i += 1) {
if ((diff[relids[i]].removed === false || added) &&
diff[relids[i]].pointer && diff[relids[i]].pointer.base) {
newNode = true;
children[i] = TASYNC.join(
setBaseOfNewNode(root, path + '/' + relids[i], diff[relids[i]].pointer.base),
setBaseRelationsOfNewNodes(root, path + '/' + relids[i], diff[relids[i]], added || newNode)
);
} else {
children[i] = TASYNC.call(
setBaseRelationsOfNewNodes, root, path + '/' + relids[i], diff[relids[i]], added
);
}
}
return TASYNC.lift(children);
}
function applyAttributeChanges(node, attrDiff) {
var i, keys;
keys = Object.keys(attrDiff);
for (i = 0; i < keys.length; i++) {
if (attrDiff[keys[i]] === CONSTANTS.TO_DELETE_STRING) {
self.delAttribute(node, keys[i]);
} else {
self.setAttribute(node, keys[i], attrDiff[keys[i]]);
}
}
}
function applyRegistryChanges(node, regDiff) {
var i, keys;
keys = Object.keys(regDiff);
for (i = 0; i < keys.length; i++) {
if (regDiff[keys[i]] === CONSTANTS.TO_DELETE_STRING) {
self.delRegistry(node, keys[i]);
} else {
self.setRegistry(node, keys[i], regDiff[keys[i]]);
}
}
}
function setPointer(node, name, target) {
var targetNode;
if (target === null) {
targetNode = null;
} else {
targetNode = self.loadByPath(self.getRoot(node), target);
}
return TASYNC.call(function (t) {
//TODO watch if handling of base changes!!!
self.setPointer(node, name, t);
return;
}, targetNode);
}
function applyPointerChanges(node, diff) {
var done,
pointerDiff = diff.pointer || {},
keys = Object.keys(pointerDiff),
i;