de.fu_berlin.inf.dpp.project.SharedResourcesManager.java Source code

Java tutorial

Introduction

Here is the source code for de.fu_berlin.inf.dpp.project.SharedResourcesManager.java

Source

/*
 * DPP - Serious Distributed Pair Programming
 * (c) Freie Universitt Berlin - Fachbereich Mathematik und Informatik - 2006
 * (c) Riad Djemili - 2006
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 1, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package de.fu_berlin.inf.dpp.project;

import static java.text.MessageFormat.format;

import java.io.ByteArrayInputStream;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
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.IResourceDelta;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.IJobChangeListener;
import org.eclipse.core.runtime.jobs.IJobManager;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.swt.widgets.Shell;
import org.picocontainer.Startable;
import org.picocontainer.annotations.Inject;

import de.fu_berlin.inf.dpp.activities.FileActivity;
import de.fu_berlin.inf.dpp.activities.FolderActivity;
import de.fu_berlin.inf.dpp.activities.IActivity;
import de.fu_berlin.inf.dpp.activities.IResourceActivity;
import de.fu_berlin.inf.dpp.activities.SPath;
import de.fu_berlin.inf.dpp.activities.VCSActivity;
import de.fu_berlin.inf.dpp.annotations.Component;
import de.fu_berlin.inf.dpp.editor.EditorManager;
import de.fu_berlin.inf.dpp.filesystem.EclipseFileImpl;
import de.fu_berlin.inf.dpp.filesystem.EclipseFolderImpl;
import de.fu_berlin.inf.dpp.filesystem.EclipsePathImpl;
import de.fu_berlin.inf.dpp.filesystem.EclipseProjectImpl;
import de.fu_berlin.inf.dpp.filesystem.EclipseResourceImpl;
import de.fu_berlin.inf.dpp.filesystem.ResourceAdapterFactory;
import de.fu_berlin.inf.dpp.observables.FileReplacementInProgressObservable;
import de.fu_berlin.inf.dpp.session.AbstractActivityConsumer;
import de.fu_berlin.inf.dpp.session.AbstractActivityProducer;
import de.fu_berlin.inf.dpp.session.IActivityConsumer;
import de.fu_berlin.inf.dpp.session.ISarosSession;
import de.fu_berlin.inf.dpp.synchronize.Blockable;
import de.fu_berlin.inf.dpp.synchronize.StopManager;
import de.fu_berlin.inf.dpp.ui.util.SWTUtils;
import de.fu_berlin.inf.dpp.util.FileUtils;
import de.fu_berlin.inf.dpp.vcs.VCSAdapter;
import de.fu_berlin.inf.dpp.vcs.VCSResourceInfo;

/**
 * This manager is responsible for handling all resource changes that aren't
 * handled by the EditorManager, that is for changes that aren't done by
 * entering text in a text editor. It produces and consumes file, folder, and
 * VCS activities.
 * <p>
 * TODO Extract AbstractActivityProducer/Consumer functionality in another
 * classes ResourceActivityProducer/Consumer, rename to
 * SharedResourceChangeListener.
 */
/*
 * For a good introduction to Eclipse's resource change notification mechanisms
 * see
 * http://www.eclipse.org/articles/Article-Resource-deltas/resource-deltas.html
 */
@Component(module = "core")
public class SharedResourcesManager extends AbstractActivityProducer implements IResourceChangeListener, Startable {
    /** The {@link IResourceChangeEvent}s we're going to register for. */
    /*
     * haferburg: We're really only interested in
     * IResourceChangeEvent.POST_CHANGE events. I don't know why other events
     * were tracked, so I removed them.
     * 
     * We're definitely not interested in PRE_REFRESH, refreshes are only
     * interesting when they result in an actual change, in which case we will
     * receive a POST_CHANGE event anyways.
     * 
     * We also don't need PRE_CLOSE, since we'll also get a POST_CHANGE and
     * still have to test project.isOpen().
     * 
     * We might want to add PRE_DELETE if the user deletes our shared project
     * though.
     */
    static final int INTERESTING_EVENTS = IResourceChangeEvent.POST_CHANGE;

    private static final Logger log = Logger.getLogger(SharedResourcesManager.class);

    /**
     * If the StopManager has paused the project, the SharedResourcesManager
     * doesn't react to resource changes.
     */
    protected boolean pause = false;

    protected final ISarosSession sarosSession;

    protected final StopManager stopManager;

    private final Map<IProject, SharedProject> sharedProjects = Collections
            .synchronizedMap(new HashMap<IProject, SharedProject>());
    /**
     * Should return <code>true</code> while executing resource changes to avoid
     * an infinite resource event loop.
     */
    @Inject
    protected FileReplacementInProgressObservable fileReplacementInProgressObservable;

    @Inject
    protected EditorManager editorManager;

    protected Blockable stopManagerListener = new Blockable() {
        @Override
        public void unblock() {
            SharedResourcesManager.this.pause = false;
        }

        @Override
        public void block() {
            SharedResourcesManager.this.pause = true;
        }
    };

    @Override
    public void start() {
        sarosSession.addActivityProducer(this);
        sarosSession.addActivityConsumer(consumer);
        stopManager.addBlockable(stopManagerListener);
        ResourcesPlugin.getWorkspace().addResourceChangeListener(this, INTERESTING_EVENTS);
    }

    @Override
    public void stop() {
        ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
        sarosSession.removeActivityProducer(this);
        sarosSession.removeActivityConsumer(consumer);
        stopManager.removeBlockable(stopManagerListener);
    }

    private IJobChangeListener jobChangeListener = new JobChangeAdapter() {
        @Override
        public void done(IJobChangeEvent event) {
            Job job = event.getJob();
            log.trace("Job " + job.getName() + " done");
            job.removeJobChangeListener(jobChangeListener);
        }
    };

    private ResourceActivityFilter pendingActivities = new ResourceActivityFilter();

    public SharedResourcesManager(ISarosSession sarosSession, StopManager stopManager) {
        this.sarosSession = sarosSession;
        this.stopManager = stopManager;
    }

    /**
     * This method is called from Eclipse when changes to resource are detected
     */
    @Override
    public void resourceChanged(IResourceChangeEvent event) {

        if (fileReplacementInProgressObservable.isReplacementInProgress())
            return;

        if (pause) {
            logPauseWarning(event);
            return;
        }

        if (log.isTraceEnabled()) {
            IJobManager jobManager = Job.getJobManager();
            Job currentJob = jobManager.currentJob();
            if (currentJob != null) {
                currentJob.addJobChangeListener(jobChangeListener);
                log.trace("currentJob='" + currentJob.getName() + "'");
            }
        }
        if (event.getType() == IResourceChangeEvent.POST_CHANGE) {
            // Creations, deletions, modifications of files and folders.
            handlePostChange(event);
        } else {
            log.error("Unhandled event type in in SharedResourcesManager: " + event);
        }
    }

    protected void handlePostChange(IResourceChangeEvent event) {

        if (!sarosSession.hasWriteAccess()) {
            return;
        }

        IResourceDelta delta = event.getDelta();
        log.trace(".resourceChanged() - Delta will be processed");
        if (delta == null) {
            log.error("Unexpected empty delta in " + "SharedResourcesManager: " + event);
            return;
        }

        if (log.isTraceEnabled())
            log.trace("handlePostChange\n" + deltaToString(delta));

        assert delta.getResource() instanceof IWorkspaceRoot;

        // Iterate over all projects.
        boolean postpone = false;
        final boolean useVersionControl = sarosSession.useVersionControl();
        IResourceDelta[] projectDeltas = delta.getAffectedChildren();
        for (IResourceDelta projectDelta : projectDeltas) {
            assert projectDelta.getResource() instanceof IProject;
            IProject project = (IProject) projectDelta.getResource();
            if (!sarosSession.isShared(ResourceAdapterFactory.create(project)))
                continue;

            if (!checkOpenClosed(project))
                continue;

            if (useVersionControl && !checkVCSConnection(project))
                continue;

            SharedProject sharedProject = sharedProjects.get(project);

            if (sharedProject == null)
                continue;

            VCSAdapter vcs = useVersionControl ? VCSAdapter.getAdapter(project) : null;
            ProjectDeltaVisitor visitor;
            if (vcs == null) {
                visitor = new ProjectDeltaVisitor(editorManager, sarosSession, sharedProject);
            } else {
                visitor = vcs.getProjectDeltaVisitor(editorManager, sarosSession, sharedProject);
            }

            try {
                /*
                 * There is some magic involved here. The ProjectDeltaVisitor
                 * will ignore changed files that are currently opened in an
                 * editor to prevent transmitting the whole file content of the
                 * modified file.
                 * 
                 * FIXME document this behavior in the ProjectDeltaVisitor !
                 */
                projectDelta.accept(visitor, IContainer.INCLUDE_HIDDEN);
            } catch (CoreException e) {
                // The Eclipse documentation doesn't specify when
                // CoreExceptions can occur.
                log.debug(format("ProjectDeltaVisitor of project {0} " + "failed for some reason.",
                        project.getName()), e);
            }
            if (visitor.postponeSending()) {
                postpone = true;
            }
            log.trace("Adding new activities " + visitor.pendingActivities);
            pendingActivities.enterAll(visitor.pendingActivities);

            // if (!postpone)
            // assert sharedProject.checkIntegrity();

            log.trace("sharedProject.resourceMap: \n" + sharedProject.resourceMap);
        }
        if (!postpone) {
            fireActivities();
        } else if (!pendingActivities.isEmpty()) {
            log.debug("Postponing sending the activities");
        }
    }

    protected boolean checkOpenClosed(IProject project) {

        SharedProject sharedProject = sharedProjects.get(project);

        if (sharedProject == null)
            return false;

        boolean isProjectOpen = project.isOpen();
        if (sharedProject.updateProjectIsOpen(isProjectOpen)) {
            if (isProjectOpen) {
                // Since the project was just opened, we would get
                // a notification that each file in the project was just
                // added, so we're simply going to ignore this delta. Any
                // resources that were modified externally would be
                // out-of-sync anyways, so when the user refreshes them
                // we'll get notified.
                return false;
            } else {
                // The project was just closed, what do we do here?
            }
        }
        if (!isProjectOpen)
            return false;
        return true;
    }

    /**
     * Returns false if the VCS changed.
     * 
     * @param project
     * @return
     */
    protected boolean checkVCSConnection(IProject project) {

        SharedProject sharedProject = sharedProjects.get(project);

        if (sharedProject == null)
            return true;

        VCSAdapter vcs = VCSAdapter.getAdapter(project);
        VCSAdapter oldVcs = sharedProject.getVCSAdapter();

        if (sharedProject.updateVcs(vcs)) {
            if (vcs == null) {
                // Disconnect
                boolean deleteContent = oldVcs == null || !oldVcs.hasLocalCache(project);
                VCSActivity activity = VCSActivity.disconnect(sarosSession, ResourceAdapterFactory.create(project),
                        deleteContent);
                pendingActivities.enter(activity);
                sharedProject.updateRevision(null);
                sharedProject.updateVcsUrl(null);
            } else {
                // Connect
                VCSResourceInfo info = vcs.getResourceInfo(project);
                String repositoryString = vcs.getRepositoryString(project);
                if (repositoryString == null || info.getURL() == null) {
                    // HACK For some reason, Subclipse returns null values
                    // here. Pretend the vcs is still null and wait for the
                    // next time we get here.
                    sharedProject.updateVcs(null);
                    return false;
                }

                String directory = info.getURL().substring(repositoryString.length());
                VCSActivity activity = VCSActivity.connect(sarosSession, ResourceAdapterFactory.create(project),
                        repositoryString, directory, vcs.getProviderID(project));
                pendingActivities.enter(activity);
                sharedProject.updateVcsUrl(info.getURL());
                sharedProject.updateRevision(info.getRevision());

                log.debug("Connect to VCS");
            }
            return false;
        }

        return true;
    }

    /**
     * Fires the ordered activities. To be run before change event ends.
     */
    protected void fireActivities() {
        if (pendingActivities.isEmpty())
            return;
        final List<IResourceActivity> orderedActivities = pendingActivities.retrieveAll();
        log.trace("Sending activities " + orderedActivities.toString());
        for (final IActivity activity : orderedActivities) {
            /*
             * Make sure we only send a VCSActivity if VC is enabled for this
             * session.
             */
            if (sarosSession.useVersionControl() || !(activity instanceof VCSActivity)) {
                fireActivity(activity);
            } else {
                log.error("Tried to send VCSActivity with VC support disabled.");
            }
        }
    }

    /*
     * coezbek: This warning is misleading! The consistency recovery process
     * might cause IResourceChangeEvents (which do not need to be replicated)
     * [Added in branches/10.2.26.r2028, the commit message claims "Improved
     * logging of ResourceChanges while paused".]
     * 
     * haferburg: When is this even called? We don't get here while this class
     * executes any activity. We can only get here when pause is true, but not
     * fileReplacementInProgressObservable. Also, why add a misleading warning
     * in the first place??
     */
    protected void logPauseWarning(IResourceChangeEvent event) {
        if (event.getType() == IResourceChangeEvent.POST_CHANGE) {

            IResourceDelta delta = event.getDelta();
            if (delta == null) {
                log.error("Resource changed while paused" + " but unexpected empty delta in "
                        + "SharedResourcesManager: " + event);
                return;
            }

            log.warn("Resource changed while paused:\n" + deltaToString(delta));
        } else {
            log.error("Unexpected event type in in logPauseWarning: " + event);
        }
    }

    protected String deltaToString(IResourceDelta delta) {
        ToStringResourceDeltaVisitor visitor = new ToStringResourceDeltaVisitor();
        try {
            delta.accept(visitor, IContainer.INCLUDE_PHANTOMS | IContainer.INCLUDE_HIDDEN
                    | IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS);
        } catch (CoreException e) {
            log.error("ToStringResourceDelta visitor crashed", e);
            return "";
        }
        return visitor.toString();
    }

    private final IActivityConsumer consumer = new AbstractActivityConsumer() {
        @Override
        public void exec(IActivity activity) {
            if (!(activity instanceof FileActivity || activity instanceof FolderActivity
                    || activity instanceof VCSActivity))
                return;

            /*
             * FIXME this will lockout everything. File changes made in the
             * meantime from another background job are not recognized. See
             * AddMultipleFilesTest STF test which fails randomly.
             */
            fileReplacementInProgressObservable.startReplacement();
            log.trace("execing " + activity);

            super.exec(activity);

            fileReplacementInProgressObservable.replacementDone();
            log.trace("done execing " + activity);
        }

        @Override
        public void receive(FileActivity activity) {
            try {
                handleFileActivity(activity);
            } catch (CoreException e) {
                log.error("Failed to execute activity: " + activity, e);
            }
        }

        @Override
        public void receive(FolderActivity activity) {
            try {
                handleFolderActivity(activity);
            } catch (CoreException e) {
                log.error("Failed to execute activity: " + activity, e);
            }
        }

        @Override
        public void receive(VCSActivity activity) {
            handleVCSActivity(activity);
        }
    };

    protected void handleFileActivity(FileActivity activity) throws CoreException {

        if (activity.isRecovery()) {
            handleFileRecovery(activity);
            return;
        }

        // TODO check if we should open / close existing editors here too
        switch (activity.getType()) {
        case CREATED:
            handleFileCreation(activity);
            break;
        case REMOVED:
            handleFileDeletion(activity);
            break;
        case MOVED:
            handleFileMove(activity);
            break;
        }
    }

    private void handleFileRecovery(FileActivity activity) throws CoreException {
        SPath path = activity.getPath();

        log.debug("performing recovery for file: " + activity.getPath().getFullPath());

        editorManager.saveLazy(path);

        boolean editorWasOpen = editorManager.isOpenEditor(path);

        if (editorWasOpen)
            editorManager.closeEditor(path);

        FileActivity.Type type = activity.getType();

        try {
            if (type == FileActivity.Type.CREATED)
                handleFileCreation(activity);
            else if (type == FileActivity.Type.REMOVED)
                handleFileDeletion(activity);
            else
                log.warn("performing recovery for type " + type + " is not supported");
        } finally {
            /*
             * always reset Jupiter algorithm, because upon receiving that
             * activity, it was already reset on the host side
             */
            sarosSession.getConcurrentDocumentClient().reset(path);
        }

        if (editorWasOpen && type != FileActivity.Type.REMOVED)
            editorManager.openEditor(path);
    }

    /**
     * Updates encoding of a file. A best effort is made to use the inherited
     * encoding if available. Does nothing if the file does not exist or the
     * encoding to set is <code>null</code>
     * 
     * @param encoding
     *            the encoding that should be used
     * @param file
     *            the file to update
     * @throws CoreException
     *             if setting the encoding failed
     */
    private void updateFileEncoding(final String encoding, final IFile file) throws CoreException {

        if (encoding == null)
            return;

        if (!file.exists())
            return;

        try {
            Charset.forName(encoding);
        } catch (Exception e) {
            log.warn("encoding " + encoding + " for file " + file + " is not available on this platform", e);
            return;
        }

        String projectEncoding = null;
        String fileEncoding = null;

        try {
            projectEncoding = file.getProject().getDefaultCharset();
        } catch (CoreException e) {
            log.warn("could not determine project encoding for project " + file.getProject(), e);
        }

        try {
            fileEncoding = file.getCharset();
        } catch (CoreException e) {
            log.warn("could not determine file encoding for file " + file, e);
        }

        if (encoding.equals(fileEncoding)) {
            log.debug("encoding does not need to be changed for file: " + file);
            return;
        }

        // use inherited encoding if possible
        if (encoding.equals(projectEncoding)) {
            log.debug(
                    "changing encoding for file " + file + " to use default project encoding: " + projectEncoding);
            file.setCharset(null, new NullProgressMonitor());
            return;
        }

        log.debug("changing encoding for file " + file + " to encoding: " + encoding);

        file.setCharset(encoding, new NullProgressMonitor());
    }

    private void handleFileMove(FileActivity activity) throws CoreException {
        IPath newFilePath = ((EclipsePathImpl) activity.getPath().getFile().getFullPath()).getDelegate();

        IResource oldResource = ((EclipseFileImpl) activity.getOldPath().getFile()).getDelegate();

        FileUtils.mkdirs(((EclipseFileImpl) activity.getPath().getFile()).getDelegate());
        FileUtils.move(newFilePath, oldResource);

        if (activity.getContent() == null)
            return;

        handleFileCreation(activity);
    }

    private void handleFileDeletion(FileActivity activity) throws CoreException {
        IFile file = ((EclipseFileImpl) activity.getPath().getFile()).getDelegate();

        if (file.exists())
            FileUtils.delete(file);
        else
            log.warn("could not delete file " + file + " because it does not exist");
    }

    private void handleFileCreation(FileActivity activity) throws CoreException {
        IFile file = ((EclipseFileImpl) activity.getPath().getFile()).getDelegate();

        final String encoding = activity.getEncoding();

        byte[] actualContent = FileUtils.getLocalFileContent(file);
        byte[] newContent = activity.getContent();

        if (!Arrays.equals(newContent, actualContent)) {
            FileUtils.writeFile(new ByteArrayInputStream(newContent), file);
        } else {
            log.debug("FileActivity " + activity + " dropped (same content)");
        }

        if (encoding != null)
            updateFileEncoding(encoding, file);
    }

    protected void handleFolderActivity(FolderActivity activity) throws CoreException {

        SPath path = activity.getPath();

        IFolder folder = ((EclipseFolderImpl) path.getProject().getFolder(path.getProjectRelativePath()))
                .getDelegate();

        if (activity.getType() == FolderActivity.Type.CREATED) {
            FileUtils.create(folder);
        } else if (activity.getType() == FolderActivity.Type.REMOVED) {
            try {
                if (folder.exists())
                    FileUtils.delete(folder);
            } catch (CoreException e) {
                log.warn("Removing folder failed: " + folder);
            }
        }
    }

    protected void handleVCSActivity(VCSActivity activity) {
        final VCSActivity.Type activityType = activity.getType();
        SPath path = activity.getPath();

        final IResource resource = ((EclipseResourceImpl) path.getResource()).getDelegate();

        final IProject project = ((EclipseProjectImpl) path.getProject()).getDelegate();

        final String url = activity.getURL();
        final String directory = activity.getDirectory();
        final String revision = activity.getParam1();

        // Connect is special since the project doesn't have a VCSAdapter
        // yet.
        final VCSAdapter vcs = activityType == VCSActivity.Type.CONNECT ? VCSAdapter.getAdapter(revision)
                : VCSAdapter.getAdapter(project);
        if (vcs == null) {
            log.warn("Could not execute VCS activity. Do you have the Subclipse plug-in installed?");
            if (activity.containedActivity.size() > 0) {
                log.trace("contained activities: " + activity.containedActivity.toString());
            }
            for (IResourceActivity a : activity.containedActivity) {
                consumer.exec(a);
            }
            return;
        }

        try {
            // TODO Should these operations run in an IWorkspaceRunnable?
            Shell shell = SWTUtils.getShell();
            ProgressMonitorDialog progressMonitorDialog = new ProgressMonitorDialog(shell);
            progressMonitorDialog.open();
            Shell pmdShell = progressMonitorDialog.getShell();
            pmdShell.setText("Saros running VCS operation");
            log.trace("about to call progressMonitorDialog.run");
            progressMonitorDialog.run(true, false, new IRunnableWithProgress() {
                @Override
                public void run(IProgressMonitor progress)

                        throws InvocationTargetException, InterruptedException {
                    log.trace("progressMonitorDialog.run started");
                    if (!SWTUtils.isSWT())
                        log.trace("not in SWT thread");
                    if (activityType == VCSActivity.Type.CONNECT) {
                        vcs.connect(project, url, directory, progress);
                    } else if (activityType == VCSActivity.Type.DISCONNECT) {
                        vcs.disconnect(project, revision != null, progress);
                    } else if (activityType == VCSActivity.Type.SWITCH) {
                        vcs.switch_(resource, url, revision, progress);
                    } else if (activityType == VCSActivity.Type.UPDATE) {
                        vcs.update(resource, revision, progress);
                    } else {
                        log.error("VCS activity type not implemented yet.");
                    }
                    log.trace("progressMonitorDialog.run done");
                }

            });
            pmdShell.dispose();
        } catch (InvocationTargetException e) {
            // TODO We can't get here, right?
            throw new IllegalStateException(e);
        } catch (InterruptedException e) {
            log.error("Code not designed to be interrupted!");
        }
    }

    // HACK
    public void projectAdded(de.fu_berlin.inf.dpp.filesystem.IProject project) {
        synchronized (sharedProjects) {
            IProject eclipseProject = ((EclipseProjectImpl) project).getDelegate();
            sharedProjects.put(eclipseProject, new SharedProject(eclipseProject, sarosSession));
        }
    }

    // HACK
    public void projectRemoved(de.fu_berlin.inf.dpp.filesystem.IProject project) {
        synchronized (sharedProjects) {

            SharedProject sharedProject = sharedProjects.remove(((EclipseProjectImpl) project).getDelegate());
            if (sharedProject != null)
                sharedProject.delete();
        }
    }
}