org.eclipse.emf.compare.ide.ui.internal.logical.ProjectModelResolver.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.emf.compare.ide.ui.internal.logical.ProjectModelResolver.java

Source

/*******************************************************************************
 * Copyright (c) 2011, 2013 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
 * 
 * Contributors:
 *     Obeo - initial API and implementation
 *******************************************************************************/
package org.eclipse.emf.compare.ide.ui.internal.logical;

import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.intersection;
import static com.google.common.collect.Sets.newLinkedHashSet;
import static org.eclipse.emf.compare.ide.ui.internal.util.PlatformElementUtil.adaptAs;
import static org.eclipse.emf.compare.ide.utils.ResourceUtil.createURIFor;
import static org.eclipse.emf.compare.ide.utils.ResourceUtil.hasContentType;

import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;

import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.core.resources.IFile;
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.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIMessages;
import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
import org.eclipse.emf.compare.ide.ui.logical.IModelResolver;
import org.eclipse.emf.compare.ide.ui.logical.IStorageProvider;
import org.eclipse.emf.compare.ide.ui.logical.IStorageProviderAccessor;
import org.eclipse.emf.compare.ide.ui.logical.IStorageProviderAccessor.DiffSide;
import org.eclipse.emf.compare.ide.ui.logical.SynchronizationModel;
import org.eclipse.emf.compare.ide.utils.StorageTraversal;
import org.eclipse.emf.compare.ide.utils.StorageURIConverter;

/**
 * This implementation of an {@link IModelResolver} will look up in the whole project contained by the
 * "starting points" of the models to resolve in order to check for referencing parents. Once this is done,
 * we'll know the whole logical model for the "local" resource. The right and origin (if any) resources will
 * be provided with the same traversal of resources, expanded with a "top-down" approach : load all models of
 * the traversal from the remote side, then resolve their containment tree to check whether there are new
 * remote resources in the logical model.
 * 
 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
 */
public class ProjectModelResolver extends LogicalModelResolver {
    /** Content types of the files to consider as potential models. */
    private static final String[] MODEL_CONTENT_TYPES = new String[] { "org.eclipse.emf.compare.content.type", //$NON-NLS-1$
            "org.eclipse.emf.ecore", //$NON-NLS-1$
            "org.eclipse.emf.ecore.xmi", }; //$NON-NLS-1$

    /**
     * Keeps track of the discovered dependency graph. Model resolvers are created from the extension point
     * registry, we can thus keep this graph around to avoid multiple crawlings of the same IProject. Team,
     * and the EMFResourceMapping, tend to be over-enthusiast with the resolution of model traversals. For
     * example, a single "right-click -> compare with -> commit..." with EGit ends up calling 8 distinct times
     * for the resource traversal of the selected resource.
     */
    private Graph<URI> dependencyGraph;

    /**
     * This resolver will keep a resource listener over the workspace in order to keep its dependencies graph
     * in sync.
     */
    private ModelResourceListener resourceListener;

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.compare.ide.ui.logical.AbstractModelResolver#initialize()
     */
    @Override
    public void initialize() {
        super.initialize();
        this.dependencyGraph = new Graph<URI>();
        this.resourceListener = new ModelResourceListener();
        ResourcesPlugin.getWorkspace().addResourceChangeListener(this.resourceListener);
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.compare.ide.ui.logical.AbstractModelResolver#dispose()
     */
    @Override
    public void dispose() {
        ResourcesPlugin.getWorkspace().removeResourceChangeListener(this.resourceListener);
        this.resourceListener = null;
        this.dependencyGraph = null;
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.compare.ide.ui.internal.logical.LogicalModelResolver#resolveLocalModels(org.eclipse.core.resources.IResource,
     *      org.eclipse.core.resources.IResource, org.eclipse.core.resources.IResource,
     *      org.eclipse.core.runtime.IProgressMonitor)
     */
    @Override
    public SynchronizationModel resolveLocalModels(IResource left, IResource right, IResource origin,
            IProgressMonitor monitor) {
        if (!(left instanceof IFile) || !(right instanceof IFile)) {
            return super.resolveLocalModels(left, right, origin, monitor);
        }

        updateChangedDependencies(monitor);
        updateDependencies((IFile) left, monitor);
        updateDependencies((IFile) right, monitor);
        if (origin instanceof IFile) {
            updateDependencies((IFile) origin, monitor);
        }

        final Set<IFile> startingPoints;
        if (origin != null) {
            startingPoints = ImmutableSet.of((IFile) left, (IFile) right, (IFile) origin);
        } else {
            startingPoints = ImmutableSet.of((IFile) left, (IFile) right);
        }

        final Set<IStorage> leftTraversal = resolveTraversal((IFile) left,
                difference(startingPoints, Collections.singleton(left)), monitor);
        final Set<IStorage> rightTraversal = resolveTraversal((IFile) right,
                difference(startingPoints, Collections.singleton(right)), monitor);
        final Set<IStorage> originTraversal;
        if (origin instanceof IFile) {
            originTraversal = resolveTraversal((IFile) origin,
                    difference(startingPoints, Collections.singleton(origin)), monitor);
        } else {
            originTraversal = Collections.emptySet();
        }

        /*
         * If one resource of the logical model was pointing to both (or "all three") of our starting
         * elements, we'll have way too many things in our traversal. We need to remove the intersection
         * before going any further.
         */
        Set<IStorage> intersection = intersection(leftTraversal, rightTraversal);
        if (!originTraversal.isEmpty()) {
            intersection = intersection(intersection, originTraversal);
        }
        logCoherenceThreats(startingPoints, intersection);

        final Set<IStorage> actualLeft = newLinkedHashSet(difference(leftTraversal, intersection));
        final Set<IStorage> actualRight = newLinkedHashSet(difference(rightTraversal, intersection));
        final Set<IStorage> actualOrigin = newLinkedHashSet(difference(originTraversal, intersection));
        return new SynchronizationModel(new StorageTraversal(actualLeft), new StorageTraversal(actualRight),
                new StorageTraversal(actualOrigin));
    }

    /**
     * When executing local comparisons, we resolve the full logical model of both (or "all three of") the
     * compared files.
     * <p>
     * If there is one resource in the scope that references all of these starting points, then we'll have
     * perfectly identical logical models for all comparison sides. Because of that, we need to constrain the
     * logical model of each starting point to only parts that are not accessible from other starting points.
     * This might cause coherence issues as merging could thus "break" references from other files to our
     * compared ones.
     * </p>
     * <p>
     * This method will be used to browse the files that are removed from the logical model, and log a warning
     * for the files that are removed even though they are "parents" of one of the starting points.
     * </p>
     * 
     * @param startingPoints
     *            Starting points of the comparison.
     * @param removedFromModel
     *            All files that have been removed from the comparison scope.
     */
    private void logCoherenceThreats(Set<IFile> startingPoints, Set<IStorage> removedFromModel) {
        final Set<URI> coherenceThreats = new LinkedHashSet<URI>();
        for (IStorage start : startingPoints) {
            final URI startURI = createURIFor(start);
            for (IStorage removed : removedFromModel) {
                final URI removedURI = createURIFor(removed);
                if (dependencyGraph.hasChild(removedURI, startURI)) {
                    coherenceThreats.add(removedURI);
                }
            }
        }

        if (!coherenceThreats.isEmpty()) {
            final String message = EMFCompareIDEUIMessages.getString("ModelResolver.coherenceWarning"); //$NON-NLS-1$
            final String details = Iterables.toString(coherenceThreats);
            EMFCompareIDEUIPlugin.getDefault().getLog()
                    .log(new Status(IStatus.WARNING, EMFCompareIDEUIPlugin.PLUGIN_ID, message + '\n' + details));
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.compare.ide.ui.internal.logical.LogicalModelResolver#resolveModels(org.eclipse.emf.compare.ide.ui.logical.IStorageProviderAccessor,
     *      org.eclipse.core.resources.IStorage, org.eclipse.core.resources.IStorage,
     *      org.eclipse.core.resources.IStorage, org.eclipse.core.runtime.IProgressMonitor)
     */
    @Override
    public SynchronizationModel resolveModels(IStorageProviderAccessor storageAccessor, IStorage left,
            IStorage right, IStorage origin, IProgressMonitor monitor) {
        final IFile leftFile = adaptAs(left, IFile.class);
        if (leftFile == null) {
            return super.resolveModels(storageAccessor, leftFile, right, origin, monitor);
        }

        updateChangedDependencies(monitor);
        updateDependencies(leftFile, storageAccessor, monitor);

        final Set<IStorage> leftTraversal = resolveLocalTraversal(storageAccessor, leftFile, monitor);
        final Set<IStorage> rightTraversal = resolveTraversal(storageAccessor, DiffSide.REMOTE, leftTraversal,
                monitor);
        final Set<IStorage> originTraversal;
        if (origin != null) {
            originTraversal = resolveTraversal(storageAccessor, DiffSide.ORIGIN, leftTraversal, monitor);
        } else {
            originTraversal = Collections.emptySet();
        }

        return new SynchronizationModel(new StorageTraversal(leftTraversal), new StorageTraversal(rightTraversal),
                new StorageTraversal(originTraversal));
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.compare.ide.ui.internal.logical.LogicalModelResolver#resolveLocalModel(org.eclipse.core.resources.IResource,
     *      org.eclipse.core.runtime.IProgressMonitor)
     */
    @Override
    public StorageTraversal resolveLocalModel(IResource start, IProgressMonitor monitor) {
        if (!(start instanceof IFile)) {
            return super.resolveLocalModel(start, monitor);
        }

        updateChangedDependencies(monitor);
        updateDependencies((IFile) start, monitor);

        return new StorageTraversal(resolveTraversal((IFile) start, monitor));
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.compare.ide.ui.internal.logical.LogicalModelResolver#canResolve(org.eclipse.core.resources.IStorage)
     */
    @Override
    public boolean canResolve(IStorage sourceStorage) {
        if (sourceStorage instanceof IFile) {
            IFile file = (IFile) sourceStorage;
            return file.getProject().isAccessible() && ((IFile) sourceStorage).exists();
        }
        return false;
    }

    /**
     * Updates the dependency graph for the given file.
     * 
     * @param file
     *            File which dependencies we are to update.
     * @param monitor
     *            Monitor to report progress on.
     */
    private void updateDependencies(IFile file, IProgressMonitor monitor) {
        final URI startURI = createURIFor(file);
        if (!dependencyGraph.contains(startURI)) {
            final IProject project = file.getProject();
            final ModelResourceVisitor modelVisitor = new ModelResourceVisitor(dependencyGraph, monitor);
            try {
                project.accept(modelVisitor);
            } catch (CoreException e) {
                Throwables.propagate(e);
            }
        }
    }

    /**
     * Updates the dependency graph for the given file.
     * 
     * @param file
     *            File which dependencies we are to update.
     * @param storageAccessor
     *            The accessor that can be used to retrieve synchronization information between our resources.
     * @param monitor
     *            Monitor to report progress on.
     */
    private void updateDependencies(IFile file, IStorageProviderAccessor storageAccessor,
            IProgressMonitor monitor) {
        final URI leftURI = createURIFor(file);
        if (!dependencyGraph.contains(leftURI)) {
            final IProject project = file.getProject();
            final ModelResourceVisitor modelVisitor = new ModelResourceVisitor(storageAccessor, DiffSide.SOURCE,
                    dependencyGraph, monitor);
            try {
                project.accept(modelVisitor);
            } catch (CoreException e) {
                // TODO log
            }
        }
    }

    /**
     * Checks all dependencies that have changed since we last checked (as returned by the
     * {@link #resourceListener}).
     * 
     * @param monitor
     *            Monitor to report progress on.
     */
    private void updateChangedDependencies(IProgressMonitor monitor) {
        final Set<URI> removedURIs = resourceListener.popRemovedURIs();
        final Set<URI> changedURIs = Sets.difference(resourceListener.popChangedURIs(), removedURIs);

        for (URI removed : removedURIs) {
            dependencyGraph.remove(removed);
        }

        for (URI changed : changedURIs) {
            dependencyGraph.remove(changed);

            final IFile file = getFileAt(changed);
            updateDependencies(file, monitor);
        }
    }

    /**
     * Checks whether the given file has one of the content types described in {@link #MODEL_CONTENT_TYPES}.
     * 
     * @param file
     *            The file which contents are to be checked.
     * @return <code>true</code> if this file has one of the "model" content types.
     */
    protected static final boolean hasModelType(IFile file) {
        boolean isModel = false;
        for (int i = 0; i < MODEL_CONTENT_TYPES.length && !isModel; i++) {
            isModel = hasContentType(file, MODEL_CONTENT_TYPES[i]);
        }
        return isModel;
    }

    /**
     * This will be used to resolve the traversal of a file's logical model, according to
     * {@link #dependencyGraph}.
     * 
     * @param resource
     *            The resource for which we need the full logical model.
     * @param monitor
     *            Monitor on which to report progress to the user.
     * @return The set of all storages that compose the logical model of <code>resource</code>.
     */
    private Set<IStorage> resolveTraversal(IFile resource, IProgressMonitor monitor) {
        final Set<IStorage> traversal = new LinkedHashSet<IStorage>();
        final URI startURI = createURIFor(resource);

        final Iterable<URI> uris = dependencyGraph.getSubgraphOf(startURI);
        for (URI uri : uris) {
            traversal.add(getFileAt(uri));
        }
        return traversal;
    }

    /**
     * This will be used in case of remote comparisons to resolve the local side's traversal (if there is a
     * local side).
     * 
     * @param storageAccessor
     *            The accessor that can be used to retrieve synchronization information between our resources.
     * @param resource
     *            The resource for which we need the full logical model.
     * @param monitor
     *            Monitor on which to report progress to the user.
     * @return The set of all storages that compose the logical model of <code>resource</code>.
     */
    private Set<IStorage> resolveLocalTraversal(IStorageProviderAccessor storageAccessor, IFile resource,
            IProgressMonitor monitor) {
        final Set<IStorage> traversal = new LinkedHashSet<IStorage>();
        final URI startURI = createURIFor(resource);

        final Iterable<URI> uris = dependencyGraph.getSubgraphOf(startURI);
        for (URI uri : uris) {
            final IFile file = getFileAt(uri);
            try {
                if (!storageAccessor.isInSync(file)) {
                    traversal.add(file);
                }
            } catch (CoreException e) {
                // swallow
            }
        }
        return traversal;
    }

    /**
     * This will be used to resolve the traversal of a file's logical model, according to
     * {@link #dependencyGraph}.
     * 
     * @param resource
     *            The resource for which we need the full logical model.
     * @param bounds
     *            The resources constituting starting points of "other" logical models. This will be used to
     *            constrain the dependency sub-graph.
     * @param monitor
     *            Monitor on which to report progress to the user.
     * @return The set of all storages that compose the logical model of <code>resource</code>.
     */
    private Set<IStorage> resolveTraversal(IFile resource, Set<IFile> bounds, IProgressMonitor monitor) {
        final Set<IStorage> traversal = new LinkedHashSet<IStorage>();
        final URI startURI = createURIFor(resource);

        final Set<URI> uriBounds = new LinkedHashSet<URI>(bounds.size());
        for (IFile bound : bounds) {
            uriBounds.add(createURIFor(bound));
        }

        final Iterable<URI> uris = dependencyGraph.getBoundedSubgraphOf(startURI, uriBounds);
        for (URI uri : uris) {
            traversal.add(getFileAt(uri));
        }
        return traversal;
    }

    /**
     * Returns the IFile located at the given URI.
     * 
     * @param uri
     *            URI we need the file for.
     * @return The IFile located at the given URI.
     */
    private IFile getFileAt(URI uri) {
        final StringBuilder path = new StringBuilder();
        List<String> segments = uri.segmentsList();
        if (uri.isPlatformResource()) {
            segments = segments.subList(1, segments.size());
        }
        for (String segment : segments) {
            path.append(URI.decode(segment)).append('/');
        }
        return ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(path.toString()));
    }

    /**
     * This will be used to resolve the logical model of a remote resource variant. Since we have no direct
     * access to the resources themselves, we cannot simply browse one of their container for the traversal.
     * Instead of that, we'll use the local traversal as a "reference", load remote variants of all of these
     * local files, and re-resolve them in a "top-down" approach in case there are new resources on the remote
     * side.
     * 
     * @param storageAccessor
     *            The accessor that can be used to retrieve synchronization information between our resources.
     * @param side
     *            Side of the logical model to resolve. Used in conjunction with the storage accessor to
     *            retrieve appropriate contents for the remote variants.
     * @param localTraversal
     *            Traversal resolved for the logical model of the local file.
     * @param monitor
     *            Monitor on which to report progress to the user.
     * @return The set of all remote storages composing the same logical model as the given local traversal.
     */
    private Set<IStorage> resolveTraversal(IStorageProviderAccessor storageAccessor, DiffSide side,
            Set<IStorage> localTraversal, IProgressMonitor monitor) {
        final DependencyResourceSet resourceSet = new DependencyResourceSet(dependencyGraph);
        final StorageURIConverter converter = new RevisionedURIConverter(resourceSet.getURIConverter(),
                storageAccessor, side);
        resourceSet.setURIConverter(converter);

        final Set<IStorage> storages = Sets.newLinkedHashSet();
        for (IStorage local : localTraversal) {
            final IFile localFile = adaptAs(local, IFile.class);

            try {
                final IStorageProvider remoteStorageProvider = storageAccessor.getStorageProvider(localFile, side);
                if (remoteStorageProvider != null) {
                    final IStorage start = remoteStorageProvider.getStorage(monitor);

                    if (resourceSet.resolveAll(start, monitor)) {
                        if (!contains(storages, start)) {
                            storages.add(start);
                        }
                        for (IStorage loaded : converter.getLoadedRevisions()) {
                            if (!contains(storages, loaded)) {
                                storages.add(loaded);
                            }
                        }
                    } else {
                        // failed to load a remote version of this resource
                    }
                } else {
                    // file only exist locally
                }
            } catch (CoreException e) {
                // failed to load a remote version of this resource
            }
        }
        return storages;
    }

    /**
     * This implementation of a resource visitor will allow us to browse all models in a given hierarchy.
     * 
     * @author <a href="mailto:laurent.goubet@obeo.fr">laurent Goubet</a>
     */
    private static class ModelResourceVisitor implements IResourceVisitor {
        /** Resource Set in which we should load the temporary resources. */
        private final DependencyResourceSet resourceSet;

        /** Monitor to report progress on. */
        // FIXME logarithmic progress
        private final IProgressMonitor monitor;

        /**
         * Instantiates a resource visitor.
         * 
         * @param graph
         *            The dependency graph that is to be populated/completed.
         * @param monitor
         *            The monitor to report progress on.
         */
        public ModelResourceVisitor(Graph<URI> graph, IProgressMonitor monitor) {
            this.resourceSet = new DependencyResourceSet(graph);
            this.monitor = monitor;
            final StorageURIConverter converter = new StorageURIConverter(resourceSet.getURIConverter());
            this.resourceSet.setURIConverter(converter);
        }

        /**
         * Instantiates a resource visitor given the storage accessor to use for I/O operations.
         * 
         * @param storageAccessor
         *            The accessor to use for all I/O operations to fetch resource content.
         * @param side
         *            Side of the resources. Used in conjunction with the storage accessor to fetch proper
         *            content.
         * @param graph
         *            The dependency graph that is to be populated/completed.
         * @param monitor
         *            The monitor to report progress on.
         */
        public ModelResourceVisitor(IStorageProviderAccessor storageAccessor, DiffSide side, Graph<URI> graph,
                IProgressMonitor monitor) {
            this.resourceSet = new DependencyResourceSet(graph);
            this.monitor = monitor;
            final StorageURIConverter converter = new RevisionedURIConverter(resourceSet.getURIConverter(),
                    storageAccessor, side);
            this.resourceSet.setURIConverter(converter);
        }

        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.core.resources.IResourceVisitor#visit(org.eclipse.core.resources.IResource)
         */
        public boolean visit(IResource resource) throws CoreException {
            if (resource instanceof IFile) {
                IFile file = (IFile) resource;
                if (hasModelType(file)) {
                    resourceSet.resolveAll(file, monitor);
                    return true;
                }
                return false;
            }
            return true;
        }
    }

    /**
     * This will listen to workspace changes and react to all changes on "model" resources as determined by
     * {@link ProjectModelResolver#MODEL_CONTENT_TYPES}.
     * 
     * @author <a href="mailto:laurent.goubet@obeo.fr">laurent Goubet</a>
     */
    private static class ModelResourceListener implements IResourceChangeListener {
        /** Keeps track of the URIs that need to be reparsed when next we need the dependencies graph . */
        protected Set<URI> changedURIs;

        /** Tracks the files that have been removed. */
        protected Set<URI> removedURIs;

        /** Initializes this listener. */
        public ModelResourceListener() {
            this.changedURIs = new LinkedHashSet<URI>();
            this.removedURIs = new LinkedHashSet<URI>();
        }

        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
         */
        public void resourceChanged(IResourceChangeEvent event) {
            final IResourceDelta delta = event.getDelta();
            if (delta == null) {
                return;
            }

            try {
                delta.accept(new ModelResourceDeltaVisitor());
            } catch (CoreException e) {
                EMFCompareIDEUIPlugin.getDefault().log(e);
            }
        }

        /**
         * Retrieves the set of all changed URIs since we last updated the dependencies graph, and clears it
         * for subsequent calls.
         * 
         * @return The set of all changed URIs since we last updated the dependencies graph.
         */
        public Set<URI> popChangedURIs() {
            final Set<URI> changed;
            synchronized (changedURIs) {
                changed = ImmutableSet.copyOf(changedURIs);
                changedURIs.clear();
            }
            return changed;
        }

        /**
         * Retrieves the set of all removed URIs since we last updated the dependencies graph, and clears it
         * for subsequent calls.
         * 
         * @return The set of all removed URIs since we last updated the dependencies graph.
         */
        public Set<URI> popRemovedURIs() {
            final Set<URI> removed;
            synchronized (removedURIs) {
                removed = ImmutableSet.copyOf(removedURIs);
                removedURIs.clear();
            }
            return removed;
        }

        /**
         * Visits a resource delta to collect the changed and removed files' URIs.
         * 
         * @author <a href="mailto:laurent.goubet@obeo.fr">laurent Goubet</a>
         */
        private class ModelResourceDeltaVisitor implements IResourceDeltaVisitor {
            /**
             * {@inheritDoc}
             * 
             * @see org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta)
             */
            public boolean visit(IResourceDelta delta) throws CoreException {
                if (delta.getFlags() == IResourceDelta.MARKERS || delta.getResource().getType() != IResource.FILE) {
                    return true;
                }

                final IFile file = (IFile) delta.getResource();
                final URI fileURI = createURIFor(file);
                // We can't check the content type of a removed resource
                if (delta.getKind() == IResourceDelta.REMOVED) {
                    synchronized (removedURIs) {
                        removedURIs.add(fileURI);
                    }
                } else if (hasModelType(file)) {
                    if ((delta.getKind() & (IResourceDelta.CHANGED | IResourceDelta.ADDED)) != 0) {
                        synchronized (changedURIs) {
                            changedURIs.add(fileURI);
                        }
                    }
                }

                return true;
            }
        }
    }
}