blob: 8747181253f8220d193637c4fbcede553713ac51 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2006 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.core.commands.operations;
import java.util.ArrayList;
import java.util.List;
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.
* <p>
* This class may be instantiated by clients.
* </p>
*
* @since 3.1
*/
public final class TriggeredOperations extends AbstractOperation implements
ICompositeOperation, IAdvancedUndoableOperation,
IContextReplacingOperation {
private IUndoableOperation triggeringOperation;
private IOperationHistory history;
private List 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.
*
* @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;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.commands.operations.IUndoableOperation#add(org.eclipse.core.commands.operations.IUndoableOperation)
*/
public void add(IUndoableOperation operation) {
children.add(operation);
recomputeContexts();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.commands.operations.IUndoableOperation#remove(org.eclipse.core.commands.operations.IUndoableOperation)
*/
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 childrenToRestore = new ArrayList(children);
children = new ArrayList(0);
recomputeContexts();
operation.dispose();
// now replace the triggering operation
history.replaceOperation(this,
(IUndoableOperation[]) 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.
*
* @param context
* the undo context being removed from the receiver.
*/
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 toBeRemoved = new ArrayList();
for (int i = 0; i < children.size(); i++) {
IUndoableOperation child = (IUndoableOperation) 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((IUndoableOperation) toBeRemoved.get(i));
}
if (recompute) {
recomputeContexts();
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.commands.operations.IUndoableOperation#execute(org.eclipse.core.runtime.IProgressMonitor,
* org.eclipse.core.runtime.IAdaptable)
*/
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 e) {
history.closeOperation(false, false, IOperationHistory.EXECUTE);
throw e;
} catch (RuntimeException e) {
history.closeOperation(false, false, IOperationHistory.EXECUTE);
throw e;
}
}
return IOperationHistory.OPERATION_INVALID_STATUS;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.commands.operations.IUndoableOperation#redo(org.eclipse.core.runtime.IProgressMonitor,
* org.eclipse.core.runtime.IAdaptable)
*/
public IStatus redo(IProgressMonitor monitor, IAdaptable info)
throws ExecutionException {
if (triggeringOperation != null) {
history.openOperation(this, IOperationHistory.REDO);
List 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 e) {
children = childrenToRestore;
history.closeOperation(false, false, IOperationHistory.REDO);
throw e;
} catch (RuntimeException e) {
children = childrenToRestore;
history.closeOperation(false, false, IOperationHistory.REDO);
throw e;
}
}
return IOperationHistory.OPERATION_INVALID_STATUS;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.commands.operations.IUndoableOperation#undo(org.eclipse.core.runtime.IProgressMonitor,
* org.eclipse.core.runtime.IAdaptable)
*/
public IStatus undo(IProgressMonitor monitor, IAdaptable info)
throws ExecutionException {
if (triggeringOperation != null) {
history.openOperation(this, IOperationHistory.UNDO);
List 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 e) {
children = childrenToRestore;
history.closeOperation(false, false, IOperationHistory.UNDO);
throw e;
} catch (RuntimeException e) {
children = childrenToRestore;
history.closeOperation(false, false, IOperationHistory.UNDO);
throw e;
}
}
return IOperationHistory.OPERATION_INVALID_STATUS;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.commands.operations.IUndoableOperation#canUndo()
*/
public boolean canUndo() {
if (triggeringOperation != null) {
return triggeringOperation.canUndo();
}
return false;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.commands.operations.IUndoableOperation#canExecute()
*/
public boolean canExecute() {
if (triggeringOperation != null) {
return triggeringOperation.canExecute();
}
return false;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.commands.operations.IUndoableOperation#canRedo()
*/
public boolean canRedo() {
if (triggeringOperation != null) {
return triggeringOperation.canRedo();
}
return false;
}
/*
* Dispose all operations in the receiver.
*/
public void dispose() {
for (int i = 0; i < children.size(); i++) {
((IUndoableOperation) (children.get(i))).dispose();
}
if (triggeringOperation != null) {
triggeringOperation.dispose();
}
}
/*
* Recompute contexts in light of some change in the children
*/
private void recomputeContexts() {
ArrayList allContexts = new ArrayList();
if (triggeringOperation != null) {
IUndoContext[] contexts = triggeringOperation.getContexts();
for (int i = 0; i < contexts.length; i++) {
allContexts.add(contexts[i]);
}
}
for (int i = 0; i < children.size(); i++) {
IUndoContext[] contexts = ((IUndoableOperation) children.get(i))
.getContexts();
for (int j = 0; j < contexts.length; j++) {
if (!allContexts.contains(contexts[j])) {
allContexts.add(contexts[j]);
}
}
}
contexts = allContexts;
}
/*
* Remove all non-triggering children
*/
private void removeAllChildren() {
IUndoableOperation[] nonTriggers = (IUndoableOperation[]) children
.toArray(new IUndoableOperation[children.size()]);
for (int i = 0; i < nonTriggers.length; i++) {
children.remove(nonTriggers[i]);
nonTriggers[i].dispose();
}
}
/**
* Return the operation that triggered the other operations in this
* composite.
*
* @return the IUndoableOperation that triggered the other children.
*/
public IUndoableOperation getTriggeringOperation() {
return triggeringOperation;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.commands.operations.IAdvancedModelOperation#getAffectedObjects()
*/
public Object[] getAffectedObjects() {
if (triggeringOperation instanceof IAdvancedUndoableOperation) {
return ((IAdvancedUndoableOperation) triggeringOperation)
.getAffectedObjects();
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.commands.operations.IAdvancedModelOperation#aboutToNotify(org.eclipse.core.commands.operations.OperationHistoryEvent)
*/
public void aboutToNotify(OperationHistoryEvent event) {
if (triggeringOperation instanceof IAdvancedUndoableOperation) {
((IAdvancedUndoableOperation) triggeringOperation)
.aboutToNotify(event);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.commands.operations.IAdvancedUndoableOperation#computeUndoableStatus(org.eclipse.core.runtime.IProgressMonitor)
*/
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;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.commands.operations.IAdvancedUndoableOperation#computeRedoableStatus(org.eclipse.core.runtime.IProgressMonitor)
*/
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.
* <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
*/
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 = (IUndoableOperation) 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.
*
* @param context
* the context to be added
*
* @since 3.2
*/
public void addContext(IUndoContext context) {
if (triggeringOperation != null) {
triggeringOperation.addContext(context);
recomputeContexts();
}
}
}