/* Copyright (c) 2013 Boundless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* Johnathan Garrett (LMN Solutions) - initial implementation
*/
package org.locationtech.geogig.remote;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Stack;
import org.locationtech.geogig.api.ObjectId;
import com.google.common.collect.ImmutableList;
/**
* Provides a method of traversing the commit graph with overridable functions to determine when to
* prune the traversal, and when to process a commit node.
*/
abstract class CommitTraverser {
private Queue<CommitNode> commitQueue;
public Stack<ObjectId> commits;
public List<ObjectId> have;
private Hashtable<ObjectId, ImmutableList<ObjectId>> commitParents;
/**
* Traversal node that stores information about the ObjectId of the commit and it's depth from
* the starting node.
*/
protected class CommitNode {
ObjectId objectId;
int depth;
public CommitNode(ObjectId objectId, int depth) {
this.objectId = objectId;
this.depth = depth;
}
public ObjectId getObjectId() {
return objectId;
}
public int getDepth() {
return depth;
}
/**
* Use the hash code of the ObjectId.
*/
@Override
public int hashCode() {
return objectId.hashCode();
}
/**
* Ignore depth when comparing two commit nodes.
*
* @return true if the objects are equal
*/
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj == this)
return true;
if (!(obj instanceof CommitNode))
return false;
CommitNode rhs = (CommitNode) obj;
return rhs.objectId.equals(objectId);
}
}
/**
* Evaluation types to control the traversal.
* <p>
* INCLUDE - process the commit (apply)
* <p>
* EXCLUDE - do not process the commit
* <p>
* CONTINUE - continue traversal past this commit
* <p>
* PRUNE - do not traverse past this commit
*/
protected enum Evaluation {
INCLUDE_AND_PRUNE, INCLUDE_AND_CONTINUE, EXCLUDE_AND_PRUNE, EXCLUDE_AND_CONTINUE
};
/**
* Constructs a new {@code CommitTraverser}.
*/
public CommitTraverser() {
commits = new Stack<ObjectId>();
have = new LinkedList<ObjectId>();
commitParents = new Hashtable<ObjectId, ImmutableList<ObjectId>>();
}
/**
* Evaluate the commit node to determine if it should be applied, and if the traversal should
* continue through this commit's parents.
*
* @param commitNode the commit to evaluate
* @return the {@link Evaluation} of the node
*/
protected abstract Evaluation evaluate(CommitNode commitNode);
/**
* Process the accepted commit node.
*
* @param commitNode the commit to apply
*/
protected void apply(CommitNode commitNode, ImmutableList<ObjectId> parents) {
if (commits.contains(commitNode.getObjectId())) {
commits.remove(commitNode.getObjectId());
}
commits.add(commitNode.getObjectId());
}
/**
* Traverse the commit graph from the given starting point.
*
* @param startPoint the commit to start traversing from.
*/
public final void traverse(ObjectId startPoint) {
this.commitQueue = new LinkedList<CommitNode>();
commitQueue.add(new CommitNode(startPoint, 1));
while (!commitQueue.isEmpty()) {
CommitNode node = commitQueue.remove();
Evaluation evaluation = evaluate(node);
ImmutableList<ObjectId> parents;
switch (evaluation) {
case INCLUDE_AND_PRUNE:
parents = getParents(node.getObjectId());
apply(node, parents);
break;
case INCLUDE_AND_CONTINUE:
parents = getParents(node.getObjectId());
apply(node, parents);
addParents(node, parents);
break;
case EXCLUDE_AND_PRUNE:
if (existsInDestination(node.getObjectId()) && !have.contains(node.getObjectId())) {
have.add(node.getObjectId());
}
break;
case EXCLUDE_AND_CONTINUE:
parents = getParents(node.getObjectId());
addParents(node, parents);
if (existsInDestination(node.getObjectId()) && !have.contains(node.getObjectId())) {
have.add(node.getObjectId());
}
break;
}
}
commitParents.clear();
}
/**
* Add the given commit's parents to the traversal queue.
*
* @param commitNode the commit whose parents need to be added
*/
private void addParents(CommitNode commitNode, ImmutableList<ObjectId> parents) {
for (ObjectId parent : parents) {
CommitNode parentNode = new CommitNode(parent, commitNode.getDepth() + 1);
if (commitQueue.contains(parentNode)) {
commitQueue.remove(parentNode);
}
commitQueue.add(parentNode);
}
}
private ImmutableList<ObjectId> getParents(ObjectId commitId) {
ImmutableList<ObjectId> parents = commitParents.get(commitId);
if (parents == null) {
parents = getParentsInternal(commitId);
commitParents.put(commitId, parents);
}
return parents;
}
/**
* Gets the parents of the provided commit.
*
* @param commitId the id of the commit whose parents need to be retrieved
* @return the list of parents
*/
protected abstract ImmutableList<ObjectId> getParentsInternal(ObjectId commitId);
/**
* Determines if the given commitId exists in the destination.
*
* @param commitId the id of the commit to find
* @return true if the commit exists in the destination
*/
protected abstract boolean existsInDestination(ObjectId commitId);
}