All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.emf.common.command.StrictCompoundCommand Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2002-2006 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
 */
package org.eclipse.emf.common.command;


import java.util.List;
import java.util.ListIterator;

import org.eclipse.emf.common.CommonPlugin;
import org.eclipse.emf.common.util.WrappedException;


/**
 * A composite command which assumes that later commands in the list 
 * may depend on the results and side-effects of earlier commands in the list.
 * Because of this, it must implement {@link Command#canExecute} more carefully,
 * i.e., in order to determine canExecute for the composite, it doesn't simply test each command.
 * It tests the first command to see if it can execute;
 * then, if there is another command in the list, it checks if the first command can undo and then goes ahead and executes it!
 * This process is repeated until the last command that is not followed by another, which then determines the final result.
 * (For efficiency, when this processing gets to the last command, that command is tested for canUndo too and that result is cached.)
 * All the commands that have been executed are then undone, if {@link #isPessimistic} is true; 
 * by default it's false.
 *
 * 

* It is important for all but the last command to have no visible side-effect! * Multiple commands with visible side-effects must be composed into a single command using just a {@link CompoundCommand} * and that composite could be the last command of a strict composite. * *

* Here is an example of how this can be used in conjunction with a {@link CommandWrapper}. *

 *   Command strictCompoundCommand = new StrictCompoundCommand();
 *   Command copyCommand = new CopyCommand(...);
 *   strictCompoundCommand.add(copyCommand);
 *
 *   Command addCommand =
 *     new CommandWrapper()
 *     {
 *       public Command createCommand()
 *       {
 *         new AddCommand(parent, copyCommand.getResult()); 
 *       }
 *     };
 *   strictCompoundCommand.append(addCommand);
 * 
* Here the add command won't know which command to create until it has the result of the copy command. * The proxy makes sure the creation of the add command is deferred and the strict composite ensures that execution dependencies are met. */ public class StrictCompoundCommand extends CompoundCommand { /** * The result for {@link Command#canUndo}. */ protected boolean isUndoable; /** * Whether commands that have been tentatively executed need to be undone. */ protected boolean isPessimistic; /** * Remember to call redo instead of execute for any command at or before this index in the list. */ protected int rightMostExecutedCommandIndex = -1; /** * Creates an empty instance. */ public StrictCompoundCommand() { super(); resultIndex = LAST_COMMAND_ALL; } /** * Creates an instance with the given label. * @param label the label. */ public StrictCompoundCommand(String label) { super(label); resultIndex = LAST_COMMAND_ALL; } /** * Creates an instance with the given label and description. * @param label the label. * @param description the description. */ public StrictCompoundCommand(String label, String description) { super(label, description); resultIndex = LAST_COMMAND_ALL; } /** * Creates an instance with the given command list. * @param commandList the list of commands. */ public StrictCompoundCommand(List commandList) { super(commandList); resultIndex = LAST_COMMAND_ALL; } /** * Creates an instance with the given label and command list. * @param label the label. * @param commandList the list of commands. */ public StrictCompoundCommand(String label, List commandList) { super(label, commandList); resultIndex = LAST_COMMAND_ALL; } /** * Creates an instance with the given label, description, and command list. * @param label the label. * @param description the description. * @param commandList the list of commands. */ public StrictCompoundCommand(String label, String description, List commandList) { super(label, description, commandList); resultIndex = LAST_COMMAND_ALL; } /** * Returns false if any command on the list returns false for {@link Command#canExecute}, * or if some command before the last one can't be undone and hence we can't test all the commands for executability. * @return whether the command can execute. */ @Override protected boolean prepare() { // Go through the commands of the list. // ListIterator commands = commandList.listIterator(); // If there are some... // if (commands.hasNext()) { boolean result = true; // The termination guard is in the body. // for (;;) { Command command = commands.next(); if (command.canExecute()) { if (commands.hasNext()) { if (command.canUndo()) { try { if (commands.previousIndex() <= rightMostExecutedCommandIndex) { command.redo(); } else { ++rightMostExecutedCommandIndex; command.execute(); } } catch (RuntimeException exception) { CommonPlugin.INSTANCE.log (new WrappedException (CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), exception).fillInStackTrace()); result = false; break; } } else { // We can't undo it, so we'd better give up. // result = false; break; } } else { // Now is the best time to record isUndoable because later we would have to do all the executes again! // This makes canUndo very simple! // isUndoable = command.canUndo(); break; } } else { // If we can't execute this one, we just can't do it at all. // result = false; break; } } // If we are pessimistic, then we need to undo all the commands that we have executed so far. // if (isPessimistic) { // The most recently processed command will never have been executed. // commands.previous(); // We want to unroll all the effects of the previous commands. // while (commands.hasPrevious()) { Command command = commands.previous(); command.undo(); } } return result; } else { isUndoable = false; return false; } } /** * Calls {@link Command#execute} for each command in the list, * but makes sure to call redo for any commands that were previously executed to compute canExecute. * In the case that {@link #isPessimistic} is false, only the last command will be executed * since the others will have been executed but not undone during {@link #prepare}. */ @Override public void execute() { if (isPessimistic) { for (ListIterator commands = commandList.listIterator(); commands.hasNext(); ) { try { // Either execute or redo the command, as appropriate. // Command command = commands.next(); if (commands.previousIndex() <= rightMostExecutedCommandIndex) { command.redo(); } else { command.execute(); } } catch (RuntimeException exception) { // Skip over the command that threw the exception. // commands.previous(); // Iterate back over the executed commands to undo them. // while (commands.hasPrevious()) { commands.previous(); Command command = commands.previous(); if (command.canUndo()) { command.undo(); } else { break; } } throw exception; } } } else if (!commandList.isEmpty()) { Command command = commandList.get(commandList.size() - 1); command.execute(); } } /** * Calls {@link Command#undo} for each command in the list. * In the case that {@link #isPessimistic} is false, only the last command will be undone * since the others will have been executed and not undo during {@link #prepare}. */ @Override public void undo() { if (isPessimistic) { super.undo(); } else if (!commandList.isEmpty()) { Command command = commandList.get(commandList.size() - 1); command.undo(); } } /** * Calls {@link Command#redo} for each command in the list. * In the case that {@link #isPessimistic} is false, only the last command will be redone * since the others will have been executed and not undo during {@link #prepare}. */ @Override public void redo() { if (isPessimistic) { super.redo(); } else if (!commandList.isEmpty()) { Command command = commandList.get(commandList.size() - 1); command.redo(); } } /** * Checks if the command can execute; * if so, it is executed, appended to the list, and true is returned, * if not, it is just disposed and false is returned. * A typical use for this is to execute commands created during the execution of another command, e.g., *
   *   class MyCommand extends AbstractCommand
   *   {
   *     protected Command subcommand;
   *
   *     //...
   *
   *     public void execute()
   *     {
   *       // ...
   *       StrictCompoundCommand subcommands = new StrictCompoundCommand();
   *       subcommands.appendAndExecute(new AddCommand(...));
   *       if (condition) subcommands.appendAndExecute(new AddCommand(...));
   *       subcommand = subcommands.unwrap();
   *     }
   *
   *     public void undo()
   *     {
   *       // ...
   *       subcommand.undo();
   *     }
   *
   *     public void redo()
   *     {
   *       // ...
   *       subcommand.redo();
   *     }
   *
   *     public void dispose()
   *     {
   *       // ...
   *       if (subcommand != null)
   *      {
   *         subcommand.dispose();
   *       }
   *     }
   *   }
   * 
* @return whether the command was successfully executed and appended. */ @Override public boolean appendAndExecute(Command command) { if (command != null) { if (!isPrepared) { if (commandList.isEmpty()) { isPrepared = true; isExecutable = true; } else { isExecutable = prepare(); isPrepared = true; isPessimistic = true; if (isExecutable) { execute(); } } } if (command.canExecute()) { try { command.execute(); commandList.add(command); ++rightMostExecutedCommandIndex; isUndoable = command.canUndo(); return true; } catch (RuntimeException exception) { CommonPlugin.INSTANCE.log (new WrappedException (CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), exception).fillInStackTrace()); } } command.dispose(); } return false; } /* * Javadoc copied from base class. */ @Override public String toString() { StringBuffer result = new StringBuffer(super.toString()); result.append(" (isUndoable: " + isUndoable + ")"); result.append(" (isPessimistic: " + isPessimistic + ")"); result.append(" (rightMostExecutedCommandIndex: " + rightMostExecutedCommandIndex + ")"); return result.toString(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy