/** * Replication Benchmarker * https://github.com/score-team/replication-benchmarker/ Copyright (C) 2013 * LORIA / Inria / SCORE Team * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package jbenchmarker.rgalocal; import collect.RangeList; import jbenchmarker.core.Document; import jbenchmarker.core.SequenceOperation; import java.util.HashMap; import java.util.NoSuchElementException; import crdt.Operation; import java.util.LinkedList; import java.util.List; import static jbenchmarker.rgalocal.RGAMerge.MAGIC; /** * Handles double local identifiers * * @author Roh, urso */ public class RGADocument<T> implements Document { public static final long MAX = Long.MAX_VALUE / 2, MIN = Long.MIN_VALUE / 2; private final HashMap<RGAS2Vector, RGANode<T>> hash; private final RangeList<RGANode<T>> localOrder; private final RGANode head; int collision; public RGADocument() { super(); collision = 0; head = new RGANode(); head.setPosition(MIN); hash = new HashMap<RGAS2Vector, RGANode<T>>(); localOrder = new RangeList<RGANode<T>>(); } @Override public String view() { StringBuilder s = new StringBuilder(); for (RGANode<T> n : localOrder) { s.append(n.getContent()); } return s.toString(); } @Override public void apply(Operation op) { RGAOperation<T> rgaop = (RGAOperation) op; if (rgaop.getType() == SequenceOperation.OpType.delete) { boolean wasVisible = remoteDelete(rgaop); if (wasVisible) { localOrder.remove(findLocal(hash.get(rgaop.getS4VPos()))); } } else { RGANode prev; if (rgaop.getS4VPos() == null) { prev = head; } else { prev = hash.get(rgaop.getS4VPos()); } List<RGANode<T>> news = new LinkedList<RGANode<T>>(); RGANode node = prev; RGAS2Vector v = rgaop.getS4VTms(); for (T e : rgaop.getBlock()) { node = remoteInsert(node, v, e); news.add(node); v = v.follower(); } RGANode next = node.getNextVisible(); long nextPos = next == null ? MAX : next.getPosition(); int index = next == null ? localOrder.size() : findLocal(next); long prevPos = index == 0 ? MIN : localOrder.get(index - 1).getPosition(), step = (nextPos - prevPos) / (rgaop.getBlock().size() * MAGIC); for (RGANode n : news) { prevPos += step; n.setPosition(prevPos); } localOrder.addAll(index, news); } if (collision > localOrder.size()) { rebalance(); } } /** * Rebalance the table */ void rebalance() { long step = Long.MAX_VALUE / (localOrder.size() * MAGIC), pos = MIN; for (RGANode<T> n : localOrder) { pos += step; n.setPosition(pos); } collision = 0; } RGANode remoteInsert(RGANode prev, RGAS2Vector s4v, T content) { RGANode newnd = new RGANode(s4v, content); RGANode next; if (prev == null) { throw new NoSuchElementException("RemoteInsert"); } next = prev.getNext(); while (next != null) { if (s4v.compareTo(next.getKey()) == RGAS2Vector.AFTER) { break; } prev = next; next = next.getNext(); } newnd.setNext(next); prev.setNext(newnd); hash.put(s4v, newnd); return newnd; } boolean remoteDelete(RGAOperation op) { boolean wasVisible; RGANode node = hash.get(op.getS4VPos()); if (node == null) { throw new NoSuchElementException("Cannot find" + op.getS4VPos()); } wasVisible = node.isVisible(); node.makeTombstone(); return wasVisible; } public RGAS2Vector getVisibleS4V(int v) { RGANode node = getVisibleNode(v); if (node == null) { throw new NoSuchElementException("getVisibleS4V"); } return node.getKey(); } public RGANode getVisibleNode(int v) { if (v == 0) { return head; } else { return localOrder.get(v - 1); } } @Override public int viewLength() { return localOrder.size(); } /** * Add a list of nodes in the local order table. * * @param i position * @param ln nodes */ void addLocal(int i, List<RGANode<T>> ln) { localOrder.addAll(i, ln); } /** * Remove a range of nodes from the local order table. * * @param p position * @param offset number of nodes */ void removeLocal(int p, int offset) { localOrder.removeRangeOffset(p, offset); } /** * Search an existing node in the local order table. Dichotomic search + * verification to handles collisions * * @param pos the position * @return the lower index with a position greater or equal pos. */ private int findLocal(RGANode<T> node) { long pos = node.getPosition(); int startIndex = 0, endIndex = localOrder.size(), middleIndex; do { middleIndex = startIndex + (endIndex - startIndex) / 2; if (localOrder.get(middleIndex).getPosition() < pos) { startIndex = middleIndex + 1; } else { endIndex = middleIndex; } } while (localOrder.get(middleIndex).getPosition() != pos); int index = middleIndex; while (!localOrder.get(index).equals(node) && index < localOrder.size() - 1 && localOrder.get(index + 1).getPosition() == pos) { ++index; ++collision; } if (localOrder.get(index).equals(node)) { return index; } index = middleIndex - 1; while (!localOrder.get(index).equals(node)) { // must be somewhere --index; ++collision; } return index; } public static long middle(long a, long b) { return a + ((b - a) / (2 * MAGIC)); } }