/******************************************************************************* * Copyright (c) 2012, 2014 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.command.impl; import static com.google.common.collect.Lists.newArrayList; import java.util.EventObject; import java.util.Iterator; import java.util.List; import org.eclipse.emf.common.command.AbstractCommand; 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.DelegatingCommandStack; import org.eclipse.emf.compare.command.ICompareCommandStack; import org.eclipse.emf.compare.command.ICompareCopyCommand; import org.eclipse.emf.edit.provider.IDisposable; /** * A simple {@link ICompareCommandStack} that delegate execution to another command stack but keep * informations about execution to properly reply to {@link ICompareCommandStack} protocol. * <p> * This implementation is not robust. If an error occurs during execution of a command, the whole state will * be corrupted and the undo/redo may have an unknown behavior. * * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a> */ public class CompareCommandStack extends DelegatingCommandStack implements ICompareCommandStack, IDisposable { /** The data structure to keep info of command executed on the right side. */ private final CompareSideCommandStack rightCommandStack; /** The data structure to keep info of command executed on the left side. */ private final CompareSideCommandStack leftCommandStack; /** The command to which we delegate to. */ private final CommandStack delegate; /** The listener of the wrapped command stacks. */ private final CommandStackListener delegateCommandStackListener; /** * Creates a new instance that delegates to the given {@code commandStack}. * * @param commandStack * the command stack to which this instance will delegate. */ public CompareCommandStack(CommandStack commandStack) { this.delegate = commandStack; this.delegateCommandStackListener = new CommandStackListener() { public void commandStackChanged(EventObject event) { notifyListeners(event.getSource()); } }; this.delegate().addCommandStackListener(delegateCommandStackListener); this.rightCommandStack = new CompareSideCommandStack(); this.leftCommandStack = new CompareSideCommandStack(); } /** * {@inheritDoc} * * @see org.eclipse.emf.edit.provider.IDisposable#dispose() */ public void dispose() { delegate().removeCommandStackListener(delegateCommandStackListener); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.command.DelegatingCommandStack#delegate() */ @Override protected CommandStack delegate() { return delegate; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.command.DelegatingCommandStack#execute(org.eclipse.emf.common.command.Command) */ @Override public void execute(Command command) { if (command instanceof ICompareCopyCommand) { if (command.canExecute()) { ICompareCopyCommand compareCommand = (ICompareCopyCommand)command; super.execute(command); final CompareSideCommandStack commandStack; if (compareCommand.isLeftToRight()) { commandStack = rightCommandStack; } else { commandStack = leftCommandStack; } if (super.canUndo()) { commandStack.executed(compareCommand); } else { commandStack.executedWithException(compareCommand); } notifyListeners(this); } } } /** * {@inheritDoc} * * @see org.eclipse.emf.common.command.BasicCommandStack#undo() */ @Override public void undo() { if (canUndo()) { if (getUndoCommand() instanceof ICompareCopyCommand) { ICompareCopyCommand compareCommand = (ICompareCopyCommand)getUndoCommand(); super.undo(); final CompareSideCommandStack commandStack; if (compareCommand.isLeftToRight()) { commandStack = rightCommandStack; } else { commandStack = leftCommandStack; } if (super.canRedo()) { commandStack.undone(); } else { commandStack.undoneWithException(); } notifyListeners(this); } } } /** * {@inheritDoc} * * @see org.eclipse.emf.common.command.BasicCommandStack#redo() */ @Override public void redo() { if (canRedo()) { if (getRedoCommand() instanceof ICompareCopyCommand) { ICompareCopyCommand compareCommand = (ICompareCopyCommand)getRedoCommand(); super.redo(); final CompareSideCommandStack commandStack; if (compareCommand.isLeftToRight()) { commandStack = rightCommandStack; } else { commandStack = leftCommandStack; } if (super.canUndo()) { commandStack.redone(); } else { commandStack.redoneWithException(); } notifyListeners(this); } } } /** * {@inheritDoc} * * @see org.eclipse.emf.common.command.CommandStack#flush() */ @Override public void flush() { super.flush(); rightCommandStack.flushed(); leftCommandStack.flushed(); notifyListeners(this); } /** * {@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(); } /** * Simple data structure acting like a command stack but without any execution capability. It is used to * record execution of {@link ICompareCopyCommand} on each side. * * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a> */ public static class CompareSideCommandStack { /** * This will force the {@link #isSaveNeeded()} to return <code>true</code>. */ private static final int IS_SAVE_NEEDED_WILL_BE_TRUE = -2; /** * The list of commands. */ private final List<ICompareCopyCommand> commandList; /** * The current position within the list from which the next execute, undo, or redo, will be performed. */ private int top; /** * The command most recently executed, undone, or redone. */ private Command mostRecentCommand; /** * The value of {@link #top} when {@link #saveIsDone} is called. */ private int saveIndex = -1; /** * Creates a new empty instance. */ public CompareSideCommandStack() { commandList = newArrayList(); top = -1; } /** * Record that the redo method has raised exceptions. */ public void redoneWithException() { mostRecentCommand = null; // Clear the list past the top. commandList.subList(top + 1, commandList.size()).clear(); } /** * Record that the undo method has raised exceptions. */ public void undoneWithException() { top--; mostRecentCommand = null; flushed(); } /** * Record the execution of the given command. * * @param command * the command to record. */ public void executed(ICompareCopyCommand command) { // If the command is executable, record it. if (command != null) { if (command.canExecute()) { // Clear the list past the top. Iterator<ICompareCopyCommand> commands = commandList.listIterator(top + 1); while (commands.hasNext()) { commands.next(); commands.remove(); } // Record the successfully executed command. mostRecentCommand = command; commandList.add(command); ++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) { // This forces isSaveNeded to always be true. saveIndex = IS_SAVE_NEEDED_WILL_BE_TRUE; } } } } /** * Will be called if the execute method of the command did not end normally. * * @param command * the command that raised exceptions. */ public void executedWithException(ICompareCopyCommand command) { mostRecentCommand = null; } /** * Record that the top of the command list has been undone. */ public void undone() { Command command = commandList.get(top--); mostRecentCommand = command; } /** * Record that the top of the command list has been redone. */ public void redone() { Command command = commandList.get(++top); mostRecentCommand = command; } /** * Disposes all the commands in the stack. */ public void flushed() { // Clear the list. commandList.clear(); top = -1; saveIndex = -1; mostRecentCommand = null; } /** * Called after a save has been successfully performed. */ public void saveIsDone() { // Remember where we are now. saveIndex = top; } /** * Returns whether the model has changes since {@link #saveIsDone} was call the last. * * @return whether the model has changes since <code>saveIsDone</code> was call the last. */ public boolean isSaveNeeded() { boolean ret = false; if (saveIndex < -1) { ret = true; } if (!ret) { if (top > saveIndex) { for (int i = top; !ret && i > saveIndex; --i) { if (!(commandList.get(i) instanceof AbstractCommand.NonDirtying)) { ret = true; } } } else { for (int i = saveIndex; !ret && i > top; --i) { if (!(commandList.get(i) instanceof AbstractCommand.NonDirtying)) { ret = true; } } } } return ret; } /** * Returns the command that will be undone if {@link #undo} is called. * * @return the command that will be undone if {@link #undo} is called. */ public Command getUndoCommand() { final Command undoCommand; if (top == -1 || top == commandList.size()) { undoCommand = null; } else { undoCommand = commandList.get(top); } return undoCommand; } /** * Returns the command that will be redone if {@link #redo} is called. * * @return the command that will be redone if {@link #redo} is called. */ public Command getRedoCommand() { final Command redoCommand; if (top + 1 >= commandList.size()) { redoCommand = null; } else { redoCommand = commandList.get(top + 1); } return redoCommand; } /** * Returns the command most recently executed, undone, or redone. * * @return the command most recently executed, undone, or redone. */ public Command getMostRecentCommand() { return mostRecentCommand; } } }