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 }