Package org.locationtech.geogig.api.plumbing

Source Code of org.locationtech.geogig.api.plumbing.DiffTree$ChangeTypeFilteringDiffConsumer

/* 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:
* Gabriel Roldan (Boundless) - initial implementation
*/
package org.locationtech.geogig.api.plumbing;

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

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nullable;

import org.geotools.geometry.jts.ReferencedEnvelope;
import org.locationtech.geogig.api.AbstractGeoGigOp;
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.RevTree;
import org.locationtech.geogig.api.plumbing.diff.BoundsFilteringDiffConsumer;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry.ChangeType;
import org.locationtech.geogig.api.plumbing.diff.DiffPathTracker;
import org.locationtech.geogig.api.plumbing.diff.PreOrderDiffWalk;
import org.locationtech.geogig.api.plumbing.diff.PreOrderDiffWalk.Consumer;
import org.locationtech.geogig.api.plumbing.diff.PreOrderDiffWalk.ForwardingConsumer;
import org.locationtech.geogig.api.plumbing.diff.PathFilteringDiffConsumer;
import org.locationtech.geogig.storage.ObjectDatabase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;

/**
* Compares the content and metadata links of blobs found via two tree objects on the repository's
* {@link ObjectDatabase}
*/
public class DiffTree extends AbstractGeoGigOp<Iterator<DiffEntry>> implements
        Supplier<Iterator<DiffEntry>> {

    private static final Logger LOGGER = LoggerFactory.getLogger(DiffTree.class);

    private final List<String> pathFilters = Lists.newLinkedList();

    private ReferencedEnvelope boundsFilter;

    private ChangeType changeTypeFilter;

    private String oldRefSpec;

    private String newRefSpec;

    private boolean reportTrees;

    private boolean recursive;

    private Predicate<Bounded> customFilter;

    /**
     * Constructs a new instance of the {@code DiffTree} operation with the given parameters.
     */
    public DiffTree() {
        this.recursive = true;
    }

    /**
     * @param oldRefSpec the ref that points to the "old" version
     * @return {@code this}
     */
    public DiffTree setOldVersion(String oldRefSpec) {
        this.oldRefSpec = oldRefSpec;
        return this;
    }

    /**
     * @param newRefSpec the ref that points to the "new" version
     * @return {@code this}
     */
    public DiffTree setNewVersion(String newRefSpec) {
        this.newRefSpec = newRefSpec;
        return this;
    }

    /**
     * @param oldTreeId the {@link ObjectId} of the "old" tree
     * @return {@code this}
     */
    public DiffTree setOldTree(ObjectId oldTreeId) {
        this.oldRefSpec = oldTreeId.toString();
        return this;
    }

    /**
     * @param newTreeId the {@link ObjectId} of the "new" tree
     * @return {@code this}
     */
    public DiffTree setNewTree(ObjectId newTreeId) {
        this.newRefSpec = newTreeId.toString();
        return this;
    }

    /**
     * @param path the path filter to use during the diff operation, replaces any other filter
     *        previously set
     * @return {@code this}
     */
    public DiffTree setPathFilter(@Nullable String path) {
        if (path == null) {
            setPathFilter((List<String>) null);
        } else {
            setPathFilter(ImmutableList.of(path));
        }
        return this;
    }

    public DiffTree setPathFilter(@Nullable List<String> pathFitlers) {
        this.pathFilters.clear();
        if (pathFitlers != null) {
            this.pathFilters.addAll(pathFitlers);
        }
        return this;
    }

    public DiffTree setBoundsFilter(@Nullable ReferencedEnvelope bounds) {
        this.boundsFilter = bounds;
        return this;
    }

    public DiffTree setCustomFilter(@Nullable Predicate<Bounded> customFilter) {
        this.customFilter = customFilter;
        return this;
    }

    public DiffTree setChangeTypeFilter(@Nullable ChangeType changeType) {
        this.changeTypeFilter = changeType;
        return this;
    }

    /**
     * Implements {@link Supplier#get()} by delegating to {@link #call()}.
     */
    @Override
    public Iterator<DiffEntry> get() {
        return call();
    }

    /**
     * Finds differences between the two specified trees.
     *
     * @return an iterator to a set of differences between the two trees
     * @see DiffEntry
     */
    @Override
    protected Iterator<DiffEntry> _call() throws IllegalArgumentException {
        checkNotNull(oldRefSpec, "old version not specified");
        checkNotNull(newRefSpec, "new version not specified");
        final RevTree oldTree = resolveTree(oldRefSpec);
        final RevTree newTree = resolveTree(newRefSpec);

        if (oldTree.equals(newTree)) {
            return Iterators.emptyIterator();
        }

        ObjectDatabase leftSource = resolveSource(oldTree.getId());
        ObjectDatabase rightSource = resolveSource(newTree.getId());
        final PreOrderDiffWalk visitor = new PreOrderDiffWalk(oldTree, newTree, leftSource,
                rightSource);

        final BlockingQueue<DiffEntry> queue = new ArrayBlockingQueue<>(100);
        final DiffEntryProducer diffProducer = new DiffEntryProducer(queue);
        diffProducer.setReportTrees(this.reportTrees);
        diffProducer.setRecursive(this.recursive);

        final List<RuntimeException> producerErrors = new LinkedList<>();

        Thread producerThread = new Thread("DiffTree producer thread") {
            @Override
            public void run() {
                Consumer consumer = diffProducer;
                if (customFilter != null) {// evaluated the latest
                    consumer = new PreOrderDiffWalk.FilteringConsumer(consumer, customFilter);
                }
                if (changeTypeFilter != null) {
                    consumer = new ChangeTypeFilteringDiffConsumer(changeTypeFilter, consumer);
                }
                if (boundsFilter != null) {
                    consumer = new BoundsFilteringDiffConsumer(boundsFilter, consumer,
                            stagingDatabase());
                }
                if (!pathFilters.isEmpty()) {// evaluated the former
                    consumer = new PathFilteringDiffConsumer(pathFilters, consumer);
                }
                try {
                    visitor.walk(consumer);
                } catch (RuntimeException e) {
                    LOGGER.error("Error traversing diffs", e);
                    producerErrors.add(e);
                } finally {
                    diffProducer.finished = true;
                }
            }
        };
        producerThread.setDaemon(true);
        producerThread.start();

        Iterator<DiffEntry> consumerIterator = new AbstractIterator<DiffEntry>() {
            @Override
            protected DiffEntry computeNext() {
                if (!producerErrors.isEmpty()) {
                    throw new RuntimeException("Error in producer thread", producerErrors.get(0));
                }
                BlockingQueue<DiffEntry> entries = queue;
                boolean finished = diffProducer.isFinished();
                boolean empty = entries.isEmpty();
                while (!finished || !empty) {
                    try {
                        DiffEntry entry = entries.poll(10, TimeUnit.MILLISECONDS);
                        if (entry != null) {
                            return entry;
                        }
                        finished = diffProducer.isFinished();
                        empty = entries.isEmpty();
                    } catch (InterruptedException e) {
                        throw Throwables.propagate(e);
                    }
                }
                return endOfData();
            }

            @Override
            protected void finalize() {
                diffProducer.finished = true;
            }
        };
        return consumerIterator;
    }

    private RevTree resolveTree(final String treeIsh) {
        RevTree tree;
        if (treeIsh.equals(ObjectId.NULL.toString())) {
            tree = RevTree.EMPTY;
        } else {
            final Optional<ObjectId> treeId = command(ResolveTreeish.class).setTreeish(treeIsh)
                    .call();
            checkArgument(treeId.isPresent(), treeIsh + " did not resolve to a tree");
            tree = command(RevObjectParse.class).setObjectId(treeId.get()).call(RevTree.class)
                    .or(RevTree.EMPTY);
        }
        return tree;
    }

    private ObjectDatabase resolveSource(ObjectId treeId) {
        return objectDatabase().exists(treeId) ? objectDatabase() : stagingDatabase();
    }

    private static class ChangeTypeFilteringDiffConsumer extends ForwardingConsumer {

        private final ChangeType changeTypeFilter;

        public ChangeTypeFilteringDiffConsumer(ChangeType changeTypeFilter, Consumer consumer) {
            super(consumer);
            this.changeTypeFilter = changeTypeFilter;
        }

        @Override
        public void feature(final Node left, final Node right) {
            if (featureApplies(left, right)) {
                super.feature(left, right);
            }
        }

        @Override
        public boolean tree(final Node left, final Node right) {
            if (isRoot(left, right) || treeApplies(left, right)) {
                return super.tree(left, right);
            }
            return false;
        }

        @Override
        public void endTree(final Node left, final Node right) {
            if (isRoot(left, right) || treeApplies(left, right)) {
                super.endTree(left, right);
            }
        }

        @Override
        public boolean bucket(final int bucketIndex, final int bucketDepth, final Bucket left,
                final Bucket right) {
            return treeApplies(left, right) && super.bucket(bucketIndex, bucketDepth, left, right);
        }

        @Override
        public void endBucket(int bucketIndex, int bucketDepth, Bucket left, Bucket right) {
            if (treeApplies(left, right)) {
                super.endBucket(bucketIndex, bucketDepth, left, right);
            }
        }

        private boolean isRoot(final Node left, final Node right) {
            return left == null ? right.getName().isEmpty() : left.getName().isEmpty();
        }

        private boolean featureApplies(final Node left, final Node right) {
            switch (changeTypeFilter) {
            case ADDED:
                return left == null;
            case MODIFIED:
                return left != null && right != null;
            case REMOVED:
                return right == null;
            default:
                throw new IllegalArgumentException("Unknown change type: " + changeTypeFilter);
            }
        }

        private boolean treeApplies(final Bounded left, final Bounded right) {
            if (left != null && right != null) {
                // if neither is null traversal of the trees must continue to figure out the
                // differences
                return true;
            }
            switch (changeTypeFilter) {
            case ADDED:
                return left == null;
            case REMOVED:
                return right == null;
            case MODIFIED:
                return false;// safe to return false as its guaranteed that either left or right is
                             // null
            default:
                throw new IllegalArgumentException("Unknown change type: " + changeTypeFilter);
            }
        }
    }

    private static class DiffEntryProducer implements Consumer {

        private DiffPathTracker tracker = new DiffPathTracker();

        private boolean reportFeatures = true, reportTrees = false;

        private BlockingQueue<DiffEntry> entries;

        private volatile boolean finished;

        private boolean recursive = true;

        public DiffEntryProducer(BlockingQueue<DiffEntry> queue) {
            this.entries = queue;
        }

        @Override
        public void feature(Node left, Node right) {
            if (!finished && reportFeatures) {
                String treePath = tracker.getCurrentPath();

                NodeRef oldRef = left == null ? null : new NodeRef(left, treePath, tracker
                        .currentLeftMetadataId().or(ObjectId.NULL));
                NodeRef newRef = right == null ? null : new NodeRef(right, treePath, tracker
                        .currentRightMetadataId().or(ObjectId.NULL));

                try {
                    entries.put(new DiffEntry(oldRef, newRef));
                } catch (InterruptedException e) {
                    // throw Throwables.propagate(e);
                }
            }
        }

        public void setRecursive(boolean recursive) {
            this.recursive = recursive;
        }

        public void setReportTrees(boolean reportTrees) {
            this.reportTrees = reportTrees;
        }

        public boolean isFinished() {
            return finished;
        }

        @Override
        public boolean tree(Node left, Node right) {
            final String parentPath = tracker.getCurrentPath();
            tracker.tree(left, right);
            // System.err.printf("%s.tree(%s, %s)\n", getClass().getSimpleName(), left, right);
            if (!finished && reportTrees) {
                if (parentPath != null) {// do not report the root tree
                    NodeRef oldRef = left == null ? null : new NodeRef(left, parentPath, tracker
                            .currentLeftMetadataId().or(ObjectId.NULL));

                    NodeRef newRef = right == null ? null : new NodeRef(right, parentPath, tracker
                            .currentRightMetadataId().or(ObjectId.NULL));
                    try {
                        entries.put(new DiffEntry(oldRef, newRef));
                    } catch (InterruptedException e) {
                        // throw Throwables.propagate(e);
                        // die gracefully
                        return false;
                    }
                }
            }
            if (recursive) {
                return !finished;
            }
            return parentPath == null;
        }

        @Override
        public void endTree(Node left, Node right) {
            // System.err.printf("%s.endTree(%s, %s)\n", getClass().getSimpleName(), left, right);
            tracker.endTree(left, right);
            if (tracker.isEmpty()) {
                finished = true;
            }
        }

        @Override
        public boolean bucket(int bucketIndex, int bucketDepth, Bucket left, Bucket right) {
            return !finished;
        }

        @Override
        public void endBucket(int bucketIndex, int bucketDepth, Bucket left, Bucket right) {
            // no action required
        }
    }

    /**
     * @param reportTrees
     * @return
     */
    public DiffTree setReportTrees(boolean reportTrees) {
        this.reportTrees = reportTrees;
        return this;
    }

    /**
     * Sets whether to return differences recursively ({@code true} or just for direct children (
     * {@code false}. Defaults to {@code true}
     */
    public DiffTree setRecursive(boolean recursive) {
        this.recursive = recursive;
        return this;
    }
}
TOP

Related Classes of org.locationtech.geogig.api.plumbing.DiffTree$ChangeTypeFilteringDiffConsumer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.