/** * <copyright> * * Copyright (c) 2002, 2010 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 - Initial API and implementation * * </copyright> * * $Id: BasicCommandStack.java,v 1.14 2008/05/04 17:03:33 emerks Exp $ */ package net.enilink.komma.common.command; import java.util.ArrayList; import java.util.Collection; import java.util.EventObject; import java.util.Iterator; import java.util.List; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import net.enilink.komma.common.CommonPlugin; import net.enilink.komma.common.util.WrappedException; /** * A basic and obvious implementation of an undoable stack of commands. See * {@link ICommand} for more details about the command methods that this * implementation uses. */ public class BasicCommandStack implements ICommandStack { /** * The list of commands. */ protected List<ICommand> commandList; /** * The current position within the list from which the next execute, undo, * or redo, will be performed. */ protected int top; /** * The command most recently executed, undone, or redone. */ protected ICommand mostRecentCommand; /** * The {@link ICommandStackListener}s. */ protected Collection<ICommandStackListener> listeners; /** * The value of {@link #top} when {@link #saveIsDone} is called. */ protected int saveIndex = -1; /** * Creates a new empty instance. */ public BasicCommandStack() { commandList = new ArrayList<ICommand>(); top = -1; listeners = new ArrayList<ICommandStackListener>(); } /* * Javadoc copied from interface. */ @Override public IStatus execute(ICommand command, IProgressMonitor monitor, IAdaptable info) throws ExecutionException { IStatus status = Status.CANCEL_STATUS; // If the command is executable, record and execute it. if (command != null) { if (command.canExecute()) { try { status = command.execute(monitor, info); // Clear the list past the top. // for (Iterator<ICommand> commands = commandList .listIterator(top + 1); commands.hasNext(); commands .remove()) { ICommand otherCommand = commands.next(); otherCommand.dispose(); } // 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 = -2; } notifyListeners(); } catch (AbortExecutionException exception) { command.dispose(); } catch (RuntimeException exception) { handleError(exception); mostRecentCommand = null; command.dispose(); notifyListeners(); } } else { command.dispose(); } } return status; } /* * Javadoc copied from interface. */ @Override public boolean canUndo() { return top != -1 && commandList.get(top).canUndo(); } /* * Javadoc copied from interface. */ @Override public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { IStatus status = Status.CANCEL_STATUS; if (canUndo()) { ICommand command = commandList.get(top--); try { status = command.undo(monitor, info); mostRecentCommand = command; } catch (RuntimeException exception) { handleError(exception); mostRecentCommand = null; flush(); } notifyListeners(); } return status; } /* * Javadoc copied from interface. */ @Override public boolean canRedo() { return top < commandList.size() - 1; } /* * Javadoc copied from interface. */ public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { IStatus status = Status.CANCEL_STATUS; if (canRedo()) { ICommand command = commandList.get(++top); try { command.redo(monitor, info); mostRecentCommand = command; } catch (RuntimeException exception) { handleError(exception); mostRecentCommand = null; // Clear the list past the top. // for (Iterator<ICommand> commands = commandList .listIterator(top--); commands.hasNext(); commands .remove()) { ICommand otherCommand = commands.next(); otherCommand.dispose(); } } notifyListeners(); } return status; } /* * Javadoc copied from interface. */ @Override public void flush() { // Clear the list. // for (Iterator<ICommand> commands = commandList.listIterator(); commands .hasNext(); commands.remove()) { ICommand command = commands.next(); command.dispose(); } commandList.clear(); top = -1; saveIndex = -1; notifyListeners(); mostRecentCommand = null; } /* * Javadoc copied from interface. */ @Override public ICommand getUndoCommand() { return top == -1 || top == commandList.size() ? null : (ICommand) commandList.get(top); } /* * Javadoc copied from interface. */ @Override public ICommand getRedoCommand() { return top + 1 >= commandList.size() ? null : (ICommand) commandList .get(top + 1); } /* * Javadoc copied from interface. */ @Override public ICommand getMostRecentCommand() { return mostRecentCommand; } /* * Javadoc copied from interface. */ @Override public void addCommandStackListener(ICommandStackListener listener) { listeners.add(listener); } /* * Javadoc copied from interface. */ @Override public void removeCommandStackListener(ICommandStackListener listener) { listeners.remove(listener); } /** * This is called to ensure that * {@link ICommandStackListener#commandStackChanged} is called for each * listener. */ protected void notifyListeners() { for (ICommandStackListener commandStackListener : listeners) { commandStackListener.commandStackChanged(new EventObject(this)); } } /** * Handles an exception thrown during command execution by logging it with * the plugin. */ protected void handleError(Exception exception) { CommonPlugin.INSTANCE.log(new WrappedException(CommonPlugin.INSTANCE .getString("_UI_IgnoreException_exception"), exception) .fillInStackTrace()); } /** * 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() { // Only if we are at the remembered index do we NOT need to save. // // return top != saveIndex; if (saveIndex < -1) { return true; } if (top > saveIndex) { for (int i = top; i > saveIndex; --i) { if (!(commandList.get(i) instanceof AbstractCommand.INonDirtying)) { return true; } } } else { for (int i = saveIndex; i > top; --i) { if (!(commandList.get(i) instanceof AbstractCommand.INonDirtying)) { return true; } } } return false; } }