KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > emf > common > command > StrictCompoundCommand


1 /**
2  * <copyright>
3  *
4  * Copyright (c) 2002-2004 IBM Corporation and others.
5  * All rights reserved. This program and the accompanying materials
6  * are made available under the terms of the Eclipse Public License v1.0
7  * which accompanies this distribution, and is available at
8  * http://www.eclipse.org/legal/epl-v10.html
9  *
10  * Contributors:
11  * IBM - Initial API and implementation
12  *
13  * </copyright>
14  *
15  * $Id: StrictCompoundCommand.java,v 1.3 2005/06/08 05:44:08 nickb Exp $
16  */

17 package org.eclipse.emf.common.command;
18
19
20 import java.util.List JavaDoc;
21 import java.util.ListIterator JavaDoc;
22
23 import org.eclipse.emf.common.CommonPlugin;
24 import org.eclipse.emf.common.util.WrappedException;
25
26
27 /**
28  * A composite command which assumes that later commands in the list
29  * may depend on the results and side-effects of earlier commands in the list.
30  * Because of this, it must implement {@link Command#canExecute} more carefully,
31  * i.e., in order to determine canExecute for the composite, it doesn't simply test each command.
32  * It tests the first command to see if it can execute;
33  * then, if there is another command in the list, it checks if the first command can undo and then goes ahead and executes it!
34  * This process is repeated until the last command that is not followed by another, which then determines the final result.
35  * (For efficiency, when this processing gets to the last command, that command is tested for canUndo too and that result is cached.)
36  * All the commands that have been executed are then undone, if {@link #isPessimistic} is <code>true</code>;
37  * by default it's <code>false</code>.
38  *
39  * <p>
40  * It is important for all but the last command to have no visible side-effect!
41  * Multiple commands with visible side-effects must be composed into a single command using just a {@link CompoundCommand}
42  * and that composite could be the last command of a strict composite.
43  *
44  * <p>
45  * Here is an example of how this can be used in conjunction with a {@link CommandWrapper}.
46  * <pre>
47  * Command strictCompoundCommand = new StrictCompoundCommand();
48  * Command copyCommand = new CopyCommand(...);
49  * strictCompoundCommand.add(copyCommand);
50  *
51  * Command addCommand =
52  * new CommandWrapper()
53  * {
54  * public Command createCommand()
55  * {
56  * new AddCommand(parent, copyCommand.getResult());
57  * }
58  * };
59  * strictCompoundCommand.append(addCommand);
60  * </pre>
61  * Here the add command won't know which command to create until it has the result of the copy command.
62  * The proxy makes sure the creation of the add command is deferred and the strict composite ensures that execution dependencies are met.
63  */

64 public class StrictCompoundCommand extends CompoundCommand
65 {
66   /**
67    * The result for {@link Command#canUndo}.
68    */

69   protected boolean isUndoable;
70
71   /**
72    * Whether commands that have been tentatively executed need to be undone.
73    */

74   protected boolean isPessimistic;
75
76   /**
77    * Remember to call redo instead of execute for any command at or before this index in the list.
78    */

79   protected int rightMostExecutedCommandIndex = -1;
80
81   /**
82    * Creates an empty instance.
83    */

84   public StrictCompoundCommand()
85   {
86     super();
87     resultIndex = LAST_COMMAND_ALL;
88   }
89
90   /**
91    * Creates an instance with the given label.
92    * @param label the label.
93    */

94   public StrictCompoundCommand(String JavaDoc label)
95   {
96     super(label);
97     resultIndex = LAST_COMMAND_ALL;
98   }
99
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 JavaDoc label, String JavaDoc 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 JavaDoc 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 JavaDoc label, List JavaDoc 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 JavaDoc label, String JavaDoc description, List JavaDoc 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   protected boolean prepare()
150   {
151     // Go through the commands of the list.
152
//
153
ListIterator JavaDoc commands = commandList.listIterator();
154
155     // If there are some...
156
//
157
if (commands.hasNext())
158     {
159       boolean result = true;
160
161       // The termination guard is in the body.
162
//
163
for (;;)
164       {
165         Command command = (Command)commands.next();
166         if (command.canExecute())
167         {
168           if (commands.hasNext())
169           {
170             if (command.canUndo())
171             {
172               try
173               {
174                 if (commands.previousIndex() <= rightMostExecutedCommandIndex)
175                 {
176                   command.redo();
177                 }
178                 else
179                 {
180                   ++rightMostExecutedCommandIndex;
181                   command.execute();
182                 }
183               }
184               catch (RuntimeException JavaDoc exception)
185               {
186                 CommonPlugin.INSTANCE.log
187                   (new WrappedException
188                     (CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), exception).fillInStackTrace());
189
190                 result = false;
191                 break;
192               }
193             }
194             else
195             {
196               // We can't undo it, so we'd better give up.
197
//
198
result = false;
199               break;
200             }
201           }
202           else
203           {
204             // Now is the best time to record isUndoable because later we would have to do all the executes again!
205
// This makes canUndo very simple!
206
//
207
isUndoable = command.canUndo();
208             break;
209           }
210         }
211         else
212         {
213           // If we can't execute this one, we just can't do it at all.
214
//
215
result = false;
216           break;
217         }
218       }
219
220       // If we are pessimistic, then we need to undo all the commands that we have executed so far.
221
//
222
if (isPessimistic)
223       {
224         // The most recently processed command will never have been executed.
225
//
226
commands.previous();
227   
228         // We want to unroll all the effects of the previous commands.
229
//
230
while (commands.hasPrevious())
231         {
232           Command command = (Command)commands.previous();
233           command.undo();
234         }
235       }
236
237       return result;
238     }
239     else
240     {
241       isUndoable = false;
242       return false;
243     }
244   }
245
246   /**
247    * Calls {@link Command#execute} for each command in the list,
248    * but makes sure to call redo for any commands that were previously executed to compute canExecute.
249    * In the case that {@link #isPessimistic} is false, only the last command will be executed
250    * since the others will have been executed but not undone during {@link #prepare}.
251    */

252   public void execute()
253   {
254     if (isPessimistic)
255     {
256       for (ListIterator JavaDoc commands = commandList.listIterator(); commands.hasNext(); )
257       {
258         try
259         {
260           // Either execute or redo the command, as appropriate.
261
//
262
Command command = (Command)commands.next();
263           if (commands.previousIndex() <= rightMostExecutedCommandIndex)
264           {
265             command.redo();
266           }
267           else
268           {
269             command.execute();
270           }
271         }
272         catch (RuntimeException JavaDoc exception)
273         {
274           // Skip over the command that threw the exception.
275
//
276
commands.previous();
277
278           // Iterate back over the executed commands to undo them.
279
//
280
while (commands.hasPrevious())
281           {
282             commands.previous();
283             Command command = (Command)commands.previous();
284             if (command.canUndo())
285             {
286               command.undo();
287             }
288             else
289             {
290               break;
291             }
292           }
293
294           throw exception;
295         }
296       }
297     }
298     else if (!commandList.isEmpty())
299     {
300       Command command = (Command)commandList.get(commandList.size() - 1);
301       command.execute();
302     }
303   }
304
305   /**
306    * Calls {@link Command#undo} for each command in the list.
307    * In the case that {@link #isPessimistic} is false, only the last command will be undone
308    * since the others will have been executed and not undo during {@link #prepare}.
309    */

310   public void undo()
311   {
312     if (isPessimistic)
313     {
314       super.undo();
315     }
316     else if (!commandList.isEmpty())
317     {
318       Command command = (Command)commandList.get(commandList.size() - 1);
319       command.undo();
320     }
321   }
322
323   /**
324    * Calls {@link Command#redo} for each command in the list.
325    * In the case that {@link #isPessimistic} is false, only the last command will be redone
326    * since the others will have been executed and not undo during {@link #prepare}.
327    */

328   public void redo()
329   {
330     if (isPessimistic)
331     {
332       super.redo();
333     }
334     else if (!commandList.isEmpty())
335     {
336       Command command = (Command)commandList.get(commandList.size() - 1);
337       command.redo();
338     }
339   }
340
341   /**
342    * Checks if the command can execute;
343    * if so, it is executed, appended to the list, and <code>true</code> is returned,
344    * if not, it is just disposed and <code>false</code> is returned.
345    * A typical use for this is to execute commands created during the execution of another command, e.g.,
346    * <pre>
347    * class MyCommand extends AbstractCommand
348    * {
349    * protected Command subcommand;
350    *
351    * //...
352    *
353    * public void execute()
354    * {
355    * // ...
356    * StrictCompoundCommand subcommands = new StrictCompoundCommand();
357    * subcommands.appendAndExecute(new AddCommand(...));
358    * if (condition) subcommands.appendAndExecute(new AddCommand(...));
359    * subcommand = subcommands.unwrap();
360    * }
361    *
362    * public void undo()
363    * {
364    * // ...
365    * subcommand.undo();
366    * }
367    *
368    * public void redo()
369    * {
370    * // ...
371    * subcommand.redo();
372    * }
373    *
374    * public void dispose()
375    * {
376    * // ...
377    * if (subcommand != null)
378    * {
379    * subcommand.dispose();
380    * }
381    * }
382    * }
383    * </pre>
384    * @return whether the command was successfully executed and appended.
385    */

386   public boolean appendAndExecute(Command command)
387   {
388     if (command != null)
389     {
390       if (!isPrepared)
391       {
392         if (commandList.isEmpty())
393         {
394           isPrepared = true;
395           isExecutable = true;
396         }
397         else
398         {
399           isExecutable = prepare();
400           isPrepared = true;
401           isPessimistic = true;
402           if (isExecutable)
403           {
404             execute();
405           }
406         }
407       }
408   
409       if (command.canExecute())
410       {
411         try
412         {
413           command.execute();
414           commandList.add(command);
415           ++rightMostExecutedCommandIndex;
416           isUndoable = command.canUndo();
417           return true;
418         }
419         catch (RuntimeException JavaDoc exception)
420         {
421           CommonPlugin.INSTANCE.log
422             (new WrappedException
423               (CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), exception).fillInStackTrace());
424         }
425       }
426   
427       command.dispose();
428     }
429
430     return false;
431   }
432
433   /*
434    * Javadoc copied from base class.
435    */

436   public String JavaDoc toString()
437   {
438     StringBuffer JavaDoc result = new StringBuffer JavaDoc(super.toString());
439     result.append(" (isUndoable: " + isUndoable + ")");
440     result.append(" (isPessimistic: " + isPessimistic + ")");
441     result.append(" (rightMostExecutedCommandIndex: " + rightMostExecutedCommandIndex + ")");
442
443     return result.toString();
444   }
445 }
446
Popular Tags