org.eclipse.team.ui.synchronize.ModelMergeOperation.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.team.ui.synchronize.ModelMergeOperation.java

Source

/*******************************************************************************
 * Copyright (c) 2006, 2009 IBM Corporation and others.
 * 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:
 * IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.team.ui.synchronize;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.*;
import org.eclipse.core.resources.mapping.ModelProvider;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.*;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.diff.*;
import org.eclipse.team.core.mapping.*;
import org.eclipse.team.internal.ui.*;
import org.eclipse.team.internal.ui.dialogs.NoChangesDialog;
import org.eclipse.ui.IWorkbenchPart;

/**
 * A model operation that executes a merge according to the merge lifecycle
 * associated with an {@link IMergeContext} and {@link IResourceMappingMerger}
 * instances obtained from the model providers involved.
 * 
 * @since 3.2
 */
public abstract class ModelMergeOperation extends ModelOperation {

    /**
     * Validate the merge context with the model providers that have mappings in
     * the scope of the context. The {@link IResourceMappingMerger} for each
     * model provider will be consulted and any non-OK status will be
     * accumulated and returned,
     * 
     * @param context
     *            the merge context being validated
     * @param monitor
     *            a progress monitor
     * @return a status or multi-status that identify any conditions that should
     *         force a preview of the merge
     */
    public static IStatus validateMerge(IMergeContext context, IProgressMonitor monitor) {
        try {
            ModelProvider[] providers = context.getScope().getModelProviders();
            monitor.beginTask(null, 100 * providers.length);
            List notOK = new ArrayList();
            for (int i = 0; i < providers.length; i++) {
                ModelProvider provider = providers[i];
                IStatus status = validateMerge(provider, context, Policy.subMonitorFor(monitor, 100));
                if (!status.isOK())
                    notOK.add(status);
            }
            if (notOK.isEmpty())
                return Status.OK_STATUS;
            if (notOK.size() == 1)
                return (IStatus) notOK.get(0);
            return new MultiStatus(TeamUIPlugin.ID, 0, (IStatus[]) notOK.toArray(new IStatus[notOK.size()]),
                    TeamUIMessages.ResourceMappingMergeOperation_3, null);
        } finally {
            monitor.done();
        }
    }

    /*
     * Validate the merge by obtaining the {@link IResourceMappingMerger} for the
     * given provider.
     * @param provider the model provider
     * @param context the merge context
     * @param monitor a progress monitor
     * @return the status obtained from the merger for the provider
     */
    private static IStatus validateMerge(ModelProvider provider, IMergeContext context, IProgressMonitor monitor) {
        IResourceMappingMerger merger = getMerger(provider);
        if (merger == null)
            return Status.OK_STATUS;
        return merger.validateMerge(context, monitor);
    }

    /*
     * Return the auto-merger associated with the given model provider using the
     * adaptable mechanism. If the model provider does not have a merger
     * associated with it, a default merger that performs the merge at the file
     * level is returned.
     * 
     * @param provider
     *            the model provider of the elements to be merged (must not be
     *            <code>null</code>)
     * @return a merger
     */
    private static IResourceMappingMerger getMerger(ModelProvider provider) {
        Assert.isNotNull(provider);
        return (IResourceMappingMerger) Utils.getAdapter(provider, IResourceMappingMerger.class);
    }

    /**
     * Create a model merge operation.
     * @param part the workbench part from which the operation was requested or <code>null</code>
     * @param manager the scope manager
     */
    protected ModelMergeOperation(IWorkbenchPart part, ISynchronizationScopeManager manager) {
        super(part, manager);
    }

    /**
     * Perform a merge. First {@link #initializeContext(IProgressMonitor)} is
     * called to determine the set of resource changes. Then the
     * {@link #executeMerge(IProgressMonitor)} method is invoked.
     * 
     * @param monitor a progress monitor
     */
    protected void execute(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
        try {
            monitor.beginTask(null, 100);
            initializeContext(Policy.subMonitorFor(monitor, 50));
            executeMerge(Policy.subMonitorFor(monitor, 50));
        } catch (CoreException e) {
            throw new InvocationTargetException(e);
        } finally {
            monitor.done();
        }
    }

    /**
     * Perform a merge. This method is invoked from
     * {@link #execute(IProgressMonitor)} after the context has been
     * initialized. If there are changes in the context, they will be validating
     * by calling {@link #validateMerge(IMergeContext, IProgressMonitor)}. If
     * there are no validation problems, {@link #performMerge(IProgressMonitor)}
     * will then be called to perform the merge. If there are problems encountered
     * or if a preview was requested, {@link #handlePreviewRequest()} is called.
     * 
     * @param monitor a progress monitor
     */
    protected void executeMerge(IProgressMonitor monitor) throws CoreException {
        monitor.beginTask(null, 100);
        if (!hasChangesOfInterest()) {
            handleNoChanges();
        } else if (isPreviewRequested()) {
            handlePreviewRequest();
        } else {
            IStatus status = ModelMergeOperation.validateMerge(getMergeContext(),
                    Policy.subMonitorFor(monitor, 10));
            if (!status.isOK()) {
                handleValidationFailure(status);
            } else {
                status = performMerge(Policy.subMonitorFor(monitor, 90));
                if (!status.isOK()) {
                    handleMergeFailure(status);
                }
            }
        }
        monitor.done();
    }

    /**
     * A preview of the merge has been requested. By default, this method does
     * nothing. Subclasses that wish to support previewing must override this
     * method to preview the merge and the {@link #getPreviewRequestMessage()}
     * to have the option presented to the user if the scope changes.
     */
    protected void handlePreviewRequest() {
        // Do nothing
    }

    /**
     * Initialize the merge context for this merge operation.
     * After this method is invoked, the {@link #getContext()}
     * method must return an instance of {@link IMergeContext}
     * that is fully initialized.
     * @param monitor a progress monitor
     * @throws CoreException
     */
    protected abstract void initializeContext(IProgressMonitor monitor) throws CoreException;

    /**
     * Method invoked when the context contains changes that failed validation
     * by at least one {@link IResourceMappingMerger}.
     * By default, the user is prompted to inform them that unmergeable changes were found
     * and the {@link #handlePreviewRequest()} method is invoked.
     * Subclasses may override.
     * @param status the status returned from the mergers that reported the validation failures
     */
    protected void handleValidationFailure(final IStatus status) {
        final boolean[] result = new boolean[] { false };
        Runnable runnable = new Runnable() {
            public void run() {
                ErrorDialog dialog = new ErrorDialog(getShell(), TeamUIMessages.ModelMergeOperation_0,
                        TeamUIMessages.ModelMergeOperation_1, status,
                        IStatus.ERROR | IStatus.WARNING | IStatus.INFO) {
                    protected void createButtonsForButtonBar(Composite parent) {
                        createButton(parent, IDialogConstants.YES_ID, IDialogConstants.YES_LABEL, false);
                        createButton(parent, IDialogConstants.NO_ID, IDialogConstants.NO_LABEL, true);
                        createDetailsButton(parent);
                    }

                    /* (non-Javadoc)
                     * @see org.eclipse.jface.dialogs.ErrorDialog#buttonPressed(int)
                     */
                    protected void buttonPressed(int id) {
                        if (id == IDialogConstants.YES_ID)
                            super.buttonPressed(IDialogConstants.OK_ID);
                        else if (id == IDialogConstants.NO_ID)
                            super.buttonPressed(IDialogConstants.CANCEL_ID);
                        super.buttonPressed(id);
                    }
                };
                int code = dialog.open();
                result[0] = code == 0;
            }
        };
        getShell().getDisplay().syncExec(runnable);
        if (result[0])
            handlePreviewRequest();
    }

    /**
     * Method invoked when the context contains unmergable changes.
     * By default, the user is prompted to inform them that unmergeable changes were found.
     * Subclasses may override.
     * @param status the status returned from the merger that reported the conflict
     */
    protected void handleMergeFailure(final IStatus status) {
        Display.getDefault().syncExec(new Runnable() {
            public void run() {
                MessageDialog.openInformation(getShell(), TeamUIMessages.MergeIncomingChangesAction_0,
                        status.getMessage());
            };
        });
        handlePreviewRequest();
    }

    /**
     * Method invoked when the context contains no changes.
     * By default, the user is prompted to inform them that no changes were found.
     * Subclasses may override.
     */
    protected void handleNoChanges() {
        Display.getDefault().syncExec(new Runnable() {
            public void run() {
                NoChangesDialog.open(getShell(), TeamUIMessages.ResourceMappingMergeOperation_0,
                        TeamUIMessages.ResourceMappingMergeOperation_1, TeamUIMessages.ModelMergeOperation_3,
                        getScope().asInputScope());
            };
        });
    }

    /**
     * Attempt a headless merge of the elements in the context of this
     * operation. The merge is performed by obtaining the
     * {@link IResourceMappingMerger} for the model providers in the context's
     * scope. The merger of the model providers are invoked in the order
     * determined by the {@link ModelOperation#sortByExtension(ModelProvider[])}
     * method. The method will stop on the first conflict encountered.
     * This method will throw a runtime exception
     * if the operation does not have a merge context.
     * 
     * @param monitor
     *            a progress monitor
     * @return a status that indicates whether the merge succeeded.
     * @throws CoreException
     *             if an error occurred
     */
    protected IStatus performMerge(IProgressMonitor monitor) throws CoreException {
        ISynchronizationContext sc = getContext();
        if (sc instanceof IMergeContext) {
            IMergeContext context = (IMergeContext) sc;
            final ModelProvider[] providers = sortByExtension(context.getScope().getModelProviders());
            final IStatus[] result = new IStatus[] { Status.OK_STATUS };
            context.run(new IWorkspaceRunnable() {
                public void run(IProgressMonitor monitor) throws CoreException {
                    try {
                        int ticks = 100;
                        monitor.beginTask(null, ticks + ((providers.length - 1) * 10));
                        for (int i = 0; i < providers.length; i++) {
                            ModelProvider provider = providers[i];
                            IStatus status = performMerge(provider, Policy.subMonitorFor(monitor, ticks));
                            ticks = 10;
                            if (!status.isOK()) {
                                // Stop at the first failure
                                result[0] = status;
                                return;
                            }
                            try {
                                Job.getJobManager().join(getContext(), monitor);
                            } catch (InterruptedException e) {
                                // Ignore
                            }
                        }
                    } finally {
                        monitor.done();
                    }
                }
            }, null /* scheduling rule */, IResource.NONE, monitor);
            return result[0];
        }
        return noMergeContextAvailable();
    }

    /**
     * Attempt to merge all the mappings that come from the given provider.
     * Return a status which indicates whether the merge succeeded or if
     * unmergeable conflicts were found. By default, this method invokes
     * the {@link IResourceMappingMerger#merge(IMergeContext, IProgressMonitor)}
     * method but does not wait for the context to update (see {@link ISynchronizationContext}.
     * Callers that are invoking the merge on multiple models should wait until the
     * context has updated before invoking merge on another merger. The following
     * line of code will wait for the context to update:
     * <pre>
     * Job.getJobManager().join(getContext(), monitor);
     * </pre>
     * <p>
     * This method will throw a runtime exception
     * if the operation does not have a merge context.
     * @param provider the model provider whose mappings are to be merged
     * @param monitor a progress monitor
     * @return a non-OK status if there were unmergable conflicts
     * @throws CoreException if an error occurred
     */
    protected IStatus performMerge(ModelProvider provider, IProgressMonitor monitor) throws CoreException {
        ISynchronizationContext sc = getContext();
        if (sc instanceof IMergeContext) {
            IMergeContext context = (IMergeContext) sc;
            IResourceMappingMerger merger = getMerger(provider);
            if (merger != null) {
                IStatus status = merger.merge(context, monitor);
                if (status.isOK() || status.getCode() == IMergeStatus.CONFLICTS) {
                    return status;
                }
                throw new TeamException(status);
            }
            return Status.OK_STATUS;
        }
        return noMergeContextAvailable();
    }

    private IStatus noMergeContextAvailable() {
        throw new IllegalStateException(TeamUIMessages.ModelMergeOperation_2);
    }

    /**
     * Return whether the context of this operation has changes that are
     * of interest to the operation. Subclasses may override.
     * @return whether the context of this operation has changes that are
     * of interest to the operation
     */
    protected boolean hasChangesOfInterest() {
        return !getContext().getDiffTree().isEmpty() && hasIncomingChanges(getContext().getDiffTree());
    }

    private boolean hasIncomingChanges(IDiffTree tree) {
        return tree.hasMatchingDiffs(ResourcesPlugin.getWorkspace().getRoot().getFullPath(), new FastDiffFilter() {
            public boolean select(IDiff node) {
                if (node instanceof IThreeWayDiff) {
                    IThreeWayDiff twd = (IThreeWayDiff) node;
                    int direction = twd.getDirection();
                    if (direction == IThreeWayDiff.INCOMING || direction == IThreeWayDiff.CONFLICTING) {
                        return true;
                    }
                } else {
                    // Return true for any two-way change
                    return true;
                }
                return false;
            }
        });
    }

    private IMergeContext getMergeContext() {
        return (IMergeContext) getContext();
    }
}