/* * $Id$ * * Copyright (c) 2000-2003 by Rodney Kinney * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License (LGPL) as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, copies are available * at http://www.opensource.org. */ package VASSAL.command; import java.util.LinkedList; import java.util.ListIterator; import VASSAL.tools.ErrorDialog; import VASSAL.tools.ThrowableUtils; /** * Command is an abstract class that does something. Any action that takes * place during a game should be encapsulated in a Command object. When * performing actions during a game, corresponding Commands will be logged * in the current logfile and/or sent to other players on the server. * * Commands can be strung together into compound commands with the * {@link #append} method. * * @see CommandEncoder */ public abstract class Command { private LinkedList<Command> seq = new LinkedList<Command>(); private Command undo; public Command() {} public Command[] getSubCommands() { return seq.toArray(new Command[seq.size()]); } /** * Execute this command by first invoking {@link #executeCommand}, then * invoking {@link #execute} recursively on all subcommands. */ public void execute() { try { executeCommand(); } catch (Throwable t) { handleFailure(t); final LinkedList<Command> oldSeq = seq; stripSubCommands(); seq = oldSeq; } for (Command cmd : seq) { try { cmd.execute(); } catch (Throwable t) { handleFailure(t); } } } private void handleFailure(Throwable t) { // find and rethrow causes which are not bugs ThrowableUtils.throwRecent(OutOfMemoryError.class, t); if (t instanceof Error) { // some unusual problem occurred throw (Error) t; } else { // report the bug here ErrorDialog.bug(t); } } /** * Perform the action which this Command represents */ protected abstract void executeCommand(); /** * If the action can be undone, return a Command that performs the * inverse action. The Command returned should only undo * {@link #executeCommand}, not the actions of subcommands */ protected abstract Command myUndoCommand(); /** * Remove all subcommands. */ public void stripSubCommands() { seq = new LinkedList<Command>(); } /** * @return true if this command does nothing */ public boolean isNull() { return false; } /** * * @return true if this command should be stored in a logfile */ public boolean isLoggable() { return !isNull(); } /** * @deprecated Use {@link #isAtomic()} */ @Deprecated protected boolean hasNullSubcommands() { return isAtomic(); } /** * Return true if this command has no sub-commands attached to it * (other than null commands). * * @return */ protected boolean isAtomic() { for (Command c : seq) { if (!c.isNull()) { return false; } } return true; } public String toString() { final StringBuilder sb = new StringBuilder(getClass().getSimpleName()); final String details = getDetails(); if (details != null) sb.append("[").append(details).append("]"); for (Command c : seq) sb.append("+").append(c.toString()); return sb.toString(); } /** Detailed information for toString() */ public String getDetails() { return null; } /** * Append a subcommand to this Command. */ public Command append(Command c) { Command retval = this; if (c != null && !c.isNull()) { if (isNull()) { retval = c; } seq.add(c); } return retval; } /** * @return a Command that undoes not only this Command's action, but also * the actions of all its subcommands. */ public Command getUndoCommand() { if (undo == null) { undo = new NullCommand(); for (ListIterator<Command> i = seq.listIterator(seq.size()); i.hasPrevious(); ) { undo = undo.append(i.previous().getUndoCommand()); } undo = undo.append(myUndoCommand()); } return undo; } }