/* Copyright (c) 2012-2014 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:
* Victor Olaya (Boundless) - initial implementation
*/
package org.locationtech.geogig.api.plumbing;
import static com.google.common.base.Preconditions.checkArgument;
import java.util.Iterator;
import java.util.List;
import org.locationtech.geogig.api.AbstractGeoGigOp;
import org.locationtech.geogig.api.Bounded;
import org.locationtech.geogig.api.NodeRef;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.Ref;
import org.locationtech.geogig.api.RevCommit;
import org.locationtech.geogig.api.RevObject;
import org.locationtech.geogig.api.RevObject.TYPE;
import org.locationtech.geogig.api.RevTree;
import org.locationtech.geogig.api.plumbing.diff.DepthTreeIterator;
import org.locationtech.geogig.di.CanRunDuringConflict;
import org.locationtech.geogig.storage.ObjectDatabase;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
/**
* List the contents of a {@link RevTree tree} object as an Iterator<{@link NodeRef}>, using
* the sepecified {@link Strategy strategy} to indicate what to return.
* <p>
* The tree to traverse is given as a {@link #setReference(String) ref spec}, as supported by
* {@link RevParse#setRefSpec(String) RevParse} and must resolve to a tree object. If no ref spec is
* specified, the root of the current working tree is assumed.
*/
@CanRunDuringConflict
public class LsTreeOp extends AbstractGeoGigOp<Iterator<NodeRef>> implements
Supplier<Iterator<NodeRef>> {
/**
* Enumeration of the possible results of the {@link LsTreeOp} operation, indicating whether to
* return the recursive contents of a tree or not, and whether to return feature and/or tree
* child references.
*/
public enum Strategy {
/**
* Default ls strategy, list the all direct child entries of a tree
*/
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, returning the tree ref when
* a tree node is found followed by the contents of the subtree
*/
DEPTHFIRST,
/**
* Recursively list the contents of a tree in depth-first order, but do not report TREE
* entries, only FEATURE ones
*/
DEPTHFIRST_ONLY_FEATURES,
/**
* Recursively list the contents of a tree in depth-first order, but do not report FEATURE
* entries, only TREE ones
*/
DEPTHFIRST_ONLY_TREES
}
private Strategy strategy;
private String ref;
private Predicate<Bounded> refBoundsFilter;
public LsTreeOp() {
this.strategy = Strategy.CHILDREN;
}
/**
* @param path a path to list its content
* @return {@code this}
*/
public LsTreeOp setReference(final String ref) {
this.ref = ref;
return this;
}
public LsTreeOp setStrategy(final Strategy strategy) {
Preconditions.checkNotNull(strategy);
this.strategy = strategy;
return this;
}
/**
* @param refBoundsFilter
* @return
*/
public LsTreeOp setBoundsFilter(Predicate<Bounded> refBoundsFilter) {
this.refBoundsFilter = refBoundsFilter;
return this;
}
/**
* @see java.util.concurrent.Callable#call()
*/
protected Iterator<NodeRef> _call() {
String ref = this.ref;
if (ref == null) {
ref = Ref.WORK_HEAD;
}
ObjectId metadataId = ObjectId.NULL;
final String path = ref.lastIndexOf(':') != -1 ? ref.substring(ref.lastIndexOf(':') + 1)
: "";
if (!path.isEmpty()) {
final String providedRefName = ref.lastIndexOf(':') != -1 ? ref.substring(0,
ref.lastIndexOf(':')) : null;
if (providedRefName != null) {
Optional<ObjectId> rootTreeId = command(ResolveTreeish.class).setTreeish(
providedRefName).call();
if (rootTreeId.isPresent()) {
RevTree rootTree = command(RevObjectParse.class).setObjectId(rootTreeId.get())
.call(RevTree.class).get();
Optional<NodeRef> treeRef = command(FindTreeChild.class).setChildPath(path)
.setIndex(true).setParent(rootTree).call();
metadataId = treeRef.isPresent() ? treeRef.get().getMetadataId()
: ObjectId.NULL;
}
}
}
// is it just a ref name?
Optional<Ref> reference = command(RefParse.class).setName(ref).call();
if (reference.isPresent()) {
if (reference.get().getObjectId().isNull()) {
return Iterators.emptyIterator();
}
}
Optional<RevObject> revObject = command(RevObjectParse.class).setRefSpec(ref).call(
RevObject.class);
Optional<NodeRef> treeRef = Optional.absent();
if (!revObject.isPresent()) {
if (Ref.WORK_HEAD.equals(ref)) { // we are requesting a listing of the whole working
// tree but it is empty
return Iterators.emptyIterator();
}
// let's try to see if it is a feature type or feature in the working tree
NodeRef.checkValidPath(ref);
treeRef = command(FindTreeChild.class).setParent(workingTree().getTree())
.setChildPath(ref).setIndex(true).call();
Preconditions.checkArgument(treeRef.isPresent(), "Invalid reference: %s", ref);
ObjectId treeId = treeRef.get().objectId();
metadataId = treeRef.get().getMetadataId();
revObject = command(RevObjectParse.class).setObjectId(treeId).call(RevObject.class);
}
checkArgument(revObject.isPresent(), "Invalid reference: %s", ref);
final TYPE type = revObject.get().getType();
switch (type) {
case FEATURE:
NodeRef nodeRef = treeRef.isPresent() ? treeRef.get() : null;
List<NodeRef> nodeRefs = Lists.newArrayList();
nodeRefs.add(nodeRef);
// If show trees options is passed in show all trees that contain this feature
if (this.strategy == Strategy.TREES_ONLY) {
if (nodeRef != null) {
while (!nodeRef.getParentPath().isEmpty()) {
treeRef = command(FindTreeChild.class).setParent(workingTree().getTree())
.setChildPath(nodeRef.getParentPath()).setIndex(true).call();
nodeRef = treeRef.get();
nodeRefs.add(nodeRef);
}
}
}
return nodeRefs.iterator();
case COMMIT:
RevCommit revCommit = (RevCommit) revObject.get();
ObjectId treeId = revCommit.getTreeId();
revObject = command(RevObjectParse.class).setObjectId(treeId).call(RevObject.class);
case TREE:
DepthTreeIterator.Strategy iterStrategy;
switch (this.strategy) {
case CHILDREN:
iterStrategy = DepthTreeIterator.Strategy.CHILDREN;
break;
case FEATURES_ONLY:
iterStrategy = DepthTreeIterator.Strategy.FEATURES_ONLY;
break;
case TREES_ONLY:
iterStrategy = DepthTreeIterator.Strategy.TREES_ONLY;
break;
case DEPTHFIRST:
iterStrategy = DepthTreeIterator.Strategy.RECURSIVE;
break;
case DEPTHFIRST_ONLY_FEATURES:
iterStrategy = DepthTreeIterator.Strategy.RECURSIVE_FEATURES_ONLY;
break;
case DEPTHFIRST_ONLY_TREES:
iterStrategy = DepthTreeIterator.Strategy.RECURSIVE_TREES_ONLY;
break;
default:
throw new IllegalStateException("Unknown strategy: " + this.strategy);
}
RevTree tree = (RevTree) revObject.get();
ObjectDatabase database = stagingDatabase();
DepthTreeIterator iter = new DepthTreeIterator(path, metadataId, tree, database,
iterStrategy);
iter.setBoundsFilter(refBoundsFilter);
return iter;
default:
throw new IllegalArgumentException(String.format("Invalid reference: %s", ref));
}
}
/**
* Implements {@link Supplier#get()} by deferring to {@link #call()}
*/
@Override
public Iterator<NodeRef> get() {
return call();
}
}