/*******************************************************************************
* Copyright (c) 2010 SAP AG.
* 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:
* Emil Simeonov - initial API and implementation.
* Dimitar Donchev - initial API and implementation.
* Dimitar Tenev - initial API and implementation.
* Nevena Manova - initial API and implementation.
* Georgi Konstantinov - initial API and implementation.
*******************************************************************************/
package org.eclipse.wst.sse.sieditor.command.common;
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.Status;
import org.eclipse.emf.workspace.AbstractEMFOperation;
import org.eclipse.emf.workspace.EMFCommandOperation;
import org.eclipse.wst.sse.sieditor.model.api.IModelObject;
import org.eclipse.wst.sse.sieditor.model.api.IModelRoot;
/**
* Inherit in order to create a custom command composed of several commands.
*
* The next command to be executed is determined by overriding the
* {@link #getNextOperation(List)} method.<br>
*
* The behaviour of transaction managing and undo/redo execution is defined by
* the {@link TransactionPolicy} and is set by the
* {@link #setTransactionPolicy(TransactionPolicy)}. <br>
*
* When facing problems with inconsistent change application on undo/redo, a
* probable fix is setting the transaction policy to
* {@link TransactionPolicy#MULTI}
*
*/
public abstract class AbstractCompositeNotificationOperation extends AbstractNotificationOperation {
protected List<AbstractNotificationOperation> subOperations;
/**
* Enumeration describing the collection of predefined behaviours for
* transaction handling of the composite notification operation. See each
* one for details:{@link #MULTI}, {@link #SINGLE}, {@link #MIXED} *(default
* behaviour)<br>
* When facing problems with inconsistent change application on undo/redo, a
* probable fix is setting the transactionPolicy to Multiple, thus fine
* graining the stack order in which transaction changes and custom
* implementations are applied
*
* @see TransactionPolicy.{@link #MULTI}<br>
* TransactionPolicy.{@link #SINGLE}<br>
* TransactionPolicy.{@link #MIXED} * <B>DEFAULT BEHAVIOUR</B> <br>
*
* changes and custom implementations are applied.
*
*/
protected enum TransactionPolicy {
/**
* <b>Each one</b> of the composed operations will be executed in a
* <b>separate transaction</b>.<br>
*
* Calls to super.doUndo()/super.doRedo() of the <b>parent</b>
* transaction will be <b>suppressed</b>.<br>
*
* On Undo/redo, the <br>
* {@link AbstractNotificationOperation#undo(IProgressMonitor, IAdaptable)}
* {@link AbstractNotificationOperation#redo(IProgressMonitor, IAdaptable)}
* <br>
* methods will be called in propper order.<br>
*
* note: policy is recomended for commands with custom implementations
* of undo/redo.
*/
MULTI,
/**
* <b>ALL</b> composed operations will be <b>forced</b> to run in the
* <b>parent transaction</b>.<br>
*
* Therefore all the changes will be contained in it.<br>
*
* Calls to super.doUndo()/super.doRedo() of the parent transaction will
* be transfered. Afterwards, in proper order, the<br>
*
* {@link AbstractNotificationOperation#undo(IProgressMonitor, IAdaptable)}
* <br>
* {@link AbstractNotificationOperation#redo(IProgressMonitor, IAdaptable)}
* <br>
* methods of each operation will be called insuring each custom
* implementation of it is applied, though not in the right moment.<br>
*
* note: This mode does not guarantee that custom implementations of
* undo/redo are applied on time.
*/
SINGLE,
/**
* Operations will be run in separate or as part of the parent's
* transaction, in regards to their
* {@link EMFCommandOperation#isReuseParentTransaction()} property.
* Therefore some the changes will be contained in the parent's
* transaction.<br>
*
* Calls to super.doUndo()/super.doRedo() of the parent transaction will
* be transfered.<br>
*
* {@link AbstractNotificationOperation#undo(IProgressMonitor, IAdaptable)}
* <br>
* {@link AbstractNotificationOperation#redo(IProgressMonitor, IAdaptable)}
* <br>
* methods of each operation will be called, in proper order,
* afterwards. <br>
*
* This policy applies change to all insuring that each custom
* implementation of it is called, though not in the right moment.<br>
*
* note: This mode does not guarantee that custom implementations of
* undo/redo are applied on time.
*/
MIXED
}
private TransactionPolicy transactionPolicy = TransactionPolicy.MIXED;
/**
* Method used to set the Transaction policy determining composed operations
* transactions behaviour of the class.
*
* @param transactionPolicy
*
* @see {@link TransactionPolicy},<br>
* {@link AbstractCompositeNotificationOperation},<br>
* TransactionPolicy.{@link #MULTI}<br>
* TransactionPolicy.{@link #SINGLE}<br>
* TransactionPolicy.{@link #MIXED} * <B>DEFAULT BEHAVIOUR</B> <br>
*/
public void setTransactionPolicy(TransactionPolicy transactionPolicy) {
this.transactionPolicy = transactionPolicy;
}
public AbstractCompositeNotificationOperation(final IModelRoot root, final IModelObject modelObjToRefresh,
final String operationLabel) {
super(root, modelObjToRefresh, operationLabel);
subOperations = new ArrayList<AbstractNotificationOperation>(2);
}
public AbstractCompositeNotificationOperation(final IModelRoot root, final IModelObject[] modelObjsToRefresh,
final String operationLabel) {
super(root, modelObjsToRefresh, operationLabel);
subOperations = new ArrayList<AbstractNotificationOperation>(2);
}
@Override
public boolean canUndo() {
for (final AbstractNotificationOperation entry : subOperations) {
if (!entry.canUndo()) {
return false;
}
}
return true;
}
@Override
public boolean canRedo() {
for (final AbstractNotificationOperation subOperation : subOperations) {
if (!subOperation.canRedo()) {
return false;
}
}
return true;
}
@Override
public IStatus run(final IProgressMonitor monitor, final IAdaptable info) throws ExecutionException {
IStatus status = Status.OK_STATUS;
AbstractNotificationOperation operation;
while ((operation = getNextOperation(subOperations)) != null) {
switch (transactionPolicy) {
case SINGLE:
operation.setReuseParentTransaction(true);
break;
case MULTI:
operation.setReuseParentTransaction(false);
break;
}
status = operation.execute(monitor, info);
if (!status.isOK()) {
break;
}
subOperations.add(operation);
}
return status;
}
/**
* Override to provide the next operation to be executed.<br>
* Called when execution starts and after each composed command execution.
*
* When using {@link TransactionPolicy#MIXED}, use the
* {@link AbstractEMFOperation#setReuseParentTransaction(boolean)} to state
* weather the command should be executed in own separate transaction.
* Implement such cases with caution for undo/redo changes application.
*
* @param subOperations
* the list of already added and executed commands
* @return {@link AbstractNotificationOperation} the next operation to be
* executed.
*
* @see {@link TransactionPolicy},<br>
* {@link AbstractCompositeNotificationOperation},<br>
* {@link #setTransactionPolicy(TransactionPolicy)}
*/
public abstract AbstractNotificationOperation getNextOperation(List<AbstractNotificationOperation> subOperations);
}