org.eclipse.egit.core.project.GitProjectData.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.egit.core.project.GitProjectData.java

Source

/*******************************************************************************
 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
 * Copyright (C) 2008, Google Inc.
 *
 * 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
 *******************************************************************************/
package org.eclipse.egit.core.project;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.egit.core.Activator;
import org.eclipse.egit.core.CoreText;
import org.eclipse.egit.core.GitCorePreferences;
import org.eclipse.egit.core.GitProvider;
import org.eclipse.egit.core.internal.trace.GitTraceLocation;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.WindowCache;
import org.eclipse.jgit.storage.file.WindowCacheConfig;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.RepositoryProvider;

/**
 * This class keeps information about how a project is mapped to
 * a Git repository.
 */
public class GitProjectData {
    private static final Map<IProject, GitProjectData> projectDataCache = new HashMap<IProject, GitProjectData>();

    private static Set<RepositoryChangeListener> repositoryChangeListeners = new HashSet<RepositoryChangeListener>();

    @SuppressWarnings("synthetic-access")
    private static final IResourceChangeListener rcl = new RCL();

    private static class RCL implements IResourceChangeListener {
        @SuppressWarnings("synthetic-access")
        public void resourceChanged(final IResourceChangeEvent event) {
            switch (event.getType()) {
            case IResourceChangeEvent.PRE_CLOSE:
                uncache((IProject) event.getResource());
                break;
            case IResourceChangeEvent.PRE_DELETE:
                try {
                    delete((IProject) event.getResource());
                } catch (IOException e) {
                    Activator.logError(e.getMessage(), e);
                }
                break;
            default:
                break;
            }
        }
    }

    private static QualifiedName MAPPING_KEY = new QualifiedName(GitProjectData.class.getName(),
            "RepositoryMapping"); //$NON-NLS-1$

    /**
     * Start listening for resource changes.
     *
     * @param includeChange true to listen to content changes
     */
    public static void attachToWorkspace(final boolean includeChange) {
        trace("attachToWorkspace - addResourceChangeListener"); //$NON-NLS-1$
        ResourcesPlugin.getWorkspace().addResourceChangeListener(rcl,
                (includeChange ? IResourceChangeEvent.POST_CHANGE : 0) | IResourceChangeEvent.PRE_CLOSE
                        | IResourceChangeEvent.PRE_DELETE);
    }

    /**
     * Stop listening to resource changes
     */
    public static void detachFromWorkspace() {
        trace("detachFromWorkspace - removeResourceChangeListener"); //$NON-NLS-1$
        ResourcesPlugin.getWorkspace().removeResourceChangeListener(rcl);
    }

    /**
     * Register a new listener for repository modification events.
     * <p>
     * This is a no-op if <code>objectThatCares</code> has already been
     * registered.
     * </p>
     *
     * @param objectThatCares
     *            the new listener to register. Must not be null.
     */
    public static synchronized void addRepositoryChangeListener(final RepositoryChangeListener objectThatCares) {
        if (objectThatCares == null)
            throw new NullPointerException();
        repositoryChangeListeners.add(objectThatCares);
    }

    /**
     * Remove a registered {@link RepositoryChangeListener}
     *
     * @param objectThatCares
     *            The listener to remove
     */
    public static synchronized void removeRepositoryChangeListener(final RepositoryChangeListener objectThatCares) {
        repositoryChangeListeners.remove(objectThatCares);
    }

    /**
     * Notify registered {@link RepositoryChangeListener}s of a change.
     *
     * @param which
     *            the repository which has had changes occur within it.
     */
    static void fireRepositoryChanged(final RepositoryMapping which) {
        for (RepositoryChangeListener listener : getRepositoryChangeListeners())
            listener.repositoryChanged(which);
    }

    /**
     * Get a copy of the current set of repository change listeners
     * <p>
     * The array has no references, so is safe for iteration and modification
     *
     * @return a copy of the current repository change listeners
     */
    private static synchronized RepositoryChangeListener[] getRepositoryChangeListeners() {
        return repositoryChangeListeners.toArray(new RepositoryChangeListener[repositoryChangeListeners.size()]);
    }

    /**
     * @param p
     * @return {@link GitProjectData} for the specified project
     */
    public synchronized static GitProjectData get(final IProject p) {
        try {
            GitProjectData d = lookup(p);
            if (d == null && RepositoryProvider.getProvider(p) instanceof GitProvider) {
                d = new GitProjectData(p).load();
                cache(p, d);
            }
            return d;
        } catch (IOException err) {
            Activator.logError(CoreText.GitProjectData_missing, err);
            return null;
        }
    }

    /**
     * Drop the Eclipse project from our association of projects/repositories
     *
     * @param p
     *            Eclipse project
     * @throws IOException
     *             if deletion of property files failed
     */
    public static void delete(final IProject p) throws IOException {
        trace("delete(" + p.getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
        GitProjectData d = lookup(p);
        if (d == null)
            deletePropertyFiles(p);
        else
            d.deletePropertyFilesAndUncache();
    }

    static void trace(final String m) {
        // TODO is this the right location?
        if (GitTraceLocation.CORE.isActive())
            GitTraceLocation.getTrace().trace(GitTraceLocation.CORE.getLocation(), "(GitProjectData) " + m); //$NON-NLS-1$
    }

    private synchronized static void cache(final IProject p, final GitProjectData d) {
        projectDataCache.put(p, d);
    }

    private synchronized static void uncache(final IProject p) {
        if (projectDataCache.remove(p) != null) {
            trace("uncacheDataFor(" //$NON-NLS-1$
                    + p.getName() + ")"); //$NON-NLS-1$
        }
    }

    private synchronized static GitProjectData lookup(final IProject p) {
        return projectDataCache.get(p);
    }

    /**
     * Update the settings for the global window cache of the workspace.
     */
    public static void reconfigureWindowCache() {
        final WindowCacheConfig c = new WindowCacheConfig();
        IEclipsePreferences d = new DefaultScope().getNode(Activator.getPluginId());
        IEclipsePreferences p = new InstanceScope().getNode(Activator.getPluginId());
        c.setPackedGitLimit(p.getInt(GitCorePreferences.core_packedGitLimit,
                d.getInt(GitCorePreferences.core_packedGitLimit, 0)));
        c.setPackedGitWindowSize(p.getInt(GitCorePreferences.core_packedGitWindowSize,
                d.getInt(GitCorePreferences.core_packedGitWindowSize, 0)));
        c.setPackedGitMMAP(p.getBoolean(GitCorePreferences.core_packedGitMMAP,
                d.getBoolean(GitCorePreferences.core_packedGitMMAP, false)));
        c.setDeltaBaseCacheLimit(p.getInt(GitCorePreferences.core_deltaBaseCacheLimit,
                d.getInt(GitCorePreferences.core_deltaBaseCacheLimit, 0)));
        WindowCache.reconfigure(c);
    }

    private final IProject project;

    private final Collection<RepositoryMapping> mappings = new ArrayList<RepositoryMapping>();

    private final Set<IResource> protectedResources = new HashSet<IResource>();

    /**
     * Construct a {@link GitProjectData} for the mapping
     * of a project.
     *
     * @param p Eclipse project
     */
    public GitProjectData(final IProject p) {
        project = p;
    }

    /**
     * @return the Eclipse project mapped through this resource.
     */
    public IProject getProject() {
        return project;
    }

    /**
     * TODO: is this right?
     *
     * @param newMappings
     */
    public void setRepositoryMappings(final Collection<RepositoryMapping> newMappings) {
        mappings.clear();
        mappings.addAll(newMappings);
        remapAll();
    }

    /**
     * Hide our private parts from the navigators other browsers.
     *
     * @throws CoreException
     */
    public void markTeamPrivateResources() throws CoreException {
        for (final Object rmObj : mappings) {
            final RepositoryMapping rm = (RepositoryMapping) rmObj;
            final IContainer c = rm.getContainer();
            if (c == null)
                continue; // Not fully mapped yet?

            final IResource dotGit = c.findMember(Constants.DOT_GIT);
            if (dotGit != null) {
                try {
                    final Repository r = rm.getRepository();
                    final File dotGitDir = dotGit.getLocation().toFile().getCanonicalFile();
                    if (dotGitDir.equals(r.getDirectory())) {
                        trace("teamPrivate " + dotGit); //$NON-NLS-1$
                        dotGit.setTeamPrivateMember(true);
                    }
                } catch (IOException err) {
                    throw new CoreException(Activator.error(CoreText.Error_CanonicalFile, err));
                }
            }
        }
    }

    /**
     * @param f
     * @return true if a resource is protected in this repository
     */
    public boolean isProtected(final IResource f) {
        return protectedResources.contains(f);
    }

    /**
     * @param resource any workbench resource contained within this project.
     * @return the mapping for the specified project
     */
    public RepositoryMapping getRepositoryMapping(IResource resource) {
        IResource r = resource;
        try {
            for (; r != null; r = r.getParent()) {
                final RepositoryMapping m;

                if (!r.isAccessible())
                    continue;
                m = (RepositoryMapping) r.getSessionProperty(MAPPING_KEY);
                if (m != null)
                    return m;
            }
        } catch (CoreException err) {
            Activator.logError(CoreText.GitProjectData_failedFindingRepoMapping, err);
        }
        return null;
    }

    private void deletePropertyFilesAndUncache() throws IOException {
        deletePropertyFiles(getProject());
        uncache(getProject());
    }

    private static void deletePropertyFiles(IProject project) throws IOException {
        final File dir = propertyFile(project).getParentFile();
        FileUtils.delete(dir, FileUtils.RECURSIVE);
        trace("deleteDataFor(" //$NON-NLS-1$
                + project.getName() + ")"); //$NON-NLS-1$
    }

    /**
     * Store information about the repository connection in the workspace
     *
     * @throws CoreException
     */
    public void store() throws CoreException {
        final File dat = propertyFile();
        final File tmp;
        boolean ok = false;

        try {
            trace("save " + dat); //$NON-NLS-1$
            tmp = File.createTempFile("gpd_", //$NON-NLS-1$
                    ".prop", //$NON-NLS-1$
                    dat.getParentFile());
            final FileOutputStream o = new FileOutputStream(tmp);
            try {
                final Properties p = new Properties();
                for (final RepositoryMapping repoMapping : mappings) {
                    repoMapping.store(p);
                }
                p.store(o, "GitProjectData"); //$NON-NLS-1$
                ok = true;
            } finally {
                o.close();
                if (!ok && tmp.exists()) {
                    FileUtils.delete(tmp);
                }
            }
            if (dat.exists())
                FileUtils.delete(dat);
            if (!tmp.renameTo(dat)) {
                if (tmp.exists())
                    FileUtils.delete(tmp);
                throw new CoreException(Activator.error(NLS.bind(CoreText.GitProjectData_saveFailed, dat), null));
            }
        } catch (IOException ioe) {
            throw new CoreException(Activator.error(NLS.bind(CoreText.GitProjectData_saveFailed, dat), ioe));
        }
    }

    private File propertyFile() {
        return propertyFile(getProject());
    }

    private static File propertyFile(IProject project) {
        return new File(project.getWorkingLocation(Activator.getPluginId()).toFile(), "GitProjectData.properties"); //$NON-NLS-1$
    }

    private GitProjectData load() throws IOException {
        final File dat = propertyFile();
        trace("load " + dat); //$NON-NLS-1$

        final FileInputStream o = new FileInputStream(dat);
        try {
            final Properties p = new Properties();
            p.load(o);

            mappings.clear();
            for (final Object keyObj : p.keySet()) {
                final String key = keyObj.toString();
                if (RepositoryMapping.isInitialKey(key)) {
                    mappings.add(new RepositoryMapping(p, key));
                }
            }
        } finally {
            o.close();
        }

        remapAll();
        return this;
    }

    private void remapAll() {
        protectedResources.clear();
        for (final RepositoryMapping repoMapping : mappings) {
            map(repoMapping);
        }
    }

    private void map(final RepositoryMapping m) {
        final IResource r;
        final File git;
        final IResource dotGit;
        IContainer c = null;

        m.clear();
        r = getProject().findMember(m.getContainerPath());
        if (r instanceof IContainer) {
            c = (IContainer) r;
        } else {
            c = (IContainer) r.getAdapter(IContainer.class);
        }

        if (c == null) {
            Activator.logError(CoreText.GitProjectData_mappedResourceGone,
                    new FileNotFoundException(m.getContainerPath().toString()));
            m.clear();
            return;
        }
        m.setContainer(c);

        git = c.getLocation().append(m.getGitDirPath()).toFile();
        if (!git.isDirectory() || !new File(git, "config").isFile()) { //$NON-NLS-1$
            Activator.logError(CoreText.GitProjectData_mappedResourceGone,
                    new FileNotFoundException(m.getContainerPath().toString()));
            m.clear();
            return;
        }

        try {
            m.setRepository(Activator.getDefault().getRepositoryCache().lookupRepository(git));
        } catch (IOException ioe) {
            Activator.logError(CoreText.GitProjectData_mappedResourceGone,
                    new FileNotFoundException(m.getContainerPath().toString()));
            m.clear();
            return;
        }

        m.fireRepositoryChanged();

        trace("map " //$NON-NLS-1$
                + c + " -> " //$NON-NLS-1$
                + m.getRepository());
        try {
            c.setSessionProperty(MAPPING_KEY, m);
        } catch (CoreException err) {
            Activator.logError(CoreText.GitProjectData_failedToCacheRepoMapping, err);
        }

        dotGit = c.findMember(Constants.DOT_GIT);
        if (dotGit != null && dotGit.getLocation().toFile().equals(git)) {
            protect(dotGit);
        }
    }

    private void protect(IResource resource) {
        IResource c = resource;
        while (c != null && !c.equals(getProject())) {
            trace("protect " + c); //$NON-NLS-1$
            protectedResources.add(c);
            c = c.getParent();
        }
    }
}