001    /**
002     * <copyright> 
003     *
004     * Copyright (c) 2002-2006 IBM Corporation and others.
005     * All rights reserved.   This program and the accompanying materials
006     * are made available under the terms of the Eclipse Public License v1.0
007     * which accompanies this distribution, and is available at
008     * http://www.eclipse.org/legal/epl-v10.html
009     * 
010     * Contributors: 
011     *   IBM - Initial API and implementation
012     *
013     * </copyright>
014     *
015     * $Id: StrictCompoundCommand.java,v 1.4 2006/12/05 20:19:53 emerks Exp $
016     */
017    package org.eclipse.emf.common.command;
018    
019    
020    import java.util.List;
021    import java.util.ListIterator;
022    
023    import org.eclipse.emf.common.CommonPlugin;
024    import org.eclipse.emf.common.util.WrappedException;
025    
026    
027    /**
028     * A composite command which assumes that later commands in the list 
029     * may depend on the results and side-effects of earlier commands in the list.
030     * Because of this, it must implement {@link Command#canExecute} more carefully,
031     * i.e., in order to determine canExecute for the composite, it doesn't simply test each command.
032     * It tests the first command to see if it can execute;
033     * then, if there is another command in the list, it checks if the first command can undo and then goes ahead and executes it!
034     * This process is repeated until the last command that is not followed by another, which then determines the final result.
035     * (For efficiency, when this processing gets to the last command, that command is tested for canUndo too and that result is cached.)
036     * All the commands that have been executed are then undone, if {@link #isPessimistic} is <code>true</code>; 
037     * by default it's <code>false</code>.
038     *
039     * <p>
040     * It is important for all but the last command to have no visible side-effect!
041     * Multiple commands with visible side-effects must be composed into a single command using just a {@link CompoundCommand}
042     * and that composite could be the last command of a strict composite.
043     *
044     * <p>
045     * Here is an example of how this can be used in conjunction with a {@link CommandWrapper}.
046     * <pre>
047     *   Command strictCompoundCommand = new StrictCompoundCommand();
048     *   Command copyCommand = new CopyCommand(...);
049     *   strictCompoundCommand.add(copyCommand);
050     *
051     *   Command addCommand =
052     *     new CommandWrapper()
053     *     {
054     *       public Command createCommand()
055     *       {
056     *         new AddCommand(parent, copyCommand.getResult()); 
057     *       }
058     *     };
059     *   strictCompoundCommand.append(addCommand);
060     * </pre>
061     * Here the add command won't know which command to create until it has the result of the copy command.
062     * The proxy makes sure the creation of the add command is deferred and the strict composite ensures that execution dependencies are met.
063     */
064    public class StrictCompoundCommand extends CompoundCommand 
065    {
066      /**
067       * The result for {@link Command#canUndo}.
068       */
069      protected boolean isUndoable;
070    
071      /**
072       * Whether commands that have been tentatively executed need to be undone.
073       */
074      protected boolean isPessimistic;
075    
076      /**
077       * Remember to call redo instead of execute for any command at or before this index in the list.
078       */
079      protected int rightMostExecutedCommandIndex = -1;
080    
081      /**
082       * Creates an empty instance.
083       */
084      public StrictCompoundCommand()
085      {
086        super();
087        resultIndex = LAST_COMMAND_ALL;
088      }
089    
090      /**
091       * Creates an instance with the given label.
092       * @param label the label.
093       */
094      public StrictCompoundCommand(String label)
095      {
096        super(label);
097        resultIndex = LAST_COMMAND_ALL;
098      }
099    
100      /**
101       * Creates an instance with the given label and description.
102       * @param label the label.
103       * @param description the description.
104       */
105      public StrictCompoundCommand(String label, String description)
106      {
107        super(label, description);
108        resultIndex = LAST_COMMAND_ALL;
109      }
110    
111      /**
112       * Creates an instance with the given command list.
113       * @param commandList the list of commands.
114       */
115      public StrictCompoundCommand(List<Command> commandList)
116      {     
117        super(commandList);
118        resultIndex = LAST_COMMAND_ALL;
119      }
120    
121      /**
122       * Creates an instance with the given label and command list.
123       * @param label the label.
124       * @param commandList the list of commands.
125       */
126      public StrictCompoundCommand(String label, List<Command> commandList)
127      {     
128        super(label, commandList);
129        resultIndex = LAST_COMMAND_ALL;
130      }
131    
132      /**
133       * Creates an instance with the given label, description, and command list.
134       * @param label the label.
135       * @param description the description.
136       * @param commandList the list of commands.
137       */
138      public StrictCompoundCommand(String label, String description, List<Command> commandList)
139      {     
140        super(label, description, commandList);
141        resultIndex = LAST_COMMAND_ALL;
142      }
143    
144      /**
145       * Returns <code>false</code> if any command on the list returns <code>false</code> for {@link Command#canExecute},
146       * or if some command before the last one can't be undone and hence we can't test all the commands for executability.
147       * @return whether the command can execute.
148       */
149      @Override
150      protected boolean prepare() 
151      {
152        // Go through the commands of the list.
153        //
154        ListIterator<Command> commands = commandList.listIterator(); 
155    
156        // If there are some...
157        //
158        if (commands.hasNext())
159        {
160          boolean result = true;
161    
162          // The termination guard is in the body.
163          //
164          for (;;)
165          {
166            Command command = commands.next();
167            if (command.canExecute())
168            {
169              if (commands.hasNext())
170              {
171                if (command.canUndo())
172                {
173                  try
174                  {
175                    if (commands.previousIndex() <= rightMostExecutedCommandIndex)
176                    {
177                      command.redo();
178                    }
179                    else
180                    {
181                      ++rightMostExecutedCommandIndex;
182                      command.execute();
183                    }
184                  }
185                  catch (RuntimeException exception)
186                  {
187                    CommonPlugin.INSTANCE.log
188                      (new WrappedException
189                        (CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), exception).fillInStackTrace());
190    
191                    result = false;
192                    break;
193                  }
194                }
195                else
196                {
197                  // We can't undo it, so we'd better give up.
198                  //
199                  result = false;
200                  break;
201                }
202              }
203              else
204              {
205                // Now is the best time to record isUndoable because later we would have to do all the executes again!
206                // This makes canUndo very simple!
207                //
208                isUndoable = command.canUndo();
209                break;
210              }
211            }
212            else
213            {
214              // If we can't execute this one, we just can't do it at all.
215              //
216              result = false;
217              break;
218            }
219          }
220    
221          // If we are pessimistic, then we need to undo all the commands that we have executed so far.
222          //
223          if (isPessimistic)
224          {
225            // The most recently processed command will never have been executed.
226            //
227            commands.previous();
228      
229            // We want to unroll all the effects of the previous commands.
230            //
231            while (commands.hasPrevious()) 
232            {
233              Command command = commands.previous();
234              command.undo();
235            }
236          }
237    
238          return result;
239        }
240        else
241        {
242          isUndoable = false;
243          return false;
244        }
245      }
246    
247      /**
248       * Calls {@link Command#execute} for each command in the list, 
249       * but makes sure to call redo for any commands that were previously executed to compute canExecute.
250       * In the case that {@link #isPessimistic} is false, only the last command will be executed
251       * since the others will have been executed but not undone during {@link #prepare}.
252       */
253      @Override
254      public void execute() 
255      {
256        if (isPessimistic)
257        {
258          for (ListIterator<Command> commands = commandList.listIterator(); commands.hasNext(); ) 
259          {
260            try
261            {
262              // Either execute or redo the command, as appropriate.
263              //
264              Command command = commands.next();
265              if (commands.previousIndex() <= rightMostExecutedCommandIndex)
266              {
267                command.redo();
268              }
269              else
270              {
271                command.execute();
272              }
273            }
274            catch (RuntimeException exception)
275            {
276              // Skip over the command that threw the exception.
277              //
278              commands.previous();
279    
280              // Iterate back over the executed commands to undo them.
281              //
282              while (commands.hasPrevious())
283              {
284                commands.previous();
285                Command command = commands.previous();
286                if (command.canUndo())
287                {
288                  command.undo();
289                }
290                else
291                {
292                  break;
293                }
294              }
295    
296              throw exception;
297            }
298          }
299        }
300        else if (!commandList.isEmpty())
301        {
302          Command command = commandList.get(commandList.size() - 1);
303          command.execute();
304        }
305      }
306    
307      /**
308       * Calls {@link Command#undo} for each command in the list.
309       * In the case that {@link #isPessimistic} is false, only the last command will be undone
310       * since the others will have been executed and not undo during {@link #prepare}.
311       */
312      @Override
313      public void undo() 
314      {
315        if (isPessimistic)
316        {
317          super.undo();
318        }
319        else if (!commandList.isEmpty())
320        {
321          Command command = commandList.get(commandList.size() - 1);
322          command.undo();
323        }
324      }
325    
326      /**
327       * Calls {@link Command#redo} for each command in the list.
328       * In the case that {@link #isPessimistic} is false, only the last command will be redone
329       * since the others will have been executed and not undo during {@link #prepare}.
330       */
331      @Override
332      public void redo() 
333      {
334        if (isPessimistic)
335        {
336          super.redo();
337        }
338        else if (!commandList.isEmpty())
339        {
340          Command command = commandList.get(commandList.size() - 1);
341          command.redo();
342        }
343      }
344    
345      /**
346       * Checks if the command can execute; 
347       * if so, it is executed, appended to the list, and <code>true</code> is returned,
348       * if not, it is just disposed and <code>false</code> is returned.
349       * A typical use for this is to execute commands created during the execution of another command, e.g.,
350       * <pre>
351       *   class MyCommand extends AbstractCommand
352       *   {
353       *     protected Command subcommand;
354       *
355       *     //...
356       *
357       *     public void execute()
358       *     {
359       *       // ...
360       *       StrictCompoundCommand subcommands = new StrictCompoundCommand();
361       *       subcommands.appendAndExecute(new AddCommand(...));
362       *       if (condition) subcommands.appendAndExecute(new AddCommand(...));
363       *       subcommand = subcommands.unwrap();
364       *     }
365       *
366       *     public void undo()
367       *     {
368       *       // ...
369       *       subcommand.undo();
370       *     }
371       *
372       *     public void redo()
373       *     {
374       *       // ...
375       *       subcommand.redo();
376       *     }
377       *
378       *     public void dispose()
379       *     {
380       *       // ...
381       *       if (subcommand != null)
382       *      {
383       *         subcommand.dispose();
384       *       }
385       *     }
386       *   }
387       * </pre>
388       * @return whether the command was successfully executed and appended.
389       */
390      @Override
391      public boolean appendAndExecute(Command command)
392      {
393        if (command != null)
394        {
395          if (!isPrepared)
396          {
397            if (commandList.isEmpty())
398            {
399              isPrepared = true;
400              isExecutable = true;
401            }
402            else
403            {
404              isExecutable = prepare();
405              isPrepared = true;
406              isPessimistic = true;
407              if (isExecutable)
408              {
409                execute();
410              }
411            }
412          }
413      
414          if (command.canExecute())
415          {
416            try
417            {
418              command.execute();
419              commandList.add(command);
420              ++rightMostExecutedCommandIndex;
421              isUndoable = command.canUndo();
422              return true;
423            }
424            catch (RuntimeException exception)
425            {
426              CommonPlugin.INSTANCE.log
427                (new WrappedException
428                  (CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), exception).fillInStackTrace());
429            }
430          }
431      
432          command.dispose();
433        }
434    
435        return false;
436      }
437    
438      /*
439       * Javadoc copied from base class.
440       */
441      @Override
442      public String toString()
443      {
444        StringBuffer result = new StringBuffer(super.toString());
445        result.append(" (isUndoable: " + isUndoable + ")");
446        result.append(" (isPessimistic: " + isPessimistic + ")");
447        result.append(" (rightMostExecutedCommandIndex: " + rightMostExecutedCommandIndex + ")");
448    
449        return result.toString();
450      }
451    }