org.eclipse.ui.operations.NonLocalUndoUserApprover.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.ui.operations.NonLocalUndoUserApprover.java

Source

/*******************************************************************************
 * Copyright (c) 2005, 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.ui.operations;

import java.util.ArrayList;

import org.eclipse.core.commands.operations.IAdvancedUndoableOperation;
import org.eclipse.core.commands.operations.IOperationApprover;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.internal.util.Util;

/**
 * <p>
 * An operation approver that prompts the user to see if a non-local undo should
 * proceed inside an editor. A non-local undo is detected when an operation
 * being undone or redone affects elements other than those described by the
 * editor itself. Clients can optionally specify a class, the preferred
 * comparison class, that should be used when comparing objects affected by the
 * editor with objects affected by an undo or redo operation. Comparisons
 * between the affected objects inside the editor and those described by the
 * operation will first be done by simply performing an equality check, using
 * {@link java.lang.Object#equals(java.lang.Object)}. If an object described by
 * an operation is not equal to one of the objects affected by the editor, and
 * if it is not an instance of the preferred comparison class, but is an
 * instance of {@link org.eclipse.core.runtime.IAdaptable}, then the operation
 * approver will also attempt to retrieve an adapter on that object for the
 * preferred comparison class and perform a second equality check using the
 * adapter.
 * </p>
 * <p>
 * This class may be instantiated by clients.
 * </p>
 * 
 * 
 * @since 3.1
 */
public final class NonLocalUndoUserApprover implements IOperationApprover {

    private IUndoContext context;

    private IEditorPart part;

    private Object[] elements;

    private Class affectedObjectsClass;

    private ArrayList elementsAndAdapters;

    /**
     * Create a NonLocalUndoUserApprover associated with the specified editor
     * and undo context
     * 
     * @param context
     *            the undo context of operations in question.
     * @param part
     *            the editor part that is displaying the element
     * @param affectedObjects
     *            the objects that are affected by the editor and considered to
     *            be objects local to the editor. The objects are typically
     *            instances of the preferredComparisonClass or else provide
     *            adapters for the preferredComparisonClass, although this is
     *            not required.
     * @param preferredComparisonClass
     *            the preferred class to be used when comparing the editor's
     *            affectedObjects with those provided by the undoable operation
     *            using
     *            {@link org.eclipse.core.commands.operations.IAdvancedUndoableOperation#getAffectedObjects()}.
     *            If the operation's affected objects are not instances of the
     *            specified class, but are instances of
     *            {@link org.eclipse.core.runtime.IAdaptable}, then an adapter
     *            for this class will be requested. The preferredComparisonClass
     *            may be <code>null</code>, which indicates that there is no
     *            expected class or adapter necessary for the comparison.
     */
    public NonLocalUndoUserApprover(IUndoContext context, IEditorPart part, Object[] affectedObjects,
            Class preferredComparisonClass) {
        super();
        this.context = context;
        this.part = part;
        this.affectedObjectsClass = preferredComparisonClass;
        this.elements = affectedObjects;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.core.commands.operations.IOperationApprover#proceedRedoing(org.eclipse.core.commands.operations.IUndoableOperation,
     *      org.eclipse.core.commands.operations.IOperationHistory,
     *      org.eclipse.core.runtime.IAdaptable)
     */
    public IStatus proceedRedoing(IUndoableOperation operation, IOperationHistory history, IAdaptable uiInfo) {

        // return immediately if the operation is not relevant
        if (!requiresApproval(operation, uiInfo)) {
            return Status.OK_STATUS;
        }

        String message = NLS.bind(WorkbenchMessages.Operations_nonLocalRedoWarning, operation.getLabel(),
                part.getEditorInput().getName());
        return proceedWithOperation(operation, message, WorkbenchMessages.Operations_discardRedo,
                WorkbenchMessages.Workbench_redoToolTip);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.core.commands.operations.IOperationApprover#proceedUndoing(org.eclipse.core.commands.operations.IUndoableOperation,
     *      org.eclipse.core.commands.operations.IOperationHistory,
     *      org.eclipse.core.runtime.IAdaptable)
     */
    public IStatus proceedUndoing(IUndoableOperation operation, IOperationHistory history, IAdaptable uiInfo) {

        // return immediately if the operation is not relevant
        if (!requiresApproval(operation, uiInfo)) {
            return Status.OK_STATUS;
        }

        String message = NLS.bind(WorkbenchMessages.Operations_nonLocalUndoWarning, operation.getLabel(),
                part.getEditorInput().getName());
        return proceedWithOperation(operation, message, WorkbenchMessages.Operations_discardUndo,
                WorkbenchMessages.Workbench_undoToolTip);

    }

    /*
     * Determine whether the operation in question affects elements outside of
     * the editor. If this can be determined and it does affect other elements,
     * prompt the user as to whether the operation should proceed.
     */
    private IStatus proceedWithOperation(IUndoableOperation operation, final String message,
            final String discardButton, final String title) {

        // if the operation cannot tell us about its modified elements, there's
        // nothing we can do.
        if (!(operation instanceof IAdvancedUndoableOperation)) {
            return Status.OK_STATUS;
        }

        // Obtain the operation's affected objects.
        Object[] modifiedElements = ((IAdvancedUndoableOperation) operation).getAffectedObjects();

        // Since the operation participates in describing its affected objects,
        // we assume for the rest of this method that an inability to
        // determine a match implies that a non-local operation is occurring.
        // This is a conservative assumption that provides more user prompting.

        boolean local;
        if (modifiedElements == null) {
            // The operation could not determine which elements are affected.
            // Consider the operation non-local.
            local = false;
        } else {
            // The operation answered some array of affected objects. Consider
            // the operation local until a non-match is found. Note that an
            // empty
            // array of affected objects is considered a local change.
            local = true;
            for (int i = 0; i < modifiedElements.length; i++) {
                Object modifiedElement = modifiedElements[i];
                if (!elementsContains(modifiedElement)) {
                    // the modified element is not known by the editor
                    local = false;
                    // one last try - try to adapt the modified element if a
                    // preferred
                    // comparison class has been provided.
                    if (affectedObjectsClass != null) {
                        Object adapter = Util.getAdapter(modifiedElement, affectedObjectsClass);
                        if (adapter != null && elementsContains(adapter)) {
                            local = true;
                        }
                    }
                    // if the element did not match the affected objects, no
                    // need to check any others.
                    if (!local) {
                        break;
                    }
                }
            }
        }
        if (local) {
            return Status.OK_STATUS;
        }

        // The operation affects more than just our element.  Find out if
        // we should proceed, cancel, or discard the undo.  Must be done in
        // a syncExec because operation approval notifications may come from
        // a background thread.
        final int[] answer = new int[1];
        PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
            public void run() {
                MessageDialog dialog = new MessageDialog(part.getSite().getShell(), title, null, message,
                        MessageDialog.QUESTION,
                        new String[] { IDialogConstants.OK_LABEL, discardButton, IDialogConstants.CANCEL_LABEL },
                        0); // yes is the default
                answer[0] = dialog.open();
            }
        });
        switch (answer[0]) {
        case 0:
            return Status.OK_STATUS;
        case 1:
            return IOperationHistory.OPERATION_INVALID_STATUS;
        default:
            // Cancel by default to include ESC key and shell close,
            // which return SWT.DEFAULT, and any other unexpected return codes
            return Status.CANCEL_STATUS;
        }
    }

    /*
     * Answer whether this operation is relevant enough to this operation
     * approver that it should be examined in detail.
     */
    private boolean requiresApproval(IUndoableOperation operation, IAdaptable uiInfo) {
        // no approval is required if the operation doesn't have our undo
        // context
        if (!(operation.hasContext(context))) {
            return false;
        }

        // no approval is required if the operation only has our context
        if (operation.getContexts().length == 1) {
            return false;
        }

        // no approval is required if we can ascertain that the operation did
        // not originate
        // in our context.
        if (uiInfo != null) {
            IUndoContext originatingContext = (IUndoContext) Util.getAdapter(uiInfo, IUndoContext.class);
            if (originatingContext != null && !(originatingContext.matches(context))) {
                return false;
            }
        }

        return true;
    }

    /*
     * Return whether or not the collection of editor elements plus any of their
     * adapters contains the specified object.
     */
    private boolean elementsContains(Object someObject) {
        if (elements == null) {
            return false;
        }
        if (elementsAndAdapters == null) {
            // Compute a list of not just the elements, but any adapters they
            // may provide on the preferred class if they are not instances of
            // the preferred class. This is done only once.
            elementsAndAdapters = new ArrayList(elements.length);
            for (int i = 0; i < elements.length; i++) {
                Object element = elements[i];
                elementsAndAdapters.add(element);
                if (affectedObjectsClass != null && !affectedObjectsClass.isInstance(element)) {
                    Object adapter = Util.getAdapter(element, affectedObjectsClass);
                    if (adapter != null) {
                        elementsAndAdapters.add(adapter);
                    }
                }
            }
        }
        for (int i = 0; i < elementsAndAdapters.size(); i++) {
            if (elementsAndAdapters.get(i).equals(someObject)) {
                return true;
            }
        }
        return false;
    }
}