package jbenchmarker.rgaTreeSplitBalanced; import jbenchmarker.core.Document; import jbenchmarker.core.SequenceOperation; import jbenchmarker.rgaTreeSplitBalanced.RgaSInsertion; import jbenchmarker.rgaTreeSplitBalanced.RgaSS3Vector; import jbenchmarker.rgaTreeSplitBalanced.RgaSDocument.Position; import java.util.HashMap; import java.util.List; import java.util.ArrayList; import java.util.NoSuchElementException; import crdt.Operation; public class RgaSDocument<T> implements Document { private HashMap<RgaSS3Vector, RgaSNode> hash; private RgaSNode head; private RgaSTree root; private int size = 0; private int nodeNumberInTree=0; private int tombstoneNumber=0; private int nbIns=0; public RgaSDocument() { super(); head = new RgaSNode(); hash=(new HashMap<RgaSS3Vector, RgaSNode>()); hash.put(null,head); } /* * Methods to handle local operations: */ public void insert(Position position, List<T> content, RgaSS3Vector s3vtms) { RgaSNode newnd = new RgaSNode(s3vtms, content); RgaSNode node = position.node; int offset = position.offset; remoteSplit(node, offset); RgaSNode nodeTree=node.getNextVisible(); insertInLocalTree( nodeTree,newnd); newnd.setNext(node.getNext()); node.setNext(newnd); hash.put(s3vtms, newnd); size+=newnd.size(); } public void delete(RgaSNode node, int offset1, int offset2) { if (offset1>node.getOffset()) node = remoteSplit(node, offset1); if (offset2>node.getOffset()) remoteSplit(node,offset2); size-=node.size(); tombstoneNumber++; deleteInLocalTree(node); node.makeTombstone(); } /* * Remote operations * */ public void apply(Operation op) { RgaSOperation rgaop = (RgaSOperation) op; if (rgaop.getType() == SequenceOperation.OpType.delete) { remove((RgaSDeletion)rgaop); } else { nbIns++; update( (RgaSInsertion) rgaop); balanceTree(); } } private void update(RgaSInsertion op) { RgaSNode node, newnd = new RgaSNode(op.getS3vtms(), op.getContent()); node = findGoodNode( op.getS3vpos() , op.getOffset1() ); if (op.getOffset1()> node.getOffset()){ remoteSplit(node, op.getOffset1()); } node = handleConcurrence(node,op.getS3vtms()); insertInLocalTree( node.getNextVisible(),newnd); newnd.setNext(node.getNext()); node.setNext(newnd); hash.put(op.getS3vtms(), newnd); size+=newnd.size(); } private void remove(RgaSDeletion op) { RgaSNode node = findGoodNode( op.getS3vpos() , op.getOffset1() ); if (op.getOffset1()>node.getOffset()){ node = remoteSplit(node,op.getOffset1()); } while (node.getOffset() + node.size() < op.getOffset2()){ if (node.isVisible()){ size-=node.size(); tombstoneNumber++; deleteInLocalTree(node); node.makeTombstone(); } node=node.getLink(); } if (op.getOffset2()>node.getOffset()){ remoteSplit(node,op.getOffset2()); if (node.isVisible()){ size-=node.size(); deleteInLocalTree(node); tombstoneNumber++; node.makeTombstone(); } } } public RgaSNode remoteSplit(RgaSNode node, int offsetAbs) { RgaSNode end=null; if (offsetAbs-node.getOffset()>0 && node.size()-offsetAbs+node.getOffset()>0){ List<T> a= null; List<T> b = null; if (node.isVisible()){ a = node.getContent().subList(0,offsetAbs-node.getOffset()); b = node.getContent().subList(offsetAbs-node.getOffset(),node.size()); } end = new RgaSNode(node.clone(), b, offsetAbs); end.setSize(node.size()-offsetAbs+node.getOffset()); end.setNext(node.getNext()); node.setContent(a); node.setSize(offsetAbs-node.getOffset()); node.setNext(end); node.setLink(end); hash.put(node.getKey(), node); hash.put(end.getKey(), end); if (node.isVisible()){ RgaSTree treeEnd = new RgaSTree(end, null, node.getTree().getRightSon()); node.getTree().setRoot(node); node.getTree().setRightSon(treeEnd); nodeNumberInTree++; } } return node.getLink(); } public RgaSNode findGoodNode(RgaSS3Vector vect, int off){ RgaSNode target = hash.get(vect); while (target.getOffset() + target.size() < off){ target=target.getLink(); } return target; } private RgaSNode handleConcurrence(RgaSNode node, RgaSS3Vector s3v){ RgaSNode next = node.getNext(); while (next!=null) { if (s3v.compareTo(next.getKey()) == RgaSS3Vector.AFTER) { break; } node = next; next = next.getNext(); } return node; } /* Methods to apply each type of local operations: * * findPosInLocalTree * insertInLocalTree * deleteInLocalTree */ public Position findPosInLocalTree(int pos){ RgaSTree tree = root; if (pos<=0 ) return new Position(head, 0); else { while (!(tree.getLeftSize()< pos && pos <= tree.getLeftSize() +tree.getRoot().size())){ if (pos<=tree.getLeftSize()) tree=tree.getLeftSon(); else { pos-=tree.getLeftSize()+tree.getRoot().size(); tree=tree.getRightSon(); } } return new Position(tree.getRoot(), pos + tree.getRoot().getOffset() - tree.getLeftSize()); } } public void insertInLocalTree(RgaSNode nodePos, RgaSNode newnd){ RgaSTree tree = (nodePos== null) ? null : nodePos.getTree(); RgaSTree newTree = new RgaSTree(newnd, null, null); if (root==null )root=newTree; else if (nodePos==null) findMostRight(root, 0).setRightSon(newTree); else if (tree.getLeftSon()== null) tree.setLeftSon(newTree); else findMostRight(tree.getLeftSon(),0).setRightSon(newTree); while (newTree.getFather()!=null){ newTree=newTree.getFather(); newTree.setSize(newTree.size()+newnd.size()); } nodeNumberInTree++; } public void deleteInLocalTree(RgaSNode nodeDel){ RgaSTree tree = nodeDel.getTree(), father = null; boolean isRoot = (tree.equals(this.root)) ? true : false; boolean hasRightSon = (tree.getRightSon()!=null) ? true: false; boolean hasLeftSon = (tree.getLeftSon()!=null) ? true: false; boolean isLeaf = (!hasRightSon && !hasLeftSon) ? true: false; boolean isLeftSon = false; if (!isRoot){ father = tree.getFather(); if (father.getLeftSon()==null); else if (father.getLeftSon().equals(tree)) isLeftSon = true; } if (isRoot){ // if the tree is the root, so... if (isLeaf) root = null; else if (hasLeftSon && !hasRightSon) root=root.getLeftSon(); else if (!hasLeftSon && hasRightSon) root=root.getRightSon(); else { findMostLeft(tree.getRightSon(), root.getLeftSon().size()).setLeftSon(root.getLeftSon()); root=root.getRightSon(); } } else if (isLeaf){ // else if it is a leaf, so... if (isLeftSon)father.setLeftSon(null); else father.setRightSon(null); } else { // else ... if (!hasRightSon){ if (isLeftSon) father.setLeftSon(tree.getLeftSon()); else father.setRightSon(tree.getLeftSon()); } else if (!hasLeftSon){ if (isLeftSon) father.setLeftSon(tree.getRightSon()); else father.setRightSon(tree.getRightSon()); } else { RgaSTree tree2 = findMostLeft(tree.getRightSon(), tree.getLeftSon().size()); tree2.setLeftSon(tree.getLeftSon()); if (isLeftSon) father.setLeftSon(tree.getRightSon()); else father.setRightSon(tree.getRightSon()); } } tree=null; nodeNumberInTree--; while (father!=null){ // soutract the size of the deleted node in all fathers and grandfathers father.setSize(father.size()-nodeDel.size()); father=father.getFather(); } } /* Methods to display the view of the document * * view(): normal view of the document, without separator between each node * viewWithSeparator(): view with separators between each node for debugging * treeView(): normal view of the local tree, without separator between each node * treeViewWithSeparator(): view of the local tree with separators between each node for debugging */ /* @Override public String view() { String s = new String(); RgaSNode node = head.getNextVisible(); while (node != null) { if (node.isVisible() && node.getContent()!=null) { for (int i=0; i<node.size();i++) s+=node.getContent().get(i); } node = node.getNextVisible(); } return s.toString(); }*/ @Override public String view() { return treeView(new StringBuilder(),root); } public String viewWithSeparator() { StringBuilder s = new StringBuilder(); StringBuilder a = new StringBuilder(); RgaSNode node = head.getNext(); while (node != null) { if (node.isVisible() && node.getContent()!=null) { a = new StringBuilder(); for (int i=0; i<node.size();i++){ a.append(node.getContent().get(i)); } s.append("->|"+a+"|"); } node = node.getNext(); } return s.toString(); } public void treeViewWithSeparator(RgaSTree tree, int profondeur){ if (tree!=null){ if (tree.getLeftSon()!=null) treeViewWithSeparator(tree.getLeftSon(),profondeur + 1); for (int i=0; i < profondeur; i++){ System.out.print(" "); } System.out.println("-->"+ tree.getRoot().getContentAsString()+", " +tree.size()); if (tree.getRightSon()!=null) treeViewWithSeparator(tree.getRightSon(),profondeur + 1); } } public String treeView(StringBuilder buf,RgaSTree tree){ if (tree!=null){ if (tree.getLeftSon()!=null) treeView(buf,tree.getLeftSon()); buf.append(tree.getRoot().getContentAsString()); if (tree.getRightSon()!=null) treeView(buf,tree.getRightSon()); } return buf.toString(); } /* * * other methods used in local and remote operations */ public RgaSTree findMostLeft(RgaSTree tree, int i){ while (tree.getLeftSon()!=null){ tree.setSize(tree.size()+i); tree=tree.getLeftSon(); } tree.setSize(tree.size()+i); return tree; } public RgaSTree findMostRight(RgaSTree tree, int i){ while (tree.getRightSon()!=null){ tree.setSize(tree.size()+i); tree=tree.getRightSon(); } tree.setSize(tree.size()+i); return tree; } protected class Position { protected RgaSNode node; protected int offset; public Position(RgaSNode n, int offset) { this.node= n; this.offset = offset; } public String toString(){ return "[" + node + "," + offset +"]"; } } public Position getPosition(RgaSNode node, int start){ Position pos; int i = node.size(); while (node != null && i <= start ) { node = node.getNextVisible(); if (node!=null) { i+=node.size(); } } if (node!=null){ return pos = new Position(node, node.size() - i + start); } else { return pos = new Position(null, 0); } } public HashMap<RgaSS3Vector, RgaSNode> getHash() { return hash; } public RgaSNode getHead(){ return head; } @Override public int viewLength() { return size; } public RgaSTree getRoot() { return root; } private void balanceTree(){ if (nodeNumberInTree> 3000 && nbIns >(nodeNumberInTree)/(0.14*Math.log(nodeNumberInTree)/Math.log(2))){ nbIns=0; List<RgaSNode> content = createNodeList(new ArrayList(), getRoot()); createBalancedTree(new RgaSTree(), content, 0, content.size()); addGoodSize(getRoot()); } } public List createNodeList(List list, RgaSTree tree){ if (tree!=null){ if (tree.getLeftSon()!=null){ createNodeList(list, tree.getLeftSon()); } list.add(tree.getRoot()); if (tree.getRightSon()!=null){ createNodeList(list, tree.getRightSon()); } tree.getRoot().setTree(null); tree=null; } return list; } public void createBalancedTree(RgaSTree tree, final List<RgaSNode> content, final int begin, final int length) { if (tree!=null && !content.isEmpty()){ final int leftSubtree = (length - 1) / 2 ; final int rightSubtree = length -1 - leftSubtree; if (leftSubtree > 0) { final RgaSTree leftChildren = new RgaSTree(); tree.setLeftSon(leftChildren); createBalancedTree(leftChildren, content, begin, leftSubtree); } content.get(begin + leftSubtree).setTree(tree); tree.setRoot(content.get(begin + leftSubtree)); tree.setSize(0); if (rightSubtree > 0) { final RgaSTree rightChildren = new RgaSTree(); tree.setRightSon(rightChildren); createBalancedTree(rightChildren, content, begin + leftSubtree + 1, rightSubtree); } root = tree; } } public void addGoodSize(RgaSTree tree){ if (tree!=null){ if (tree.getLeftSon()!=null){ addGoodSize(tree.getLeftSon()); tree.setSize(tree.size()+tree.getLeftSize()); } tree.setSize(tree.size()+tree.getRootSize()); if (tree.getRightSon()!=null){ addGoodSize(tree.getRightSon()); tree.setSize(tree.size()+tree.getRightSize()); } } } private int checkTreeDepth(RgaSTree tree, int height) { int hright = (tree.getRightSon()== null) ? -1 : checkTreeDepth(tree.getRightSon(), height); int hleft = (tree.getLeftSon() == null ? -1 : checkTreeDepth(tree.getLeftSon(),height)); height=Math.max(hright, hleft)+1; return height; } private int checkTreeAverageDepth(RgaSTree tree, int height) { if (tree!=null){ if (tree.getLeftSon()!=null){ height+=treeDepth(tree.getLeftSon()); height=checkTreeAverageDepth(tree.getLeftSon(), height); } height++; if (tree.getRightSon()!=null){ height+=treeDepth(tree.getRightSon()); height=checkTreeAverageDepth(tree.getRightSon(),height); } } return height; } private int treeDepth(RgaSTree tree){ int i=0; while (!tree.equals(root)){ i++; tree=tree.getFather(); } return i; } }