logoots-structs
Version:
Provides several data structures to represent a text using a ropes-like structure and manipulating it
756 lines (702 loc) • 24.3 kB
JavaScript
Utils = require('logoots-utils');
Identifier = require('./identifier');
IdentifierInterval = require('./identifierinterval');
IDFactory = require('./idfactory');
InfiniteString = require('./infinitestring');
Iterator = require('./iterator');
IteratorHelperIdentifier = require('./iteratorhelperidentifier');
LogootSAdd = require('./logootsadd');
LogootSBlock = require('./logootsblock');
LogootSDel = require('./logootsdel');
ResponseIntNode = require('./responseintnode');
RopesNodes = require('./ropesnodes');
TextDelete = require('./textdelete');
TextInsert = require('./textinsert');
var LogootSRopes = function (replicaNumber) {
this.replicaNumber = replicaNumber || 0;
this.clock = 0;
this.root = null;
this.mapBaseToBlock= {};
this.str = '';
};
LogootSRopes.prototype.getBlock = function (id) {
var ret = this.mapBaseToBlock[id.base];
if(ret==null) {
ret = new LogootSBlock(id);
this.mapBaseToBlock[id.base] = ret;
}
return ret;
};
//TODO: implémenter les LogootSOperations
LogootSRopes.prototype.addBlock = function (args) {
if(args['idi'] != null
&& args['str'] != null
&& args['from'] != null
&& args['startOffset'] != null) {
var idi = args['idi'];
var str = args['str'];
var from = args['from'];
var startOffset = args['startOffset'];
var path = [];
var path2 = [];
var result = [];
var con = true;
var i = startOffset;
while (con) {
path.push(from);
var ihi = new IteratorHelperIdentifier(idi,
from.getIdentifierInterval());
var split;
switch (ihi.computeResults()) {
case Utils.Result.B1AfterB2:
if (from.right == null) {
var args2 = {
'length': str.length,
'offset': idi.begin,
'block': this.getBlock(idi)
};
from.right = new RopesNodes(args2);
i = i + from.getSizeNodeAndChildren(0) + from.length;
result.push(new TextInsert(i, str));
con = false;
}
else {
i = i + from.getSizeNodeAndChildren(0) + from.length;
from = from.right;
}
break;
case Utils.Result.B1BeforeB2:
if (from.left == null) {
var args2 = {
'length': str.length,
'offset': idi.begin,
'block': this.getBlock(idi)
};
from.left = new RopesNodes(args2);
result.push(new TextInsert(i, str));
con = false;
}
else {
from = from.left;
}
break;
case Utils.Result.B1InsideB2: // split b2 the object node
split = Math.min(from.maxOffset(), ihi.nextOffset);
var args2 = {
'length': str.length,
'offset': idi.begin,
'block': this.getBlock(idi)
};
var rp = new RopesNodes(args2);
var args2 = {
'node': rp,
'size': split - from.offset + 1
};
path.push(from.split(args2));
i = i + from.getSizeNodeAndChildren(0);
result.push(new TextInsert(i + split - from.offset + 1, str));
con = false;
break;
case Utils.Result.B2InsideB1: // split b1 the node to insert
var split2 = /* Math.min(idi.getEnd(), */ihi.nextOffset/* ) */;
var ls = str.substr(0, split2 + 1 - idi.begin);
var idi1 = new IdentifierInterval(idi.base,
idi.begin, split2);
if (from.left == null) {
var args2 = {
'length': ls.length,
'offset': idi1.begin,
'block': this.getBlock(idi1),
};
from.left = new RopesNodes(args2);
result.push(new TextInsert(i, ls));
}
else {
var args2 = {
'idi': idi1,
'str': ls,
'from': from.left,
'startOffset': i
};
Utils.pushAll(result, this.addBlock(args2));
}
// i=i+ls.size();
ls = str.substr(split2 + 1 - idi.begin, str.length);
idi1 = new IdentifierInterval(idi.base, split2 + 1, idi.end);
i = i + from.getSizeNodeAndChildren(0) + from.length;
if (from.right == null) {
var args2 = {
'length': ls.length,
'offset': idi1.begin,
'block': this.getBlock(idi1)
};
from.right = new RopesNodes(args2);
result.push(new TextInsert(i, ls));
}
else {
var args2 = {
'idi': idi1,
'str': ls,
'from': from.right,
'startOffset': i
};
Utils.pushAll(result, this.addBlock(args2));
}
con = false;
break;
case Utils.Result.B1ConcatB2: // node to insert concat the node
if (from.left != null) {
path2 = Utils.copy(path);
path2.push(from.left);
this.getXest(Utils.RopesNodes.RIGHT, path2);
split = from.getIdBegin().minOffsetAfterPrev(
Utils.getLast(path2).getIdEnd(), idi.begin);
var l = str.substr(split + 1 - idi.begin, str.length);
from.appendBegin(l.length);
result.push(new TextInsert(i, l));
this.ascendentUpdate(path, l.length);
str = str.substr(0, split + 1 - idi.begin);
idi = new IdentifierInterval(idi.base, idi.begin, split);
// check if previous is smaller or not
if (idi.end >= idi.begin) {
from = from.left;
}
else {
con = false;
break;
}
}
else {
result.push(new TextInsert(i, str));
from.appendBegin(str.length);
this.ascendentUpdate(path, str.length);
con = false;
break;
}
break;
case Utils.Result.B2ConcatB1:// concat at end
if (from.right != null) {
path2 = Utils.copy(path);
path2.push(from.right);
this.getXest(Utils.RopesNodes.LEFT, path2);
split = from.getIdEnd().maxOffsetBeforeNex(
Utils.getLast(path2).getIdBegin(), idi.end);
var l = str.substr(0, split + 1 - idi.begin);
i = i + from.getSizeNodeAndChildren(0) + from.length;
from.appendEnd(l.length);
result.push(new TextInsert(i, l));
this.ascendentUpdate(path, l.length);
str = str.substr(split + 1 - idi.begin, str.length);
idi = new IdentifierInterval(idi.base, split + 1, idi.end);
if (idi.end >= idi.begin) {
from = from.right;
i = i + l.length;
}
else {
con = false;
break;
}
}
else {
i = i + from.getSizeNodeAndChildren(0) + from.length;
result.push(new TextInsert(i, str));
from.appendEnd(str.length);
this.ascendentUpdate(path, str.length);
con = false;
break;
}
break;
default:
console.log("Not implemented yet");
return -1;
}
}
this.balance(path);
return result;
}
else if(args['id'] != null && args['str'] != null) {
var id = args['id'];
var str = args['str'];
var l = [];
var idi = new IdentifierInterval(id.base, id.last,
id.last + str.length - 1);
if (this.root == null) {
var bl = new LogootSBlock(idi);
this.mapBaseToBlock[bl.id.base] = bl;
var args2 = {
'length': str.length,
'offset': id.last,
'block': bl
};
this.root = new RopesNodes(args2);
l.push(new TextInsert(0, str));
return l;
}
else {
var args2 = {
'idi': idi,
'str': str,
'from': this.root,
'startOffset': 0
};
return this.addBlock(args2);
}
}
};
LogootSRopes.prototype.searchFull = function (node, id, path) {
if(node == null)
return false;
path.push(node);
if(node.getIdBegin().toCompare(id) == 0
|| this.searchFull(node.left, id, path)
|| this.searchFull(node.right, id, path)) {
return true;
}
path.pop();
return false;
};
LogootSRopes.prototype.mkNode = function (id1, id2, length) {
var base = IDFactory.createBetweenPosition(id1, id2, this.replicaNumber, this.clock++);
var idi = new IdentifierInterval(base, 0, length - 1);
var newBlock = new LogootSBlock(idi);
this.mapBaseToBlock[idi.base] = newBlock;
var args = {
'length': length,
'offset': 0,
'block': newBlock
};
return new RopesNodes(args);
};
LogootSRopes.prototype.insertLocal = function (pos, l) {
if(this.root == null) {// empty tree
this.root = this.mkNode(null, null, l.length);
this.root.block.mine = true;
this.str = Utils.insert(this.str, pos, l);
return new LogootSAdd(this.root.getIdBegin(), l);
}
else {
var newNode;
var length = this.viewLength();
this.str = Utils.insert(this.str, pos, l);
var path;
if(pos == 0) {// begin of string
path = [];
path.push(this.root);
var args = {
'i': Utils.RopesNodes.LEFT,
'path': path
};
var n = this.getXest(args);
if (n.isAppendableBefore()) {
var id = n.appendBegin(l.length);
this.ascendentUpdate(path, l.length);
return new LogootSAdd(id, l);
}
else {// add node
newNode = this.mkNode(null, n.getIdBegin(), l.length);
newNode.block.mine = true;
n.left = newNode;
}
}
else if(pos >= length) {// end
path = [];
path.push(this.root);
var args = {
'i': Utils.RopesNodes.RIGHT,
'path': path
};
var n = this.getXest(args);
if (n.isAppendableAfter()) {// append
var id = n.appendEnd(l.length);
this.ascendentUpdate(path, l.length);
return new LogootSAdd(id, l);
}
else {// add at end
newNode = this.mkNode(n.getIdEnd(), null, l.length);
newNode.block.mine = true;
n.right = newNode;
}
}
else {// middle
var args = {'pos': pos};
var inPos = this.search(args);
if(inPos.i > 0) {// split
var id1 = inPos.node.block.id.getBaseId(inPos.node.offset + inPos.i - 1);
var id2 = inPos.node.block.id.getBaseId(inPos.node.offset + inPos.i);
newNode = this.mkNode(id1, id2, l.length);
newNode.block.mine = true;
path = inPos.path;
var args = {
'size': inPos.i,
'node': newNode
};
path.push(inPos.node.split(args));
}
else {
var args = {'pos': pos - 1};
var prev = this.search(args);
if(inPos.node.isAppendableBefore()
&& inPos.node
.getIdBegin()
.hasPlaceBefore(prev.node.getIdEnd(),
l.length)) {// append before
var id = inPos.node.appendBegin(l.length);
this.ascendentUpdate(inPos.path, l.length);
return new LogootSAdd(id, l);
}
else {
if (prev.node.isAppendableAfter()
&& prev.node
.getIdEnd()
.hasPlaceAfter(
inPos.node.getIdBegin(),
l.length)) {// append after
var id = prev.node.appendEnd(l.length);
this.ascendentUpdate(prev.path, l.length);
return new LogootSAdd(id, l);
}
else {
newNode = this.mkNode(prev.node.getIdEnd(), inPos
.node.getIdBegin(), l.length);
newNode.block.mine = true;
newNode.right = prev.node.right;
prev.node.right = newNode;
path = prev.path;
path.push(newNode);
}
}
}
}
this.balance(path);
return new LogootSAdd(newNode.getIdBegin(), l);
}
};
LogootSRopes.prototype.getXest = function (args) {
if(args['i']!=null && args['n']!=null)
{
var i = args['i'];
var n = args['n'];
while (n.getChild(i) != null) {
n = n.getChild(i);
}
return n;
}
else if(args['i']!=null && args['path']!=null)
{
var i = args['i'];
var path = args['path'];
var n = path[path.length-1];
while (n.getChild(i) != null) {
n = n.getChild(i);
path.push(n);
}
return n;
}
};
LogootSRopes.prototype.search = function (args) {
if(args['id']!=null && args['path']!=null)
{
var id = args['id'];
var path = args['path'];
var i = 0;
var node = this.root;
while(node != null) {
path.push(node);
if(id.compareTo(node.getIdBegin()) < 0) {
node = node.left;
}
else if(id.compareTo(node.getIdEnd()) > 0) {
i = i + node.getSizeNodeAndChildren(0) + node.length;
node = node.right;
}
else {
i = i + node.getSizeNodeAndChildren(0);
return i;
}
}
return -1;
}
else if(args['pos']!=null)
{
var pos = args['pos'];
var node = this.root;
var path = [];
while(node != null) {
path.push(node);
var before = node.left == null ? 0 : node.left.sizeNodeAndChildren;
if(pos < before) {
node = node.left;
}
else if(pos < before + node.length) {
return new ResponseIntNode(pos - before, node, path);
}
else {
pos -= before + node.length;
node = node.right;
}
}
return null;
}
};
LogootSRopes.prototype.ascendentUpdate = function (path, length) {
for (var i = path.length - 1; i >= 0; i--) {
path[i].addString(length);
}
};
LogootSRopes.prototype.delBlock = function (id) {
var l = [];
var i;
while (true) {
var path = [];
var args = {
'id': id.getBeginId(),
'path': path
}
if((i = this.search(args)) == -1) {
if (id.begin < id.end) {
id = new IdentifierInterval(id.base, id.begin + 1,
id.end);
}
else {
return l;
}
}
else {
var node = Utils.getLast(path);
var end = Math.min(id.end, node.maxOffset());
var pos = i + id.begin - node.offset;
var length = end - id.begin + 1;
l.push(new TextDelete(pos, length));
var t = node.deleteOffsets(id.begin, end);
if (node.length == 0) {// del node
this.delNode(path);
}
else if (t != null) {
path.push(t);
this.balance(path);
}
else {
this.ascendentUpdate(path, id.begin - end - 1);
}
if (end == id.end) {
break;
}
else {
id = new IdentifierInterval(id.base, end, id.end);
}
}
}
return l;
};
LogootSRopes.prototype.delLocal = function (begin, end) {
this.str = Utils.del(this.str, begin, end);
var length = end - begin + 1;
var li = [];
do {
var args = {'pos': begin};
var start = this.search(args);
if(start!=null) {
var be = start.node.offset + start.i;
var en = Math.min(be + length - 1, start.node.maxOffset());
li.push(new IdentifierInterval(start.node.block.id.base, be, en));
var r = start.node.deleteOffsets(be, en);
length -= en - be + 1;
if (start.node.length == 0) {
this.delNode(start.path);
}
else if (r != null) {// node has been splited
start.path.push(r);
this.balance(start.path);
}
else {
this.ascendentUpdate(start.path, be - en - 1);
}
}
else
length=0;
} while (length > 0);
return new LogootSDel(li);
};
LogootSRopes.prototype.delNode = function (path) {
var node = Utils.getLast(path);
if (node.block.nbElement == 0) {
this.mapBaseToBlock[node.block.id.base]=null;
}
if (node.right == null) {
if (node == this.root) {
this.root = node.left;
} else {
path.pop();
Utils.getLast(path).replaceChildren(node, node.left);
}
} else if (node.left == null) {
if (node == this.root) {
this.root = node.right;
} else {
path.pop();
Utils.getLast(path).replaceChildren(node, node.right);
}
} else {// two children
path.push(node.right);
var min = this.getMinPath(path);
node.become(min);
path.pop();
Utils.getLast(path).replaceChildren(min, min.right);
}
this.balance(path);
};
LogootSRopes.prototype.getMinPath = function (path) {
var node = Utils.getLast(path);
if (node == null) {
return null;
}
while (node.left != null) {
node = node.left;
path.push(node);
}
return node;
};
LogootSRopes.prototype.getLeftest = function (node) {
if (node == null) {
return null;
}
while (node.left != null) {
node = node.left;
}
return node;
};
LogootSRopes.prototype.getMinId = function (node) {
var back = this.getLeftest(node);
return back != null ? back.getIdBegin() : null;
};
//TODO: Implémenter la balance de Google (voir AVL.js) et vérifier ses performances en comparaison
LogootSRopes.prototype.balance = function (path) {
var node = path.length == 0 ? null : path.pop();
var father = path.length == 0 ? null : Utils.getLast(path);
while (node != null) {
node.sumDirectChildren();
var balance = node.balanceScore();
while (Math.abs(balance) >= 2) {
if (balance >= 2) {
if (node.right != null && node.right.balanceScore() <= -1) {
father = this.rotateRL(node, father);// double left
}
else {
father = this.rotateLeft(node, father);
}
}
else {
if (node.left != null && node.left.balanceScore() >= 1) {
father = this.rotateLR(node, father);// Double right
}
else {
father = this.rotateRight(node, father);
}
}
path.push(father);
balance = node.balanceScore();
}
node = path.length == 0 ? null : path.pop();
father = path.length == 0 ? null : Utils.getLast(path);
}
};
LogootSRopes.prototype.rotateLeft = function (node, father) {
var r = node.right;
if (node == this.root) {
this.root = r;
}
else {
father.replaceChildren(node, r);
}
node.right = r.left;
r.left = node;
node.sumDirectChildren();
r.sumDirectChildren();
return r;
};
LogootSRopes.prototype.rotateRight = function (node, father) {
var r = node.left;
if (node == this.root) {
this.root = r;
}
else {
father.replaceChildren(node, r);
}
node.left = r.right;
r.right = node;
node.sumDirectChildren();
r.sumDirectChildren();
return r;
};
LogootSRopes.prototype.rotateRL = function (node, father) {
this.rotateRight(node.right, node);
return this.rotateLeft(node, father);
};
LogootSRopes.prototype.rotateLR = function (node, father) {
this.rotateLeft(node.left, node);
return this.rotateRight(node, father);
};
LogootSRopes.prototype.getNext = function (path) {
var node = Utils.getLast(path);
if (node.right == null) {
if (path.length > 1) {
var father = path.get(path.length - 2);
if (father.left == node) {
path.pop();
return true;
}
}
return false;
}
else {
path.push(node.right);
var args = {
'i': Children.LEFT,
'path': path
};
this.getXest(args);
return true;
}
};
LogootSRopes.prototype.duplicate = function (newReplicaNumber) {
var copy = this.copy();
copy.replicaNumber = newReplicaNumber;
copy.clock = 0;
return copy;
}
LogootSRopes.prototype.copy = function () {
var o = new LogootSRopes(this.replicaNumber);
o.str = this.str;
o.clock = this.clock;
o.root = this.root != null ? this.root.copy() : null;
o.mapBaseToBlock = {};
for (var key in this.mapBaseToBlock)
{
o.mapBaseToBlock[key] = this.mapBaseToBlock[key];
}
return o;
};
LogootSRopes.prototype.copyFromJSON = function(ropes) {
var key;
this.str = ropes.str;
for(key in ropes.mapBaseToBlock) {
this.mapBaseToBlock[key] = ropes.mapBaseToBlock[key];
}
if(ropes.root !== null && ropes.root !== undefined) {
var node = ropes.root;
var args = {
'block': null,
'offset': node.offset,
'length': node.length
};
this.root = new RopesNodes(args);
this.root.copyFromJSON(node);
}
};
LogootSRopes.prototype.view = function () {
return this.str;
};
LogootSRopes.prototype.viewLength = function () {
return this.str.length;
};
module.exports = LogootSRopes;