Copyright (c) 2005, 2016 IBM Corporation and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 Contributors: IBM Corporation - initial API and implementation
/******************************************************************************* * Copyright (c) 2005, 2016 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/
package org.eclipse.core.commands.operations; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Status;
Triggered operations are a specialized implementation of a composite operation that keeps track of operations triggered by the execution of some primary operation. The composite knows which operation was the trigger for subsequent operations, and adds all triggered operations as children. When execution, undo, or redo is performed, only the triggered operation is executed, undone, or redone if it is still present. If the trigger is removed from the triggered operations, then the child operations will replace the triggered operations in the history.

This class may be instantiated by clients.

Since:3.1
/** * Triggered operations are a specialized implementation of a composite * operation that keeps track of operations triggered by the execution of some * primary operation. The composite knows which operation was the trigger for * subsequent operations, and adds all triggered operations as children. When * execution, undo, or redo is performed, only the triggered operation is * executed, undone, or redone if it is still present. If the trigger is removed * from the triggered operations, then the child operations will replace the * triggered operations in the history. * <p> * This class may be instantiated by clients. * </p> * * @since 3.1 */
public final class TriggeredOperations extends AbstractOperation implements ICompositeOperation, IAdvancedUndoableOperation, IAdvancedUndoableOperation2, IContextReplacingOperation { private IUndoableOperation triggeringOperation; private IOperationHistory history; private List<IUndoableOperation> children = new ArrayList<>();
Construct a composite triggered operations using the specified undoable operation as the trigger. Use the label of this trigger as the label of the operation.
Params:
  • operation – the operation that will trigger other operations.
  • history – the operation history containing the triggered operations.
/** * Construct a composite triggered operations using the specified undoable * operation as the trigger. Use the label of this trigger as the label of * the operation. * * @param operation * the operation that will trigger other operations. * @param history * the operation history containing the triggered operations. */
public TriggeredOperations(IUndoableOperation operation, IOperationHistory history) { super(operation.getLabel()); triggeringOperation = operation; recomputeContexts(); this.history = history; } @Override public void add(IUndoableOperation operation) { children.add(operation); recomputeContexts(); } @Override public void remove(IUndoableOperation operation) { if (operation == triggeringOperation) { // the triggering operation is being removed, so we must replace // this composite with its individual triggers. triggeringOperation = null; // save the children before replacing the operation, since this // operation will be disposed as part of replacing it. We don't want // the children to be disposed since they are to replace this // operation. List<IUndoableOperation> childrenToRestore = new ArrayList<>(children); if (childrenToRestore.size() > 1) { // the children are in the order they were executed in, so we // need to reverse the order so they are put on the undo stack // in the correct order. The first operation is the first one // that is undone. We only need to reverse the order when the // operation being replaced is on the undo stack. If it's on the // redo stack, then the children are already in the correct // order to be redone. Set<IUndoableOperation> undoHistory = new HashSet<>(); for (IUndoContext context : this.getContexts()) { if (context != null) { undoHistory.addAll(Arrays.asList(history.getUndoHistory(context))); } } if (undoHistory.contains(this)) { Collections.reverse(childrenToRestore); } } children = new ArrayList<>(0); recomputeContexts(); operation.dispose(); // now replace the triggering operation history.replaceOperation(this, childrenToRestore.toArray(new IUndoableOperation[childrenToRestore.size()])); } else { children.remove(operation); operation.dispose(); recomputeContexts(); } }
Remove the specified context from the receiver. This method is typically invoked when the history is being flushed for a certain context. In the case of triggered operations, if the only context for the triggering operation is being removed, then the triggering operation must be replaced in the operation history with the atomic operations that it triggered. If the context being removed is not the only context for the triggering operation, the triggering operation will remain, and the children will each be similarly checked.
Params:
  • context – the undo context being removed from the receiver.
/** * Remove the specified context from the receiver. This method is typically * invoked when the history is being flushed for a certain context. In the * case of triggered operations, if the only context for the triggering * operation is being removed, then the triggering operation must be * replaced in the operation history with the atomic operations that it * triggered. If the context being removed is not the only context for the * triggering operation, the triggering operation will remain, and the * children will each be similarly checked. * * @param context * the undo context being removed from the receiver. */
@Override public void removeContext(IUndoContext context) { boolean recompute = false; // first check to see if we are removing the only context of the // triggering operation if (triggeringOperation != null && triggeringOperation.hasContext(context)) { if (triggeringOperation.getContexts().length == 1) { remove(triggeringOperation); return; } triggeringOperation.removeContext(context); recompute = true; } // the triggering operation remains, check all the children ArrayList<IUndoableOperation> toBeRemoved = new ArrayList<>(); for (int i = 0; i < children.size(); i++) { IUndoableOperation child = children.get(i); if (child.hasContext(context)) { if (child.getContexts().length == 1) { toBeRemoved.add(child); } else { child.removeContext(context); } recompute = true; } } for (int i = 0; i < toBeRemoved.size(); i++) { remove(toBeRemoved.get(i)); } if (recompute) { recomputeContexts(); } } @Override public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { if (triggeringOperation != null) { history.openOperation(this, IOperationHistory.EXECUTE); try { IStatus status = triggeringOperation.execute(monitor, info); history.closeOperation(status.isOK(), false, IOperationHistory.EXECUTE); return status; } catch (ExecutionException | RuntimeException e) { history.closeOperation(false, false, IOperationHistory.EXECUTE); throw e; } } return IOperationHistory.OPERATION_INVALID_STATUS; } @Override public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { if (triggeringOperation != null) { history.openOperation(this, IOperationHistory.REDO); List<IUndoableOperation> childrenToRestore = new ArrayList<>(children); try { removeAllChildren(); IStatus status = triggeringOperation.redo(monitor, info); if (!status.isOK()) { children = childrenToRestore; } history.closeOperation(status.isOK(), false, IOperationHistory.REDO); return status; } catch (ExecutionException | RuntimeException e) { children = childrenToRestore; history.closeOperation(false, false, IOperationHistory.REDO); throw e; } } return IOperationHistory.OPERATION_INVALID_STATUS; } @Override public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { if (triggeringOperation != null) { history.openOperation(this, IOperationHistory.UNDO); List<IUndoableOperation> childrenToRestore = new ArrayList<>(children); try { removeAllChildren(); IStatus status = triggeringOperation.undo(monitor, info); if (!status.isOK()) { children = childrenToRestore; } history.closeOperation(status.isOK(), false, IOperationHistory.UNDO); return status; } catch (ExecutionException | RuntimeException e) { children = childrenToRestore; history.closeOperation(false, false, IOperationHistory.UNDO); throw e; } } return IOperationHistory.OPERATION_INVALID_STATUS; } @Override public boolean canUndo() { if (triggeringOperation != null) { return triggeringOperation.canUndo(); } return false; } @Override public boolean canExecute() { if (triggeringOperation != null) { return triggeringOperation.canExecute(); } return false; } @Override public boolean canRedo() { if (triggeringOperation != null) { return triggeringOperation.canRedo(); } return false; } /* * Dispose all operations in the receiver. */ @Override public void dispose() { for (int i = 0; i < children.size(); i++) { (children.get(i)).dispose(); } if (triggeringOperation != null) { triggeringOperation.dispose(); } } /* * Recompute contexts in light of some change in the children */ private void recomputeContexts() { ArrayList<IUndoContext> allContexts = new ArrayList<>(); if (triggeringOperation != null) { IUndoContext[] contexts = triggeringOperation.getContexts(); allContexts.addAll(Arrays.asList(contexts)); } for (int i = 0; i < children.size(); i++) { IUndoContext[] contexts = children.get(i).getContexts(); for (IUndoContext context : contexts) { if (!allContexts.contains(context)) { allContexts.add(context); } } } contexts = allContexts; } /* * Remove all non-triggering children */ private void removeAllChildren() { IUndoableOperation[] nonTriggers = children.toArray(new IUndoableOperation[children.size()]); for (IUndoableOperation nonTrigger : nonTriggers) { children.remove(nonTrigger); nonTrigger.dispose(); } }
Return the operation that triggered the other operations in this composite.
Returns:the IUndoableOperation that triggered the other children.
/** * Return the operation that triggered the other operations in this * composite. * * @return the IUndoableOperation that triggered the other children. */
public IUndoableOperation getTriggeringOperation() { return triggeringOperation; } @Override public Object[] getAffectedObjects() { if (triggeringOperation instanceof IAdvancedUndoableOperation) { return ((IAdvancedUndoableOperation) triggeringOperation).getAffectedObjects(); } return null; } @Override public void aboutToNotify(OperationHistoryEvent event) { if (triggeringOperation instanceof IAdvancedUndoableOperation) { ((IAdvancedUndoableOperation) triggeringOperation).aboutToNotify(event); } } @Override public IStatus computeUndoableStatus(IProgressMonitor monitor) throws ExecutionException { if (triggeringOperation instanceof IAdvancedUndoableOperation) { try { return ((IAdvancedUndoableOperation) triggeringOperation).computeUndoableStatus(monitor); } catch (OperationCanceledException e) { return Status.CANCEL_STATUS; } } return Status.OK_STATUS; } @Override public IStatus computeRedoableStatus(IProgressMonitor monitor) throws ExecutionException { if (triggeringOperation instanceof IAdvancedUndoableOperation) { try { return ((IAdvancedUndoableOperation) triggeringOperation).computeRedoableStatus(monitor); } catch (OperationCanceledException e) { return Status.CANCEL_STATUS; } } return Status.OK_STATUS; }
Replace the undo context of the receiver with the provided replacement undo context. In the case of triggered operations, all contained operations are checked and any occurrence of the original context is replaced with the new undo context.

This message has no effect if the original undo context is not present in the receiver.

Params:
  • original – the undo context which is to be replaced
  • replacement – the undo context which is replacing the original
Since:3.2
/** * Replace the undo context of the receiver with the provided replacement * undo context. In the case of triggered operations, all contained * operations are checked and any occurrence of the original context is * replaced with the new undo context. * <p> * This message has no effect if the original undo context is not present in * the receiver. * * @param original * the undo context which is to be replaced * @param replacement * the undo context which is replacing the original * @since 3.2 */
@Override public void replaceContext(IUndoContext original, IUndoContext replacement) { // first check the triggering operation if (triggeringOperation != null && triggeringOperation.hasContext(original)) { if (triggeringOperation instanceof IContextReplacingOperation) { ((IContextReplacingOperation) triggeringOperation).replaceContext(original, replacement); } else { triggeringOperation.removeContext(original); triggeringOperation.addContext(replacement); } } // Now check all the children for (int i = 0; i < children.size(); i++) { IUndoableOperation child = children.get(i); if (child.hasContext(original)) { if (child instanceof IContextReplacingOperation) { ((IContextReplacingOperation) child).replaceContext( original, replacement); } else { child.removeContext(original); child.addContext(replacement); } } } recomputeContexts(); }
Add the specified context to the operation. Overridden in TriggeredOperations to add the specified undo context to the triggering operation.
Params:
  • context – the context to be added
Since:3.2
/** * Add the specified context to the operation. Overridden in * TriggeredOperations to add the specified undo context to the triggering * operation. * * @param context * the context to be added * * @since 3.2 */
@Override public void addContext(IUndoContext context) { if (triggeringOperation != null) { triggeringOperation.addContext(context); recomputeContexts(); } }
Since:3.6
/** * @since 3.6 */
@Override public IStatus computeExecutionStatus(IProgressMonitor monitor) throws ExecutionException { if (triggeringOperation instanceof IAdvancedUndoableOperation2) { try { return ((IAdvancedUndoableOperation2) triggeringOperation).computeExecutionStatus(monitor); } catch (OperationCanceledException e) { return Status.CANCEL_STATUS; } } return Status.OK_STATUS; }
Since:3.6
/** * @since 3.6 */
@Override public void setQuietCompute(boolean quiet) { if (triggeringOperation instanceof IAdvancedUndoableOperation2) { ((IAdvancedUndoableOperation2) triggeringOperation).setQuietCompute(quiet); } }
Since:3.6
/** * @since 3.6 */
@Override public boolean runInBackground() { if (triggeringOperation instanceof IAdvancedUndoableOperation2) { return ((IAdvancedUndoableOperation2) triggeringOperation).runInBackground(); } return false; } }