/** * 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.mu; import crdt.Factory; import crdt.Operation; import java.util.*; import java.util.Map.Entry; import jbenchmarker.core.SequenceOperation; import jbenchmarker.core.SequenceOperation.OpType; import jbenchmarker.logoot.*; /** * A Move and Update document. Each element is uniquely identified. It is * associated to a set of unique CRDT position, and a map unique timestamp to * content value. Only one value appear in document view (LWW). * * @author urso */ public class MuDocument<T> implements TimestampedDocument, Factory<MuDocument<T>> { private int myClock; protected int replicaNumber; final protected NavigableMap<ListIdentifier, Timestamp> positions; final protected Map<Timestamp, Cell<T>> elements; final protected LogootStrategy strategy; public MuDocument(int r, LogootStrategy strategy) { super(); positions = new TreeMap<ListIdentifier, Timestamp>(); elements = new HashMap<Timestamp, Cell<T>>(); this.strategy = strategy; this.replicaNumber = r; this.myClock = 0; } @Override public String view() { StringBuilder s = new StringBuilder(); for (Timestamp ts : positions.values()) { s.append(elements.get(ts).value()); } return s.toString(); } @Override public void apply(Operation op) { MuOperation<T> lg = (MuOperation<T>) op; Timestamp target = lg.getTarget(); Cell<T> c = elements.get(target); if (lg.getType() == OpType.replace) { // hack replace c.places.remove(lg.getPosition()); positions.remove(lg.getPosition()); elements.put(lg.getTimestamp(), new Cell(lg.getDestination(), lg.getTimestamp(), lg.getContent())); positions.put(lg.getDestination(), lg.getTimestamp()); } else { if (c == null) { elements.put(target, c = new Cell()); } if (lg.getPosition() != null) { c.places.remove(lg.getPosition()); positions.remove(lg.getPosition()); } if (lg.getDestination() != null) { c.places.add(lg.getDestination()); positions.put(lg.getDestination(), target); } if (lg.getOldVersions() != null) { c.contents.keySet().removeAll(lg.getOldVersions()); } if (lg.getContent() != null) { c.contents.put(lg.getTimestamp(), lg.getContent()); } if (!c.places.isEmpty() && c.contents.isEmpty()) { // remove tombstone from view positions.keySet().removeAll(c.places); } } } public List<Operation> insert(int position, List<T> lc, SequenceOperation opt) { List<ListIdentifier> pos = generateIdentifiers(position, lc.size()); Iterator<ListIdentifier> itp = pos.iterator(); List<Operation> patch = new LinkedList<Operation>(); Iterator<T> itc = lc.iterator(); while (itp.hasNext()) { ListIdentifier p = itp.next(); T value = itc.next(); Timestamp ts = new Timestamp(p.clock(), p.replica()); MuOperation op = new MuOperation(ts, null, p, null, ts, value, OpType.insert); apply(op); patch.add(op); } return patch; } public List<Operation> delete(int position, int length, SequenceOperation opt) { List<Entry<ListIdentifier, Timestamp>> elems = new ArrayList<Entry<ListIdentifier, Timestamp>>(positions.entrySet()).subList(position, position + length); List<Operation> patch = new LinkedList<Operation>(); for (Entry<ListIdentifier, Timestamp> e : elems) { Cell<T> c = elements.get(e.getValue()); MuOperation op; if (c.places.size() > 1) { // move clones hack : treat as single delete op = new MuOperation(e.getValue(), e.getKey(), null, null, null, null, OpType.delete); } else { op = new MuOperation(e.getValue(), e.getKey(), null, new TreeSet(c.contents.keySet()), null, null, OpType.delete); } apply(op); patch.add(op); } return patch; } List<Operation> update(int position, List<T> content, SequenceOperation opt) { List<Entry<ListIdentifier, Timestamp>> elems = new ArrayList<Entry<ListIdentifier, Timestamp>>(positions.entrySet()).subList(position, position + content.size()); List<Operation> patch = new LinkedList<Operation>(); Iterator<T> itc = content.iterator(); for (Entry<ListIdentifier, Timestamp> e : elems) { Cell<T> c = elements.get(e.getValue()); MuOperation op; if (c.places.size() > 1) { // move clones hack : treat as replace ListIdentifier pos = generateAfter(e.getKey()); op = new MuOperation(e.getValue(), e.getKey(), pos, null, Timestamp.of(pos), itc.next(), OpType.replace); } else { op = new MuOperation(e.getValue(), null, null, new TreeSet(c.contents.keySet()), nextTimestamp(), itc.next(), OpType.update); } apply(op); patch.add(op); } return patch; } List<Operation> move(int position, int destination, List<T> content, SequenceOperation opt) { List<Entry<ListIdentifier, Timestamp>> elems = new ArrayList<Entry<ListIdentifier, Timestamp>>(positions.entrySet()).subList(position, position + content.size()); List<Operation> patch = new LinkedList<Operation>(); Iterator<ListIdentifier> itpos = generateIdentifiers(destination < position ? destination : destination + content.size(), content.size()).iterator(); Iterator<T> itc = content.iterator(); for (Entry<ListIdentifier, Timestamp> e : elems) { Cell<T> c = elements.get(e.getValue()); ListIdentifier d = itpos.next(); T t = itc.next(); MuOperation op = c.places.size() > 1 // move clones hack : treat as replace ? new MuOperation(e.getValue(), e.getKey(), d, null, Timestamp.of(d), t, OpType.replace) : c.value().equals(t) ? new MuOperation(e.getValue(), e.getKey(), d, null, null, null, OpType.move) : new MuOperation(e.getValue(), e.getKey(), d, new TreeSet(c.contents.keySet()), nextTimestamp(), t, OpType.move); apply(op); patch.add(op); } return patch; } @Override public int viewLength() { return positions.size(); } @Override public int nextClock() { return this.myClock++; } void setClock(int c) { this.myClock = c; } public void setReplicaNumber(int replicaNumber) { this.replicaNumber = replicaNumber; } @Override public int getReplicaNumber() { return replicaNumber; } private Timestamp nextTimestamp() { return new Timestamp(nextClock(), replicaNumber); } // TODO Should be more efficient Iterator<Entry<ListIdentifier, Timestamp>> ith(int i) { int k = i; Iterator<Entry<ListIdentifier, Timestamp>> it = positions.entrySet().iterator(); while (--i > 0) { it.next(); } return it; } /** * Produce n new identifiers after this position. */ List<ListIdentifier> generateIdentifiers(int position, int N) { Iterator<Entry<ListIdentifier, Timestamp>> it = ith(position); ListIdentifier previous = position == 0 ? strategy.begin() : it.next().getKey(), next = position == viewLength() ? strategy.end() : it.next().getKey(); return strategy.generateLineIdentifiers(this, previous, next, N); } private ListIdentifier generateAfter(ListIdentifier posid) { ListIdentifier next = positions.higherKey(posid); if (next == null) { next = strategy.end(); } return strategy.generateLineIdentifiers(this, posid, next, 1).get(0); } @Override public MuDocument<T> create() { return new MuDocument<T>(replicaNumber, strategy); } Object getObject(ListIdentifier p) { throw new UnsupportedOperationException("Not yet implemented"); } } /** * Elements * * @author urso * @param <T> Elemen */ class Cell<T> { Set<ListIdentifier> places; NavigableMap<Timestamp, T> contents; public Cell() { this.places = new TreeSet<ListIdentifier>(); this.contents = new TreeMap<Timestamp, T>(); } Cell(ListIdentifier pos, Timestamp ts, T value) { this(); places.add(pos); contents.put(ts, value); } T value() { return contents.isEmpty() ? null : contents.lastEntry().getValue(); } } class Timestamp implements Comparable<Timestamp> { static Timestamp of(ListIdentifier pos) { return new Timestamp(pos.clock(), pos.replica()); } final int clock; final int replica; public Timestamp(int clock, int replica) { this.clock = clock; this.replica = replica; } @Override public int compareTo(Timestamp o) { if (this.clock == o.clock) { return Integer.compare(this.replica, o.replica); } return this.clock - o.clock; } @Override public String toString() { return "<" + clock + ", " + replica + '>'; } }