org.locationtech.geogig.porcelain.RemoveOp.java Source code

Java tutorial

Introduction

Here is the source code for org.locationtech.geogig.porcelain.RemoveOp.java

Source

/* Copyright (c) 2012-2016 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.porcelain;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.locationtech.geogig.di.CanRunDuringConflict;
import org.locationtech.geogig.model.Node;
import org.locationtech.geogig.model.ObjectId;
import org.locationtech.geogig.model.Ref;
import org.locationtech.geogig.model.RevObject.TYPE;
import org.locationtech.geogig.model.RevTree;
import org.locationtech.geogig.plumbing.DiffCount;
import org.locationtech.geogig.plumbing.LsTreeOp;
import org.locationtech.geogig.plumbing.LsTreeOp.Strategy;
import org.locationtech.geogig.plumbing.ResolveTreeish;
import org.locationtech.geogig.repository.AbstractGeoGigOp;
import org.locationtech.geogig.repository.DefaultProgressListener;
import org.locationtech.geogig.repository.DiffEntry;
import org.locationtech.geogig.repository.DiffObjectCount;
import org.locationtech.geogig.repository.NodeRef;
import org.locationtech.geogig.repository.ProgressListener;
import org.locationtech.geogig.repository.StagingArea;
import org.locationtech.geogig.repository.WorkingTree;

import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * Removes a features and/or tree nodes from the working tree and index
 * 
 */
@CanRunDuringConflict
public class RemoveOp extends AbstractGeoGigOp<DiffObjectCount> {

    private List<String> pathsToRemove;

    /**
     * If true, any path in {@link #addPathToRemove(String)} that points to a tree instead of a
     * feature, is completely removed from the working tree, including the tree node itself.
     * <p>
     * This argument is mutually exclusive with {@link #truncate}
     */
    private boolean recursive;

    /**
     * If true, any path in {@link #addPathToRemove(String)} that points to a tree instead of a
     * feature, is truncated, leaving the tree node empty.
     * <p>
     * This argument is mutually exclusive with {@link #recursive}
     */
    private boolean truncate;

    public RemoveOp() {
        this.pathsToRemove = new ArrayList<String>();
    }

    /**
     * @param path a path to remove
     * @return {@code this}
     */
    public RemoveOp addPathToRemove(final String path) {
        pathsToRemove.add(path);
        return this;
    }

    /**
     * Indicates whether to recursively remove tree nodes pointed out to by any
     * {@link #addPathToRemove(String) added path}, including the tree nodes themselves.
     * <p>
     * If true, any path added to {@link #addPathToRemove(String)} that points to a tree instead of
     * a feature, is completely removed from the working tree, including the tree node itself.
     * <p>
     * NOTE this argument is mutually exclusive with {@link #setTruncate(boolean)}, and an
     * {@link IllegalArgumentException} will be thrown when the command is executed if both are set
     * to {@code true}.
     */
    public RemoveOp setRecursive(boolean recursive) {
        this.recursive = recursive;
        return this;
    }

    /**
     * Indicates whether to truncate tree nodes pointed out to by any
     * {@link #addPathToRemove(String) added path}, leaving such trees empty.
     * <p>
     * If true, any path added to {@link #addPathToRemove(String)} that points to a tree instead of
     * a feature, is truncated and the tree node is left empty.
     * <p>
     * NOTE this argument is mutually exclusive with {@link #setRecursive(boolean)}, and an
     * {@link IllegalArgumentException} will be thrown when the command is executed if both are set
     * to {@code true}.
     */
    public RemoveOp setTruncate(boolean truncate) {
        this.truncate = truncate;
        return this;
    }

    /**
     * @see java.util.concurrent.Callable#call()
     */
    protected DiffObjectCount _call() {
        checkArgument(!pathsToRemove.isEmpty(), "No paths to remove were indicated");
        checkArgument(!(recursive && truncate), "recursive and truncate arguments are mutually exclusive");

        // Check that all paths are valid and exist
        final WorkingTree workingTree = workingTree();

        final RevTree initialWorkTree = workingTree.getTree();

        final Map<String, NodeRef> deleteTrees = getDeleteTrees(initialWorkTree);
        final List<String> deleteFeatures = filterFeatures(deleteTrees.keySet());

        if (!deleteTrees.isEmpty() && !(recursive || truncate)) {
            String path = deleteTrees.values().iterator().next().path();
            throw new IllegalArgumentException(
                    String.format("Cannot remove tree %s if recursive or truncate is not specified", path));
        }

        final ProgressListener listener = getProgressListener();

        for (Entry<String, NodeRef> e : deleteTrees.entrySet()) {
            final ObjectId currentWorkHead = workingTree.getTree().getId();
            String treePath = e.getKey();
            checkState(recursive || truncate);

            ObjectId newWorkHead;
            if (recursive) {
                newWorkHead = workingTree.delete(treePath);
            } else {
                newWorkHead = workingTree.truncate(treePath);
            }

            if (currentWorkHead.equals(newWorkHead)) {
                listener.setDescription(String.format("Tree %s not found", treePath));
            } else {
                listener.setDescription(
                        String.format("%s %s tree", (recursive ? "Deleted" : "Truncated"), treePath));
            }
            if (listener.isCanceled()) {
                return null;
            }
        }

        if (listener.isCanceled()) {
            return null;
        }

        if (!deleteFeatures.isEmpty()) {
            listener.setDescription("Deleting features...");
            workingTree.delete(deleteFeatures.iterator(), listener);
        }

        listener.setDescription("Staging changes...");

        final RevTree finalWorkTree = workingTree.getTree();

        Optional<ObjectId> headTree = command(ResolveTreeish.class).setTreeish(Ref.HEAD).call();
        ObjectId stageTree = stagingArea().getTree().getId();
        final boolean nothingElseStaged = headTree.isPresent() && headTree.get().equals(stageTree);
        if (nothingElseStaged) {
            stagingArea().updateStageHead(finalWorkTree.getId());
        } else {
            stageDeletes(deleteTrees.values().iterator(), deleteFeatures.iterator());
        }

        getProgressListener().setDescription("Computing result count...");
        List<String> paths = new ArrayList<String>(deleteTrees.keySet());
        paths.addAll(deleteFeatures);
        final DiffObjectCount stageCount = command(DiffCount.class).setOldTree(initialWorkTree.getId())//
                .setNewTree(finalWorkTree.getId())//
                .setFilter(paths)//
                .call();

        return stageCount;
    }

    private void stageDeletes(Iterator<NodeRef> trees, Iterator<String> features) {
        final StagingArea index = stagingArea();

        Iterator<DiffEntry> treeDeletes = Iterators.transform(trees, (treeRef) -> new DiffEntry(treeRef, null));

        Iterator<DiffEntry> featureDeletes = Iterators.transform(features, (featurePath) -> {
            Node node = Node.create(NodeRef.nodeFromPath(featurePath), ObjectId.NULL, ObjectId.NULL, TYPE.FEATURE,
                    null);
            String parentPath = NodeRef.parentPath(featurePath);
            NodeRef oldFeature = new NodeRef(node, parentPath, ObjectId.NULL);
            return new DiffEntry(oldFeature, null);
        });

        ProgressListener progress = DefaultProgressListener.NULL;
        index.stage(progress, Iterators.concat(treeDeletes, featureDeletes), -1);
    }

    /**
     * @return an iterable with all elements in {@link #pathsToRemove} whose parent path are not in
     *         {@code deleteTrees}
     */
    private List<String> filterFeatures(Set<String> deleteTrees) {
        List<String> filtered = new ArrayList<>(this.pathsToRemove.size());
        for (String pathToRemove : pathsToRemove) {
            if (deleteTrees.contains(pathToRemove) || deleteTrees.contains(NodeRef.parentPath(pathToRemove))) {
                continue;
            }
            NodeRef.checkValidPath(pathToRemove);
            filtered.add(pathToRemove);
        }
        return filtered;
    }

    private Map<String, NodeRef> getDeleteTrees(RevTree workTree) {

        Iterator<NodeRef> childTrees = command(LsTreeOp.class).setStrategy(Strategy.DEPTHFIRST_ONLY_TREES)
                .setReference(workTree.getId().toString()).call();

        ImmutableMap<String, NodeRef> treesByPath = Maps.uniqueIndex(childTrees, (ref) -> ref.path());

        Set<String> requestedTrees = Sets.intersection(treesByPath.keySet(), new HashSet<>(pathsToRemove));
        Predicate<String> keyPredicate = Predicates.in(requestedTrees);
        Map<String, NodeRef> requestedTreesMap = Maps.filterKeys(treesByPath, keyPredicate);
        return requestedTreesMap;
    }

}