package jbenchmarker.ot.soct4;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import jbenchmarker.core.LocalOperation;
import jbenchmarker.core.SequenceOperation;
import jbenchmarker.core.SequenceOperation.OpType;
import jbenchmarker.ot.soct4.common.SimpleSequencer;
import jbenchmarker.ot.soct4.common.StringID;
import crdt.CRDTMessage;
import crdt.OperationBasedOneMessage;
import crdt.PreconditionException;
import crdt.Replica;
/**
* This algorithms requires two steps of communication to execute a local
* operation. First it gets a timestamp and only after the message is sent to
* other nodes.
*
* The reply from a local execution is the timestamp request
*
* @author balegas
*
* @param <T>
*/
public class SOCT4Algorithm<T extends Serializable & Comparable<T>> implements
Replica<SOCT4Algorithm> {
protected Logger log = Logger.getLogger(SOCT4Algorithm.class.getName());
PriorityBlockingQueue<SOCT4OP<T>> remoteQueue;
private List<SequenceOperation<T>> atomList;
private SOCT4OP<T>[] history;
private int historyPointer, deliveryPointer;
private StringID siteID;
private int replicaNumber;
private boolean continueProc;
private boolean active;
private int generatedIds;
private SimpleSequencer sequencer = SimpleSequencer.getInstance();
// The identifier of the last operation delivered on this site
int nOps;
@SuppressWarnings("unchecked")
public SOCT4Algorithm(StringID siteID, boolean dumpState) {
this.active = true;
this.siteID = siteID;
this.history = new SOCT4OP[2];
this.historyPointer = -1;
this.deliveryPointer = -1;
this.nOps = -1;
Comparator<SOCT4OP<T>> comparator = new Comparator<SOCT4OP<T>>() {
@Override
public int compare(SOCT4OP<T> o1, SOCT4OP<T> o2) {
// Not safe, can be > MAX_INT. However it is unlikely
return o1.getTimestamp() - o2.getTimestamp();
}
};
this.remoteQueue = new PriorityBlockingQueue<SOCT4OP<T>>(1, comparator);
this.atomList = new LinkedList<SequenceOperation<T>>();
}
@Override
public CRDTMessage applyLocal(LocalOperation op)
throws PreconditionException {
CRDTMessage opWithTs = localExecution((SequenceOperation<T>) op, true);
return opWithTs;
}
/**
* Assumes sequential execution
*
* @param op
* @param sendToObservers
* @return Timestamp request
*/
public synchronized CRDTMessage localExecution(SequenceOperation<T> op,
boolean sendToObservers) {
processOperation(op);
checkHistorySize();
int ts = sequencer.ticket();
SOCT4OP<T> soct4op = new SOCT4OP<T>(this.siteID, ts, op,
sendToObservers);
history[++historyPointer] = soct4op;
// TODO: Message is only broadcast when all previous timestamps of local operations
// have integrated
return new OperationBasedOneMessage(soct4op);
}
protected void processOperation(SequenceOperation<T> op) {
processOperation(op, atomList);
}
private void processOperation(SequenceOperation<T> op,
List<SequenceOperation<T>> atomList) {
switch (op.getType()) {
case insert:
atomList.add(op.getPosition(), op);
break;
case delete:
atomList.remove(op.getPosition());
break;
case noop:
break;
default:
log.log(Level.WARNING, "Operation not supported " + op.getType());
break;
}
}
// TODO: code that broadcasts the message based on timestamp order
/**
* private synchronized void deferredBroadCast() { if (localQueue.size() >
* 0) { if (localQueue.peek().getTimestamp() == nOps + 1)
* multicastSOCT4Msg(new SOCT4SOCT4Message(localQueue.poll())); } }
**/
private synchronized void sequentialReception(SOCT4OP<T> soct4op) {
remoteQueue.add(soct4op);
while (remoteQueue.size() > 0) {
if (remoteQueue.peek().getTimestamp() - 1 == nOps) {
try {
integration(remoteQueue.poll());
} catch (OperationTranspositionException e) {
log.log(Level.WARNING,
"Exception while integrating operations");
e.printStackTrace();
}
nOps++;
} else {
break;
}
}
}
private void integration(SOCT4OP<T> op)
throws OperationTranspositionException {
synchronized (history) {
if (!op.getSiteId().equals(siteID)) {
checkHistorySize();
for (int j = historyPointer; j > deliveryPointer; j--) {
history[j + 1] = history[j];
}
history[op.getTimestamp()] = op;
historyPointer++;
deliveryPointer++;
for (int j = deliveryPointer + 1; j <= historyPointer; j++) {
SOCT4OP<T> opL = history[j];
history[j] = transposeForward(op, opL);
op = transposeForward(opL, op);
}
processOperation(op.getSequenceOperation());
} else
deliveryPointer++;
}
// This is for OT/CRDT integration
/**
* SOCT4Message msg = new SOCT4Message(op.getSequenceOperation()); if
* (op.sendToObservers())
* synchronousNotifyObservers(SOCT4Event.SOCT4_INTEGRATION_FINISHED
* ,msg);
**/
// TODO: after integrating a message, the algorithm checks if the next
// local operation can be broadcast
// deferredBroadCast();
}
private SOCT4OP<T> transposeForward(SOCT4OP<T> soct4op1, SOCT4OP<T> soct4op2)
throws OperationTranspositionException {
SOCT4OP<T> newOp;
SequenceOperation<T> newSequenceOp;
SequenceOperation<T> op1 = soct4op1.getSequenceOperation();
SequenceOperation<T> op2 = soct4op2.getSequenceOperation();
if (op1.getType() == OpType.insert && op2.getType() == OpType.insert) {
if (op1.getPosition() < op2.getPosition()) {
newSequenceOp = new SequenceOperation<T>(OpType.insert,
op2.getPosition() + 1, 0, op2.getContent());
newOp = new SOCT4OP<T>(soct4op2.getSiteId(),
SOCT4OP.NO_TIMESTAMP, newSequenceOp);
} else if (op1.getPosition() > op2.getPosition()) {
newSequenceOp = new SequenceOperation<T>(OpType.insert,
op2.getPosition(), 0, op2.getContent());
newOp = new SOCT4OP<T>(soct4op2.getSiteId(),
SOCT4OP.NO_TIMESTAMP, newSequenceOp);
} else {
if (op1.getContentAsString()
.compareTo(op2.getContentAsString()) == 0) {
newOp = new SOCT4OP<T>(soct4op2.getSiteId(),
SOCT4OP.NO_TIMESTAMP, SequenceOperation.noop());
} else {
newSequenceOp = new SequenceOperation<T>(OpType.insert,
op2.getPosition(), 0, op2.getContent());
newOp = new SOCT4OP<T>(soct4op2.getSiteId(),
SOCT4OP.NO_TIMESTAMP, newSequenceOp);
}
}
} else if (op1.getType() == OpType.delete
&& op2.getType() == OpType.delete) {
if (op1.getPosition() < op2.getPosition()) {
newSequenceOp = new SequenceOperation<T>(OpType.delete,
op2.getPosition() - 1, 0, op2.getContent());
newOp = new SOCT4OP<T>(soct4op2.getSiteId(),
SOCT4OP.NO_TIMESTAMP, newSequenceOp);
} else if (op1.getPosition() > op2.getPosition()) {
newSequenceOp = new SequenceOperation<T>(OpType.delete,
op2.getPosition(), 0, op2.getContent());
newOp = new SOCT4OP<T>(soct4op2.getSiteId(),
SOCT4OP.NO_TIMESTAMP, newSequenceOp);
} else {
newOp = new SOCT4OP<T>(soct4op2.getSiteId(),
SOCT4OP.NO_TIMESTAMP, SequenceOperation.noop());
}
} else if (op1.getType() == OpType.insert
&& op2.getType() == OpType.delete) {
if (op1.getPosition() < op2.getPosition()) {
newSequenceOp = new SequenceOperation<T>(op2.getType(),
op2.getPosition() + 1, 0, op2.getContent());
newOp = new SOCT4OP<T>(soct4op2.getSiteId(),
SOCT4OP.NO_TIMESTAMP, newSequenceOp);
} else {
newSequenceOp = new SequenceOperation<T>(op2.getType(),
op2.getPosition(), 0, op2.getContent());
newOp = new SOCT4OP<T>(soct4op2.getSiteId(),
SOCT4OP.NO_TIMESTAMP, newSequenceOp);
}
} else if (op1.getType() == OpType.delete
&& op2.getType() == OpType.insert) {
if (op1.getPosition() < op2.getPosition()) {
newSequenceOp = new SequenceOperation<T>(op2.getType(),
op2.getPosition() - 1, 0, op2.getContent());
newOp = new SOCT4OP<T>(soct4op2.getSiteId(),
SOCT4OP.NO_TIMESTAMP, newSequenceOp);
} else {
newSequenceOp = new SequenceOperation<T>(op2.getType(),
op2.getPosition(), 0, op2.getContent());
newOp = new SOCT4OP<T>(soct4op2.getSiteId(),
SOCT4OP.NO_TIMESTAMP, newSequenceOp);
}
} else {
throw new OperationTranspositionException();
}
return newOp;
}
private void checkHistorySize() {
synchronized (history) {
if (historyPointer + 1 == history.length) {
history = Arrays.copyOf(history, history.length * 2);
}
}
}
public void stopNode() {
continueProc = false;
}
/**
* TODO: This method generates a new atomlist from the operation history.
* (The atomlist has unordered operations)
*
* @return
*/
public String getLocalAtomSequenceToString() {
List<SequenceOperation<T>> atoms = new LinkedList();
StringBuilder string = new StringBuilder();
// for (Atom<T> a : atomList) {
// string.append(a.toString());
// }
for (SOCT4OP<T> op : history) {
if (op == null)
break;
processOperation(op.getSequenceOperation(), atoms);
}
for (SequenceOperation<T> a : atoms) {
string.append(a.getContentAsString());
}
return string.toString();
}
public LinkedList<SOCT4OP<T>> dumpAtomList() {
LinkedList<SOCT4OP<T>> output = new LinkedList<SOCT4OP<T>>();
for (int i = 0; i <= historyPointer; i++) {
SOCT4OP<T> hi = history[i];
output.add(hi);
}
return output;
}
public void toggleBroadCast() {
active = !active;
}
@Override
public void applyRemote(CRDTMessage msg) {
SOCT4OP op = (SOCT4OP) ((OperationBasedOneMessage) msg).getOperation();
sequentialReception(op);
}
@Override
public int getReplicaNumber() {
return replicaNumber;
}
@Override
public void setReplicaNumber(int replicaNumber) {
this.replicaNumber = replicaNumber;
}
// TODO: Does this have to be implemented?
@Override
public SOCT4Algorithm lookup() {
return null;
}
}