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

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

There is a newer version: 2.4.3
Show 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
 *
 * 
 *
 * $Id: CompoundCommand.java,v 1.6 2007/06/12 20:56:17 emerks Exp $
 */
package org.eclipse.emf.common.command;


import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;

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


/**
 * A command that comprises a sequence of subcommands.
 * Derived classes can control the way results are accumulated from the individual commands;
 * the default behaviour is to return the result of the last command.
 */
public class CompoundCommand extends AbstractCommand 
{
  /**
   * The list of subcommands.
   */
  protected List commandList;

  /**
   * When {@link #resultIndex} is set to this, 
   * {@link #getResult} and {@link #getAffectedObjects} are delegated to the last command, if any, in the list.
   */
  public static final int LAST_COMMAND_ALL = Integer.MIN_VALUE;

  /**
   * When {@link #resultIndex} is set to this, 
   * {@link #getResult} and {@link #getAffectedObjects}
   * are set to the result of merging the corresponding collection of each command in the list.
   */
  public static final int MERGE_COMMAND_ALL = Integer.MIN_VALUE - 1;

  /**
   * The index of the command whose result and affected objects are forwarded.
   * Negative values have special meaning, as defined by the static constants.
   * A value of -1 indicates that the last command in the list should be used.
   * We could have more special behaviours implemented for other negative values.
   */
  protected int resultIndex = MERGE_COMMAND_ALL;

  /**
   * Creates an empty instance.
   */
  public CompoundCommand()
  {
    super();
    commandList = new ArrayList();
  }

  /**
   * Creates an instance with the given label.
   * @param label the label.
   */
  public CompoundCommand(String label) 
  {
    super(label);
    commandList = new ArrayList();
  }
  
  /**
   * Creates an instance with the given label and description.
   * @param label the label.
   * @param description the description.
   */
  public CompoundCommand(String label, String description) 
  {
    super(label, description);
    commandList = new ArrayList();
  }
  
  /**
   * Creates an instance with the given list.
   * @param commandList the list of commands.
   */
  public CompoundCommand(List commandList)
  {
    super();
    this.commandList = commandList;
  }

  /**
   * Creates instance with the given label and list.
   * @param label the label.
   * @param commandList the list of commands.
   */
  public CompoundCommand(String label, List commandList)
  {
    super(label);
    this.commandList = commandList;
  }

  /**
   * Creates an instance with the given label, description, and list.
   * @param label the label.
   * @param description the description.
   * @param commandList the list of commands.
   */
  public CompoundCommand(String label, String description, List commandList)
  {
    super(label, description);
    this.commandList = commandList;
  }

  /**
   * Creates an empty instance with the given result index.
   * @param resultIndex the {@link #resultIndex}.
   */
  public CompoundCommand(int resultIndex)
  {
    super();
    this.resultIndex = resultIndex;
    commandList = new ArrayList();
  }

  /**
   * Creates an instance with the given result index and label.
   * @param resultIndex the {@link #resultIndex}.
   * @param label the label.
   */
  public CompoundCommand(int resultIndex, String label) 
  {
    super(label);
    this.resultIndex = resultIndex;
    commandList = new ArrayList();
  }
  
  /**
   * Creates an instance with the given result index, label, and description.
   * @param resultIndex the {@link #resultIndex}.
   * @param label the label.
   * @param description the description.
   */
  public CompoundCommand(int resultIndex, String label, String description) 
  {
    super(label, description);
    this.resultIndex = resultIndex;
    commandList = new ArrayList();
  }
  
  /**
   * Creates an instance with the given result index and list.
   * @param resultIndex the {@link #resultIndex}.
   * @param commandList the list of commands.
   */
  public CompoundCommand(int resultIndex, List commandList)
  {
    super();
    this.resultIndex = resultIndex;
    this.commandList = commandList;
  }

  /**
   * Creates an instance with the given resultIndex, label, and list.
   * @param resultIndex the {@link #resultIndex}.
   * @param label the label.
   * @param commandList the list of commands.
   */
  public CompoundCommand(int resultIndex, String label, List commandList)
  {
    super(label);
    this.resultIndex = resultIndex;
    this.commandList = commandList;
  }

  /**
   * Creates an instance with the given result index, label, description, and list.
   * @param resultIndex the {@link #resultIndex}.
   * @param label the label.
   * @param description the description.
   * @param commandList the list of commands.
   */
  public CompoundCommand(int resultIndex, String label, String description, List commandList)
  {
    super(label, description);
    this.resultIndex = resultIndex;
    this.commandList = commandList;
  }

  /**
   * Returns whether there are commands in the list.
   * @return whether there are commands in the list.
   */
  public boolean isEmpty()
  {
    return commandList.isEmpty(); 
  }

  /**
   * Returns an unmodifiable view of the commands in the list.
   * @return an unmodifiable view of the commands in the list.
   */
  public List getCommandList()
  {
    return Collections.unmodifiableList(commandList);
  }

  /**
   * Returns the index of the command whose result and affected objects are forwarded.
   * Negative values have special meaning, as defined by the static constants.
   * @return the index of the command whose result and affected objects are forwarded.
   * @see #LAST_COMMAND_ALL
   * @see #MERGE_COMMAND_ALL
   */
  public int getResultIndex()
  {
    return resultIndex;
  }

  /**
   * Returns whether all the commands can execute so that {@link #isExecutable} can be cached.
   * An empty command list causes false to be returned.
   * @return whether all the commands can execute.
   */
  @Override
  protected boolean prepare()
  {
    if (commandList.isEmpty())
    {
      return false;
    }
    else
    {
      for (Command command : commandList)
      {
        if (!command.canExecute())
        {
          return false;
        }
      }

      return true;
    }
  }

  /**
   * Calls {@link Command#execute} for each command in the list.
   */
  public void execute() 
  {
    for (ListIterator commands = commandList.listIterator(); commands.hasNext(); ) 
    {
      try
      {
        Command command = commands.next();
        command.execute();
      }
      catch (RuntimeException exception)
      {
        // Skip over the command that threw the exception.
        //
        commands.previous();

        try
        {
          // Iterate back over the executed commands to undo them.
          //
          while (commands.hasPrevious())
          {
            Command command = commands.previous();
            if (command.canUndo())
            {
              command.undo();
            }
            else
            {
              break;
            }
          }
        }
        catch (RuntimeException nestedException)
        {
          CommonPlugin.INSTANCE.log
            (new WrappedException
              (CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), nestedException).fillInStackTrace());
        }

        throw exception;
      }
    }
  }

  /**
   * Returns false if any of the commands return false for {@link Command#canUndo}.
   * @return false if any of the commands return false for canUndo.
   */
  @Override
  public boolean canUndo() 
  {
    for (Command command : commandList)
    {
      if (!command.canUndo())
      {
        return false;
      }
    }

    return true;
  }

  /**
   * Calls {@link Command#undo} for each command in the list, in reverse order.
   */
  @Override
  public void undo() 
  {
    for (ListIterator commands = commandList.listIterator(commandList.size()); commands.hasPrevious(); ) 
    {
      try
      {
        Command command = commands.previous();
        command.undo();
      }
      catch (RuntimeException exception)
      {
        // Skip over the command that threw the exception.
        //
        commands.next();

        try
        {
          // Iterate forward over the undone commands to redo them.
          //
          while (commands.hasNext())
          {
            Command command = commands.next();
            command.redo();
          }
        }
        catch (RuntimeException nestedException)
        {
          CommonPlugin.INSTANCE.log
            (new WrappedException
              (CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), nestedException).fillInStackTrace());
        }


        throw exception;
      }
    }
  }

  /**
   * Calls {@link Command#redo} for each command in the list.
   */
  public void redo() 
  {
    for (ListIterator commands = commandList.listIterator(); commands.hasNext(); ) 
    {
      try
      {
        Command command = commands.next();
        command.redo();
      }
      catch (RuntimeException exception)
      {
        // Skip over the command that threw the exception.
        //
        commands.previous();

        try
        {
          // Iterate back over the executed commands to undo them.
          //
          while (commands.hasPrevious())
          {
            Command command = commands.previous();
            command.undo();
          }
        }
        catch (RuntimeException nestedException)
        {
          CommonPlugin.INSTANCE.log
            (new WrappedException
              (CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), nestedException).fillInStackTrace());
        }

        throw exception;
      }
    }
  }

  /**
   * Determines the result by composing the results of the commands in the list;
   * this is affected by the setting of {@link #resultIndex}.
   * @return the result.
   */
  @Override
  public Collection getResult()
  {
    if (commandList.isEmpty())
    {
      return Collections.EMPTY_LIST;
    }
    else if (resultIndex == LAST_COMMAND_ALL)
    {
      return commandList.get(commandList.size() - 1).getResult();
    }
    else if (resultIndex == MERGE_COMMAND_ALL)
    {
      return getMergedResultCollection();
    }
    else if (resultIndex < commandList.size())
    {
      return commandList.get(resultIndex).getResult();
    }
    else
    {
      return Collections.EMPTY_LIST;
    }
  }

  /**
   * Returns the merged collection of all command results.
   * @return the merged collection of all command results.
   */
  protected Collection getMergedResultCollection()
  {
    Collection result = new ArrayList();

    for (Command command : commandList)
    {
      result.addAll(command.getResult());
    }

    return result;
  }


  /**
   * Determines the affected objects by composing the affected objects of the commands in the list;
   * this is affected by the setting of {@link #resultIndex}.
   * @return the affected objects.
   */
  @Override
  public Collection getAffectedObjects()
  {
    if (commandList.isEmpty())
    {
      return Collections.EMPTY_LIST;
    }
    else if (resultIndex == LAST_COMMAND_ALL)
    {
      return commandList.get(commandList.size() - 1).getAffectedObjects();
    }
    else if (resultIndex == MERGE_COMMAND_ALL)
    {
      return getMergedAffectedObjectsCollection();
    }
    else if (resultIndex < commandList.size())
    {
      return commandList.get(resultIndex).getAffectedObjects();
    }
    else
    {
      return Collections.EMPTY_LIST;
    }
  }

  /**
   * Returns the merged collection of all command affected objects.
   * @return the merged collection of all command affected objects.
   */
  protected Collection getMergedAffectedObjectsCollection()
  {
    Collection result = new ArrayList();

    for (Command command : commandList)
    {
      result.addAll(command.getAffectedObjects());
    }

    return result;
  }

  /**
   * Determines the label by composing the labels of the commands in the list;
   * this is affected by the setting of {@link #resultIndex}.
   * @return the label.
   */
  @Override
  public String getLabel()
  {
    if (label != null)
    {
      return label;
    } 
    else if (commandList.isEmpty())
    {
      return CommonPlugin.INSTANCE.getString("_UI_CompoundCommand_label");
    }
    else if (resultIndex == LAST_COMMAND_ALL || resultIndex == MERGE_COMMAND_ALL)
    {
      return commandList.get(commandList.size() - 1).getLabel();
    }
    else if (resultIndex < commandList.size())
    {
      return commandList.get(resultIndex).getLabel();
    }
    else
    {
      return CommonPlugin.INSTANCE.getString("_UI_CompoundCommand_label");
    }
  }

  /**
   * Determines the description by composing the descriptions of the commands in the list;
   * this is affected by the setting of {@link #resultIndex}.
   * @return the description.
   */
  @Override
  public String getDescription()
  {
    if (description != null)
    {
      return description;
    } 
    else if (commandList.isEmpty())
    {
      return CommonPlugin.INSTANCE.getString("_UI_CompoundCommand_description");
    }
    else if (resultIndex == LAST_COMMAND_ALL || resultIndex == MERGE_COMMAND_ALL)
    {
      return commandList.get(commandList.size() - 1).getDescription();
    }
    else if (resultIndex < commandList.size())
    {
      return commandList.get(resultIndex).getDescription();
    }
    else
    {
      return CommonPlugin.INSTANCE.getString("_UI_CompoundCommand_description");
    }
  }

  /**
   * Adds a command to this compound command's list of commands.
   * @param command the command to append.
   */
  public void append(Command command) 
  {
    if (isPrepared)
    {
      throw new IllegalStateException("The command is already prepared");
    }

    if (command != null)
    {
      commandList.add(command);
    }
  }

  /**
   * 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 CommandBase
   *   {
   *     protected Command subcommand;
   *
   *     //...
   *
   *     public void execute()
   *     {
   *       // ...
   *       Compound subcommands = new CompoundCommand();
   *       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();
   *       }
   *     }
   *   }
   * 
* Another use is in an execute override of compound command itself: *
   *   class MyCommand extends CompoundCommand
   *   {
   *     public void execute()
   *     {
   *       // ...
   *       appendAndExecute(new AddCommand(...));
   *       if (condition) appendAndExecute(new AddCommand(...));
   *     }
   *   }
   * 
* Note that appending commands will modify what getResult and getAffectedObjects return, * so you may want to set the resultIndex flag. * @param command the command. * @return whether the command was successfully executed and appended. */ public boolean appendAndExecute(Command command) { if (command != null) { if (!isPrepared) { if (commandList.isEmpty()) { isPrepared = true; isExecutable = true; } else { isExecutable = prepare(); isPrepared = true; if (isExecutable) { execute(); } } } if (command.canExecute()) { try { command.execute(); commandList.add(command); return true; } catch (RuntimeException exception) { CommonPlugin.INSTANCE.log (new WrappedException (CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), exception).fillInStackTrace()); } } command.dispose(); } return false; } /** * Adds a command to this compound command's the list of commands and returns true, * if command.{@link org.eclipse.emf.common.command.Command#canExecute() canExecute()} returns true; * otherwise, it simply calls command.{@link org.eclipse.emf.common.command.Command#dispose() dispose()} * and returns false. * @param command the command. * @return whether the command was executed and appended. */ public boolean appendIfCanExecute(Command command) { if (command == null) { return false; } else if (command.canExecute()) { commandList.add(command); return true; } else { command.dispose(); return false; } } /** * Calls {@link Command#dispose} for each command in the list. */ @Override public void dispose() { for (Command command : commandList) { command.dispose(); } } /** * Returns one of three things: * {@link org.eclipse.emf.common.command.UnexecutableCommand#INSTANCE}, if there are no commands, * the one command, if there is exactly one command, * or this, if there are multiple commands; * this command is {@link #dispose}d in the first two cases. * You should only unwrap a compound command if you created it for that purpose, e.g., *
   *   CompoundCommand subcommands = new CompoundCommand();
   *   subcommands.append(x);
   *   if (condition) subcommands.append(y);
   *   Command result = subcommands.unwrap();
   * 
* is a good way to create an efficient accumulated result. * @return the unwrapped command. */ public Command unwrap() { switch (commandList.size()) { case 0: { dispose(); return UnexecutableCommand.INSTANCE; } case 1: { Command result = commandList.remove(0); dispose(); return result; } default: { return this; } } } @Override public String toString() { StringBuffer result = new StringBuffer(super.toString()); result.append(" (commandList: #" + commandList.size() + ")"); result.append(" (resultIndex: " + resultIndex + ")"); return result.toString(); } }