/* Copyright (c) 2012-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:
* Gabriel Roldan (Boundless) - initial implementation
*/
package org.locationtech.geogig.api.plumbing.diff;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Iterator;
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.NodeRef;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.RevObject.TYPE;
import org.locationtech.geogig.api.RevTree;
import org.locationtech.geogig.storage.ObjectDatabase;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Iterators;
/**
* An iterator over a {@link RevTree} that can return different results depending on the
* {@link #Strategy} given;
*/
public class DepthTreeIterator extends AbstractIterator<NodeRef> {
public enum Strategy {
/**
* Default strategy, list the all direct child entries of a tree, no recursion
*/
CHILDREN,
/**
* List only the direct child entries of a tree that are of type FEATURE
*/
FEATURES_ONLY,
/**
* List only the direct child entries of a tree that are of type TREE
*/
TREES_ONLY,
/**
* Recursively list the contents of a tree in depth-first order, including both TREE and
* FEATURE entries
*/
RECURSIVE,
/**
* Recursively list the contents of a tree in depth-first order, but do not report TREE
* entries, only FEATURE ones
*/
RECURSIVE_FEATURES_ONLY,
/**
* Recursively list the contents of a tree in depth-first order, but do not report TREE
* entries, only FEATURE ones
*/
RECURSIVE_TREES_ONLY
}
private Iterator<NodeRef> iterator;
private ObjectDatabase source;
private Strategy strategy;
private Predicate<Bounded> boundsFilter;
private NodeToRef functor;
private RevTree tree;
private String treePath;
private ObjectId metadataId;
private static class NodeToRef implements Function<Node, NodeRef> {
private final String treePath;
private final ObjectId metadataId;
public NodeToRef(String treePath, ObjectId metadataId) {
this.treePath = treePath;
this.metadataId = metadataId;
}
@Override
public NodeRef apply(Node node) {
return new NodeRef(node, treePath, node.getMetadataId().or(metadataId));
}
};
public DepthTreeIterator(final String treePath, final ObjectId metadataId, RevTree tree,
ObjectDatabase source, Strategy strategy) {
checkNotNull(treePath);
checkNotNull(metadataId);
checkNotNull(tree);
checkNotNull(source);
checkNotNull(strategy);
this.tree = tree;
this.treePath = treePath;
this.metadataId = metadataId;
this.source = source;
this.strategy = strategy;
this.functor = new NodeToRef(treePath, metadataId);
this.boundsFilter = Predicates.alwaysTrue();
}
public void setBoundsFilter(@Nullable Predicate<Bounded> boundsFilter) {
Predicate<Bounded> alwaysTrue = Predicates.alwaysTrue();
this.boundsFilter = boundsFilter == null ? alwaysTrue : boundsFilter;
}
@Override
protected NodeRef computeNext() {
if (iterator == null) {
switch (strategy) {
case CHILDREN:
iterator = Iterators.transform(new Children(tree), functor);
break;
case FEATURES_ONLY:
iterator = Iterators.transform(new Features(tree), functor);
break;
case TREES_ONLY:
iterator = Iterators.transform(new Trees(tree), functor);
break;
case RECURSIVE:
iterator = new Recursive(treePath, metadataId, tree, true, true);
break;
case RECURSIVE_FEATURES_ONLY:
iterator = new Recursive(treePath, metadataId, tree, true, false);
break;
case RECURSIVE_TREES_ONLY:
iterator = new Recursive(treePath, metadataId, tree, false, true);
break;
default:
throw new IllegalArgumentException("Unrecognized strategy: " + strategy);
}
}
if (iterator.hasNext()) {
return iterator.next();
}
return endOfData();
}
private class Recursive extends AbstractIterator<NodeRef> {
private boolean features;
private boolean trees;
private Iterator<Node> myEntries;
private Iterator<NodeRef> currEntryIterator;
private NodeToRef functor;
public Recursive(String treePath, ObjectId metadataId, RevTree tree, boolean features,
boolean trees) {
Preconditions.checkArgument(features || trees);
this.functor = new NodeToRef(treePath, metadataId);
this.features = features;
this.trees = trees;
if (!features) {
this.myEntries = new Trees(tree);
} else {
this.myEntries = new Children(tree);
}
currEntryIterator = Iterators.emptyIterator();
}
@Override
protected NodeRef computeNext() {
while (!currEntryIterator.hasNext()) {
if (myEntries.hasNext()) {
currEntryIterator = resolveEntryIterator(myEntries.next());
} else {
return endOfData();
}
}
return currEntryIterator.next();
}
private Iterator<NodeRef> resolveEntryIterator(Node next) {
if (TYPE.FEATURE.equals(next.getType())) {
if (features) {
return Iterators.singletonIterator(functor.apply(next));
}
return Iterators.emptyIterator();
}
Preconditions.checkArgument(TYPE.TREE.equals(next.getType()));
ObjectId treeId = next.getObjectId();
RevTree childTree = source.getTree(treeId);
String childTreePath = NodeRef.appendChild(this.functor.treePath, next.getName());
Iterator<NodeRef> children = new Recursive(childTreePath, next.getMetadataId().or(
functor.metadataId), childTree, features, trees);
if (trees) {
children = Iterators.concat(Iterators.singletonIterator(functor.apply(next)),
children);
}
return children;
}
}
private class Children extends AbstractIterator<Node> {
private Iterator<Node> children;
public Children(RevTree tree) {
if (tree.buckets().isPresent()) {
this.children = new Buckets(tree);
} else {
this.children = Iterators.filter(tree.children(), boundsFilter);
}
}
@Override
protected Node computeNext() {
if (children.hasNext()) {
return children.next();
}
return endOfData();
}
}
private class Features extends AbstractIterator<Node> {
private Iterator<Node> features;
public Features(RevTree tree) {
if (tree.features().isPresent()) {
this.features = Iterators.filter(tree.features().get().iterator(), boundsFilter);
} else if (tree.buckets().isPresent()) {
this.features = new FeatureBuckets(tree);
} else {
this.features = Iterators.emptyIterator();
}
}
@Override
protected Node computeNext() {
if (features.hasNext()) {
return features.next();
}
return endOfData();
}
}
private class Trees extends AbstractIterator<Node> {
private Iterator<Node> trees;
public Trees(RevTree tree) {
if (tree.numTrees() == 0) {
this.trees = Iterators.emptyIterator();
} else if (tree.trees().isPresent()) {
this.trees = Iterators.filter(tree.trees().get().iterator(), boundsFilter);
} else if (tree.buckets().isPresent()) {
this.trees = new TreeBuckets(tree);
} else {
this.trees = Iterators.emptyIterator();
}
}
@Override
protected Node computeNext() {
if (trees.hasNext()) {
return trees.next();
}
return endOfData();
}
}
/**
* Returns all direct children of a buckets tree
*/
private class Buckets extends AbstractIterator<Node> {
private Iterator<Bucket> buckets;
private Iterator<Node> bucketEntries;
public Buckets(RevTree tree) {
Preconditions.checkArgument(tree.buckets().isPresent());
buckets = Iterators.filter(tree.buckets().get().values().iterator(), boundsFilter);
bucketEntries = Iterators.emptyIterator();
}
@Override
protected Node computeNext() {
while (!bucketEntries.hasNext()) {
if (buckets.hasNext()) {
Bucket nextBucket = buckets.next();
bucketEntries = resolveBucketEntries(nextBucket.id());
} else {
return endOfData();
}
}
return bucketEntries.next();
}
/**
* @param bucketId
* @return
*/
protected Iterator<Node> resolveBucketEntries(ObjectId bucketId) {
RevTree bucketTree = source.getTree(bucketId);
if (bucketTree.buckets().isPresent()) {
return new Buckets(bucketTree);
}
return new Children(bucketTree);
}
}
/**
* Returns all direct children of a buckets tree of type TREE
*/
private class TreeBuckets extends Buckets {
public TreeBuckets(RevTree tree) {
super(tree);
}
@Override
protected Iterator<Node> resolveBucketEntries(ObjectId bucketId) {
RevTree bucketTree = source.getTree(bucketId);
if (bucketTree.numTrees() == 0) {
return Iterators.emptyIterator();
}
if (bucketTree.trees().isPresent()) {
return new Trees(bucketTree);
}
if (bucketTree.buckets().isPresent()) {
return new TreeBuckets(bucketTree);
}
return Iterators.emptyIterator();
}
}
/**
* Returns all direct children of a buckets tree of type FEATURE
*/
private class FeatureBuckets extends Buckets {
public FeatureBuckets(RevTree tree) {
super(tree);
}
@Override
protected Iterator<Node> resolveBucketEntries(ObjectId bucketId) {
RevTree bucketTree = source.getTree(bucketId);
if (bucketTree.buckets().isPresent()) {
return new FeatureBuckets(bucketTree);
}
if (bucketTree.features().isPresent()) {
return new Features(bucketTree);
}
return Iterators.emptyIterator();
}
}
}