package org.locationtech.geogig.api.plumbing.diff;
import java.util.Stack;
import javax.annotation.Nullable;
import org.locationtech.geogig.api.Bounded;
import org.locationtech.geogig.api.Bucket;
import org.locationtech.geogig.api.Node;
import org.locationtech.geogig.api.RevTree;
import org.locationtech.geogig.storage.ObjectDatabase;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
/**
* Provides a means to "walk" the differences between two {@link RevTree trees} in depth-first order
* and emit diff events to a {@link Consumer}.
* <p>
* Skipping whole subtrees can be achieved by passing a {@link Predicate Predicate<Bounded>} that
* will be evaluated for each pair of tree {@link Node nodes} or {@link Bucket buckets}.
*/
public class PostOrderDiffWalk {
private static final Predicate<Bounded> ACEPT_ALL = Predicates.alwaysTrue();
private PreOrderDiffWalk inOrder;
public PostOrderDiffWalk(RevTree left, RevTree right, ObjectDatabase leftSource,
ObjectDatabase rightSource) {
this.inOrder = new PreOrderDiffWalk(left, right, leftSource, rightSource);
}
public final void walk(final Consumer consumer) {
walk(ACEPT_ALL, consumer);
}
public final void walk(final Predicate<Bounded> filter, final Consumer consumer) {
DepthFirstConsumer depthFirstConsumer = new DepthFirstConsumer(filter, consumer);
inOrder.walk(depthFirstConsumer);
}
private static class DepthFirstConsumer implements
org.locationtech.geogig.api.plumbing.diff.PreOrderDiffWalk.Consumer {
private static final class Entry {
private Bounded left;
private Bounded right;
private int bucketIndex;
private int bucketDepth;
private boolean accepted;
static Entry tree(Node left, Node right, boolean accepted) {
Entry e = new Entry();
e.left = left;
e.right = right;
e.accepted = accepted;
return e;
}
static Entry bucket(int bucketIndex, int bucketDepth, Bucket left, Bucket right,
boolean accepted) {
Entry e = new Entry();
e.left = left;
e.right = right;
e.bucketIndex = bucketIndex;
e.bucketDepth = bucketDepth;
e.accepted = accepted;
return e;
}
public void apply(Consumer consumer) {
if (accepted) {
if (isNode()) {
consumer.tree((Node) left, (Node) right);
} else {
consumer.bucket(bucketIndex, bucketDepth, (Bucket) left, (Bucket) right);
}
}
}
private boolean isNode() {
return left == null ? right instanceof Node : left instanceof Node;
}
}
private Stack<Entry> stack = new Stack<>();
private Predicate<Bounded> filter;
private Consumer consumer;
public DepthFirstConsumer(Predicate<Bounded> filter, Consumer consumer) {
this.filter = filter;
this.consumer = consumer;
}
@Override
public void feature(Node left, Node right) {
boolean accept = filter.apply(left) || filter.apply(right);
if (accept) {
consumer.feature(left, right);
}
}
@Override
public boolean tree(Node left, Node right) {
boolean accept = filter.apply(left) || filter.apply(right);
stack.push(Entry.tree(left, right, accept));
return accept;
}
@Override
public void endTree(Node left, Node right) {
Entry entry = stack.pop();
entry.apply(consumer);
}
@Override
public boolean bucket(int bucketIndex, int bucketDepth, Bucket left, Bucket right) {
boolean accept = filter.apply(left) || filter.apply(right);
stack.push(Entry.bucket(bucketIndex, bucketDepth, left, right, accept));
return accept;
}
@Override
public void endBucket(int bucketIndex, int bucketDepth, Bucket left, Bucket right) {
Entry entry = stack.pop();
entry.apply(consumer);
}
}
public static interface Consumer {
public abstract void feature(@Nullable final Node left, @Nullable final Node right);
public abstract void tree(@Nullable final Node left, @Nullable final Node right);
public abstract void bucket(final int bucketIndex, final int bucketDepth,
@Nullable final Bucket left, @Nullable final Bucket right);
}
}