blob: 064efdaee61eccc072e9bed8b59e57cd364d2eb5 [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2002-2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM - Initial API and implementation
*
* </copyright>
*
* $Id: BasicCommandStack.java,v 1.3 2005/06/06 05:36:11 david_williams Exp $
*/
package org.eclipse.emf.common.command;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EventObject;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.wst.sse.core.internal.Logger;
/**
* A basic and obvious implementation of an undoable stack of commands. See
* {@link Command} for more details about the command methods that this
* implementation uses.
*/
public class BasicCommandStack implements CommandStack {
/**
* The list of commands.
*/
protected List 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 Command mostRecentCommand;
/**
* The {@link CommandStackListener}s.
*/
protected Collection 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();
top = -1;
listeners = new ArrayList();
}
/*
* Javadoc copied from interface.
*/
public void execute(Command command) {
// If the command is executable, record and execute it.
//
if (command != null && command.canExecute()) {
// Clear the list past the top.
//
for (ListIterator commands = commandList.listIterator(top + 1); commands.hasNext(); commands.remove()) {
Command otherCommand = (Command) commands.next();
otherCommand.dispose();
}
try {
command.execute();
mostRecentCommand = command;
commandList.add(command);
++top;
}
catch (RuntimeException exception) {
handleError(exception);
mostRecentCommand = null;
command.dispose();
}
// 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();
}
else {
command.dispose();
}
}
/*
* Javadoc copied from interface.
*/
public boolean canUndo() {
return top != -1 && ((Command) commandList.get(top)).canUndo();
}
/*
* Javadoc copied from interface.
*/
public void undo() {
if (canUndo()) {
Command command = (Command) commandList.get(top--);
try {
command.undo();
mostRecentCommand = command;
}
catch (RuntimeException exception) {
handleError(exception);
mostRecentCommand = null;
flush();
}
notifyListeners();
}
}
/*
* Javadoc copied from interface.
*/
public boolean canRedo() {
return top < commandList.size() - 1;
}
/*
* Javadoc copied from interface.
*/
public void redo() {
if (canRedo()) {
Command command = (Command) commandList.get(++top);
try {
command.redo();
mostRecentCommand = command;
}
catch (RuntimeException exception) {
handleError(exception);
mostRecentCommand = null;
// Clear the list past the top.
//
for (ListIterator commands = commandList.listIterator(top--); commands.hasNext(); commands.remove()) {
Command otherCommand = (Command) commands.next();
otherCommand.dispose();
}
}
notifyListeners();
}
}
/*
* Javadoc copied from interface.
*/
public void flush() {
// Clear the list.
//
for (ListIterator commands = commandList.listIterator(); commands.hasNext(); commands.remove()) {
Command command = (Command) commands.next();
command.dispose();
}
commandList.clear();
top = -1;
saveIndex = -1;
notifyListeners();
mostRecentCommand = null;
}
/*
* Javadoc copied from interface.
*/
public Command getUndoCommand() {
return top == -1 || top == commandList.size() ? null : (Command) commandList.get(top);
}
/*
* Javadoc copied from interface.
*/
public Command getRedoCommand() {
return top + 1 >= commandList.size() ? null : (Command) commandList.get(top + 1);
}
/*
* Javadoc copied from interface.
*/
public Command getMostRecentCommand() {
return mostRecentCommand;
}
/*
* Javadoc copied from interface.
*/
public void addCommandStackListener(CommandStackListener listener) {
listeners.add(listener);
}
/*
* Javadoc copied from interface.
*/
public void removeCommandStackListener(CommandStackListener listener) {
listeners.remove(listener);
}
/**
* This is called to ensure that
* {@link CommandStackListener#commandStackChanged} is called for each
* listener.
*/
protected void notifyListeners() {
for (Iterator i = listeners.iterator(); i.hasNext();) {
((CommandStackListener) i.next()).commandStackChanged(new EventObject(this));
}
}
/**
* Handles an exception thrown during command execution by loging it with
* the plugin.
*/
protected void handleError(Exception exception) {
Logger.logException(UnDoCommonMessages._UI_IgnoreException_exception, exception);
}
/**
* 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.NonDirtying)) {
return true;
}
}
}
else {
for (int i = saveIndex; i > top; --i) {
if (!(commandList.get(i) instanceof AbstractCommand.NonDirtying)) {
return true;
}
}
}
return false;
}
}