webgme-engine
Version:
WebGME server and Client API without a GUI
1,262 lines (1,051 loc) • 59.9 kB
JavaScript
/*globals define*/
/*eslint-env node, browser*/
/**
* @author mmaroti / https://github.com/mmaroti
*/
define([
'common/core/CoreAssert',
'common/core/tasync',
'common/util/random',
'common/core/constants',
'common/storage/constants',
'common/util/key',
'common/regexp'
], function (ASSERT, TASYNC, RANDOM, CONSTANTS, STORAGE_CONSTANTS, generateKey, REGEXP) {
'use strict';
function InverseOverlaysCache(maxSize, logger) {
var self = this;
maxSize = maxSize || 10000;
this._backup = {};
this._cache = {};
this._size = 0;
this.getItem = function (key) {
if (self._cache[key]) {
return self._cache[key];
}
if (self._backup[key]) {
return self._backup[key];
}
return null;
};
this.setItem = function (key, data) {
if (!self._cache[key]) {
if (self._size === maxSize) {
self._size = 0;
self._backup = self._cache;
self._cache = {};
}
self._size += 1;
self._cache[key] = data;
} else {
logger.warn('trying to add inverse relation object multiple times [#' + key + ']');
}
};
}
function CoreRel(innerCore, options) {
ASSERT(typeof options === 'object');
ASSERT(typeof options.globConf === 'object');
ASSERT(typeof innerCore === 'object');
var logger = innerCore.logger,
self = this,
_shardSize = options.globConf.core.overlayShardSize,
_shardingLimit = Math.floor(_shardSize / 2),
key;
for (key in innerCore) {
this[key] = innerCore[key];
}
//removing direct storage functions on this level
delete this.loadObject;
delete this.insertObject;
this._inverseCache = new InverseOverlaysCache(options.globConf.core.inverseRelationsCacheSize,
logger.fork('inverseCache'));
logger.debug('initialized CoreRel');
//<editor-fold=Helper Functions>
function test(text, cond) {
if (!cond) {
throw new Error(text);
}
}
function isObject(node) {
node = innerCore.normalize(node);
return typeof node.data === 'object' && node.data !== null;
}
function isValidNodeThrow(node) {
test('coretree', innerCore.isValidNode(node));
test('isobject', isObject(node));
}
function getRelativePointerPathFrom(node, source, name) {
ASSERT(self.isValidNode(node) && typeof source === 'string' && typeof name === 'string');
var target,
ovrInfo;
do {
ovrInfo = self.overlayInquiry(node, source, name);
if (typeof ovrInfo.value === 'string') {
target = ovrInfo.value;
break;
}
source = CONSTANTS.PATH_SEP + innerCore.getRelid(node) + source;
node = innerCore.getParent(node);
} while (node);
return {
target: target,
node: node
};
}
function storeNewInverseOverlays(node) {
var hash = self.getHash(node),
relid;
if (hash && node.inverseOverlays && node.inverseOverlaysMutable) {
self._inverseCache.setItem(hash, node.inverseOverlays);
delete node.inverseOverlaysMutable;
for (relid in node.children) {
storeNewInverseOverlays(node.children[relid]);
}
}
}
function hasShardedOverlays(node) {
return (self.getProperty(node, CONSTANTS.OVERLAYS_PROPERTY) ||
{})[CONSTANTS.OVERLAY_SHARD_INDICATOR] === true;
}
function updateSmallestOverlayShardIndex(node) {
var shardId,
minimalItemCount = _shardSize + 1;
for (shardId in node.overlays) {
if (node.overlays[shardId].itemCount < minimalItemCount) {
minimalItemCount = node.overlays[shardId].itemCount;
node.minimalOverlayShardId = shardId;
}
}
}
function attachOverlays(node) {
if (hasShardedOverlays(node) !== true) {
return node;
}
var overlays = self.getProperty(node, CONSTANTS.OVERLAYS_PROPERTY),
shardId,
shardIds = [],
loadPromises = [];
for (shardId in overlays) {
if (REGEXP.DB_HASH.test(overlays[shardId]) === true) {
shardIds.push(shardId);
loadPromises.push(innerCore.loadObject(overlays[shardId]));
}
}
return TASYNC.call(function (overlayShards) {
var i;
node.overlays = {};
node.overlayMutations = {};
node.overlayInitials = {};
for (i = 0; i < overlayShards.length; i += 1) {
shardId = shardIds[i];
node.overlays[shardId] = overlayShards[i];
node.overlayInitials[shardId] = overlayShards[i];
node.overlayMutations[shardId] = false;
}
updateSmallestOverlayShardIndex(node);
return node;
}, TASYNC.lift(loadPromises));
}
// We only shard regular GME nodes, technical sub-nodes do not get sharded
function shouldHaveShardedOverlays(node) {
return Object.keys(self.getProperty(node, CONSTANTS.OVERLAYS_PROPERTY) || {}).length >=
_shardingLimit && self.getPath(node).indexOf('_') === -1;
}
function addNewOverlayShard(node) {
var overlaysNode = self.getChild(node, CONSTANTS.OVERLAYS_PROPERTY),
shardId = RANDOM.generateRelid(overlaysNode.data),
newShardObject = {
type: STORAGE_CONSTANTS.OVERLAY_SHARD_TYPE,
itemCount: 0,
items: {}
};
newShardObject[self.ID_NAME] = '';
node.overlays[shardId] = newShardObject;
node.overlayMutations[shardId] = true;
self.setProperty(overlaysNode, shardId, null);
return shardId;
}
function removeOverlayShard(node, shardId) {
// At this point the node should always be mutated.
var overlaysNode = self.getChild(node, CONSTANTS.OVERLAYS_PROPERTY);
delete node.overlays[shardId];
delete node.overlayMutations[shardId];
self.deleteProperty(overlaysNode, shardId);
}
function transformOverlays(node) {
var originalOverlays = self.getProperty(node, CONSTANTS.OVERLAYS_PROPERTY),
count = _shardSize,
source,
name,
shardId;
self.deleteChild(node, CONSTANTS.OVERLAYS_PROPERTY);
self.removeChildFromCache(node, CONSTANTS.OVERLAYS_PROPERTY);
self.setProperty(self.getChild(node, CONSTANTS.OVERLAYS_PROPERTY), CONSTANTS.OVERLAY_SHARD_INDICATOR, true);
node.overlays = {};
node.overlayMutations = {};
node.overlayInitials = {};
node.minimalOverlayShardId = null;
for (source in originalOverlays) {
if (source !== CONSTANTS.MUTABLE_PROPERTY) {
for (name in originalOverlays[source]) {
if (name !== CONSTANTS.MUTABLE_PROPERTY) {
if (count >= _shardSize) {
shardId = addNewOverlayShard(node);
node.minimalOverlayShardId = shardId;
count = 0;
}
node.overlays[shardId].items[source] = node.overlays[shardId].items[source] || {};
node.overlays[shardId].items[source][name] = originalOverlays[source][name];
node.overlays[shardId].itemCount += 1;
count += 1;
}
}
}
}
// In the unlikely event that during transition the original shard is empty.
if (Object.keys(node.overlays).length === 0) {
node.minimalOverlayShardId = addNewOverlayShard(node);
}
}
function ensureOverlayShardMutated(node, shardId) {
var overlayNode;
if (node.overlayMutations[shardId] !== true) {
node.overlayMutations[shardId] = true;
node.overlays[shardId] = JSON.parse(JSON.stringify(node.overlays[shardId]));
overlayNode = self.getChild(node, CONSTANTS.OVERLAYS_PROPERTY);
self.setProperty(overlayNode, shardId, null);
}
}
function putEntryIntoOverlayShard(node, shardId, source, name, target) {
if (node.overlays[shardId].itemCount >= _shardSize &&
Object.hasOwn(node.overlays[shardId].items, source) === false) {
shardId = addNewOverlayShard(node);
node.minimalOverlayShardId = shardId;
}
ensureOverlayShardMutated(node, shardId);
node.overlays[shardId].items[source] = node.overlays[shardId].items[source] || {};
node.overlays[shardId].items[source][name] = target;
node.overlays[shardId].itemCount += 1;
if (node.minimalOverlayShardId === shardId && node.overlays[shardId].itemCount >= _shardSize) {
updateSmallestOverlayShardIndex(node);
}
}
function putEntryIntoShardedOverlays(node, source, name, target) {
// At this point we expect that everything was checked and we can simply look for
// the proper place of the entry.
var shardId;
for (shardId in node.overlays) {
if (Object.hasOwn(node.overlays[shardId].items, source)) {
putEntryIntoOverlayShard(node, shardId, source, name, target);
return;
}
}
putEntryIntoOverlayShard(node, node.minimalOverlayShardId, source, name, target);
}
function removeEntryFromShardedOverlays(node, source, name) {
var shardId;
for (shardId in node.overlays) {
if (node.overlays[shardId].items[source]) {
if (typeof node.overlays[shardId].items[source][name] === 'string') {
ensureOverlayShardMutated(node, shardId);
delete node.overlays[shardId].items[source][name];
node.overlays[shardId].itemCount -= 1;
}
break;
}
}
}
function persistShardedOverlays(node, stackedObjects) {
// This recursive function will save objects, right before calling the underlying persist.
var relids,
shardId,
source,
hash,
overlayNode,
shouldUpdateSmallest = false,
i;
if (self.isMutable(node) !== true) {
return;
}
relids = self.getChildrenRelids(node);
for (i = 0; i < relids.length; i += 1) {
if (self.childLoaded(node, relids[i]) === true) {
persistShardedOverlays(self.getChild(node, relids[i]), stackedObjects);
}
}
overlayNode = self.getChild(node, CONSTANTS.OVERLAYS_PROPERTY);
for (shardId in node.overlayMutations) {
// We only remove shards if they were empty at loading as well. Otherwise
// node eventing would be impossible.
if (node.overlayMutations[shardId] === true) {
node.overlayMutations[shardId] = false;
node.overlays[shardId][self.ID_NAME] = '';
node.overlays[shardId].__v = STORAGE_CONSTANTS.VERSION;
// if we persist an empty shard we have to ensure that its hash will be unique
if (node.overlays[shardId].itemCount === 0) {
node.overlays[shardId].oldHash = node.overlayInitials[shardId] ?
node.overlayInitials[shardId][self.ID_NAME] || null : null;
node.overlays[shardId].items = {};
} else {
for (source in node.overlays[shardId].items) {
if (Object.keys(node.overlays[shardId].items[source]).length === 0) {
delete node.overlays[shardId].items[source];
}
}
}
hash = '#' + generateKey(node.overlays[shardId], options.globConf);
node.overlays[shardId][self.ID_NAME] = hash;
innerCore.insertObject(node.overlays[shardId], stackedObjects);
stackedObjects[hash] = {
oldHash: node.overlayInitials[shardId] ? node.overlayInitials[shardId][self.ID_NAME] : null,
oldData: node.overlayInitials[shardId],
newHash: hash,
newData: node.overlays[shardId]
};
self.setProperty(overlayNode, shardId, hash);
shouldUpdateSmallest = true;
} else if (node.overlays[shardId].itemCount === 0) {
removeOverlayShard(node, shardId);
shouldUpdateSmallest = true;
}
}
if (shouldUpdateSmallest) {
updateSmallestOverlayShardIndex(node);
}
}
//</editor-fold>
//<editor-fold=Modified Methods>
this.isValidNode = function (node) {
try {
isValidNodeThrow(node);
return true;
} catch (error) {
logger.error(error.message, {metadata: {stack: error.stack, node: node}});
return false;
}
};
this.persist = function (node) {
var stackedObjects = {},
persisted;
persistShardedOverlays(node, stackedObjects);
persisted = innerCore.persist(node, stackedObjects);
storeNewInverseOverlays(self.getRoot(node));
return persisted;
};
this.loadRoot = function (hash) {
return TASYNC.call(function (root) {
return attachOverlays(root);
}, innerCore.loadRoot(hash));
};
this.loadChild = function (node, relid) {
return TASYNC.call(function (child) {
return attachOverlays(child);
}, innerCore.loadChild(node, relid));
};
this.loadByPath = function (node, relPath) {
return TASYNC.call(function (target) {
return attachOverlays(target);
}, innerCore.loadByPath(node, relPath));
};
//</editor-fold>
//<editor-fold=Added Methods>
this.getInverseOverlayOfNode = function (node) {
var hash,
inverseOverlays = {},
overlay,
overlaysObject,
shardId,
source,
name,
target;
// If the node already has inverse computed we return that
if (node.inverseOverlays) {
return node.inverseOverlays;
}
// If we find it in the cache we set that and use it
hash = self.getHash(node);
if (hash) {
inverseOverlays = self._inverseCache.getItem(hash);
if (inverseOverlays) {
node.inverseOverlays = inverseOverlays;
delete node.inverseOverlaysMutable;
return node.inverseOverlays;
}
}
// Otherwise we have to compute it
if (hasShardedOverlays(node)) {
overlaysObject = node.overlays;
} else {
overlaysObject = {single: {items: self.getProperty(node, CONSTANTS.OVERLAYS_PROPERTY) || {}}};
}
inverseOverlays = {};
for (shardId in overlaysObject) {
overlay = overlaysObject[shardId];
for (source in overlay.items) {
if (source !== CONSTANTS.MUTABLE_PROPERTY) {
for (name in overlay.items[source]) {
if (name !== CONSTANTS.MUTABLE_PROPERTY) {
target = overlay.items[source][name];
inverseOverlays[target] = inverseOverlays[target] || {};
inverseOverlays[target][name] = inverseOverlays[target][name] || [];
inverseOverlays[target][name].push(source);
}
}
}
}
}
// If it is an unmodified node, we can store the inverse, otherwise it still can change
if (hash) {
self._inverseCache.setItem(hash, inverseOverlays);
delete node.inverseOverlaysMutable;
} else {
node.inverseOverlaysMutable = true;
}
node.inverseOverlays = inverseOverlays;
return node.inverseOverlays;
};
this.isPointerName = function (name) {
ASSERT(typeof name === 'string');
//TODO this is needed as now we work with modified data as well
if (name === CONSTANTS.MUTABLE_PROPERTY) {
return false;
}
// return name.slice(-CONSTANTS.COLLECTION_NAME_SUFFIX.length) !==
// CONSTANTS.COLLECTION_NAME_SUFFIX;
return true;
};
this.getAttributeNames = function (node) {
ASSERT(self.isValidNode(node));
var data,
keys,
i,
result = [],
key;
data = (innerCore.getProperty(node, CONSTANTS.ATTRIBUTES_PROPERTY) || {});
keys = Object.keys(data);
i = keys.length;
while (--i >= 0) {
key = keys[i];
if (key.charAt(0) === '') {
logger.error('empty named attribute found in node [' + innerCore.getPath(node) + ']');
//keys.splice(i, 1);
} else if (key.charAt(0) === '_') {
//keys.splice(i, 1);
} else {
result.push(key);
}
}
return result;
};
this.getRegistryNames = function (node) {
ASSERT(self.isValidNode(node));
var data,
keys,
i,
result = [],
key;
data = (innerCore.getProperty(node, CONSTANTS.REGISTRY_PROPERTY) || {});
keys = Object.keys(data);
i = keys.length;
while (--i >= 0) {
key = keys[i];
if (keys[i].charAt(0) === '') {
logger.error('empty named attribute found in node [' + innerCore.getPath(node) + ']');
//keys.splice(i, 1);
} else if (keys[i].charAt(0) === '_') {
//keys.splice(i, 1);
} else {
result.push(key);
}
}
return result;
};
this.getAttribute = function (node, name) {
/*node = coretree.getChild(node, coretree.constants.ATTRIBUTES_PROPERTY);
return coretree.getProperty(node, name);*/
return (innerCore.getProperty(node, CONSTANTS.ATTRIBUTES_PROPERTY) || {})[name];
};
this.delAttribute = function (node, name) {
node = innerCore.getChild(node, CONSTANTS.ATTRIBUTES_PROPERTY);
innerCore.deleteProperty(node, name);
};
this.setAttribute = function (node, name, value) {
node = innerCore.getChild(node, CONSTANTS.ATTRIBUTES_PROPERTY);
innerCore.setProperty(node, name, value);
};
this.renameAttribute = function (node, oldName, newName) {
node = innerCore.getChild(node, CONSTANTS.ATTRIBUTES_PROPERTY);
innerCore.renameProperty(node, oldName, newName);
};
this.getRegistry = function (node, name) {
/*node = coretree.getChild(node, coretree.constants.REGISTRY_PROPERTY);
return coretree.getProperty(node, name);*/
return (innerCore.getProperty(node, CONSTANTS.REGISTRY_PROPERTY) || {})[name];
};
this.delRegistry = function (node, name) {
node = innerCore.getChild(node, CONSTANTS.REGISTRY_PROPERTY);
innerCore.deleteProperty(node, name);
};
this.setRegistry = function (node, name, value) {
node = innerCore.getChild(node, CONSTANTS.REGISTRY_PROPERTY);
innerCore.setProperty(node, name, value);
};
this.renameRegistry = function (node, oldName, newName) {
node = innerCore.getChild(node, CONSTANTS.REGISTRY_PROPERTY);
innerCore.renameProperty(node, oldName, newName);
};
this.overlayInquiry = function (node, source, name) {
// If name is not given, then the whole object returned.
// If no entry found, null is returned.
var shardId,
ordinaryOverlays,
result = {
shardId: null,
value: null
};
if (hasShardedOverlays(node) === true) {
for (shardId in node.overlays) {
if (node.overlays[shardId].items[source]) {
result.shardId = shardId;
if (typeof name === 'string') {
result.value = typeof node.overlays[shardId].items[source][name] === 'string' ?
node.overlays[shardId].items[source][name] : null;
} else {
result.value = node.overlays[shardId].items[source];
}
break;
}
}
} else {
ordinaryOverlays = self.getProperty(node, CONSTANTS.OVERLAYS_PROPERTY) || {};
if (Object.hasOwn(ordinaryOverlays, source)) {
if (typeof name === 'string') {
result.value = typeof ordinaryOverlays[source][name] === 'string' ?
ordinaryOverlays[source][name] : null;
} else {
result.value = ordinaryOverlays[source];
}
}
}
return result;
};
this.overlayRemove = function (node, source, name, target) {
ASSERT(self.isValidNode(node));
ASSERT(innerCore.isValidPath(source) && innerCore.isValidPath(target) && self.isPointerName(name));
ASSERT(innerCore.getCommonPathPrefixData(source, target).common === '');
var currentOverlayInfo = self.overlayInquiry(node, source, name),
index,
overlays,
overlayNode;
ASSERT(currentOverlayInfo.value === target);
if (hasShardedOverlays(node)) {
removeEntryFromShardedOverlays(node, source, name);
} else {
overlays = self.getChild(node, CONSTANTS.OVERLAYS_PROPERTY);
overlayNode = innerCore.getChild(overlays, source);
innerCore.deleteProperty(overlayNode, name);
if (innerCore.getKeys(overlayNode).length === 0) {
innerCore.deleteProperty(overlays, source);
}
}
//Now we check if we need to mutate the inverse overlays
if (node.inverseOverlays) {
if (node.inverseOverlaysMutable !== true) {
node.inverseOverlays = JSON.parse(JSON.stringify(node.inverseOverlays));
node.inverseOverlaysMutable = true;
}
index = ((node.inverseOverlays[target] || {})[name] || []).indexOf(source);
if (index !== -1) {
node.inverseOverlays[target][name].splice(index, 1);
if (node.inverseOverlays[target][name].length === 0) {
delete node.inverseOverlays[target][name];
if (Object.keys(node.inverseOverlays[target]).length === 0) {
delete node.inverseOverlays[target];
}
}
}
}
};
this.overlayQuery = function (node, prefix) {
ASSERT(self.isValidNode(node) && innerCore.isValidPath(prefix));
var overlays,
overlaysObject,
shardId,
inverseOverlays = self.getInverseOverlayOfNode(node), // We necessarily have to compute at this point,
i, path, name, list = [],
prefix2 = prefix + CONSTANTS.PATH_SEP;
if (hasShardedOverlays(node)) {
overlaysObject = node.overlays;
} else {
overlaysObject = {single: {items: self.getProperty(node, CONSTANTS.OVERLAYS_PROPERTY) || {}}};
}
for (shardId in overlaysObject) {
overlays = overlaysObject[shardId].items;
for (path in overlays) {
if (path === prefix || path.substr(0, prefix2.length) === prefix2) {
for (name in overlays[path]) {
if (self.isPointerName(name)) {
list.push({
s: path, // source
n: name, // name
t: overlays[path][name], // target
p: true // is forward relation
});
}
}
}
}
}
for (path in inverseOverlays) {
if (path === prefix || path.substr(0, prefix2.length) === prefix2) {
for (name in inverseOverlays[path]) {
for (i = 0; i < inverseOverlays[path][name].length; i += 1) {
list.push({
s: inverseOverlays[path][name][i],
n: name,
t: path,
p: false
});
}
}
}
}
return list;
};
this.overlayInsert = function (node, source, name, target) {
ASSERT(self.isValidNode(node));
ASSERT(innerCore.isValidPath(source) && innerCore.isValidPath(target) && self.isPointerName(name));
ASSERT(innerCore.getCommonPathPrefixData(source, target).common === '');
var currentOverlayInfo = self.overlayInquiry(node, source, name),
overlays,
overlay;
ASSERT(currentOverlayInfo.value === null);
if (hasShardedOverlays(node) === false && shouldHaveShardedOverlays(node)) {
transformOverlays(node);
}
if (hasShardedOverlays(node) === true) {
putEntryIntoShardedOverlays(node, source, name, target);
} else {
overlays = self.getChild(node, CONSTANTS.OVERLAYS_PROPERTY);
overlay = self.getChild(overlays, source);
self.setProperty(overlay, name, target);
}
//Now we check if we need to mutate the inverse overlays
if (node.inverseOverlays) {
if (node.inverseOverlaysMutable !== true) {
node.inverseOverlays = JSON.parse(JSON.stringify(node.inverseOverlays));
node.inverseOverlaysMutable = true;
}
node.inverseOverlays[target] = node.inverseOverlays[target] || {};
node.inverseOverlays[target][name] = node.inverseOverlays[target][name] || [];
node.inverseOverlays[target][name].push(source);
}
};
this.createNode = function (parameters, takenRelids, relidLength) {
parameters = parameters || {};
var relid = parameters.relid,
parent = parameters.parent;
ASSERT(!parent || self.isValidNode(parent));
// ASSERT(!relid || typeof relid === 'string');
var node;
if (parent) {
if (relid) {
if ((takenRelids && takenRelids[relid]) || self.getChildrenRelids(parent).indexOf(relid) > -1) {
throw new Error('Given relid already used in parent "' + relid + '".');
} else {
node = innerCore.getChild(parent, relid);
parent.childrenRelids = null;
}
} else {
node = self.createChild(parent, takenRelids, relidLength);
}
innerCore.setHashed(node, true);
} else {
node = innerCore.createRoot();
}
// As we just created the node, we can allocate an empty inverse object, that is appropriate this time
node.inverseOverlays = {};
node.inverseOverlayMutable = true;
return node;
};
this.deleteNode = function (node) {
ASSERT(self.isValidNode(node));
var parent = innerCore.getParent(node);
ASSERT(parent !== null);
self.deleteChild(parent, innerCore.getRelid(node));
};
/**
*
* @param {Node} node - Node containing the child.
* @param {string} relid - Relid of the child to be removed.
*/
this.deleteChild = function (parent, relid) {
var prefix = CONSTANTS.PATH_SEP + relid;
innerCore.deleteProperty(parent, relid);
innerCore.removeChildFromCache(parent, relid);
if (parent.childrenRelids) {
parent.childrenRelids = null;
}
while (parent) {
var list = self.overlayQuery(parent, prefix);
for (var i = 0; i < list.length; ++i) {
var entry = list[i];
self.overlayRemove(parent, entry.s, entry.n, entry.t);
}
prefix = CONSTANTS.PATH_SEP + innerCore.getRelid(parent) + prefix;
parent = innerCore.getParent(parent);
}
};
this.createChild = function (parent, takenRelids, relidLength) {
var child = innerCore.createChild(parent, takenRelids, relidLength);
parent.childrenRelids = null;
return child;
};
this.copyNode = function (node, parent, takenRelids, relidLength) {
ASSERT(self.isValidNode(node));
ASSERT(!parent || self.isValidNode(parent));
var newNode,
ancestor,
ancestorNewPath,
nodeToChangeOverlay,
base,
baseOldPath,
aboveAncestor,
list,
tempAncestor,
i,
entry,
relativePath,
source,
target;
node = innerCore.normalize(node);
if (parent) {
ancestor = innerCore.getAncestor(node, parent);
// cannot copy inside of itself
if (ancestor === node) {
return null;
}
newNode = self.createChild(parent, takenRelids, relidLength);
innerCore.setHashed(newNode, true);
innerCore.setData(newNode, innerCore.copyData(node));
ancestorNewPath = innerCore.getPath(newNode, ancestor);
base = innerCore.getParent(node);
baseOldPath = CONSTANTS.PATH_SEP + innerCore.getRelid(node);
aboveAncestor = 1;
while (base) {
list = self.overlayQuery(base, baseOldPath);
tempAncestor = innerCore.getAncestor(base, ancestor);
aboveAncestor = (base === ancestor ? 0 : tempAncestor === base ? 1 : -1);
relativePath = aboveAncestor < 0 ?
innerCore.getPath(base, ancestor) : innerCore.getPath(ancestor, base);
for (i = 0; i < list.length; ++i) {
entry = list[i];
if (entry.p) {
ASSERT(entry.s.substr(0, baseOldPath.length) === baseOldPath);
ASSERT(entry.s === baseOldPath ||
entry.s.charAt(baseOldPath.length) === CONSTANTS.PATH_SEP);
if (aboveAncestor < 0) {
//below ancestor node - further from root
source = ancestorNewPath + entry.s.substr(baseOldPath.length);
target = innerCore.joinPaths(relativePath, entry.t);
nodeToChangeOverlay = ancestor;
} else if (aboveAncestor === 0) {
//at ancestor node
var data = innerCore.getCommonPathPrefixData(ancestorNewPath, entry.t);
nodeToChangeOverlay = newNode;
while (data.firstLength-- > 0) {
nodeToChangeOverlay = innerCore.getParent(nodeToChangeOverlay);
}
source = innerCore.joinPaths(data.first, entry.s.substr(baseOldPath.length));
target = data.second;
} else {
//above ancestor node - closer to root
ASSERT(entry.s.substr(0, baseOldPath.length) === baseOldPath);
source = relativePath + ancestorNewPath + entry.s.substr(baseOldPath.length);
target = entry.t;
nodeToChangeOverlay = base;
}
self.overlayInsert(nodeToChangeOverlay, source, entry.n, target);
}
}
baseOldPath = CONSTANTS.PATH_SEP + innerCore.getRelid(base) + baseOldPath;
base = innerCore.getParent(base);
}
} else {
newNode = innerCore.createRoot();
innerCore.setData(newNode, innerCore.copyData(node));
}
if (node.inverseOverlaysMutable) {
newNode.inverseOverlays = JSON.parse(JSON.stringify(node.inverseOverlays));
} else {
newNode.inverseOverlays = node.inverseOverlays;
}
newNode.inverseOverlaysMutable = node.inverseOverlaysMutable;
if (hasShardedOverlays(node)) {
// Copy the shards-info for nodes with sharded overlay #1343
newNode.overlays = JSON.parse(JSON.stringify(node.overlays));
newNode.overlayMutations = JSON.parse(JSON.stringify(node.overlayMutations));
newNode.minimalOverlayShardId = node.minimalOverlayShardId;
}
var root = self.getRoot(newNode);
root.initial[self.getPath(newNode)] = root.initial[self.getPath(node)];
return newNode;
};
this.gatherRelationsAmongSubtrees = function (sourceRoot, targetRoot) {
var relationInformation = [],
overlaysToCheck,
commonParent, i,
commonPathInformation = innerCore.getCommonPathPrefixData(
self.getPath(sourceRoot),
self.getPath(targetRoot));
commonParent = sourceRoot;
while (self.getPath(commonParent) !== commonPathInformation.common) {
commonParent = self.getParent(commonParent);
}
overlaysToCheck = self.overlayQuery(commonParent, commonPathInformation.first);
for (i = 0; i < overlaysToCheck.length; i += 1) {
if (self.isPathInSubTree(overlaysToCheck[i].t, commonPathInformation.second)) {
relationInformation.push({
source: innerCore.joinPaths(commonPathInformation.common, overlaysToCheck[i].s),
sourceBase: innerCore.joinPaths(commonPathInformation.common,
commonPathInformation.first),
target: innerCore.joinPaths(commonPathInformation.common, overlaysToCheck[i].t),
targetBase: innerCore.joinPaths(commonPathInformation.common,
commonPathInformation.second),
name: overlaysToCheck[i].n
});
}
}
return relationInformation;
};
this.gatherRelationsOfSubtree = function (root, sourceRelPath, targetRelPath) {
var relationInformation = [],
rootPath = self.getPath(root),
overlaysToCheck, i;
overlaysToCheck = self.overlayQuery(root, sourceRelPath);
for (i = 0; i < overlaysToCheck.length; i += 1) {
if (self.isPathInSubTree(overlaysToCheck[i].t, targetRelPath)) {
relationInformation.push({
source: innerCore.joinPaths(rootPath, overlaysToCheck[i].s),
sourceBase: innerCore.joinPaths(rootPath, sourceRelPath),
target: innerCore.joinPaths(rootPath, overlaysToCheck[i].t),
targetBase: innerCore.joinPaths(rootPath, targetRelPath),
name: overlaysToCheck[i].n
});
}
}
return relationInformation;
};
this.copyNodes = function (nodes, parent, takenRelids, relidLength) {
var old2NewPath = {},
paths = [],
relationsToCopyOver = [],
copies = [],
source, target, oldTarget,
gatherRelations = function (commonPathInformation, firstNode) {
var commonParent,
overlaysToCheck,
i;
commonParent = firstNode;
while (self.getPath(commonParent) !== commonPathInformation.common) {
commonParent = self.getParent(commonParent);
}
// first -> second
overlaysToCheck = self.overlayQuery(commonParent, commonPathInformation.first);
for (i = 0; i < overlaysToCheck.length; i += 1) {
if (self.isPathInSubTree(overlaysToCheck[i].t, commonPathInformation.second)) {
relationsToCopyOver.push({
source: innerCore.joinPaths(commonPathInformation.common, overlaysToCheck[i].s),
sourceBase: innerCore.joinPaths(commonPathInformation.common,
commonPathInformation.first),
target: innerCore.joinPaths(commonPathInformation.common, overlaysToCheck[i].t),
targetBase: innerCore.joinPaths(commonPathInformation.common,
commonPathInformation.second),
name: overlaysToCheck[i].n
});
}
}
},
i, j;
//first we collect the relations that we need to preserve
for (i = 0; i < nodes.length; i += 1) {
paths.push(self.getPath(nodes[i]));
}
for (i = 0; i < nodes.length; i += 1) {
for (j = 0; j < nodes.length; j += 1) {
if (j !== i) {
gatherRelations(innerCore.getCommonPathPrefixData(paths[i], paths[j]), nodes[i]);
}
}
}
//do the actual copying
for (i = 0; i < nodes.length; i += 1) {
copies.push(self.copyNode(nodes[i], parent, takenRelids, relidLength));
old2NewPath[paths[i]] = self.getPath(copies[i]);
if (takenRelids) {
takenRelids[self.getRelid(copies[i])] = true;
}
}
// create the relations, that have to be preserved
for (i = 0; i < relationsToCopyOver.length; i += 1) {
source = relationsToCopyOver[i].source.replace(
relationsToCopyOver[i].sourceBase,
old2NewPath[relationsToCopyOver[i].sourceBase]
);
target = relationsToCopyOver[i].target.replace(
relationsToCopyOver[i].targetBase,
old2NewPath[relationsToCopyOver[i].targetBase]
);
oldTarget = self.overlayInquiry(parent, source, relationsToCopyOver[i].name);
if (oldTarget !== null && typeof oldTarget.value === 'string') {
self.overlayRemove(parent, source, relationsToCopyOver[i].name, oldTarget.value);
}
self.overlayInsert(parent, source, relationsToCopyOver[i].name, target);
}
return copies;
};
// this.copyNodes = function (nodes, parent, takenRelids, relidLength) {
// //copying multiple nodes at once for keeping their internal relations
// var paths = [],
// i, j, index, names, pointer, newNode,
// copiedNodes = [],
// // Every single element will be an object with the
// // internally pointing relations and the index of the target.
// internalRelationPaths = [];
//
// for (i = 0; i < nodes.length; i++) {
// paths.push(innerCore.getPath(nodes[i]));
// }
//
// for (i = 0; i < nodes.length; i++) {
// names = self.getPointerNames(nodes[i]);
// pointer = {};
// for (j = 0; j < names.length; j++) {
// index = paths.indexOf(self.getPointerPath(nodes[i], names[j]));
// if (index !== -1) {
// pointer[names[j]] = index;
// }
// }
// internalRelationPaths.push(pointer);
// }
//
// //now we use our simple copy
// for (i = 0; i < nodes.length; i++) {
// newNode = self.copyNode(nodes[i], parent, takenRelids, relidLength);
// copiedNodes.push(newNode);
// if (takenRelids) {
// takenRelids[self.getRelid(newNode)] = true;
// }
// }
//
// //and now back to the relations
// for (i = 0; i < internalRelationPaths.length; i++) {
// names = Object.keys(internalRelationPaths[i]);
// for (j = 0; j < names.length; j++) {
// self.setPointer(copiedNodes[i], names[j], copiedNodes[internalRelationPaths[i][names[j]]]);
// }
// }
//
// return copiedNodes;
// };
this.moveNode = function (node, parent, takenRelids, relidLength, newRelid) {
ASSERT(self.isValidNode(node) && self.isValidNode(parent));
var ancestor,
base,
baseOldPath,
aboveAncestor,
ancestorNewPath,
list,
tempAncestor,
relativePath,
i,
source,
target,
nodeToModifyOverlays,
entry,
tmp;
node = innerCore.normalize(node);
ancestor = innerCore.getAncestor(node, parent);
// cannot move inside of itself
if (ancestor === node) {
return null;
}
base = innerCore.getParent(node);
baseOldPath = CONSTANTS.PATH_SEP + innerCore.getRelid(node);
aboveAncestor = 1;
var oldNode = node;
if (typeof newRelid === 'string') {
node = innerCore.getChild(parent, newRelid);
} else {
if (takenRelids) {
if (takenRelids[innerCore.getRelid(oldNode)]) {
node = innerCore.createChild(parent, takenRelids, relidLength);
} else {
node = innerCore.getChild(parent, innerCore.getRelid(oldNode));
}
} else {
node = innerCore.getChild(parent, innerCore.getRelid(oldNode));
if (!innerCore.isEmpty(node)) {
// we have to change the relid of the node, to fit into its new
// place...
node = innerCore.createChild(parent);
}
}
}
parent.childrenRelids = null;
innerCore.setHashed(node, true);
innerCore.setData(node, innerCore.copyData(oldNode));
ancestorNewPath = innerCore.getPath(node, ancestor);
while (base) {
list = self.overlayQuery(base, baseOldPath);
tempAncestor = innerCore.getAncestor(base, ancestor);
aboveAncestor = (base === ancestor ? 0 : tempAncestor === base ? 1 : -1);
relativePath = aboveAncestor < 0 ?
innerCore.getPath(base, ancestor) : innerCore.getPath(ancestor, base);
for (i = 0; i < list.length; ++i) {
entry = list[i];
self.overlayRemove(base, entry.s, entry.n, entry.t);
if (!entry.p) {
tmp = entry.s;
entry.s = entry.t;
entry.t = tmp;
}
ASSERT(entry.s.substr(0, baseOldPath.length) === baseOldPath);
ASSERT(entry.s === baseOldPath || entry.s.charAt(baseOldPath.length) === CONSTANTS.PATH_SEP);
if (aboveAncestor < 0) {
//below ancestor node
source = ancestorNewPath + entry.s.substr(baseOldPath.length);
target = innerCore.joinPaths(relativePath, entry.t);
nodeToModifyOverlays = ancestor;
} else if (aboveAncestor === 0) {
//at ancestor node
var data = innerCore.getCommonPathPrefixData(ancestorNewPath, entry.t);
nodeToModifyOverlays = node;
while (data.firstLength-- > 0) {
nodeToModifyOverlays = innerCore.getParent(nodeToModifyOverlays);
}
source = innerCore.joinPaths(data.first, entry.s.substr(baseOldPath.length));
target = data.second;
} else {
//above ancestor node
ASSERT(entry.s.substr(0, baseOldPath.length) === baseOldPath);
source = relativePath + ancestorNewPath + entry.s.substr(baseOldPath.length);
target = entry.t;
nodeToModifyOverlays = base;
}
if (!entry.p) {
tmp = entry.s;
entry.s = entry.t;
entry.t = tmp;
tmp = source;
source = target;
target = tmp;
}
//console.log(source, target);
self.overlayInsert(nodeToModifyOverlays, source, entry.n, target);
}
baseOldPath = CONSTANTS.PATH_SEP + innerCore.getRelid(base) + baseOldPath;
base = innerCore.getParent(base);
}
var root = self.getRoot(node);
root.initial[self.getPath(node)] = root.initial[sel