org.eclipse.emf.compare.egit.internal.merge.TreeWalkResourceVariantTreeProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.emf.compare.egit.internal.merge.TreeWalkResourceVariantTreeProvider.java

Source

/*******************************************************************************
 * Copyright (C) 2015, Obeo.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 *     Laurent Goubet <laurent.goubet@obeo.fr> - initial API and implementation
 *     Axel Richard <axel.richard@obeo.fr> - Add GitPathToProjectPathConverter
 *******************************************************************************/
package org.eclipse.emf.compare.egit.internal.merge;

//CHECKSTYLE:OFF
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.egit.core.GitProvider;
import org.eclipse.egit.core.internal.util.ResourceUtil;
import org.eclipse.emf.compare.egit.internal.storage.TreeParserResourceVariant;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.variants.IResourceVariantTree;

/**
 * This will populate its three {@link IResourceVariantTree} by walking over a tree walk and caching the
 * IResources it spans.
 * <p>
 * Files that are not located within the workspace will be ignored and thus will not be accessible through the
 * trees created by this provider.
 * </p>
 * 
 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
 */
@SuppressWarnings("restriction")
public class TreeWalkResourceVariantTreeProvider implements GitResourceVariantTreeProvider {
    private final IResourceVariantTree baseTree;

    private final IResourceVariantTree oursTree;

    private final IResourceVariantTree theirsTree;

    private final Set<IResource> roots;

    private final Set<IResource> knownResources;

    private final LinkedHashMap<IPath, IProject> map = new LinkedHashMap<IPath, IProject>();

    /**
     * Constructs the resource variant trees by iterating over the given tree walk. This TreeWalk must contain
     * at least three trees corresponding to the three "sides" we need.
     * <p>
     * The tree walk will be reset to its initial state when we are done with the iteration.
     * </p>
     *
     * @param repository
     *            The repository this tree walk has been created for.
     * @param treeWalk
     *            The tree walk to iterate over.
     * @param baseIndex
     *            Index of the ancestor tree in the given TreeWalk (value returned by
     *            {@link TreeWalk#addTree(AbstractTreeIterator)})
     * @param ourIndex
     *            Index of our tree in the given TreeWalk (value returned by
     *            {@link TreeWalk#addTree(AbstractTreeIterator)})
     * @param theirIndex
     *            Index of their tree in the given TreeWalk (value returned by
     *            {@link TreeWalk#addTree(AbstractTreeIterator)})
     * @throws IOException
     *             if we somehow cannot iterate over the treewalk.
     */
    public TreeWalkResourceVariantTreeProvider(Repository repository, TreeWalk treeWalk, int baseIndex,
            int ourIndex, int theirIndex) throws IOException {
        // Record the initial state of this tree walk before iterating
        final AbstractTreeIterator[] initialTrees = new AbstractTreeIterator[treeWalk.getTreeCount()];
        for (int i = 0; i < treeWalk.getTreeCount(); i++) {
            initialTrees[i] = treeWalk.getTree(i, AbstractTreeIterator.class);
        }

        final GitResourceVariantCache baseCache = new GitResourceVariantCache();
        final GitResourceVariantCache theirsCache = new GitResourceVariantCache();
        final GitResourceVariantCache oursCache = new GitResourceVariantCache();

        while (treeWalk.next()) {
            final int modeBase = treeWalk.getRawMode(baseIndex);
            final int modeOurs = treeWalk.getRawMode(ourIndex);
            final int modeTheirs = treeWalk.getRawMode(theirIndex);
            if (!hasSignificantDifference(modeBase, modeOurs, modeTheirs)) {
                // conflict on file modes, leave the default merger handle it
                continue;
            }

            final CanonicalTreeParser base = treeWalk.getTree(baseIndex, CanonicalTreeParser.class);
            final CanonicalTreeParser ours = treeWalk.getTree(ourIndex, CanonicalTreeParser.class);
            final CanonicalTreeParser theirs = treeWalk.getTree(theirIndex, CanonicalTreeParser.class);

            final int nonZeroMode = modeBase != 0 ? modeBase : modeOurs != 0 ? modeOurs : modeTheirs;
            final IResource resource = getResourceHandleForLocation(repository, treeWalk.getPathString(),
                    FileMode.fromBits(nonZeroMode) == FileMode.TREE);

            // Resource variants only make sense for IResources.
            if (resource != null) {
                IPath workspacePath = resource.getFullPath();
                if (modeBase != 0) {
                    baseCache.setVariant(resource,
                            TreeParserResourceVariant.create(repository, base, workspacePath));
                }
                if (modeOurs != 0) {
                    oursCache.setVariant(resource,
                            TreeParserResourceVariant.create(repository, ours, workspacePath));
                }
                if (modeTheirs != 0) {
                    theirsCache.setVariant(resource,
                            TreeParserResourceVariant.create(repository, theirs, workspacePath));
                }
            }

            if (treeWalk.isSubtree()) {
                treeWalk.enterSubtree();
            }
        }

        // TODO any better way to reset the tree walk after an iteration?
        treeWalk.reset();
        for (int i = 0; i < initialTrees.length; i++) {
            initialTrees[i].reset();
            treeWalk.addTree(initialTrees[i]);
        }

        baseTree = new GitCachedResourceVariantTree(baseCache);
        theirsTree = new GitCachedResourceVariantTree(theirsCache);
        oursTree = new GitCachedResourceVariantTree(oursCache);

        roots = new LinkedHashSet<IResource>();
        roots.addAll(baseCache.getRoots());
        roots.addAll(oursCache.getRoots());
        roots.addAll(theirsCache.getRoots());

        knownResources = new LinkedHashSet<IResource>();
        knownResources.addAll(baseCache.getKnownResources());
        knownResources.addAll(oursCache.getKnownResources());
        knownResources.addAll(theirsCache.getKnownResources());
    }

    private boolean hasSignificantDifference(int modeBase, int modeOurs, int modeTheirs) {
        if (modeBase == 0) {
            if (FileMode.fromBits(modeOurs | modeTheirs) != FileMode.MISSING) {
                return true;
            } else {
                return (FileMode.fromBits(modeOurs) == FileMode.TREE
                        && FileMode.fromBits(modeTheirs) != FileMode.TREE)
                        || (FileMode.fromBits(modeOurs) != FileMode.TREE
                                && FileMode.fromBits(modeTheirs) == FileMode.TREE);
            }
        }
        return FileMode.fromBits(modeBase & modeOurs) != FileMode.MISSING
                || FileMode.fromBits(modeBase & modeTheirs) != FileMode.MISSING;
    }

    public IResourceVariantTree getBaseTree() {
        return baseTree;
    }

    public IResourceVariantTree getRemoteTree() {
        return theirsTree;
    }

    public IResourceVariantTree getSourceTree() {
        return oursTree;
    }

    public Set<IResource> getKnownResources() {
        return knownResources;
    }

    public Set<IResource> getRoots() {
        return roots;
    }

    /**
     * Returns a resource handle for this path in the workspace. Note that neither the resource nor the result
     * need exist in the workspace : this may return inexistent or otherwise non-accessible IResources.
     *
     * @param repository
     *            The repository within which is tracked this file.
     * @param repoRelativePath
     *            Repository-relative path of the file we need an handle for.
     * @param isFolder
     *            <code>true</code> if the file being sought is a folder.
     * @return The resource handle for the given path in the workspace.
     */
    public IResource getResourceHandleForLocation(Repository repository, String repoRelativePath,
            boolean isFolder) {
        IResource resource = null;

        final String workDir = repository.getWorkTree().getAbsolutePath();
        final IPath path = new Path(workDir + '/' + repoRelativePath);
        final File file = path.toFile();
        if (file.exists()) {
            if (isFolder) {
                resource = ResourceUtil.getContainerForLocation(path, false);
            } else {
                resource = ResourceUtil.getFileForLocation(path, false);
            }
        }

        if (repoRelativePath.endsWith(".project")) { //$NON-NLS-1$
            IPath parentPath = path.removeLastSegments(1);
            IProject p = ResourcesPlugin.getWorkspace().getRoot().getProject(parentPath.lastSegment().toString());
            if (map.get(parentPath) == null) {
                map.put(parentPath, p);
            }
        }

        if (resource == null) {
            // It may be a file that only exists on remote side. We need to
            // create an IResource for it.
            // If it is a project file, then create an IProject.
            final List<IPath> list = new ArrayList<IPath>(map.keySet());
            for (int i = list.size() - 1; i >= 0; i--) {
                IPath projectPath = list.get(i);
                if (projectPath.isPrefixOf(path) && !projectPath.equals(path)) {
                    final IPath projectRelativePath = path.makeRelativeTo(projectPath);
                    if (isFolder) {
                        resource = map.get(projectPath).getFolder(projectRelativePath);
                    } else {
                        resource = map.get(projectPath).getFile(projectRelativePath);
                    }
                    break;
                }
            }
        }

        if (resource == null) {
            // This is a file that no longer exists locally, yet we still need
            // to determine an IResource for it.
            // Try and find a Project in the workspace which path is a prefix of
            // the file we seek and which is mapped to the current repository.
            final IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
            for (IProject project : root.getProjects()) {
                if (RepositoryProvider.getProvider(project, GitProvider.ID) != null) {
                    final IPath projectLocation = project.getLocation();
                    if (projectLocation != null && projectLocation.isPrefixOf(path)) {
                        final IPath projectRelativePath = path.makeRelativeTo(projectLocation);
                        if (isFolder) {
                            resource = project.getFolder(projectRelativePath);
                        } else {
                            resource = project.getFile(projectRelativePath);
                        }
                        break;
                    }
                }
            }
        }

        return resource;
    }
}
// CHECKSTYLE:ON