| /******************************************************************************* |
| * Copyright (c) 2012, 2017 Obeo 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: |
| * Obeo - initial API and implementation |
| * Philip Langer - bug 521948 |
| *******************************************************************************/ |
| package org.eclipse.emf.compare.command.impl; |
| |
| import static com.google.common.collect.Lists.newArrayList; |
| |
| import com.google.common.base.Preconditions; |
| |
| import java.util.EventObject; |
| import java.util.List; |
| |
| import org.eclipse.emf.common.command.Command; |
| import org.eclipse.emf.common.command.CommandStack; |
| import org.eclipse.emf.common.command.CommandStackListener; |
| import org.eclipse.emf.compare.command.CommandStackEvent; |
| import org.eclipse.emf.compare.command.ICompareCommandStack; |
| import org.eclipse.emf.compare.command.ICompareCopyCommand; |
| import org.eclipse.emf.edit.provider.IDisposable; |
| import org.eclipse.emf.transaction.ExceptionHandler; |
| import org.eclipse.emf.transaction.impl.AbstractTransactionalCommandStack; |
| |
| /** |
| * {@link ICompareCommandStack} implementation that will delegate to two given command stacks; one for each |
| * side of the comparison. |
| * <p> |
| * This implementation is one of the most robust delegating implementation we can do. If an error occurs |
| * during execution of a command, only the dirty state will be reset, all models will stay correct. |
| * |
| * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a> |
| */ |
| public class TransactionalDualCompareCommandStack implements ICompareCommandStack, IDisposable { |
| |
| /** |
| * This value forces isSaveNeded to always be true. |
| */ |
| private static final int IS_SAVE_NEEDED_WILL_BE_TRUE = -2; |
| |
| /** The left command stack. */ |
| private final AbstractTransactionalCommandStack leftCommandStack; |
| |
| /** The right command stack. */ |
| private final AbstractTransactionalCommandStack rightCommandStack; |
| |
| /** |
| * The list of command stacks; it will record the stack of which command stack has been used to execute |
| * the commands. |
| */ |
| private final List<AbstractTransactionalCommandStack> commandStackStack; |
| |
| /** |
| * The current position within the list from which the next execute, undo, or redo, will be performed. |
| */ |
| private int top; |
| |
| /** |
| * The command stack on which a command has been most recently executed, undone, or redone. |
| */ |
| private AbstractTransactionalCommandStack mostRecentCommandStack; |
| |
| /** |
| * The value of {@link #top} when {@link #saveIsDone} is called. |
| */ |
| private int saveIndex = -1; |
| |
| /** The listeners of this DualCompareCommandStack. */ |
| private final List<CommandStackListener> listeners; |
| |
| /** The listener of the wrapped command stacks. */ |
| private final SideCommandStackListener sideCommandStackListener; |
| |
| /** |
| * Whether |
| * {@link #notifyListeners(CommandStack, org.eclipse.emf.compare.command.CommandStackEvent.Operation) |
| * notification} is currently {@link #isDeliver() enabled}. |
| */ |
| private boolean deliver = true; |
| |
| /** |
| * Recorders whether {@link #notifyListeners(CommandStack, CommandStackEvent.Operation) |
| * notifyListeners(commandStack, CommandStack.Event.EXECUTE)} as called while {@link #deliver} is false. |
| */ |
| private boolean deferredExecuteNotification; |
| |
| /** |
| * Creates an instance that delegates to two given {@link AbstractTransactionalCommandStack}. |
| * |
| * @param leftCommandStack |
| * the left command stack. |
| * @param rightCommandStack |
| * the right command stack. |
| */ |
| public TransactionalDualCompareCommandStack(AbstractTransactionalCommandStack leftCommandStack, |
| AbstractTransactionalCommandStack rightCommandStack) { |
| this.leftCommandStack = Preconditions.checkNotNull(leftCommandStack); |
| this.rightCommandStack = Preconditions.checkNotNull(rightCommandStack); |
| this.sideCommandStackListener = new SideCommandStackListener(); |
| this.leftCommandStack.addCommandStackListener(sideCommandStackListener); |
| this.rightCommandStack.addCommandStackListener(sideCommandStackListener); |
| this.listeners = newArrayList(); |
| this.commandStackStack = newArrayList(); |
| this.top = -1; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.edit.provider.IDisposable#dispose() |
| */ |
| public void dispose() { |
| leftCommandStack.removeCommandStackListener(this.sideCommandStackListener); |
| rightCommandStack.removeCommandStackListener(this.sideCommandStackListener); |
| } |
| |
| /** |
| * This is called to ensure that {@link CommandStackListener#commandStackChanged} is called for each |
| * listener. |
| * |
| * @param source |
| * the source of the event. |
| * @deprecated Override or call {@link #notifyListeners(CommandStack, CommandStackEvent.Operation) |
| * instead}. |
| */ |
| @Deprecated |
| protected void notifyListeners(Object source) { |
| if (deliver) { |
| for (CommandStackListener commandStackListener : listeners) { |
| commandStackListener.commandStackChanged(new EventObject(source)); |
| } |
| } |
| } |
| |
| /** |
| * This is called to ensure that {@link CommandStackListener#commandStackChanged} is called for each |
| * listener. |
| * |
| * @param commandStack |
| * the source of the event. |
| * @param operation |
| * the operation of the event. |
| */ |
| protected void notifyListeners(CommandStack commandStack, CommandStackEvent.Operation operation) { |
| if (deliver) { |
| deferredExecuteNotification = false; |
| for (CommandStackListener commandStackListener : listeners) { |
| commandStackListener.commandStackChanged(new CommandStackEvent(commandStack, operation)); |
| } |
| } else if (operation == CommandStackEvent.Operation.EXECUTE) { |
| deferredExecuteNotification = true; |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.common.command.CommandStack#execute(org.eclipse.emf.common.command.Command) |
| */ |
| public void execute(Command command) { |
| if (command instanceof ICompareCopyCommand) { |
| if (command.canExecute()) { |
| sideCommandStackListener.setIgnoreCommandStackChanged(true); |
| try { |
| final ICompareCopyCommand compareCommand = (ICompareCopyCommand)command; |
| final AbstractTransactionalCommandStack commandStack; |
| if (compareCommand.isLeftToRight()) { |
| commandStack = rightCommandStack; |
| } else { |
| commandStack = leftCommandStack; |
| } |
| |
| ExceptionHandler oldExceptionHandler = commandStack.getExceptionHandler(); |
| AbstractCompletionHandler completionHandler = new ExecuteCompletionHandler(); |
| commandStack.setExceptionHandler(completionHandler); |
| |
| commandStack.execute(compareCommand); |
| |
| if (!completionHandler.hadException()) { |
| completionHandler.handleSuccessfulCompletion(commandStack); |
| } |
| |
| commandStack.setExceptionHandler(oldExceptionHandler); |
| |
| notifyListeners(this, CommandStackEvent.Operation.EXECUTE); |
| } finally { |
| sideCommandStackListener.setIgnoreCommandStackChanged(false); |
| } |
| } else if (!deliver) { |
| deferredExecuteNotification = true; |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.common.command.CommandStack#canUndo() |
| */ |
| public boolean canUndo() { |
| return top != -1 && commandStackStack.get(top).canUndo(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.common.command.CommandStack#undo() |
| */ |
| public void undo() { |
| if (canUndo()) { |
| sideCommandStackListener.setIgnoreCommandStackChanged(true); |
| try { |
| AbstractTransactionalCommandStack commandStack = commandStackStack.get(top--); |
| |
| ExceptionHandler oldExceptionHandler = commandStack.getExceptionHandler(); |
| AbstractCompletionHandler completionHandler = new UndoCompletionHandler(); |
| commandStack.setExceptionHandler(completionHandler); |
| |
| commandStack.undo(); |
| |
| if (!completionHandler.hadException()) { |
| completionHandler.handleSuccessfulCompletion(commandStack); |
| } |
| |
| commandStack.setExceptionHandler(oldExceptionHandler); |
| |
| notifyListeners(this, CommandStackEvent.Operation.UNDO); |
| } finally { |
| sideCommandStackListener.setIgnoreCommandStackChanged(false); |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.common.command.CommandStack#canRedo() |
| */ |
| public boolean canRedo() { |
| return top < commandStackStack.size() - 1; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.common.command.CommandStack#getUndoCommand() |
| */ |
| public Command getUndoCommand() { |
| final Command undoCommand; |
| if (top == -1 || top == commandStackStack.size()) { |
| undoCommand = null; |
| } else { |
| undoCommand = commandStackStack.get(top).getUndoCommand(); |
| } |
| return undoCommand; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.common.command.CommandStack#getRedoCommand() |
| */ |
| public Command getRedoCommand() { |
| final Command redoCommand; |
| if (top + 1 >= commandStackStack.size()) { |
| redoCommand = null; |
| } else { |
| redoCommand = commandStackStack.get(top + 1).getRedoCommand(); |
| } |
| return redoCommand; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.common.command.CommandStack#getMostRecentCommand() |
| */ |
| public Command getMostRecentCommand() { |
| if (mostRecentCommandStack != null) { |
| return mostRecentCommandStack.getMostRecentCommand(); |
| } |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.common.command.CommandStack#redo() |
| */ |
| public void redo() { |
| if (canRedo()) { |
| sideCommandStackListener.setIgnoreCommandStackChanged(true); |
| try { |
| AbstractTransactionalCommandStack commandStack = commandStackStack.get(++top); |
| |
| ExceptionHandler oldExceptionHandler = commandStack.getExceptionHandler(); |
| AbstractCompletionHandler completionHandler = new RedoCompletionHandler(); |
| commandStack.setExceptionHandler(completionHandler); |
| |
| commandStack.redo(); |
| |
| if (!completionHandler.hadException()) { |
| completionHandler.handleSuccessfulCompletion(commandStack); |
| } |
| |
| commandStack.setExceptionHandler(oldExceptionHandler); |
| |
| notifyListeners(this, CommandStackEvent.Operation.REDO); |
| } finally { |
| sideCommandStackListener.setIgnoreCommandStackChanged(false); |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.common.command.CommandStack#flush() |
| */ |
| public void flush() { |
| commandStackStack.clear(); |
| top = -1; |
| saveIndex = -1; |
| mostRecentCommandStack = null; |
| |
| // We should flush the two sides as well. |
| leftCommandStack.flush(); |
| rightCommandStack.flush(); |
| |
| notifyListeners(this, CommandStackEvent.Operation.FLUSH); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.common.command.CommandStack#addCommandStackListener(org.eclipse.emf.common.command.CommandStackListener) |
| */ |
| public void addCommandStackListener(CommandStackListener listener) { |
| this.listeners.add(listener); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.common.command.CommandStack#removeCommandStackListener(org.eclipse.emf.common.command.CommandStackListener) |
| */ |
| public void removeCommandStackListener(CommandStackListener listener) { |
| this.listeners.remove(listener); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.compare.command.ICompareCommandStack#isLeftSaveNeeded() |
| */ |
| public boolean isLeftSaveNeeded() { |
| return leftCommandStack.isSaveNeeded(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.compare.command.ICompareCommandStack#isRightSaveNeeded() |
| */ |
| public boolean isRightSaveNeeded() { |
| return rightCommandStack.isSaveNeeded(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.compare.command.ICompareCommandStack#leftSaveIsDone() |
| */ |
| public void leftSaveIsDone() { |
| leftCommandStack.saveIsDone(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.compare.command.ICompareCommandStack#rightSaveIsDone() |
| */ |
| public void rightSaveIsDone() { |
| rightCommandStack.saveIsDone(); |
| } |
| |
| /** |
| * Returns whether delivery of notifications to {@link #addCommandStackListener(CommandStackListener) |
| * listeners} is enabled. |
| * |
| * @return whether delivery of notifications is enabled. |
| */ |
| public boolean isDeliver() { |
| return deliver; |
| } |
| |
| /** |
| * Sets whether delivery of notifications to {@link #addCommandStackListener(CommandStackListener) |
| * listeners} is enabled. When delivery is enabled, the listeners are immediately notified. |
| * |
| * @param deliver |
| * whether delivery of notifications is enabled. |
| */ |
| public void setDeliver(boolean deliver) { |
| this.deliver = deliver; |
| if (deliver && deferredExecuteNotification) { |
| notifyListeners(this, CommandStackEvent.Operation.EXECUTE); |
| } |
| } |
| |
| /** |
| * An exception handler that will keep in memory if an exception happened during the execution of a |
| * command. |
| * |
| * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a> |
| */ |
| private abstract static class AbstractCompletionHandler implements ExceptionHandler { |
| |
| /** Holds the fact that an exception happened during the execution. */ |
| private boolean hadException; |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.transaction.ExceptionHandler#handleException(java.lang.Exception) |
| */ |
| public void handleException(Exception e) { |
| hadException = true; |
| } |
| |
| /** |
| * Executes whatever needed on a successful (i.e. without exception) execution. |
| * |
| * @param commandStack |
| * the command stack on which the command has been successfully excuted. |
| */ |
| abstract void handleSuccessfulCompletion(AbstractTransactionalCommandStack commandStack); |
| |
| /** |
| * Returns true if an exception occured during the execution of a command, false otherwise. |
| * |
| * @return true if an exception occured during the execution of a command, false otherwise. |
| */ |
| boolean hadException() { |
| return hadException; |
| } |
| |
| } |
| |
| /** |
| * Completion handler of a {@link org.eclipse.emf.common.command.CommandStack#redo() redo} operation. |
| * |
| * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a> |
| */ |
| private final class RedoCompletionHandler extends AbstractCompletionHandler { |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.transaction.ExceptionHandler#handleException(java.lang.Exception) |
| */ |
| @Override |
| public void handleException(Exception e) { |
| if (!hadException()) { // avoid to handle exception multiple times. |
| super.handleException(e); |
| mostRecentCommandStack = null; |
| |
| // Clear the list past the top. |
| commandStackStack.subList(top--, commandStackStack.size()).clear(); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.compare.command.impl.TransactionalDualCompareCommandStack.AbstractCompletionHandler#handleSuccessfulCompletion(org.eclipse.emf.transaction.impl.AbstractTransactionalCommandStack) |
| */ |
| @Override |
| public void handleSuccessfulCompletion(AbstractTransactionalCommandStack commandStack) { |
| mostRecentCommandStack = commandStack; |
| } |
| } |
| |
| /** |
| * Completion handler of an {@link org.eclipse.emf.common.command.CommandStack#undo() undo} operation. |
| * |
| * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a> |
| */ |
| private final class UndoCompletionHandler extends AbstractCompletionHandler { |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.transaction.ExceptionHandler#handleException(java.lang.Exception) |
| */ |
| @Override |
| public void handleException(Exception e) { |
| if (!hadException()) { // avoid to handle exception multiple times. |
| super.handleException(e); |
| mostRecentCommandStack = null; |
| flush(); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.compare.command.impl.TransactionalDualCompareCommandStack.AbstractCompletionHandler#handleSuccessfulCompletion(org.eclipse.emf.transaction.impl.AbstractTransactionalCommandStack) |
| */ |
| @Override |
| public void handleSuccessfulCompletion(AbstractTransactionalCommandStack commandStack) { |
| mostRecentCommandStack = commandStack; |
| } |
| } |
| |
| /** |
| * Completion handler of an {@link org.eclipse.emf.common.command.CommandStack#execute(Command) execute} |
| * operation. |
| */ |
| private final class ExecuteCompletionHandler extends AbstractCompletionHandler { |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.compare.command.impl.TransactionalDualCompareCommandStack.AbstractCompletionHandler#handleException(java.lang.Exception) |
| */ |
| @Override |
| public void handleException(Exception e) { |
| if (!hadException()) { // avoid to handle exception multiple times. |
| super.handleException(e); |
| mostRecentCommandStack = null; |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.compare.command.impl.TransactionalDualCompareCommandStack.AbstractCompletionHandler#handleSuccessfulCompletion(org.eclipse.emf.transaction.impl.AbstractTransactionalCommandStack) |
| */ |
| @Override |
| void handleSuccessfulCompletion(AbstractTransactionalCommandStack commandStack) { |
| // Clear the list past the top. |
| commandStackStack.subList(top + 1, commandStackStack.size()).clear(); |
| |
| // Record the successfully executed command. |
| mostRecentCommandStack = commandStack; |
| commandStackStack.add(commandStack); |
| ++top; |
| |
| // This is kind of tricky. If the saveIndex was in the redo part of the command list which has now |
| // been wiped out, then we can never reach a point where a save is not necessary, not even if we |
| // undo all the way back to the beginning. |
| if (saveIndex >= top) { |
| saveIndex = IS_SAVE_NEEDED_WILL_BE_TRUE; |
| } |
| } |
| } |
| |
| /** |
| * A command stack listener that forwards notification to |
| * {@link TransactionalDualCompareCommandStack#notifyListeners(CommandStack, CommandStackEvent.Operation)}. |
| * Forwarding of notifications can be disabled. |
| */ |
| private final class SideCommandStackListener implements CommandStackListener { |
| /** |
| * Whether forwarding of notifications is disabled. |
| */ |
| private boolean ignoreCommandStackChanged; |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void commandStackChanged(EventObject event) { |
| if (!ignoreCommandStackChanged) { |
| AbstractTransactionalCommandStack commandStack = (AbstractTransactionalCommandStack)event |
| .getSource(); |
| // We assume that only executes happen on the side command stacks. |
| if (commandStack.getMostRecentCommand() != null) { |
| new ExecuteCompletionHandler().handleSuccessfulCompletion(commandStack); |
| notifyListeners(commandStack, CommandStackEvent.Operation.EXECUTE); |
| } |
| } |
| } |
| |
| /** |
| * Sets whether command stack changes should be forwarded. |
| * |
| * @param ignoreCommandStackChanged |
| * whether command stack changes should be forwarded. |
| */ |
| public void setIgnoreCommandStackChanged(boolean ignoreCommandStackChanged) { |
| this.ignoreCommandStackChanged = ignoreCommandStackChanged; |
| } |
| } |
| } |