Another fragment that turned out to be useless for the project, but may come handy in the future. A port to JavaScript should be easy enough, too:
import java.util.ArrayList;
import java.util.List;
public class CommandStack {
/**
* Implement for each individual change that can occur in an editor.
*
* Both {@link #execute()} and {@link #undo()} should fire property change
* events for notifying the editor of an update.
*/
public static abstract class Command {
private String name;
public Command(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
/**
* Implements the actual change.
*
* Any state-saving for {@link #undo()}
* has to occur elsewhere, eg. in the constructor.
*/
public abstract void execute();
/**
* Reverts any changes applied by {@link #execute()}.
*/
public abstract void undo();
}
private final List commands = new ArrayList();
private int currentLocation = -1;
private int saveLocation = currentLocation;
public void add(Command command) {
clearInFrontOfCurrent();
command.execute();
commands.add(command);
currentLocation++;
}
public void undo() {
commands.get(currentLocation).undo();
currentLocation--;
}
public boolean undoEnabled() {
return currentLocation >= 0;
}
public void redo() {
currentLocation++;
commands.get(currentLocation).execute();
}
public boolean redoEnabled() {
return currentLocation < commands.size() - 1;
}
public boolean dirty() {
return currentLocation != saveLocation;
}
private void clearInFrontOfCurrent() {
while (currentLocation < commands.size() - 1) {
commands.remove(currentLocation + 1);
}
}
public void markSaveLocation() {
saveLocation = currentLocation;
}
@Override
public String toString() {
return commands.toString();
}
}
And to illustrate the usage, the JUnit test:
import junit.framework.TestCase;
public class CommandStackTest extends TestCase {
CommandStack stack = new CommandStack();
private String name;
private int age;
class EditName extends Command {
private String oldName = name;
private String newName;
public EditName(String newName) {
super("Update name to " + newName);
this.newName = newName;
}
public void execute() {
name = newName;
}
public void undo() {
name = oldName;
}
};
class EditAge extends Command {
private int oldAge = age;
private int newAge;
public EditAge(int newAge) {
super("Update age to " + newAge);
this.newAge = newAge;
}
public void execute() {
age = newAge;
}
public void undo() {
age = oldAge;
}
};
public void test_basics() {
assertNull(name);
assertEquals(0, age);
assertFalse(stack.dirty());
assertFalse(stack.undoEnabled());
assertFalse(stack.redoEnabled());
stack.add(new EditName("Peter"));
assertTrue(stack.dirty());
assertEquals("Peter", name);
assertTrue(stack.undoEnabled());
assertFalse(stack.redoEnabled());
stack.undo();
assertNull(name);
assertFalse(stack.undoEnabled());
assertTrue(stack.redoEnabled());
assertFalse(stack.dirty());
stack.redo();
assertTrue(stack.dirty());
assertEquals("Peter", name);
assertTrue(stack.undoEnabled());
assertFalse(stack.redoEnabled());
stack.markSaveLocation();
assertFalse(stack.dirty());
assertEquals("Peter", name);
assertTrue(stack.undoEnabled());
assertFalse(stack.redoEnabled());
stack.add(new EditAge(10));
assertTrue(stack.dirty());
assertEquals(10, age);
assertTrue(stack.undoEnabled());
assertFalse(stack.redoEnabled());
stack.undo();
assertFalse(stack.dirty());
assertEquals(0, age);
assertTrue(stack.undoEnabled());
assertTrue(stack.redoEnabled());
stack.add(new EditName("Pan"));
assertTrue(stack.dirty());
assertEquals("Pan", name);
assertEquals(0, age);
assertTrue(stack.undoEnabled());
assertFalse(stack.redoEnabled());
stack.undo();
stack.undo();
assertTrue(stack.dirty());
assertNull(name);
assertEquals(0, age);
assertFalse(stack.undoEnabled());
assertTrue(stack.redoEnabled());
stack.redo();
stack.redo();
assertTrue(stack.dirty());
assertEquals("Pan", name);
assertEquals(0, age);
assertTrue(stack.undoEnabled());
assertFalse(stack.redoEnabled());
}
}