Package org.locationtech.geogig.repository

Source Code of org.locationtech.geogig.repository.WorkingTree

/* 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.repository;

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

import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.annotation.Nullable;

import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.data.store.FeatureIteratorIterator;
import org.geotools.factory.Hints;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.locationtech.geogig.api.Context;
import org.locationtech.geogig.api.FeatureBuilder;
import org.locationtech.geogig.api.Node;
import org.locationtech.geogig.api.NodeRef;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.Platform;
import org.locationtech.geogig.api.ProgressListener;
import org.locationtech.geogig.api.Ref;
import org.locationtech.geogig.api.RevFeature;
import org.locationtech.geogig.api.RevFeatureBuilder;
import org.locationtech.geogig.api.RevFeatureType;
import org.locationtech.geogig.api.RevFeatureTypeImpl;
import org.locationtech.geogig.api.RevObject;
import org.locationtech.geogig.api.RevObject.TYPE;
import org.locationtech.geogig.api.RevTree;
import org.locationtech.geogig.api.RevTreeBuilder;
import org.locationtech.geogig.api.data.FindFeatureTypeTrees;
import org.locationtech.geogig.api.plumbing.DiffCount;
import org.locationtech.geogig.api.plumbing.DiffWorkTree;
import org.locationtech.geogig.api.plumbing.FindOrCreateSubtree;
import org.locationtech.geogig.api.plumbing.FindTreeChild;
import org.locationtech.geogig.api.plumbing.LsTreeOp;
import org.locationtech.geogig.api.plumbing.LsTreeOp.Strategy;
import org.locationtech.geogig.api.plumbing.ResolveTreeish;
import org.locationtech.geogig.api.plumbing.RevObjectParse;
import org.locationtech.geogig.api.plumbing.UpdateRef;
import org.locationtech.geogig.api.plumbing.WriteBack;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry;
import org.locationtech.geogig.api.plumbing.diff.DiffObjectCount;
import org.locationtech.geogig.di.Singleton;
import org.locationtech.geogig.storage.BulkOpListener;
import org.locationtech.geogig.storage.BulkOpListener.CountingListener;
import org.locationtech.geogig.storage.StagingDatabase;
import org.opengis.feature.Feature;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
import org.opengis.geometry.BoundingBox;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Stopwatch;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.UnmodifiableIterator;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.Inject;
import com.vividsolutions.jts.geom.CoordinateSequenceFactory;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.impl.PackedCoordinateSequenceFactory;

/**
* A working tree is the collection of Features for a single FeatureType in GeoServer that has a
* repository associated with it (and hence is subject of synchronization).
* <p>
* It represents the set of Features tracked by some kind of geospatial data repository (like the
* GeoServer Catalog). It is essentially a "tree" with various roots and only one level of nesting,
* since the FeatureTypes held in this working tree are the equivalents of files in a git working
* tree.
* </p>
* <p>
* <ul>
* <li>A WorkingTree represents the current working copy of the versioned feature types
* <li>A WorkingTree has a Repository
* <li>A Repository holds commits and branches
* <li>You perform work on the working tree (insert/delete/update features)
* <li>Then you commit to the current Repository's branch
* <li>You can checkout a different branch from the Repository and the working tree will be updated
* to reflect the state of that branch
* </ul>
*
* @see Repository
*/
@Singleton
public class WorkingTree {

    private StagingDatabase indexDatabase;

    private Context context;

    @Inject
    public WorkingTree(final Context injector) {
        this.indexDatabase = injector.stagingDatabase();
        this.context = injector;
    }

    /**
     * Updates the WORK_HEAD ref to the specified tree.
     *
     * @param newTree the tree to be set as the new WORK_HEAD
     */
    public synchronized void updateWorkHead(ObjectId newTree) {

        context.command(UpdateRef.class).setName(Ref.WORK_HEAD).setNewValue(newTree).call();
    }

    /**
     * @return the tree represented by WORK_HEAD. If there is no tree set at WORK_HEAD, it will
     *         return the HEAD tree (no unstaged changes).
     */
    public synchronized RevTree getTree() {
        Optional<ObjectId> workTreeId = context.command(ResolveTreeish.class)
                .setTreeish(Ref.WORK_HEAD).call();

        RevTree workTree = RevTree.EMPTY;

        if (workTreeId.isPresent()) {
            if (!workTreeId.get().equals(RevTree.EMPTY_TREE_ID)) {
                workTree = context.stagingDatabase().getTree(workTreeId.get());
            }
        } else {
            // Work tree was not resolved, update it to the head.
            Optional<ObjectId> headTreeId = context.command(ResolveTreeish.class)
                    .setTreeish(Ref.HEAD).call();

            if (headTreeId.isPresent() && !headTreeId.get().equals(RevTree.EMPTY_TREE_ID)) {
                workTree = context.objectDatabase().getTree(headTreeId.get());
                updateWorkHead(workTree.getId());
            }
        }
        Preconditions.checkState(workTree != null);
        return workTree;
    }

    /**
     * @return a supplier for the working tree.
     */
    private Supplier<RevTreeBuilder> getTreeSupplier() {
        Supplier<RevTreeBuilder> supplier = new Supplier<RevTreeBuilder>() {
            @Override
            public RevTreeBuilder get() {
                return getTree().builder(indexDatabase);
            }
        };
        return Suppliers.memoize(supplier);
    }

    /**
     * Deletes a single feature from the working tree and updates the WORK_HEAD ref.
     *
     * @param path the path of the feature
     * @param featureId the id of the feature
     * @return true if the object was found and deleted, false otherwise
     */
    public boolean delete(final String path, final String featureId) {
        Optional<NodeRef> typeTreeRef = context.command(FindTreeChild.class).setIndex(true)
                .setParent(getTree()).setChildPath(path).call();

        ObjectId metadataId = null;
        if (typeTreeRef.isPresent()) {
            metadataId = typeTreeRef.get().getMetadataId();
        }

        RevTreeBuilder parentTree = context.command(FindOrCreateSubtree.class).setIndex(true)
                .setParent(Suppliers.ofInstance(Optional.of(getTree()))).setChildPath(path).call()
                .builder(indexDatabase);

        String featurePath = NodeRef.appendChild(path, featureId);
        Optional<Node> node = findUnstaged(featurePath);
        if (node.isPresent()) {
            parentTree.remove(node.get().getName());
        }

        ObjectId newTree = context.command(WriteBack.class).setAncestor(getTreeSupplier())
                .setChildPath(path).setToIndex(true).setMetadataId(metadataId)
                .setTree(parentTree.build()).call();

        updateWorkHead(newTree);

        return node.isPresent();
    }

    /**
     * Deletes a tree and the features it contains from the working tree and updates the WORK_HEAD
     * ref.
     *
     * @param path the path to the tree to delete
     * @throws Exception
     */
    public void delete(final String path) {

        final String parentPath = NodeRef.parentPath(path);
        final String childName = NodeRef.nodeFromPath(path);

        final RevTree workHead = getTree();

        RevTree parent;
        RevTreeBuilder parentBuilder;
        ObjectId parentMetadataId = ObjectId.NULL;
        if (parentPath.isEmpty()) {
            parent = workHead;
            parentBuilder = workHead.builder(indexDatabase);
        } else {
            Optional<NodeRef> parentRef = context.command(FindTreeChild.class).setParent(workHead)
                    .setChildPath(parentPath).setIndex(true).call();
            if (!parentRef.isPresent()) {
                return;
            }

            parentMetadataId = parentRef.get().getMetadataId();
            parent = context.command(RevObjectParse.class).setObjectId(parentRef.get().objectId())
                    .call(RevTree.class).get();
            parentBuilder = parent.builder(indexDatabase);
        }
        RevTree newParent = parentBuilder.remove(childName).build();
        indexDatabase.put(newParent);
        if (parent.getId().equals(newParent.getId())) {
            return;// nothing changed
        }

        ObjectId newWorkHead;
        if (parentPath.isEmpty()) {
            newWorkHead = newParent.getId();
        } else {
            newWorkHead = context.command(WriteBack.class).setToIndex(true)
                    .setAncestor(workHead.builder(indexDatabase)).setChildPath(parentPath)
                    .setTree(newParent).setMetadataId(parentMetadataId).call();
        }
        updateWorkHead(newWorkHead);
    }

    /**
     * Deletes a collection of features of the same type from the working tree and updates the
     * WORK_HEAD ref.
     *
     * @param typeName feature type
     * @param filter - currently unused
     * @param affectedFeatures features to remove
     * @throws Exception
     */
    public void delete(final Name typeName, final Filter filter,
            final Iterator<Feature> affectedFeatures) throws Exception {

        Optional<NodeRef> typeTreeRef = context.command(FindTreeChild.class).setIndex(true)
                .setParent(getTree()).setChildPath(typeName.getLocalPart()).call();

        ObjectId parentMetadataId = null;
        if (typeTreeRef.isPresent()) {
            parentMetadataId = typeTreeRef.get().getMetadataId();
        }

        RevTreeBuilder parentTree = context.command(FindOrCreateSubtree.class)
                .setParent(Suppliers.ofInstance(Optional.of(getTree()))).setIndex(true)
                .setChildPath(typeName.getLocalPart()).call().builder(indexDatabase);

        String fid;
        String featurePath;

        while (affectedFeatures.hasNext()) {
            fid = affectedFeatures.next().getIdentifier().getID();
            featurePath = NodeRef.appendChild(typeName.getLocalPart(), fid);
            Optional<Node> ref = findUnstaged(featurePath);
            if (ref.isPresent()) {
                parentTree.remove(ref.get().getName());
            }
        }

        ObjectId newTree = context.command(WriteBack.class)
                .setAncestor(getTree().builder(indexDatabase)).setMetadataId(parentMetadataId)
                .setChildPath(typeName.getLocalPart()).setToIndex(true).setTree(parentTree.build())
                .call();

        updateWorkHead(newTree);
    }

    /**
     * Deletes a feature type from the working tree and updates the WORK_HEAD ref.
     *
     * @param typeName feature type to remove
     * @throws Exception
     */
    public void delete(final Name typeName) throws Exception {
        checkNotNull(typeName);

        RevTreeBuilder workRoot = getTree().builder(indexDatabase);

        final String treePath = typeName.getLocalPart();
        if (workRoot.get(treePath).isPresent()) {
            workRoot.remove(treePath);
            RevTree newRoot = workRoot.build();
            indexDatabase.put(newRoot);
            updateWorkHead(newRoot.getId());
        }
    }

    /**
     *
     * @param features the features to delete
     */
    public void delete(Iterator<String> features) {
        Map<String, RevTreeBuilder> parents = Maps.newHashMap();

        final RevTree currentWorkHead = getTree();
        while (features.hasNext()) {
            String featurePath = features.next();
            // System.err.println("removing " + feature);
            String parentPath = NodeRef.parentPath(featurePath);
            RevTreeBuilder parentTree;
            if (parents.containsKey(parentPath)) {
                parentTree = parents.get(parentPath);
            } else {
                parentTree = context.command(FindOrCreateSubtree.class).setIndex(true)
                        .setParent(Suppliers.ofInstance(Optional.of(currentWorkHead)))
                        .setChildPath(parentPath).call().builder(indexDatabase);
                parents.put(parentPath, parentTree);
            }
            String featureName = NodeRef.nodeFromPath(featurePath);
            parentTree.remove(featureName);
        }
        ObjectId newTree = null;
        for (Map.Entry<String, RevTreeBuilder> entry : parents.entrySet()) {
            String path = entry.getKey();

            RevTreeBuilder parentTree = entry.getValue();
            RevTree newTypeTree = parentTree.build();

            ObjectId metadataId = null;
            Optional<NodeRef> currentTreeRef = context.command(FindTreeChild.class).setIndex(true)
                    .setParent(currentWorkHead).setChildPath(path).call();
            if (currentTreeRef.isPresent()) {
                metadataId = currentTreeRef.get().getMetadataId();
            }
            newTree = context.command(WriteBack.class).setAncestor(getTreeSupplier())
                    .setChildPath(path).setToIndex(true).setTree(newTypeTree)
                    .setMetadataId(metadataId).call();
            updateWorkHead(newTree);
        }
    }

    public synchronized NodeRef createTypeTree(final String treePath, final FeatureType featureType) {

        final RevTree workHead = getTree();
        Optional<NodeRef> typeTreeRef = context.command(FindTreeChild.class).setIndex(true)
                .setParent(workHead).setChildPath(treePath).call();

        final RevFeatureType revType = RevFeatureTypeImpl.build(featureType);
        if (typeTreeRef.isPresent()) {
            throw new IllegalArgumentException("Tree already exists at " + treePath);
        }
        indexDatabase.put(revType);

        final ObjectId metadataId = revType.getId();
        final RevTree newTree = new RevTreeBuilder(indexDatabase).build();

        ObjectId newWorkHeadId = context.command(WriteBack.class).setToIndex(true)
                .setAncestor(workHead.builder(indexDatabase)).setChildPath(treePath)
                .setTree(newTree).setMetadataId(metadataId).call();
        updateWorkHead(newWorkHeadId);

        return context.command(FindTreeChild.class).setIndex(true).setParent(getTree())
                .setChildPath(treePath).call().get();
    }

    /**
     * Insert a single feature into the working tree and updates the WORK_HEAD ref.
     *
     * @param parentTreePath path of the parent tree to insert the feature into
     * @param feature the feature to insert
     */
    public Node insert(final String parentTreePath, final Feature feature) {

        final FeatureType featureType = feature.getType();

        NodeRef treeRef;

        Optional<NodeRef> typeTreeRef = context.command(FindTreeChild.class).setIndex(true)
                .setParent(getTree()).setChildPath(parentTreePath).call();
        ObjectId metadataId;
        if (typeTreeRef.isPresent()) {
            treeRef = typeTreeRef.get();
            RevFeatureType newFeatureType = RevFeatureTypeImpl.build(featureType);
            metadataId = newFeatureType.getId().equals(treeRef.getMetadataId()) ? ObjectId.NULL
                    : newFeatureType.getId();
            if (!newFeatureType.getId().equals(treeRef.getMetadataId())) {
                indexDatabase.put(newFeatureType);
            }
        } else {
            treeRef = createTypeTree(parentTreePath, featureType);
            metadataId = ObjectId.NULL;// treeRef.getMetadataId();
        }

        // ObjectId metadataId = treeRef.getMetadataId();
        final Node node = putInDatabase(feature, metadataId);

        RevTreeBuilder parentTree = context.command(FindOrCreateSubtree.class).setIndex(true)
                .setParent(Suppliers.ofInstance(Optional.of(getTree())))
                .setChildPath(parentTreePath).call().builder(indexDatabase);

        parentTree.put(node);
        final ObjectId treeMetadataId = treeRef.getMetadataId();

        ObjectId newTree = context.command(WriteBack.class).setAncestor(getTreeSupplier())
                .setChildPath(parentTreePath).setToIndex(true).setTree(parentTree.build())
                .setMetadataId(treeMetadataId).call();

        updateWorkHead(newTree);

        final String featurePath = NodeRef.appendChild(parentTreePath, node.getName());
        Optional<NodeRef> featureRef = context.command(FindTreeChild.class).setIndex(true)
                .setParent(getTree()).setChildPath(featurePath).call();
        return featureRef.get().getNode();
    }

    public void insert(final String treePath,
            @SuppressWarnings("rawtypes") final FeatureSource source, final Query query,
            ProgressListener listener) {

        final NodeRef treeRef = findOrCreateTypeTree(treePath, source);

        Long collectionSize = null;
        try {
            // try for a fast count
            int count = source.getCount(Query.ALL);
            if (count > -1) {
                collectionSize = Long.valueOf(count);
            }
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }

        final int nFetchThreads;
        {
            // maxFeatures is assumed to be supported by all data sources, so supportsPaging depends
            // only on offset being supported
            boolean supportsPaging = source.getQueryCapabilities().isOffsetSupported();
            if (supportsPaging) {
                Platform platform = context.platform();
                int availableProcessors = platform.availableProcessors();
                nFetchThreads = Math.max(2, availableProcessors / 2);
            } else {
                nFetchThreads = 1;
            }
        }

        final ExecutorService executorService = Executors.newFixedThreadPool(2 + nFetchThreads,
                new ThreadFactoryBuilder().setNameFormat("WorkingTree-tree-builder-%d").build());

        listener.started();

        Stopwatch sw = Stopwatch.createStarted();

        final RevTree origTree = indexDatabase.getTree(treeRef.objectId());
        Platform platform = context.platform();
        RevTreeBuilder2 builder = new RevTreeBuilder2(indexDatabase, origTree,
                treeRef.getMetadataId(), platform, executorService);

        List<Future<Integer>> insertBlobsFuture = insertBlobs(source, query, executorService,
                listener, collectionSize, nFetchThreads, builder);

        RevTree newFeatureTree;
        try {
            long insertedCount = 0;
            for (Future<Integer> f : insertBlobsFuture) {
                insertedCount += f.get().longValue();
            }
            sw.stop();
            listener.setDescription(insertedCount + " distinct features inserted in " + sw);

            listener.setDescription("Building final tree...");

            sw.reset().start();
            newFeatureTree = builder.build();

            listener.setDescription(String.format("%d features tree built in %s",
                    newFeatureTree.size(), sw.stop()));
            listener.complete();

        } catch (Exception e) {
            throw Throwables.propagate(Throwables.getRootCause(e));
        } finally {
            executorService.shutdown();
        }
        ObjectId newTree = context.command(WriteBack.class).setAncestor(getTreeSupplier())
                .setChildPath(treePath).setMetadataId(treeRef.getMetadataId()).setToIndex(true)
                .setTree(newFeatureTree).call();

        updateWorkHead(newTree);

    }

    private NodeRef findOrCreateTypeTree(final String treePath,
            @SuppressWarnings("rawtypes") final FeatureSource source) {

        final NodeRef treeRef;
        {
            Optional<NodeRef> typeTreeRef = context.command(FindTreeChild.class).setIndex(true)
                    .setParent(getTree()).setChildPath(treePath).call();

            if (typeTreeRef.isPresent()) {
                treeRef = typeTreeRef.get();
            } else {
                FeatureType featureType = source.getSchema();
                treeRef = createTypeTree(treePath, featureType);
            }
        }
        return treeRef;
    }

    @SuppressWarnings("rawtypes")
    private List<Future<Integer>> insertBlobs(final FeatureSource source, final Query baseQuery,
            final ExecutorService executorService, final ProgressListener listener,
            final @Nullable Long collectionSize, int nTasks, RevTreeBuilder2 builder) {

        int partitionSize = 0;
        BulkOpListener bulkOpListener;
        if (collectionSize == null) {
            nTasks = 1;
            partitionSize = Integer.MAX_VALUE;
            bulkOpListener = BulkOpListener.NOOP_LISTENER;
        } else {
            final int total = collectionSize.intValue();
            partitionSize = total / nTasks;
            bulkOpListener = new BulkOpListener() {
                int inserted = 0;

                @Override
                public synchronized void inserted(ObjectId object,
                        @Nullable Integer storageSizeBytes) {
                    listener.setProgress((float) (++inserted * 100) / total);
                }
            };
        }

        List<Future<Integer>> results = Lists.newArrayList();
        for (int i = 0; i < nTasks; i++) {
            Integer offset = nTasks == 1 ? null : i * partitionSize;
            Integer limit = nTasks == 1 ? null : partitionSize;
            if (i == nTasks - 1) {
                limit = null;// let the last task take any remaining
                             // feature
            }
            results.add(executorService.submit(new BlobInsertTask(source, offset, limit,
                    bulkOpListener, builder)));
        }
        return results;
    }

    private final class BlobInsertTask implements Callable<Integer> {

        private final BulkOpListener listener;

        @SuppressWarnings("rawtypes")
        private FeatureSource source;

        private Integer offset;

        private Integer limit;

        private RevTreeBuilder2 builder;

        private BlobInsertTask(@SuppressWarnings("rawtypes") FeatureSource source,
                @Nullable Integer offset, @Nullable Integer limit, BulkOpListener listener,
                RevTreeBuilder2 builder) {
            this.source = source;
            this.offset = offset;
            this.limit = limit;
            this.listener = listener;
            this.builder = builder;
        }

        @SuppressWarnings({ "rawtypes", "unchecked" })
        @Override
        public Integer call() throws Exception {

            final Query query = new Query();
            CoordinateSequenceFactory coordSeq = new PackedCoordinateSequenceFactory();
            query.getHints().add(new Hints(Hints.JTS_COORDINATE_SEQUENCE_FACTORY, coordSeq));
            query.setStartIndex(offset);
            if (limit != null && limit.intValue() > 0) {
                query.setMaxFeatures(limit);
            }
            FeatureCollection collection = source.getFeatures(query);
            FeatureIterator features = collection.features();
            Iterator<Feature> fiterator = new FeatureIteratorIterator<Feature>(features);

            Iterator<RevObject> objects = Iterators.transform(fiterator,
                    new Function<Feature, RevObject>() {
                        @Override
                        public RevFeature apply(final Feature feature) {
                            final RevFeature revFeature = RevFeatureBuilder.build(feature);

                            ObjectId id = revFeature.getId();
                            String name = feature.getIdentifier().getID();
                            BoundingBox bounds = feature.getBounds();
                            FeatureType type = feature.getType();

                            builder.putFeature(id, name, bounds, type);
                            return revFeature;
                        }

                    });

            CountingListener countingListener = BulkOpListener.newCountingListener();
            try {
                indexDatabase.putAll(objects, BulkOpListener.composite(listener, countingListener));
            } finally {
                features.close();
            }
            return countingListener.inserted();
        }
    }

    /**
     * Inserts a collection of features into the working tree and updates the WORK_HEAD ref.
     *
     * @param treePath the path of the tree to insert the features into
     * @param features the features to insert
     * @param listener a {@link ProgressListener} for the current process
     * @param insertedTarget if provided, inserted features will be added to this list
     * @param collectionSize number of features to add
     * @throws Exception
     */
    public void insert(final String treePath, Iterator<? extends Feature> features,
            final ProgressListener listener, @Nullable final List<Node> insertedTarget,
            @Nullable final Integer collectionSize) {

        final Function<Feature, String> providedPath = new Function<Feature, String>() {
            @Override
            public String apply(Feature input) {
                return treePath;
            }
        };

        insert(providedPath, features, listener, insertedTarget, collectionSize);
    }

    /**
     * Inserts the given {@code features} into the working tree, using the {@code treePathResolver}
     * function to determine to which tree each feature is added.
     *
     * @param treePathResolver a function that determines the path of the tree where each feature
     *        node is stored
     * @param features the features to insert, possibly of different schema and targetted to
     *        different tree paths
     * @param listener a progress listener
     * @param insertedTarget if provided, all nodes created will be added to this list. Beware of
     *        possible memory implications when inserting a lot of features.
     * @param collectionSize if given, used to determine progress and notify the {@code listener}
     * @return the total number of inserted features
     */
    public void insert(final Function<Feature, String> treePathResolver,
            Iterator<? extends Feature> features, final ProgressListener listener,
            @Nullable final List<Node> insertedTarget, @Nullable final Integer collectionSize) {

        checkArgument(collectionSize == null || collectionSize.intValue() > -1);

        final int nTreeThreads = Math.max(2, Runtime.getRuntime().availableProcessors() / 2);
        final ExecutorService treeBuildingService = Executors.newFixedThreadPool(nTreeThreads,
                new ThreadFactoryBuilder().setNameFormat("WorkingTree-tree-builder-%d").build());

        final WorkingTreeInsertHelper insertHelper;

        insertHelper = new WorkingTreeInsertHelper(indexDatabase, context, getTree(),
                treePathResolver, treeBuildingService);

        UnmodifiableIterator<? extends Feature> filtered = Iterators.filter(features,
                new Predicate<Feature>() {
                    @Override
                    public boolean apply(Feature feature) {
                        if (listener.isCanceled()) {
                            return false;
                        }
                        if (feature instanceof FeatureToDelete) {
                            insertHelper.remove((FeatureToDelete) feature);
                            return false;
                        } else {
                            return true;
                        }
                    }

                });
        Iterator<RevObject> objects = Iterators.transform(filtered,
                new Function<Feature, RevObject>() {

                    private int count;

                    @Override
                    public RevFeature apply(Feature feature) {
                        final RevFeature revFeature = RevFeatureBuilder.build(feature);
                        ObjectId id = revFeature.getId();
                        final Node node = insertHelper.put(id, feature);

                        if (insertedTarget != null) {
                            insertedTarget.add(node);
                        }

                        count++;
                        if (collectionSize == null) {
                            listener.setProgress(count);
                        } else {
                            listener.setProgress((float) (count * 100) / collectionSize.intValue());
                        }
                        return revFeature;
                    }

                });
        try {
            listener.started();

            indexDatabase.putAll(objects);
            if (listener.isCanceled()) {
                return;
            }
            listener.setDescription("Building trees for "
                    + new TreeSet<String>(insertHelper.getTreeNames()));
            Stopwatch sw = Stopwatch.createStarted();

            Map<NodeRef, RevTree> trees = insertHelper.buildTrees();

            listener.setDescription(String.format("Trees built in %s", sw.stop()));

            for (Map.Entry<NodeRef, RevTree> treeEntry : trees.entrySet()) {
                if (!listener.isCanceled()) {
                    NodeRef treeRef = treeEntry.getKey();
                    RevTree newFeatureTree = treeEntry.getValue();

                    String treePath = treeRef.path();

                    ObjectId newRootTree = context.command(WriteBack.class)
                            .setAncestor(getTreeSupplier()).setChildPath(treePath)
                            .setMetadataId(treeRef.getMetadataId()).setToIndex(true)
                            .setTree(newFeatureTree).call();
                    updateWorkHead(newRootTree);
                }
            }
            listener.complete();
        } finally {
            treeBuildingService.shutdownNow();
        }
    }

    /**
     * Updates a collection of features in the working tree and updates the WORK_HEAD ref.
     *
     * @param treePath the path of the tree to insert the features into
     * @param features the features to insert
     * @param listener a {@link ProgressListener} for the current process
     * @param collectionSize number of features to add
     * @throws Exception
     */
    public void update(final String treePath, final Iterator<Feature> features,
            final ProgressListener listener, @Nullable final Integer collectionSize)
            throws Exception {

        checkArgument(collectionSize == null || collectionSize.intValue() > -1);

        final Integer size = collectionSize == null || collectionSize.intValue() < 1 ? null
                : collectionSize.intValue();

        insert(treePath, features, listener, null, size);
    }

    /**
     * Determines if a specific feature type is versioned (existing in the main repository).
     *
     * @param typeName feature type to check
     * @return true if the feature type is versioned, false otherwise.
     */
    public boolean hasRoot(final Name typeName) {
        String localPart = typeName.getLocalPart();

        Optional<NodeRef> typeNameTreeRef = context.command(FindTreeChild.class).setIndex(true)
                .setChildPath(localPart).call();

        return typeNameTreeRef.isPresent();
    }

    /**
     * @param pathFilter if specified, only changes that match the filter will be returned
     * @return an iterator for all of the differences between the work tree and the index based on
     *         the path filter.
     */
    public Iterator<DiffEntry> getUnstaged(final @Nullable String pathFilter) {
        Iterator<DiffEntry> unstaged = context.command(DiffWorkTree.class).setFilter(pathFilter)
                .setReportTrees(true).call();
        return unstaged;
    }

    /**
     * @param pathFilter if specified, only changes that match the filter will be counted
     * @return the number differences between the work tree and the index based on the path filter.
     */
    public DiffObjectCount countUnstaged(final @Nullable String pathFilter) {
        DiffObjectCount count = context.command(DiffCount.class).setOldVersion(Ref.STAGE_HEAD)
                .setNewVersion(Ref.WORK_HEAD).addFilter(pathFilter).call();
        return count;
    }

    /**
     * Returns true if there are no unstaged changes, false otherwise
     */
    public boolean isClean() {
        Optional<ObjectId> resolved = context.command(ResolveTreeish.class)
                .setTreeish(Ref.STAGE_HEAD).call();
        return getTree().getId().equals(resolved.or(ObjectId.NULL));
    }

    /**
     * @param path finds a {@link Node} for the feature at the given path in the index
     * @return the Node for the feature at the specified path if it exists in the work tree,
     *         otherwise Optional.absent()
     */
    public Optional<Node> findUnstaged(final String path) {
        Optional<NodeRef> nodeRef = context.command(FindTreeChild.class).setIndex(true)
                .setParent(getTree()).setChildPath(path).call();
        if (nodeRef.isPresent()) {
            return Optional.of(nodeRef.get().getNode());
        } else {
            return Optional.absent();
        }
    }

    /**
     * Adds a single feature to the staging database.
     *
     * @param feature the feature to add
     * @param metadataId
     * @return the Node for the inserted feature
     */
    private Node putInDatabase(final Feature feature, final ObjectId metadataId) {

        checkNotNull(feature);
        checkNotNull(metadataId);

        final RevFeature newFeature = RevFeatureBuilder.build(feature);
        final ObjectId objectId = newFeature.getId();
        final Envelope bounds = (ReferencedEnvelope) feature.getBounds();
        final String nodeName = feature.getIdentifier().getID();

        indexDatabase.put(newFeature);

        Node newObject = Node.create(nodeName, objectId, metadataId, TYPE.FEATURE, bounds);
        return newObject;
    }

    /**
     * @return a list of all the feature type names in the working tree
     * @see FindFeatureTypeTrees
     */
    public List<NodeRef> getFeatureTypeTrees() {

        List<NodeRef> typeTrees = context.command(FindFeatureTypeTrees.class)
                .setRootTreeRef(Ref.WORK_HEAD).call();
        return typeTrees;
    }

    /**
     * Updates the definition of a Feature type associated as default feature type to a given path.
     * It also modifies the metadataId associated to features under the passed path, which used the
     * previous default feature type.
     *
     * @param path the path
     * @param featureType the new feature type definition to set as default for the passed path
     */
    public NodeRef updateTypeTree(final String treePath, final FeatureType featureType) {

        // TODO: This is not the optimal way of doing this. A better solution should be found.

        final RevTree workHead = getTree();
        Optional<NodeRef> typeTreeRef = context.command(FindTreeChild.class).setIndex(true)
                .setParent(workHead).setChildPath(treePath).call();
        Preconditions.checkArgument(typeTreeRef.isPresent(), "Tree does not exist: %s", treePath);

        Iterator<NodeRef> iter = context.command(LsTreeOp.class).setReference(treePath)
                .setStrategy(Strategy.DEPTHFIRST_ONLY_FEATURES).call();

        final RevFeatureType revType = RevFeatureTypeImpl.build(featureType);
        indexDatabase.put(revType);

        final ObjectId metadataId = revType.getId();
        RevTreeBuilder treeBuilder = new RevTreeBuilder(indexDatabase);

        final RevTree newTree = treeBuilder.build();
        ObjectId newWorkHeadId = context.command(WriteBack.class).setToIndex(true)
                .setAncestor(workHead.builder(indexDatabase)).setChildPath(treePath)
                .setTree(newTree).setMetadataId(metadataId).call();
        updateWorkHead(newWorkHeadId);

        Map<ObjectId, FeatureBuilder> featureBuilders = Maps.newHashMap();
        while (iter.hasNext()) {
            NodeRef noderef = iter.next();
            RevFeature feature = context.command(RevObjectParse.class)
                    .setObjectId(noderef.objectId()).call(RevFeature.class).get();
            if (!featureBuilders.containsKey(noderef.getMetadataId())) {
                RevFeatureType ft = context.command(RevObjectParse.class)
                        .setObjectId(noderef.getMetadataId()).call(RevFeatureType.class).get();
                featureBuilders.put(noderef.getMetadataId(), new FeatureBuilder(ft));
            }
            FeatureBuilder fb = featureBuilders.get(noderef.getMetadataId());
            String parentPath = NodeRef.parentPath(NodeRef.appendChild(treePath, noderef.path()));
            insert(parentPath, fb.build(noderef.getNode().getName(), feature));
        }

        return context.command(FindTreeChild.class).setIndex(true).setParent(getTree())
                .setChildPath(treePath).call().get();

    }
}
TOP

Related Classes of org.locationtech.geogig.repository.WorkingTree

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.