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 }