package com.github.ruediste1.btrbck.dom;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
@XmlRootElement
public class VersionHistory implements Serializable {
private static final long serialVersionUID = 1L;
public static class HistoryNode implements Comparable<HistoryNode> {
public int snapshotNr;
public final Set<HistoryNode> parents = new HashSet<>();
public final Set<Integer> parentNrs = new HashSet<>();
@Override
public int compareTo(HistoryNode o) {
return Integer.compare(snapshotNr, o.snapshotNr);
}
@Override
public String toString() {
return "(Node " + snapshotNr + "parents: " + parentNrs + ")";
}
}
@XmlAttribute
private final int version = 1;
@XmlElementRef
final ArrayList<VersionHistoryEntry> entries = new ArrayList<>();
public TreeMap<Integer, HistoryNode> calculateNodes() {
TreeMap<Integer, HistoryNode> result = new TreeMap<>();
// construct nodes
HashSet<Integer> parents = new HashSet<>();
int snapshotNr = 0;
for (VersionHistoryEntry entry : entries) {
if (entry instanceof SnapshotVersionHistoryEntry) {
int count = ((SnapshotVersionHistoryEntry) entry).count;
for (int i = 0; i < count; i++) {
HistoryNode node = new HistoryNode();
node.snapshotNr = snapshotNr++;
if (!parents.isEmpty()) {
node.parentNrs.addAll(parents);
parents.clear();
}
result.put(node.snapshotNr, node);
parents.add(node.snapshotNr);
}
} else if (entry instanceof RestoreVersionHistoryEntry) {
parents.add(((RestoreVersionHistoryEntry) entry).restoredSnapshotNr);
} else {
throw new RuntimeException("Should not happen");
}
}
// resolve parent nodes
for (HistoryNode node : result.values()) {
for (Integer nr : node.parentNrs) {
node.parents.add(result.get(nr));
}
}
return result;
}
public int getVersionCount() {
int count = 0;
for (VersionHistoryEntry entry : entries) {
count += entry.getRepresentedSnapshotCount();
}
return count;
}
public VersionHistoryEntry getLastEntry() {
if (entries.isEmpty()) {
return null;
}
return entries.get(entries.size() - 1);
}
/**
* Add a restore entry. If the last entry is already a restore for the same
* snapshot, do nothing.
*/
public void addRestore(UUID streamId, int restoredSnapshotNr) {
VersionHistoryEntry lastEntry = getLastEntry();
if (lastEntry instanceof RestoreVersionHistoryEntry) {
RestoreVersionHistoryEntry lastRestoreEntry = (RestoreVersionHistoryEntry) lastEntry;
if (lastRestoreEntry.streamId.equals(streamId)
&& lastRestoreEntry.restoredSnapshotNr == restoredSnapshotNr) {
// skip adding an entry
return;
}
}
RestoreVersionHistoryEntry entry = new RestoreVersionHistoryEntry(
streamId);
entry.restoredSnapshotNr = restoredSnapshotNr;
entries.add(entry);
}
public void addVersion(UUID streamId) {
VersionHistoryEntry entry = getLastEntry();
if (entry == null || (!entry.streamId.equals(streamId))
|| (!(entry instanceof SnapshotVersionHistoryEntry))) {
entries.add(new SnapshotVersionHistoryEntry(streamId));
} else {
((SnapshotVersionHistoryEntry) entry).count++;
}
}
private static class SnapshotIterator {
PeekingIterator<VersionHistoryEntry> it;
int remainingCount;
public SnapshotIterator(VersionHistory history) {
it = Iterators.peekingIterator(history.entries.iterator());
advanceToNextEntry();
}
private void advanceToNextEntry() {
// skip leading non-snapshot entries
while (it.hasNext()) {
if (!(it.peek() instanceof SnapshotVersionHistoryEntry)) {
it.next();
} else {
break;
}
}
if (it.hasNext()) {
remainingCount = peek().count;
}
}
private SnapshotVersionHistoryEntry peek() {
SnapshotVersionHistoryEntry peek = (SnapshotVersionHistoryEntry) it
.peek();
return peek;
}
int remainingSameUuid() {
return remainingCount;
}
UUID nextUuid() {
return peek().streamId;
}
boolean hasNext() {
return it.hasNext();
}
void advance(UUID id, int count) {
if (!id.equals(nextUuid())) {
throw new RuntimeException("id mismatch");
}
int toGo = count;
if (remainingCount > count) {
remainingCount -= count;
} else {
toGo -= remainingCount;
it.next();
advanceToNextEntry();
if (hasNext() && id.equals(nextUuid())) {
advance(id, toGo);
}
}
}
}
public boolean isAncestorOf(VersionHistory child) {
SnapshotIterator itParent = new SnapshotIterator(this);
SnapshotIterator itChild = new SnapshotIterator(child);
while (itParent.hasNext() && itChild.hasNext()) {
if (!itParent.nextUuid().equals(itChild.nextUuid())) {
// different id encountered, no ancestor-child relation
return false;
}
int remaining = Math.min(itParent.remainingSameUuid(),
itChild.remainingSameUuid());
UUID id = itParent.nextUuid();
itParent.advance(id, remaining);
itChild.advance(id, remaining);
}
if (!itParent.hasNext()) {
// we ate the whole parent, so the histories are equal or a
// parent-child relationship
return true;
} else {
return false;
}
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj.getClass() != getClass()) {
return false;
}
VersionHistory other = (VersionHistory) obj;
return entries.equals(other.entries);
}
@Override
public String toString() {
return "VersionHistory " + entries.toString();
}
}