/**
* 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.treedoc;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Unique position identifier for Treedoc operations. Identifier is a sequence
* of pairs: (direction, timestamp), indexed by integer [0, length()]. Direction
* is {@link EdgeDirection#LEFT} or {@link EdgeDirection#RIGHT} and reflects
* treedoc tree edges.
* <p>
* Identifiers are constructed and used by {@link TreedocRoot}.
*
* @author mzawirski
*/
public class TreedocIdentifier implements Serializable {
public enum EdgeDirection {
LEFT, RIGHT
};
/**
* Treedoc tree path recorder, used to instatiate identifiers.
*
* @author mzawirski
*/
public static class Recorder {
private BitSet path = new BitSet();
private ArrayList<Integer> tagChanges = new ArrayList<Integer>();
private int index;
public void recordEdge(final EdgeDirection direction, UniqueTag tag) {
if (tag == null)
tag = UniqueTag.MIN;
path.set(index, direction == EdgeDirection.RIGHT);
if (!equalsLastTag(tag))
appendTagChange(tag);
index++;
}
private boolean equalsLastTag(UniqueTag tag) {
if (index == 0)
return false;
// Read using secret encoding;-) See appendTagChange().
final int lastReplicaId = tagChanges.get(tagChanges.size() - 2);
final int lastCounter = tagChanges.get(tagChanges.size() - 1);
return lastReplicaId == tag.getReplicaId()
&& lastCounter == tag.getCounter();
}
private void appendTagChange(final UniqueTag tag) {
if (index > 0) {
// Record the index of new tag.
tagChanges.add(index);
}
tagChanges.add(tag.getReplicaId());
tagChanges.add(tag.getCounter());
}
public TreedocIdentifier createIdentifier() {
if (index == 0)
throw new IllegalStateException(
"Cannot create empty identifier");
final int tags[] = new int[tagChanges.size()];
for (int i = 0; i < tagChanges.size(); i++)
tags[i] = tagChanges.get(i);
return new TreedocIdentifier(path, tags, index);
}
}
public static class ComponentScanner {
private EdgeDirection direction;
private UniqueTag tag;
public EdgeDirection getDirection() {
return direction;
}
public UniqueTag getTag() {
return tag;
}
}
/**
* Directions on the path.
*/
private final BitSet path;
/**
* UniqueTags on the path, encoded as sequence of (replicaId, counter,
* newTagIndex) triples, where newTagIndex is the index where the tag
* changes. Last triple is actually a pair without newTagIndex component.
*/
private final int tags[];
private final int length;
private TreedocIdentifier(final BitSet path, final int tags[],
final int length) {
this.path = path;
this.tags = tags;
this.length = length;
}
/**
* @return new iterator over the identifier components. Note that
* ComponentScanner object is mutable between #next() calls.
*/
public Iterator<ComponentScanner> iterator() {
return new ComponentIterator();
}
public int length() {
return length;
}
/**
* Makes a deep copy of identifier.
*/
public TreedocIdentifier clone() {
final int clonedTags[] = Arrays.copyOf(tags, tags.length);
return new TreedocIdentifier((BitSet) path.clone(), clonedTags, length);
}
@Override
public String toString() {
final StringBuilder buf = new StringBuilder();
final Iterator<ComponentScanner> iter = iterator();
while (iter.hasNext()) {
final ComponentScanner component = iter.next();
buf.append(component.getDirection() == EdgeDirection.LEFT ? '0'
: '1');
buf.append(':');
buf.append(component.getTag());
buf.append("|");
}
// Shrink last "|".
return buf.substring(0, buf.length() - 1);
}
private class ComponentIterator implements Iterator<ComponentScanner> {
private ComponentScanner component = new ComponentScanner();
private int index;
private int tagIndex;
@Override
public ComponentScanner next() {
if (!hasNext())
throw new NoSuchElementException();
component.direction = path.get(index) ? EdgeDirection.RIGHT
: EdgeDirection.LEFT;
// TODO: This code looks awful and depends on Recorder's code.
// Refactor.
if ((component.tag == null && tags[tagIndex] != UniqueTag.MIN
.getReplicaId())
|| (component.tag != null && (tags[tagIndex] != component.tag
.getReplicaId() || tags[tagIndex + 1] != component.tag
.getCounter()))) {
if (tags[tagIndex] == UniqueTag.MIN.getReplicaId()
&& tags[tagIndex + 1] == UniqueTag.MIN.getReplicaId()) {
component.tag = null;
} else {
component.tag = new UniqueTag(tags[tagIndex],
tags[tagIndex + 1]);
}
}
index++;
if (tagIndex + 2 < tags.length && index >= tags[tagIndex + 2])
tagIndex += 3;
return component;
}
@Override
public boolean hasNext() {
return index < length;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}