| /****************************************************************************** |
| * Copyright (c) 2006, 2009 IBM Corporation 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: |
| * IBM Corporation - initial API and implementation |
| ****************************************************************************/ |
| |
| package org.eclipse.gmf.runtime.common.core.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 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.resources.IFile; |
| 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 org.eclipse.gmf.runtime.common.core.internal.CommonCoreDebugOptions; |
| import org.eclipse.gmf.runtime.common.core.internal.CommonCorePlugin; |
| import org.eclipse.gmf.runtime.common.core.internal.CommonCoreStatusCodes; |
| import org.eclipse.gmf.runtime.common.core.internal.l10n.CommonCoreMessages; |
| import org.eclipse.gmf.runtime.common.core.util.Log; |
| import org.eclipse.gmf.runtime.common.core.util.Trace; |
| |
| /** |
| * 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 { |
| |
| private final List children; |
| |
| private boolean executed; |
| |
| /** |
| * 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 children) { |
| super(label, null); |
| |
| if (children != null) { |
| this.children = new ArrayList(children); |
| } else { |
| this.children = new ArrayList(4); |
| } |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * 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 getChildren() { |
| return children; |
| } |
| |
| // Documentation copied from interface |
| public int size() { |
| return getChildren().size(); |
| } |
| |
| /** |
| * 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); |
| } |
| } |
| |
| /** |
| * 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]); |
| } |
| } |
| } |
| |
| /** |
| * 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); |
| } |
| } |
| |
| /** |
| * 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]); |
| } |
| } |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * I can execute if I am not empty and all of my children can execute. |
| */ |
| public boolean canExecute() { |
| boolean result = !isEmpty() && super.canExecute(); |
| |
| for (Iterator iter = iterator(); result && iter.hasNext();) { |
| result = ((IUndoableOperation) iter.next()).canExecute(); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * Disposes of each of my children. |
| */ |
| public void dispose() { |
| |
| for (Iterator iter = iterator(); iter.hasNext();) { |
| IUndoableOperation nextOperation = (IUndoableOperation) iter.next(); |
| nextOperation.dispose(); |
| } |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * Appends a command onto a (possibly) existing composeite 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; |
| } |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * Returns a list containing all of the return values from |
| * <code>ICommand</code> children. |
| */ |
| protected List getReturnValues() { |
| |
| List returnValues = new ArrayList(); |
| |
| 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 (getClass().isInstance(command)) { |
| // 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; |
| } |
| |
| /** |
| * Implements the execution logic by sequential execution of my children. |
| */ |
| protected CommandResult doExecuteWithResult( |
| IProgressMonitor progressMonitor, IAdaptable info) |
| throws ExecutionException { |
| |
| List result = new ArrayList(size()); |
| |
| progressMonitor.beginTask(getLabel(), size()); |
| |
| try { |
| |
| for (ListIterator 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( |
| CommonCorePlugin.getDefault(), |
| "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(CommonCorePlugin.getDefault(), |
| "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(CommonCorePlugin.getDefault(), |
| CommonCoreDebugOptions.EXCEPTIONS_THROWING, |
| CompositeCommand.class, "execute", e); //$NON-NLS-1$ |
| throw e; |
| } |
| } |
| |
| } finally { |
| progressMonitor.done(); |
| } |
| |
| return new CommandResult(aggregateStatuses(result), getReturnValues()); |
| } |
| |
| /** |
| * Undoes the previous operations in the iterator. |
| * |
| * @param iter |
| * the execution iterator |
| * @param info |
| * the execution info |
| */ |
| private void unwindFailedExecute(ListIterator iter, IAdaptable info) { |
| |
| while (iter.hasPrevious()) { |
| // unwind the child operations |
| IUndoableOperation prev = (IUndoableOperation) iter.previous(); |
| if (!prev.canUndo()) { |
| // Can't unwind |
| Log.error(CommonCorePlugin.getDefault(), |
| CommonCoreStatusCodes.EXECUTE_RECOVERY_FAILED, |
| CommonCoreMessages.bind( |
| CommonCoreMessages.executeRecoveryFailed, |
| CommonCoreMessages.cannotUndoExecuted)); |
| break; |
| } |
| |
| try { |
| prev.undo(new NullProgressMonitor(), info); |
| |
| } catch (ExecutionException inner) { |
| Log.error(CommonCorePlugin.getDefault(), |
| CommonCoreStatusCodes.EXECUTE_RECOVERY_FAILED, |
| CommonCoreMessages.bind( |
| CommonCoreMessages.executeRecoveryFailed, inner |
| .getLocalizedMessage())); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * I redo by asking my children to redo, in forward order. |
| */ |
| protected CommandResult doRedoWithResult(IProgressMonitor progressMonitor, |
| IAdaptable info) |
| throws ExecutionException { |
| |
| final List result = new ArrayList(size()); |
| |
| progressMonitor.beginTask(getLabel(), size()); |
| |
| try { |
| |
| for (ListIterator 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(CommonCorePlugin.getDefault(), |
| "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(CommonCorePlugin.getDefault(), |
| "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(CommonCorePlugin.getDefault(), |
| CommonCoreDebugOptions.EXCEPTIONS_THROWING, |
| CompositeCommand.class, "redo", e); //$NON-NLS-1$ |
| throw e; |
| } |
| } |
| |
| } finally { |
| progressMonitor.done(); |
| } |
| |
| return new CommandResult(aggregateStatuses(result), getReturnValues()); |
| } |
| |
| /** |
| * Undoes the previous operations in the iterator. |
| * |
| * @param iter |
| * the execution iterator |
| * @param info |
| * the execution info |
| */ |
| private void unwindFailedRedo(ListIterator iter, IAdaptable info) { |
| |
| while (iter.hasPrevious()) { |
| // unwind the child operations |
| IUndoableOperation prev = (IUndoableOperation) iter.previous(); |
| if (!prev.canUndo()) { |
| // Can't unwind |
| Log.error(CommonCorePlugin.getDefault(), |
| CommonCoreStatusCodes.REDO_RECOVERY_FAILED, |
| CommonCoreMessages.bind( |
| CommonCoreMessages.redoRecoveryFailed, |
| CommonCoreMessages.cannotUndo)); |
| break; |
| } |
| |
| try { |
| prev.undo(new NullProgressMonitor(), info); |
| |
| } catch (ExecutionException inner) { |
| Log.error(CommonCorePlugin.getDefault(), |
| CommonCoreStatusCodes.REDO_RECOVERY_FAILED, |
| CommonCoreMessages.bind( |
| CommonCoreMessages.redoRecoveryFailed, inner |
| .getLocalizedMessage())); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * I undo by asking my children to undo, in reverse order. |
| */ |
| protected CommandResult doUndoWithResult(IProgressMonitor progressMonitor, |
| IAdaptable info) |
| throws ExecutionException { |
| |
| final List result = new ArrayList(size()); |
| |
| progressMonitor.beginTask(getLabel(), size()); |
| |
| try { |
| |
| for (ListIterator 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(CommonCorePlugin.getDefault(), |
| "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(CommonCorePlugin.getDefault(), |
| "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(CommonCorePlugin.getDefault(), |
| CommonCoreDebugOptions.EXCEPTIONS_THROWING, |
| CompositeCommand.class, "undo", e); //$NON-NLS-1$ |
| throw e; |
| } |
| } |
| |
| } finally { |
| progressMonitor.done(); |
| } |
| |
| return new CommandResult(aggregateStatuses(result), getReturnValues()); |
| } |
| |
| /** |
| * Redoes the next operations in the iterator. |
| * |
| * @param iter |
| * the execution iterator |
| * @param info |
| * the execution info |
| */ |
| private void unwindFailedUndo(ListIterator iter, IAdaptable info) { |
| |
| while (iter.hasNext()) { |
| // unwind the child operations |
| IUndoableOperation next = (IUndoableOperation) iter.next(); |
| if (!next.canRedo()) { |
| // Can't unwind |
| Log.error(CommonCorePlugin.getDefault(), |
| CommonCoreStatusCodes.UNDO_RECOVERY_FAILED, |
| CommonCoreMessages.bind( |
| CommonCoreMessages.undoRecoveryFailed, |
| CommonCoreMessages.cannotRedo)); |
| break; |
| } |
| |
| try { |
| next.redo(new NullProgressMonitor(), info); |
| |
| } catch (ExecutionException inner) { |
| Log.error(CommonCorePlugin.getDefault(), |
| CommonCoreStatusCodes.UNDO_RECOVERY_FAILED, |
| CommonCoreMessages.bind( |
| CommonCoreMessages.undoRecoveryFailed, inner |
| .getLocalizedMessage())); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * 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 statuses) { |
| final IStatus result; |
| |
| if (statuses.isEmpty()) { |
| result = Status.OK_STATUS; |
| } else if (statuses.size() == 1) { |
| result = ((IStatus) statuses.get(0)); |
| } else { |
| // find the most severe status, to use its plug-in, code, and |
| // message |
| IStatus[] statusArray = (IStatus[]) 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; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * 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(CommonCorePlugin.getDefault(), |
| CommonCoreDebugOptions.EXCEPTIONS_THROWING, |
| CompositeCommand.class, "assertNotExecuted", exc); //$NON-NLS-1$ |
| throw exc; |
| } |
| } |
| |
| /** |
| * Returns a list containing all of the affected files from |
| * <code>ICommand</code> children. |
| */ |
| public List getAffectedFiles() { |
| |
| HashSet result = new HashSet(); |
| |
| for (Iterator i = iterator(); i.hasNext();) { |
| IUndoableOperation nextOperation = (IUndoableOperation) i.next(); |
| |
| if (nextOperation instanceof ICommand) { |
| List nextAffected = ((ICommand) nextOperation) |
| .getAffectedFiles(); |
| |
| if (nextAffected != null) { |
| result.addAll(nextAffected); |
| } |
| } |
| } |
| return new ArrayList(result); |
| } |
| |
| /** |
| * 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 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 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 listIterator(int index) { |
| return new ChildListIterator(index); |
| } |
| |
| /** |
| * Custom iterator implementation that maintains my undo contexts correctly |
| * when elements are removed. |
| * |
| * @author ldamus |
| */ |
| private class ChildIterator |
| implements Iterator { |
| |
| protected Object last; |
| |
| protected final ListIterator iter; |
| |
| ChildIterator() { |
| this(0); |
| } |
| |
| ChildIterator(int index) { |
| iter = getChildren().listIterator(index); |
| } |
| |
| public void remove() { |
| assertNotExecuted(); |
| |
| iter.remove(); |
| didRemove((IUndoableOperation) last); |
| last = null; |
| } |
| |
| public Object next() { |
| last = iter.next(); |
| return last; |
| } |
| |
| public boolean hasNext() { |
| return iter.hasNext(); |
| } |
| } |
| |
| /** |
| * 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 { |
| |
| ChildListIterator(int index) { |
| super(index); |
| } |
| |
| public void add(Object o) { |
| assertNotExecuted(); |
| |
| if (!getChildren().contains(o)) { |
| iter.add(o); |
| didAdd((IUndoableOperation) o); |
| } |
| } |
| |
| public void set(Object o) { |
| assertNotExecuted(); |
| |
| if (!getChildren().contains(o)) { |
| didRemove((IUndoableOperation) last); |
| iter.set(o); |
| last = o; |
| didAdd((IUndoableOperation) o); |
| } |
| } |
| |
| public int previousIndex() { |
| return iter.previousIndex(); |
| } |
| |
| public int nextIndex() { |
| return iter.nextIndex(); |
| } |
| |
| public Object previous() { |
| last = iter.previous(); |
| return last; |
| } |
| |
| public boolean hasPrevious() { |
| return iter.hasPrevious(); |
| } |
| } |
| |
| @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; |
| } |
| } |