UNPKG

webgme-engine

Version:

WebGME server and Client API without a GUI

1,247 lines (1,108 loc) 128 kB
/*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;