/******************************************************************************* * 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 com.google.common.base.Preconditions; import java.util.EventObject; import java.util.List; import org.eclipse.emf.common.command.BasicCommandStack; import org.eclipse.emf.common.command.Command; import org.eclipse.emf.common.command.CommandStackListener; import org.eclipse.emf.compare.command.ICompareCommandStack; import org.eclipse.emf.compare.command.ICompareCopyCommand; import org.eclipse.emf.edit.provider.IDisposable; /** * {@link ICompareCommandStack} implementation that will delegates to two given command stacks; one for each * side of the comparison. * <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 DualCompareCommandStack 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 BasicCommandStack leftCommandStack; /** The right command stack. */ private final BasicCommandStack rightCommandStack; /** * The list of command stack; it will record the stack of which command stack has been used to execute the * commands. */ private final List<BasicCommandStack> 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 BasicCommandStack mostRecentCommandStack; /** * The value of {@link #top} when {@link #saveIsDone} is called. */ private int saveIndex = -1; /** The listener of this DualCompareCommandStack. */ private final List<CommandStackListener> listeners; /** The listener of the wrapped command stacks. */ private final CommandStackListener sideCommandStackListener; /** * Creates an instance that delegates to two given {@link BasicCommandStack}. * * @param leftCommandStack * the left command stack. * @param rightCommandStack * the right command stack. */ public DualCompareCommandStack(BasicCommandStack leftCommandStack, BasicCommandStack rightCommandStack) { Preconditions.checkArgument(leftCommandStack != rightCommandStack); this.leftCommandStack = Preconditions.checkNotNull(leftCommandStack); this.rightCommandStack = Preconditions.checkNotNull(rightCommandStack); this.sideCommandStackListener = new CommandStackListener() { public void commandStackChanged(EventObject event) { notifyListeners(event.getSource()); } }; 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 notification */ protected void notifyListeners(Object source) { for (CommandStackListener commandStackListener : listeners) { commandStackListener.commandStackChanged(new EventObject(source)); } } /** * {@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()) { doExecute(command); } } } /** * Executes the given command. * * @param command * the command to execute */ private void doExecute(Command command) { final BasicCommandStack commandStack; final ICompareCopyCommand compareCommand = (ICompareCopyCommand)command; if (compareCommand.isLeftToRight()) { commandStack = rightCommandStack; } else { commandStack = leftCommandStack; } commandStack.execute(compareCommand); if (commandStack.canUndo()) { // 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; } } else { mostRecentCommandStack = null; } notifyListeners(this); } /** * {@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()) { BasicCommandStack commandStack = commandStackStack.get(top--); commandStack.undo(); if (commandStack.canRedo()) { mostRecentCommandStack = commandStack; } else { // something bad happened mostRecentCommandStack = null; flush(); } notifyListeners(this); } } /** * {@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()) { BasicCommandStack commandStack = commandStackStack.get(++top); commandStack.redo(); if (commandStack.canUndo()) { mostRecentCommandStack = commandStack; } else { // something bad happened. mostRecentCommandStack = null; // Clear the list past the top. commandStackStack.subList(top--, commandStackStack.size()).clear(); } notifyListeners(this); } } /** * {@inheritDoc} * * @see org.eclipse.emf.common.command.CommandStack#flush() */ public void flush() { commandStackStack.clear(); top = -1; saveIndex = -1; mostRecentCommandStack = null; notifyListeners(this); } /** * {@inheritDoc} * * @see org.eclipse.emf.common.command.CommandStack#addCommandStackListener(org.eclipse.emf.common.command.CommandStackListener) */ public void addCommandStackListener(CommandStackListener listener) { listeners.add(listener); leftCommandStack.addCommandStackListener(listener); rightCommandStack.addCommandStackListener(listener); } /** * {@inheritDoc} * * @see org.eclipse.emf.common.command.CommandStack#removeCommandStackListener(org.eclipse.emf.common.command.CommandStackListener) */ public void removeCommandStackListener(CommandStackListener listener) { listeners.remove(listener); leftCommandStack.removeCommandStackListener(listener); rightCommandStack.removeCommandStackListener(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(); } }