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

Java tutorial

Introduction

Here is the source code for org.eclipse.emf.compare.ide.ui.internal.logical.resolver.LocalModelsResolution.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
 * 
 * Contributors:
 *     Obeo - initial API and implementation
 *******************************************************************************/
package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;

import static com.google.common.collect.Sets.intersection;
import static org.eclipse.emf.compare.ide.utils.ResourceUtil.asURI;
import static org.eclipse.emf.compare.ide.utils.ResourceUtil.createURIFor;

import com.google.common.base.Preconditions;
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.Set;
import java.util.concurrent.Callable;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
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.SynchronizationModel;
import org.eclipse.emf.compare.ide.utils.StorageTraversal;

/**
 * Called by EMF Compare in order to resolve the logical models corresponding to the given IResources. Only
 * local data is available.
 * 
 * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
 */
public class LocalModelsResolution extends AbstractResolution {
    /** The local resolver. */
    private final IResourceDependencyLocalResolver resolver;

    /** The left resource, never null. */
    private final IResource left;

    /** The right resource, never null. */
    private final IResource right;

    /** The origin resource, may be null. */
    private final IResource origin;

    /**
     * Constructor.
     * 
     * @param dependencyProvider
     *            The dependency provider
     * @param scheduler
     *            multi-thread support
     * @param eventBus
     *            The event bus to signal events
     * @param left
     *            left resource
     * @param right
     *            right resource
     * @param origin
     *            common ancestor resource, may be null
     * @param monitor
     *            The progress monitor to use
     */
    public LocalModelsResolution(IResolutionContext context, IResource left, IResource right, IResource origin,
            IProgressMonitor monitor) {
        super(context, monitor);
        this.left = Preconditions.checkNotNull(left);
        this.right = Preconditions.checkNotNull(right);
        this.origin = origin;
        this.resolver = context.getLocalResolver();
    }

    /**
     * Executes this treatment.
     * 
     * @return The Logical model to use to compare the 2 or 3 resources.
     */
    public SynchronizationModel run() {
        if (logger.isDebugEnabled()) {
            logger.debug("run() - START"); //$NON-NLS-1$
        }
        try {
            if (allResourcesAreFiles()) {
                return resolveLocalFiles();
            } else {
                return resolveLocalResources();
            }
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("run() - FINISH"); //$NON-NLS-1$
            }
            monitor.setWorkRemaining(0);
        }
    }

    /**
     * Indicates whether all the resources are instances of {@link IFile}.
     * 
     * @return <code>true</code> if and only if left and right are {@link IFile}s and origin is null or is an
     *         {@link IFile}.
     */
    protected boolean allResourcesAreFiles() {
        return left instanceof IFile && right instanceof IFile && (origin == null || origin instanceof IFile);
    }

    /**
     * Resolve the local resources.
     * 
     * @return The synchronization model that contains the logical models of each side.
     */
    private SynchronizationModel resolveLocalResources() {
        // Sub-optimal implementation, we'll only try and resolve each side individually
        final StorageTraversal leftTraversal;
        final StorageTraversal rightTraversal;
        final StorageTraversal originTraversal;
        if (logger.isDebugEnabled()) {
            logger.debug("resolveLocalResources()"); //$NON-NLS-1$
        }
        // CHECKSTYLE:OFF No, I won't create constants.
        if (origin != null) {
            leftTraversal = resolveLocalModel(left, monitor.newChild(33));
            rightTraversal = resolveLocalModel(right, monitor.newChild(33));
            originTraversal = resolveLocalModel(origin, monitor.newChild(34));
        } else {
            leftTraversal = resolveLocalModel(left, monitor.newChild(50));
            rightTraversal = resolveLocalModel(right, monitor.newChild(50));
            originTraversal = new StorageTraversal(Sets.<IStorage>newLinkedHashSet());
        }
        // CHECKSTYLE:ON

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

    /**
     * Resolve one local model.
     * 
     * @param start
     *            The resource to start from.
     * @param subMonitor
     *            The progress monitor to use
     * @return The traversal for the given resource.
     */
    private StorageTraversal resolveLocalModel(final IResource start, SubMonitor subMonitor) {
        LocalModelResolution comp = new LocalModelResolution(context, subMonitor);
        return comp.run(start);
    }

    /**
     * Resolve several local files.
     * 
     * @return The synchronization model that contains the logical models of each side.
     */
    private SynchronizationModel resolveLocalFiles() {
        if (logger.isDebugEnabled()) {
            logger.debug("resolveLocalFiles()"); //$NON-NLS-1$
        }
        return call(new Callable<SynchronizationModel>() {
            public SynchronizationModel call() throws Exception {
                if (logger.isDebugEnabled()) {
                    logger.debug("Updating dependencies"); //$NON-NLS-1$
                }
                if (origin instanceof IFile) {
                    resolver.updateDependencies(monitor, diagnostic, (IFile) left, (IFile) right, (IFile) origin);
                } else {
                    resolver.updateDependencies(monitor, diagnostic, (IFile) left, (IFile) right);
                }

                final URI leftURI = createURIFor((IFile) left);
                final URI rightURI = createURIFor((IFile) right);
                final URI originURI;
                final Set<IFile> startingPoints;
                if (origin instanceof IFile) {
                    startingPoints = ImmutableSet.of((IFile) left, (IFile) right, (IFile) origin);
                    originURI = createURIFor((IFile) origin);
                } else {
                    startingPoints = ImmutableSet.of((IFile) left, (IFile) right);
                    originURI = null;
                }

                if (monitor.isCanceled()) {
                    throw new OperationCanceledException();
                }

                if (logger.isDebugEnabled()) {
                    logger.debug("Computing traversals"); //$NON-NLS-1$
                }
                final Set<IStorage> leftTraversal;
                final Set<IStorage> rightTraversal;
                final Set<IStorage> originTraversal;
                if (origin instanceof IFile) {
                    leftTraversal = resolveTraversal((IFile) left, ImmutableSet.of(rightURI, originURI));
                    rightTraversal = resolveTraversal((IFile) right, ImmutableSet.of(leftURI, originURI));
                    originTraversal = resolveTraversal((IFile) origin, ImmutableSet.of(leftURI, rightURI));
                } else {
                    leftTraversal = resolveTraversal((IFile) left, Collections.singleton(rightURI));
                    rightTraversal = resolveTraversal((IFile) right, Collections.singleton(leftURI));
                    originTraversal = Collections.emptySet();
                }

                if (logger.isDebugEnabled()) {
                    logger.debug("Computing synchronization model"); //$NON-NLS-1$
                }
                // 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(Iterables.transform(startingPoints, asURI()),
                        Iterables.transform(intersection, asURI()));

                final Set<IStorage> actualLeft = new LinkedHashSet<IStorage>(
                        Sets.difference(leftTraversal, intersection));
                final Set<IStorage> actualRight = new LinkedHashSet<IStorage>(
                        Sets.difference(rightTraversal, intersection));
                final Set<IStorage> actualOrigin = new LinkedHashSet<IStorage>(
                        Sets.difference(originTraversal, intersection));
                final SynchronizationModel synchronizationModel = new SynchronizationModel(
                        new StorageTraversal(actualLeft), new StorageTraversal(actualRight),
                        new StorageTraversal(actualOrigin), diagnostic.getDiagnostic());

                return synchronizationModel;
            }
        });
    }

    /**
     * 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(Iterable<URI> startingPoints, Iterable<URI> removedFromModel) {
        final Set<URI> coherenceThreats = new LinkedHashSet<URI>();
        for (URI start : startingPoints) {
            for (URI removed : removedFromModel) {
                if (context.getDependencyProvider().hasChild(removed, start)) {
                    coherenceThreats.add(removed);
                }
            }
        }

        if (!coherenceThreats.isEmpty()) {
            // FIXME: should be added to diagnostic instead
            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));
        }
    }
}