/******************************************************************************* * Copyright (c) 2009, 2010 Fraunhofer IWU and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Fraunhofer IWU - initial API and implementation *******************************************************************************/ package net.enilink.komma.common.command; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Set; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.operations.ICompositeOperation; import org.eclipse.core.commands.operations.IUndoContext; import org.eclipse.core.commands.operations.IUndoableOperation; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubProgressMonitor; import net.enilink.komma.common.CommonPlugin; import net.enilink.komma.common.internal.CommonDebugOptions; import net.enilink.komma.common.internal.CommonStatusCodes; import net.enilink.komma.common.util.Log; import net.enilink.komma.common.util.Trace; import net.enilink.komma.common.util.WrappedException; /** * An undoable command that is composed of child {@link IUndoableOperation}s * that are not known to modify EMF model resources, but can contain * model-affecting children. Execute, undo, redo and dispose result in execute, * undo, redo and dispose on each child operation. The operation provides a list * of {@link IFile}s that may be modified when the operation is executed, undone * or redone. * <P> * The children are explicitly composed by a client before the composite is * executed. Children cannot be added or removed after the composite has been * executed. * <P> * The undo contexts of the composite are a union of the undo contexts of its * children. * <P> * If a child command returns a cancel or an error status during execution, undo * or redo, the remaining child commands are not processed and those that have * already been executed are rolled back. * * @author ldamus */ public class CompositeCommand extends AbstractCommand implements ICompositeCommand { /** * Custom iterator implementation that maintains my undo contexts correctly * when elements are removed. * * @author ldamus */ private class ChildIterator implements Iterator<IUndoableOperation> { protected final ListIterator<IUndoableOperation> iter; protected IUndoableOperation last; ChildIterator() { this(0); } ChildIterator(int index) { iter = getChildren().listIterator(index); } public boolean hasNext() { return iter.hasNext(); } public IUndoableOperation next() { last = iter.next(); return last; } public void remove() { assertNotExecuted(); iter.remove(); didRemove((IUndoableOperation) last); last = null; } } /** * Custom list-iterator implementation that maintains my undo contexts * correctly, as well as uniqueness of the list contents. * * @author ldamus */ private class ChildListIterator extends ChildIterator implements ListIterator<IUndoableOperation> { ChildListIterator(int index) { super(index); } public void add(IUndoableOperation o) { assertNotExecuted(); if (!getChildren().contains(o)) { iter.add(o); didAdd((IUndoableOperation) o); } } public boolean hasPrevious() { return iter.hasPrevious(); } public int nextIndex() { return iter.nextIndex(); } public IUndoableOperation previous() { last = iter.previous(); return last; } public int previousIndex() { return iter.previousIndex(); } public void set(IUndoableOperation o) { assertNotExecuted(); if (!getChildren().contains(o)) { didRemove(last); iter.set(o); last = o; didAdd(o); } } } /** * Appends a command onto a (possibly) existing composite of commands. * * @param command * an existing command, which may be a composite, a single * command, or <code>null</code> * @param next * a command to append to the composite (may also be * <code>null</code>, which produces no effect) * * @return the new composite, which is just <code>next</code> if * <code>command</code> was <code>null</code> */ public static ICommand compose(ICommand command, ICommand next) { if (command == null) { return next; } else if (next != null) { return command.compose(next); } else { return command; } } private final List<IUndoableOperation> children; private boolean executed; /** * Initializes me with a blank label. */ public CompositeCommand() { this(null); } /** * Initializes me with a label. * * @param label * a user-readable label */ public CompositeCommand(String label) { this(label, null); } /** * Initializes me with a label and a list of child operations. * * @param label * a user-readable label * @param children * a list of child {@link IUndoableOperation}s */ public CompositeCommand(String label, List<? extends IUndoableOperation> children) { this(label, null, children); } /** * Initializes me with a label, a description and a list of child * operations. * * @param label * a user-readable label * @param description * a user-readable description * @param children * a list of child {@link IUndoableOperation}s */ public CompositeCommand(String label, String description, List<? extends IUndoableOperation> children) { super(label, description); if (children != null) { this.children = new ArrayList<IUndoableOperation>(children); } else { this.children = new ArrayList<IUndoableOperation>(4); } } /** * Adds a child operation to me. This should only be done before I am * executed. Has no effect if I already contain this operation as a child. * * @param operation * a new child operation * * @throws IllegalStateException * if I have already been successfully executed */ public void add(IUndoableOperation operation) { assertNotExecuted(); if (!getChildren().contains(operation)) { getChildren().add(operation); didAdd(operation); } } /** * Creates a suitable aggregate from these statuses. If there are no * statuses to aggregate, then an OK status is returned. If there is a * single status to aggregate, then it is returned. Otherwise, a * multi-status is returned with the provided statuses as children. * * @param statuses * the statuses to aggregate. May have zero, one, or more * elements (all must be {@link IStatus}es) * * @return the multi-status */ protected IStatus aggregateStatuses(List<IStatus> statuses) { final IStatus result; if (statuses.isEmpty()) { result = Status.OK_STATUS; } else if (statuses.size() == 1) { result = statuses.get(0); } else { // find the most severe status, to use its plug-in, code, and // message IStatus[] statusArray = statuses.toArray(new IStatus[statuses .size()]); IStatus worst = statusArray[0]; for (int i = 1; i < statusArray.length; i++) { if (statusArray[i].getSeverity() > worst.getSeverity()) { worst = statusArray[i]; } } result = new MultiStatus(worst.getPlugin(), worst.getCode(), statusArray, worst.getMessage(), null); } return result; } /** * Queries whether any of my children has the specified context. * * @param ctx * a context * * @return <code>false</code> if none of my children has the specified * context; <code>true</code>, otherwise */ private boolean anyChildHasContext(IUndoContext ctx) { boolean result = false; for (Iterator<?> iter = iterator(); !result && iter.hasNext();) { result = ((IUndoableOperation) iter.next()).hasContext(ctx); } return result; } /** * Checks if the command can execute; if so, it is executed, appended to the * list, and true is returned, if not, it is just disposed and false is * returned. A typical use for this is to execute commands created during * the execution of another command, e.g., * * <pre> * class MyCommand extends CommandBase { * protected Command subcommand; * * // ... * * public void execute() * { * // ... * Compound subcommands = new CompositeCommand(); * subcommands.addAndExecute(new AddCommand(...)); * if (condition) subcommands.addAndExecute(new AddCommand(...)); * subcommand = subcommands.unwrap(); * } * * public void undo() { * // ... * subcommand.undo(); * } * * public void redo() { * // ... * subcommand.redo(); * } * * public void dispose() { * // ... * if (subcommand != null) { * subcommand.dispose(); * } * } * } * </pre> * * Another use is in an execute override of compound command itself: * * <pre> * class MyCommand extends CompositeCommand { * public void execute() * { * // ... * addAndExecute(new AddCommand(...)); * if (condition) addAndExecute(new AddCommand(...)); * } * } * </pre> * * Note that appending commands will modify what getResult and * getAffectedObjects return, so you may want to set the resultIndex flag. * * @param command * the command. * @return whether the command was successfully executed and appended. */ public IStatus addAndExecute(ICommand command, IProgressMonitor monitor, IAdaptable info) throws ExecutionException { IStatus status = Status.CANCEL_STATUS; if (command != null) { if (!isPrepared) { if (getChildren().isEmpty()) { isPrepared = true; isExecutable = true; } else { isExecutable = prepare(); isPrepared = true; if (isExecutable) { status = execute(monitor, info); if (!status.isOK()) { return status; } } } } if (command.canExecute()) { try { status = command.execute(monitor, info); getChildren().add(command); if (status.isOK()) { for (IUndoContext context : command.getContexts()) { addContext(context); } } return status; } catch (RuntimeException exception) { CommonPlugin.INSTANCE .log(new WrappedException( CommonPlugin.INSTANCE .getString("_UI_IgnoreException_exception"), exception).fillInStackTrace()); } } command.dispose(); } return status; } /** * Adds a command to this compound command's the list of commands and * returns <code>true</code>, if * <code>command.{@link net.enilink.komma.common.command.ICommand#canExecute() canExecute()}</code> * returns true; otherwise, it simply calls * <code>command.{@link net.enilink.komma.common.command.ICommand#dispose() dispose()}</code> * and returns <code>false</code>. * * @param command * the command. * @return whether the command was executed and appended. */ public boolean appendIfCanExecute(ICommand command) { if (command == null) { return false; } else if (command.canExecute()) { getChildren().add(command); for (IUndoContext context : command.getContexts()) { addContext(context); } return true; } else { command.dispose(); return false; } } /** * Asserts that I have not yet been executed. Changes to my children are not * permitted after I have been executed. */ protected final void assertNotExecuted() { if (isExecuted()) { IllegalStateException exc = new IllegalStateException( "Operation already executed"); //$NON-NLS-1$ Trace.throwing(CommonPlugin.getPlugin(), CommonDebugOptions.EXCEPTIONS_THROWING, CompositeCommand.class, "assertNotExecuted", exc); //$NON-NLS-1$ throw exc; } } /** * I can redo if I am not empty and all my children can all be redone. */ public boolean canRedo() { boolean result = !isEmpty() && super.canRedo(); for (Iterator<?> iter = iterator(); result && iter.hasNext();) { result = ((IUndoableOperation) iter.next()).canRedo(); } return result; } /** * I can undo if I am not empty and all my children can all be undone. */ public boolean canUndo() { boolean result = !isEmpty() && super.canUndo(); for (Iterator<?> iter = iterator(); result && iter.hasNext();) { result = ((IUndoableOperation) iter.next()).canUndo(); } return result; } /** * Adds <code>command</code> to the list of commands with which this * composite is composed. * * @param operation * The command with which to compose this command. * @return <code>this</code>. */ public final ICommand compose(IUndoableOperation operation) { if (operation != null) { add(operation); } return this; } /** * Updates my undo contexts for the addition of a new child operation. * * @param operation * a new child operation */ private void didAdd(IUndoableOperation operation) { IUndoContext[] childContexts = operation.getContexts(); for (int i = 0; i < childContexts.length; i++) { if (!hasContext(childContexts[i])) { addContext(childContexts[i]); } } } /** * Updates my undo contexts for the removal of a child operation. * * @param operation * the child operation that was removed */ private void didRemove(IUndoableOperation operation) { IUndoContext[] childContexts = operation.getContexts(); for (int i = 0; i < childContexts.length; i++) { if (!anyChildHasContext(childContexts[i])) { removeContext(childContexts[i]); } } } /** * Disposes of each of my children. */ public void dispose() { for (Iterator<?> iter = iterator(); iter.hasNext();) { IUndoableOperation nextOperation = (IUndoableOperation) iter.next(); nextOperation.dispose(); } } /** * Implements the execution logic by sequential execution of my children. */ protected CommandResult doExecuteWithResult( IProgressMonitor progressMonitor, IAdaptable info) throws ExecutionException { List<IStatus> result = new ArrayList<IStatus>(size()); progressMonitor.beginTask(getLabel(), size()); try { for (ListIterator<IUndoableOperation> iter = listIterator(); iter .hasNext();) { IUndoableOperation next = (IUndoableOperation) iter.next(); try { IStatus status = next.execute(new SubProgressMonitor( progressMonitor, 1), info); result.add(status); int severity = status.getSeverity(); if (severity == IStatus.CANCEL || severity == IStatus.ERROR) { // Undo the operation to date, excluding the current // child, and don't proceed Trace.trace( CommonPlugin.getPlugin(), "Composite operation execution recovery: child command status is CANCEL or ERROR."); //$NON-NLS-1$ // back-track over the operation that failed iter.previous(); unwindFailedExecute(iter, info); break; } else if (progressMonitor.isCanceled()) { // Undo the operation to date, including the current // child, and don't proceed Trace.trace(CommonPlugin.getPlugin(), "Composite operation redo recovery: child command monitor is cancelled."); //$NON-NLS-1$ CommandResult cancelResult = CommandResult .newCancelledCommandResult(); result.add(cancelResult.getStatus()); unwindFailedExecute(iter, info); break; } else { progressMonitor.worked(1); executed = true; } } catch (ExecutionException e) { // Undo the operation to date, and re-throw the exception // back-track over the operation that failed iter.previous(); unwindFailedExecute(iter, info); Trace.throwing(CommonPlugin.getPlugin(), CommonDebugOptions.EXCEPTIONS_THROWING, CompositeCommand.class, "execute", e); //$NON-NLS-1$ throw e; } } } finally { progressMonitor.done(); } return new CommandResult(aggregateStatuses(result), getReturnValues()); } /** * I redo by asking my children to redo, in forward order. */ protected CommandResult doRedoWithResult(IProgressMonitor progressMonitor, IAdaptable info) throws ExecutionException { final List<IStatus> result = new ArrayList<IStatus>(size()); progressMonitor.beginTask(getLabel(), size()); try { for (ListIterator<IUndoableOperation> iter = listIterator(); iter .hasNext();) { IUndoableOperation next = (IUndoableOperation) iter.next(); try { IStatus status = next.redo(new SubProgressMonitor( progressMonitor, 1), info); result.add(status); int severity = status.getSeverity(); if (severity == IStatus.CANCEL || severity == IStatus.ERROR) { // Undo the operation to date, excluding the current // child, and don't proceed Trace.trace(CommonPlugin.getPlugin(), "Composite operation redo recovery: child command status is CANCEL or ERROR."); //$NON-NLS-1$ // back-track over the operation that failed iter.previous(); unwindFailedRedo(iter, info); break; } else if (progressMonitor.isCanceled()) { // Undo the operation to date, including the current // child, and don't proceed Trace.trace(CommonPlugin.getPlugin(), "Composite operation redo recovery: child command monitor is cancelled."); //$NON-NLS-1$ CommandResult cancelResult = CommandResult .newCancelledCommandResult(); result.add(cancelResult.getStatus()); unwindFailedRedo(iter, info); break; } else { progressMonitor.worked(1); executed = true; } } catch (ExecutionException e) { // Undo the operation to date, and re-throw the exception // back-track over the operation that failed iter.previous(); unwindFailedRedo(iter, info); Trace.throwing(CommonPlugin.getPlugin(), CommonDebugOptions.EXCEPTIONS_THROWING, CompositeCommand.class, "redo", e); //$NON-NLS-1$ throw e; } } } finally { progressMonitor.done(); } return new CommandResult(aggregateStatuses(result), getReturnValues()); } /** * I undo by asking my children to undo, in reverse order. */ protected CommandResult doUndoWithResult(IProgressMonitor progressMonitor, IAdaptable info) throws ExecutionException { final List<IStatus> result = new ArrayList<IStatus>(size()); progressMonitor.beginTask(getLabel(), size()); try { for (ListIterator<IUndoableOperation> iter = listIterator(size()); iter .hasPrevious();) { IUndoableOperation prev = (IUndoableOperation) iter.previous(); try { IStatus status = prev.undo(new SubProgressMonitor( progressMonitor, 1), info); result.add(status); int severity = status.getSeverity(); if (severity == IStatus.CANCEL || severity == IStatus.ERROR) { // Redo the operation to date, excluding the current // child, and don't proceed Trace.trace(CommonPlugin.getPlugin(), "Composite operation undo recovery: child command status is CANCEL or ERROR."); //$NON-NLS-1$ // back-track over the operation that failed or was // cancelled iter.next(); unwindFailedUndo(iter, info); break; } else if (progressMonitor.isCanceled()) { // Redo the operation to date, including the current // child, and don't proceed Trace.trace(CommonPlugin.getPlugin(), "Composite operation undo recovery: child command monitor is cancelled."); //$NON-NLS-1$ CommandResult cancelResult = CommandResult .newCancelledCommandResult(); result.add(cancelResult.getStatus()); unwindFailedUndo(iter, info); break; } else { progressMonitor.worked(1); executed = true; } } catch (ExecutionException e) { // Redo the operation to date, and re-throw the exception // back-track over the operation that failed iter.next(); unwindFailedUndo(iter, info); Trace.throwing(CommonPlugin.getPlugin(), CommonDebugOptions.EXCEPTIONS_THROWING, CompositeCommand.class, "undo", e); //$NON-NLS-1$ throw e; } } } finally { progressMonitor.done(); } return new CommandResult(aggregateStatuses(result), getReturnValues()); } /** * Returns the merged collection of all command affected objects. * * @return the merged collection of all command affected objects. */ public Collection<?> getAffectedObjects() { Collection<Object> result = new ArrayList<Object>(); for (IUndoableOperation command : getChildren()) { if (command instanceof ICommand) { result.addAll(((ICommand) command).getAffectedObjects()); } } return result; } /** * Returns a list containing all of the affected resources from * <code>ICommand</code> children. */ public Collection<Object> getAffectedResources(Object type) { Set<Object> result = new HashSet<Object>(); for (Iterator<?> i = iterator(); i.hasNext();) { IUndoableOperation nextOperation = (IUndoableOperation) i.next(); if (nextOperation instanceof ICommand) { Collection<?> nextAffected = ((ICommand) nextOperation) .getAffectedResources(type); if (nextAffected != null) { result.addAll(nextAffected); } } } return new ArrayList<Object>(result); } /** * Obtains my nested operations. Note that the return result is mutable and * is identical to my child-operation storage, so subclasses should be * careful of adding or removing contents. This should ordinarily be done * only via the {@link #add(IUndoableOperation)} and * {@link #remove(IUndoableOperation)} methods because these maintain the * undo contexts (or, equivalently, using the iterators). * * @return my list of children * * @see #add(IUndoableOperation) * @see #remove(IUndoableOperation) * @see #iterator() * @see #listIterator(int) */ protected List<IUndoableOperation> getChildren() { return children; } @Override public CommandResult getCommandResult() { CommandResult commandResult = super.getCommandResult(); if (commandResult == null) { List<IStatus> statusList = new ArrayList<IStatus>(size()); for (Iterator<?> i = iterator(); i.hasNext();) { IUndoableOperation operation = (IUndoableOperation) i.next(); if (operation instanceof ICommand) { ICommand command = (ICommand) operation; CommandResult result = command.getCommandResult(); if (result != null) { statusList.add(result.getStatus()); } } } // Don't set the command explicitly since the intermediate command // could // have children added later. return new CommandResult(aggregateStatuses(statusList), getReturnValues()); } return commandResult; } /** * Determines the label by composing the labels of the commands in the list; * this is affected by the setting of {@link #resultIndex}. * * @return the label. */ @Override public String getLabel() { if (label != null) { return label; } else if (!getChildren().isEmpty()) { for (IUndoableOperation child : getChildren()) { String childLabel = child.getLabel(); if (childLabel != null) { return childLabel; } } } return super.getLabel(); } /** * Returns a list containing all of the return values from * <code>ICommand</code> children. */ protected Object getReturnValues() { List<Object> returnValues = new ArrayList<Object>(); for (Iterator<?> i = iterator(); i.hasNext();) { IUndoableOperation operation = (IUndoableOperation) i.next(); if (operation instanceof ICommand) { ICommand command = (ICommand) operation; CommandResult result = command.getCommandResult(); if (result != null) { Object returnValue = result.getReturnValue(); if (returnValue != null) { if (command instanceof ICompositeCommand) { // unwrap the values from other composites if (returnValue != null && returnValue instanceof Collection<?>) { returnValues .addAll((Collection<?>) returnValue); } else { returnValues.add(returnValue); } } else { returnValues.add(returnValue); } } } } } return returnValues; } /** * Answers whether or not this composite operation has children. * * @return <code>true</code> if the operation does not have children, * <code>false</code> otherwise. */ public final boolean isEmpty() { return size() < 1; } /** * Answers whether or not I have been executed. * * @return <code>true</code> if I have been executed, <code>false</code> * otherwise. */ protected final boolean isExecuted() { return executed; } /** * Obtains an iterator to traverse my child operations. Removing children * via this iterator correctly maintains my undo contexts. * * @return an iterator of my children */ public Iterator<IUndoableOperation> iterator() { return new ChildIterator(); } /** * Obtains an iterator to traverse my child operations in either direction. * Adding and removing children via this iterator correctly maintains my * undo contexts. * <p> * <b>Note</b> that, unlike list iterators generally, this implementation * does not permit the addition of an operation that I already contain (the * composite does not permit duplicates). Moreover, only * {@link IUndoableOperation}s may be added, otherwise * <code>ClassCastException</code>s will result. * </p> * * @return an iterator of my children */ public ListIterator<IUndoableOperation> listIterator() { return new ChildListIterator(0); } /** * Obtains an iterator to traverse my child operations in either direction, * starting from the specified <code>index</code>. Adding and removing * children via this iterator correctly maintains my undo contexts. * <p> * <b>Note</b> that, unlike list iterators generally, this implementation * does not permit the addition of an operation that I already contain (the * composite does not permit duplicates). Moreover, only * {@link IUndoableOperation}s may be added, otherwise * <code>ClassCastException</code>s will result. * </p> * * @param index * the index in my children at which to start iterating * * @return an iterator of my children */ public ListIterator<IUndoableOperation> listIterator(int index) { return new ChildListIterator(index); } /** * I can execute if I am not empty and all of my children can execute. */ @Override protected boolean prepare() { boolean canExecute = !isEmpty(); for (Iterator<?> iter = iterator(); canExecute && iter.hasNext();) { canExecute = ((IUndoableOperation) iter.next()).canExecute(); } return canExecute; } /** * Returns the simplest form of this command that is equivalent. This is * useful for removing unnecessary nesting of commands. * <P> * If the composite has a single command, it returns the reduction of that * single command. Otherwise, it returns itself. * * @return the simplest form of this command that is equivalent */ public ICommand reduce() { switch (size()) { case 1: IUndoableOperation child = (IUndoableOperation) iterator().next(); if (child instanceof ICommand) { ICommand cmd = ((ICommand) child).reduce(); /* * Propagate the label of the original command to the reduced. */ if (getLabel() != null && getLabel().length() > 0) { cmd.setLabel(getLabel()); } return cmd; } } return this; } /** * Removes a child operation from me. This should only be done before I am * executed. Has no effect if I do not contain this operation as a child. * <p> * <b>Note</b> that I do not dispose an operation when it is removed from * me. Although this is specified in the contract of the * {@link ICompositeOperation} interface, this would not be correct, as I * did not create that operation. * </p> * * @param operation * a child operation to remove * * @throws IllegalStateException * if I have already been successfully executed */ public void remove(IUndoableOperation operation) { assertNotExecuted(); if (getChildren().remove(operation)) { didRemove(operation); } } // Documentation copied from interface public int size() { return getChildren().size(); } /** * Undoes the previous operations in the iterator. * * @param iter * the execution iterator * @param info * the execution info */ private void unwindFailedExecute(ListIterator<IUndoableOperation> iter, IAdaptable info) { while (iter.hasPrevious()) { // unwind the child operations IUndoableOperation prev = (IUndoableOperation) iter.previous(); if (!prev.canUndo()) { // Can't unwind Log.error( CommonPlugin.getPlugin(), CommonStatusCodes.EXECUTE_RECOVERY_FAILED, CommonPlugin.INSTANCE .getString( "Command_executeRecoveryFailed", CommonPlugin.INSTANCE .getString("Command_cannotUndoExecuted"))); break; } try { prev.undo(new NullProgressMonitor(), info); } catch (ExecutionException inner) { Log.error(CommonPlugin.getPlugin(), CommonStatusCodes.EXECUTE_RECOVERY_FAILED, CommonPlugin.INSTANCE.getString( "Command_executeRecoveryFailed", inner.getLocalizedMessage())); break; } } } /** * Undoes the previous operations in the iterator. * * @param iter * the execution iterator * @param info * the execution info */ private void unwindFailedRedo(ListIterator<IUndoableOperation> iter, IAdaptable info) { while (iter.hasPrevious()) { // unwind the child operations IUndoableOperation prev = (IUndoableOperation) iter.previous(); if (!prev.canUndo()) { // Can't unwind Log.error(CommonPlugin.getPlugin(), CommonStatusCodes.REDO_RECOVERY_FAILED, CommonPlugin.INSTANCE.getString( "Command_redoRecoveryFailed", CommonPlugin.INSTANCE .getString("Command_cannotUndo"))); break; } try { prev.undo(new NullProgressMonitor(), info); } catch (ExecutionException inner) { Log.error(CommonPlugin.getPlugin(), CommonStatusCodes.REDO_RECOVERY_FAILED, CommonPlugin.INSTANCE.getString( "Command_redoRecoveryFailed", inner.getLocalizedMessage())); break; } } } /** * Redoes the next operations in the iterator. * * @param iter * the execution iterator * @param info * the execution info */ private void unwindFailedUndo(ListIterator<IUndoableOperation> iter, IAdaptable info) { while (iter.hasNext()) { // unwind the child operations IUndoableOperation next = (IUndoableOperation) iter.next(); if (!next.canRedo()) { // Can't unwind Log.error(CommonPlugin.getPlugin(), CommonStatusCodes.UNDO_RECOVERY_FAILED, CommonPlugin.INSTANCE.getString( "Command_undoRecoveryFailed", CommonPlugin.INSTANCE .getString("Command_cannotRedo"))); break; } try { next.redo(new NullProgressMonitor(), info); } catch (ExecutionException inner) { Log.error(CommonPlugin.getPlugin(), CommonStatusCodes.UNDO_RECOVERY_FAILED, CommonPlugin.INSTANCE.getString( "Command_undoRecoveryFailed", inner.getLocalizedMessage())); break; } } } }