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: CompoundCommand.java,v 1.6 2007/06/12 20:56:17 emerks Exp $
016     */
017    package org.eclipse.emf.common.command;
018    
019    
020    import java.util.ArrayList;
021    import java.util.Collection;
022    import java.util.Collections;
023    import java.util.List;
024    import java.util.ListIterator;
025    
026    import org.eclipse.emf.common.CommonPlugin;
027    import org.eclipse.emf.common.util.WrappedException;
028    
029    
030    /**
031     * A command that comprises a sequence of subcommands.
032     * Derived classes can control the way results are accumulated from the individual commands;
033     * the default behaviour is to return the result of the last command.
034     */
035    public class CompoundCommand extends AbstractCommand 
036    {
037      /**
038       * The list of subcommands.
039       */
040      protected List<Command> commandList;
041    
042      /**
043       * When {@link #resultIndex} is set to this, 
044       * {@link #getResult} and {@link #getAffectedObjects} are delegated to the last command, if any, in the list.
045       */
046      public static final int LAST_COMMAND_ALL = Integer.MIN_VALUE;
047    
048      /**
049       * When {@link #resultIndex} is set to this, 
050       * {@link #getResult} and {@link #getAffectedObjects}
051       * are set to the result of merging the corresponding collection of each command in the list.
052       */
053      public static final int MERGE_COMMAND_ALL = Integer.MIN_VALUE - 1;
054    
055      /**
056       * The index of the command whose result and affected objects are forwarded.
057       * Negative values have special meaning, as defined by the static constants.
058       * A value of -1 indicates that the last command in the list should be used.
059       * We could have more special behaviours implemented for other negative values.
060       */
061      protected int resultIndex = MERGE_COMMAND_ALL;
062    
063      /**
064       * Creates an empty instance.
065       */
066      public CompoundCommand()
067      {
068        super();
069        commandList = new ArrayList<Command>();
070      }
071    
072      /**
073       * Creates an instance with the given label.
074       * @param label the label.
075       */
076      public CompoundCommand(String label) 
077      {
078        super(label);
079        commandList = new ArrayList<Command>();
080      }
081      
082      /**
083       * Creates an instance with the given label and description.
084       * @param label the label.
085       * @param description the description.
086       */
087      public CompoundCommand(String label, String description) 
088      {
089        super(label, description);
090        commandList = new ArrayList<Command>();
091      }
092      
093      /**
094       * Creates an instance with the given list.
095       * @param commandList the list of commands.
096       */
097      public CompoundCommand(List<Command> commandList)
098      {
099        super();
100        this.commandList = commandList;
101      }
102    
103      /**
104       * Creates instance with the given label and list.
105       * @param label the label.
106       * @param commandList the list of commands.
107       */
108      public CompoundCommand(String label, List<Command> commandList)
109      {
110        super(label);
111        this.commandList = commandList;
112      }
113    
114      /**
115       * Creates an instance with the given label, description, and list.
116       * @param label the label.
117       * @param description the description.
118       * @param commandList the list of commands.
119       */
120      public CompoundCommand(String label, String description, List<Command> commandList)
121      {
122        super(label, description);
123        this.commandList = commandList;
124      }
125    
126      /**
127       * Creates an empty instance with the given result index.
128       * @param resultIndex the {@link #resultIndex}.
129       */
130      public CompoundCommand(int resultIndex)
131      {
132        super();
133        this.resultIndex = resultIndex;
134        commandList = new ArrayList<Command>();
135      }
136    
137      /**
138       * Creates an instance with the given result index and label.
139       * @param resultIndex the {@link #resultIndex}.
140       * @param label the label.
141       */
142      public CompoundCommand(int resultIndex, String label) 
143      {
144        super(label);
145        this.resultIndex = resultIndex;
146        commandList = new ArrayList<Command>();
147      }
148      
149      /**
150       * Creates an instance with the given result index, label, and description.
151       * @param resultIndex the {@link #resultIndex}.
152       * @param label the label.
153       * @param description the description.
154       */
155      public CompoundCommand(int resultIndex, String label, String description) 
156      {
157        super(label, description);
158        this.resultIndex = resultIndex;
159        commandList = new ArrayList<Command>();
160      }
161      
162      /**
163       * Creates an instance with the given result index and list.
164       * @param resultIndex the {@link #resultIndex}.
165       * @param commandList the list of commands.
166       */
167      public CompoundCommand(int resultIndex, List<Command> commandList)
168      {
169        super();
170        this.resultIndex = resultIndex;
171        this.commandList = commandList;
172      }
173    
174      /**
175       * Creates an instance with the given resultIndex, label, and list.
176       * @param resultIndex the {@link #resultIndex}.
177       * @param label the label.
178       * @param commandList the list of commands.
179       */
180      public CompoundCommand(int resultIndex, String label, List<Command> commandList)
181      {
182        super(label);
183        this.resultIndex = resultIndex;
184        this.commandList = commandList;
185      }
186    
187      /**
188       * Creates an instance with the given result index, label, description, and list.
189       * @param resultIndex the {@link #resultIndex}.
190       * @param label the label.
191       * @param description the description.
192       * @param commandList the list of commands.
193       */
194      public CompoundCommand(int resultIndex, String label, String description, List<Command> commandList)
195      {
196        super(label, description);
197        this.resultIndex = resultIndex;
198        this.commandList = commandList;
199      }
200    
201      /**
202       * Returns whether there are commands in the list.
203       * @return whether there are commands in the list.
204       */
205      public boolean isEmpty()
206      {
207        return commandList.isEmpty(); 
208      }
209    
210      /**
211       * Returns an unmodifiable view of the commands in the list.
212       * @return an unmodifiable view of the commands in the list.
213       */
214      public List<Command> getCommandList()
215      {
216        return Collections.unmodifiableList(commandList);
217      }
218    
219      /**
220       * Returns the index of the command whose result and affected objects are forwarded.
221       * Negative values have special meaning, as defined by the static constants.
222       * @return the index of the command whose result and affected objects are forwarded.
223       * @see #LAST_COMMAND_ALL
224       * @see #MERGE_COMMAND_ALL
225       */
226      public int getResultIndex()
227      {
228        return resultIndex;
229      }
230    
231      /**
232       * Returns whether all the commands can execute so that {@link #isExecutable} can be cached.
233       * An empty command list causes <code>false</code> to be returned.
234       * @return whether all the commands can execute.
235       */
236      @Override
237      protected boolean prepare()
238      {
239        if (commandList.isEmpty())
240        {
241          return false;
242        }
243        else
244        {
245          for (Command command : commandList)
246          {
247            if (!command.canExecute())
248            {
249              return false;
250            }
251          }
252    
253          return true;
254        }
255      }
256    
257      /**
258       * Calls {@link Command#execute} for each command in the list.
259       */
260      public void execute() 
261      {
262        for (ListIterator<Command> commands = commandList.listIterator(); commands.hasNext(); ) 
263        {
264          try
265          {
266            Command command = commands.next();
267            command.execute();
268          }
269          catch (RuntimeException exception)
270          {
271            // Skip over the command that threw the exception.
272            //
273            commands.previous();
274    
275            try
276            {
277              // Iterate back over the executed commands to undo them.
278              //
279              while (commands.hasPrevious())
280              {
281                Command command = commands.previous();
282                if (command.canUndo())
283                {
284                  command.undo();
285                }
286                else
287                {
288                  break;
289                }
290              }
291            }
292            catch (RuntimeException nestedException)
293            {
294              CommonPlugin.INSTANCE.log
295                (new WrappedException
296                  (CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), nestedException).fillInStackTrace());
297            }
298    
299            throw exception;
300          }
301        }
302      }
303    
304      /**
305       * Returns <code>false</code> if any of the commands return <code>false</code> for {@link Command#canUndo}.
306       * @return <code>false</code> if any of the commands return <code>false</code> for <code>canUndo</code>.
307       */
308      @Override
309      public boolean canUndo() 
310      {
311        for (Command command : commandList)
312        {
313          if (!command.canUndo())
314          {
315            return false;
316          }
317        }
318    
319        return true;
320      }
321    
322      /**
323       * Calls {@link Command#undo} for each command in the list, in reverse order.
324       */
325      @Override
326      public void undo() 
327      {
328        for (ListIterator<Command> commands = commandList.listIterator(commandList.size()); commands.hasPrevious(); ) 
329        {
330          try
331          {
332            Command command = commands.previous();
333            command.undo();
334          }
335          catch (RuntimeException exception)
336          {
337            // Skip over the command that threw the exception.
338            //
339            commands.next();
340    
341            try
342            {
343              // Iterate forward over the undone commands to redo them.
344              //
345              while (commands.hasNext())
346              {
347                Command command = commands.next();
348                command.redo();
349              }
350            }
351            catch (RuntimeException nestedException)
352            {
353              CommonPlugin.INSTANCE.log
354                (new WrappedException
355                  (CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), nestedException).fillInStackTrace());
356            }
357    
358    
359            throw exception;
360          }
361        }
362      }
363    
364      /**
365       * Calls {@link Command#redo} for each command in the list.
366       */
367      public void redo() 
368      {
369        for (ListIterator<Command> commands = commandList.listIterator(); commands.hasNext(); ) 
370        {
371          try
372          {
373            Command command = commands.next();
374            command.redo();
375          }
376          catch (RuntimeException exception)
377          {
378            // Skip over the command that threw the exception.
379            //
380            commands.previous();
381    
382            try
383            {
384              // Iterate back over the executed commands to undo them.
385              //
386              while (commands.hasPrevious())
387              {
388                Command command = commands.previous();
389                command.undo();
390              }
391            }
392            catch (RuntimeException nestedException)
393            {
394              CommonPlugin.INSTANCE.log
395                (new WrappedException
396                  (CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), nestedException).fillInStackTrace());
397            }
398    
399            throw exception;
400          }
401        }
402      }
403    
404      /**
405       * Determines the result by composing the results of the commands in the list;
406       * this is affected by the setting of {@link #resultIndex}.
407       * @return the result.
408       */
409      @Override
410      public Collection<?> getResult()
411      {
412        if (commandList.isEmpty())
413        {
414          return Collections.EMPTY_LIST;
415        }
416        else if (resultIndex == LAST_COMMAND_ALL)
417        {
418          return commandList.get(commandList.size() - 1).getResult();
419        }
420        else if (resultIndex == MERGE_COMMAND_ALL)
421        {
422          return getMergedResultCollection();
423        }
424        else if (resultIndex < commandList.size())
425        {
426          return commandList.get(resultIndex).getResult();
427        }
428        else
429        {
430          return Collections.EMPTY_LIST;
431        }
432      }
433    
434      /**
435       * Returns the merged collection of all command results.
436       * @return the merged collection of all command results.
437       */
438      protected Collection<?> getMergedResultCollection()
439      {
440        Collection<Object> result = new ArrayList<Object>();
441    
442        for (Command command : commandList)
443        {
444          result.addAll(command.getResult());
445        }
446    
447        return result;
448      }
449    
450    
451      /**
452       * Determines the affected objects by composing the affected objects of the commands in the list;
453       * this is affected by the setting of {@link #resultIndex}.
454       * @return the affected objects.
455       */
456      @Override
457      public Collection<?> getAffectedObjects()
458      {
459        if (commandList.isEmpty())
460        {
461          return Collections.EMPTY_LIST;
462        }
463        else if (resultIndex == LAST_COMMAND_ALL)
464        {
465          return commandList.get(commandList.size() - 1).getAffectedObjects();
466        }
467        else if (resultIndex == MERGE_COMMAND_ALL)
468        {
469          return getMergedAffectedObjectsCollection();
470        }
471        else if (resultIndex < commandList.size())
472        {
473          return commandList.get(resultIndex).getAffectedObjects();
474        }
475        else
476        {
477          return Collections.EMPTY_LIST;
478        }
479      }
480    
481      /**
482       * Returns the merged collection of all command affected objects.
483       * @return the merged collection of all command affected objects.
484       */
485      protected Collection<?> getMergedAffectedObjectsCollection()
486      {
487        Collection<Object> result = new ArrayList<Object>();
488    
489        for (Command command : commandList)
490        {
491          result.addAll(command.getAffectedObjects());
492        }
493    
494        return result;
495      }
496    
497      /**
498       * Determines the label by composing the labels of the commands in the list;
499       * this is affected by the setting of {@link #resultIndex}.
500       * @return the label.
501       */
502      @Override
503      public String getLabel()
504      {
505        if (label != null)
506        {
507          return label;
508        } 
509        else if (commandList.isEmpty())
510        {
511          return CommonPlugin.INSTANCE.getString("_UI_CompoundCommand_label");
512        }
513        else if (resultIndex == LAST_COMMAND_ALL || resultIndex == MERGE_COMMAND_ALL)
514        {
515          return commandList.get(commandList.size() - 1).getLabel();
516        }
517        else if (resultIndex < commandList.size())
518        {
519          return commandList.get(resultIndex).getLabel();
520        }
521        else
522        {
523          return CommonPlugin.INSTANCE.getString("_UI_CompoundCommand_label");
524        }
525      }
526    
527      /**
528       * Determines the description by composing the descriptions of the commands in the list;
529       * this is affected by the setting of {@link #resultIndex}.
530       * @return the description.
531       */
532      @Override
533      public String getDescription()
534      {
535        if (description != null)
536        {
537          return description;
538        } 
539        else if (commandList.isEmpty())
540        {
541          return CommonPlugin.INSTANCE.getString("_UI_CompoundCommand_description");
542        }
543        else if (resultIndex == LAST_COMMAND_ALL || resultIndex == MERGE_COMMAND_ALL)
544        {
545          return commandList.get(commandList.size() - 1).getDescription();
546        }
547        else if (resultIndex < commandList.size())
548        {
549          return commandList.get(resultIndex).getDescription();
550        }
551        else
552        {
553          return CommonPlugin.INSTANCE.getString("_UI_CompoundCommand_description");
554        }
555      }
556    
557      /**
558       * Adds a command to this compound command's list of commands.
559       * @param command the command to append.
560       */
561      public void append(Command command) 
562      {
563        if (isPrepared)
564        {
565          throw new IllegalStateException("The command is already prepared");
566        }
567    
568        if (command != null)
569        {
570          commandList.add(command);
571        }
572      }
573    
574      /**
575       * Checks if the command can execute; 
576       * if so, it is executed, appended to the list, and true is returned,
577       * if not, it is just disposed and false is returned.
578       * A typical use for this is to execute commands created during the execution of another command, e.g.,
579       * <pre>
580       *   class MyCommand extends CommandBase
581       *   {
582       *     protected Command subcommand;
583       *
584       *     //...
585       *
586       *     public void execute()
587       *     {
588       *       // ...
589       *       Compound subcommands = new CompoundCommand();
590       *       subcommands.appendAndExecute(new AddCommand(...));
591       *       if (condition) subcommands.appendAndExecute(new AddCommand(...));
592       *       subcommand = subcommands.unwrap();
593       *     }
594       *
595       *     public void undo()
596       *     {
597       *       // ...
598       *       subcommand.undo();
599       *     }
600       *
601       *     public void redo()
602       *     {
603       *       // ...
604       *       subcommand.redo();
605       *     }
606       *
607       *     public void dispose()
608       *     {
609       *       // ...
610       *       if (subcommand != null)
611       *      {
612       *         subcommand.dispose();
613       *       }
614       *     }
615       *   }
616       * </pre>
617       * Another use is in an execute override of compound command itself:
618       * <pre>
619       *   class MyCommand extends CompoundCommand
620       *   {
621       *     public void execute()
622       *     {
623       *       // ...
624       *       appendAndExecute(new AddCommand(...));
625       *       if (condition) appendAndExecute(new AddCommand(...));
626       *     }
627       *   }
628       * </pre>
629       * Note that appending commands will modify what getResult and getAffectedObjects return,
630       * so you may want to set the resultIndex flag.
631       * @param command the command.
632       * @return whether the command was successfully executed and appended.
633       */
634      public boolean appendAndExecute(Command command)
635      {
636        if (command != null)
637        {
638          if (!isPrepared)
639          {
640            if (commandList.isEmpty())
641            {
642              isPrepared = true;
643              isExecutable = true;
644            }
645            else
646            {
647              isExecutable = prepare();
648              isPrepared = true;
649              if (isExecutable)
650              {
651                execute();
652              }
653            }
654          }
655    
656          if (command.canExecute())
657          {
658            try
659            {
660              command.execute();
661              commandList.add(command);
662              return true;
663            }
664            catch (RuntimeException exception)
665            {
666              CommonPlugin.INSTANCE.log
667                (new WrappedException
668                  (CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), exception).fillInStackTrace());
669            }
670          }
671    
672          command.dispose();
673        }
674    
675        return false;
676      }
677    
678      /**
679       * Adds a command to this compound command's the list of commands and returns <code>true</code>, 
680       * if <code>command.{@link org.eclipse.emf.common.command.Command#canExecute() canExecute()}</code> returns true;
681       * otherwise, it simply calls <code>command.{@link org.eclipse.emf.common.command.Command#dispose() dispose()}</code> 
682       * and returns <code>false</code>.
683       * @param command the command.
684       * @return whether the command was executed and appended.
685       */
686      public boolean appendIfCanExecute(Command command) 
687      {
688        if (command == null)
689        {
690          return false;
691        }
692        else if (command.canExecute())
693        {
694          commandList.add(command);
695          return true;
696        }
697        else
698        {
699          command.dispose();
700          return false;
701        }
702      }
703    
704      /**
705       * Calls {@link Command#dispose} for each command in the list.
706       */
707      @Override
708      public void dispose()
709      {
710        for (Command command : commandList)
711        {
712          command.dispose();
713        }
714      }
715    
716      /**
717       * Returns one of three things: 
718       * {@link org.eclipse.emf.common.command.UnexecutableCommand#INSTANCE}, if there are no commands,
719       * the one command, if there is exactly one command,
720       * or <code>this</code>, if there are multiple commands;
721       * this command is {@link #dispose}d in the first two cases.
722       * You should only unwrap a compound command if you created it for that purpose, e.g.,
723       * <pre>
724       *   CompoundCommand subcommands = new CompoundCommand();
725       *   subcommands.append(x);
726       *   if (condition) subcommands.append(y);
727       *   Command result = subcommands.unwrap();
728       * </pre>
729       * is a good way to create an efficient accumulated result.
730       * @return the unwrapped command.
731       */
732      public Command unwrap()
733      {
734        switch (commandList.size())
735        {
736          case 0:
737          {
738            dispose();
739            return UnexecutableCommand.INSTANCE;
740          }
741          case 1:
742          {
743            Command result = commandList.remove(0);
744            dispose();
745            return result;
746          }
747          default:
748          {
749            return this;
750          }
751        }
752      }
753    
754      @Override
755      public String toString()
756      {
757        StringBuffer result = new StringBuffer(super.toString());
758        result.append(" (commandList: #" + commandList.size() + ")");
759        result.append(" (resultIndex: " + resultIndex + ")");
760    
761        return result.toString();
762      }
763    }