/******************************************************************************* * <copyright> * * Copyright (c) 2005, 2012 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: * SAP AG - initial API, implementation and documentation * mwenz - Bug 324859 - Need Undo/Redo support for Non-EMF based domain objects * mwenz - Bug 340627 - Features should be able to indicate cancellation * mwenz - Bug 351053 - Remove the need for WorkspaceCommandStackImpl * mwenz - Bug 371717 - IllegalStateException When updating cells on Diagram * mwenz - Bug 389380 - Undo/Redo handling wrong Command executed by undo action * * </copyright> * *******************************************************************************/ package org.eclipse.graphiti.ui.internal.editor; import java.util.Map; import java.util.Stack; import org.eclipse.core.commands.operations.IOperationHistory; import org.eclipse.emf.common.command.Command; import org.eclipse.emf.transaction.RollbackException; import org.eclipse.emf.workspace.impl.WorkspaceCommandStackImpl; import org.eclipse.graphiti.IExecutionInfo; import org.eclipse.graphiti.features.ICustomUndoableFeature; import org.eclipse.graphiti.features.IFeature; import org.eclipse.graphiti.features.IFeatureAndContext; import org.eclipse.graphiti.features.context.IContext; import org.eclipse.graphiti.internal.command.DefaultExecutionInfo; import org.eclipse.graphiti.ui.internal.services.GraphitiUiInternal; /** * @noinstantiate This class is not intended to be instantiated by clients. * @noextend This class is not intended to be subclassed by clients. */ public class GFWorkspaceCommandStackImpl extends WorkspaceCommandStackImpl { private volatile boolean topLevelCommand = true; private Stack<IExecutionInfo> undoStackForExecutionInfo = new Stack<IExecutionInfo>(); private Stack<IExecutionInfo> redoStackForExecutionInfo = new Stack<IExecutionInfo>(); public GFWorkspaceCommandStackImpl(IOperationHistory history) { super(history); } /* * Bug 371717: Added synchronization to this method to support scenarios * where the diagram is changed with the help of an externally triggered * thread (e.g. operation triggered by a context menu from the project * explorer). In general: avoid different threads outrunning each other on * topLevelCommand. * * @see * org.eclipse.emf.transaction.impl.AbstractTransactionalCommandStack#execute * (org.eclipse.emf.common.command.Command, java.util.Map) */ @Override public synchronized void execute(Command command, Map<?, ?> options) throws InterruptedException, RollbackException { IExecutionInfo executionInfo = null; /* * Needed to retrieve an eventually externally created IExecutionInfo * object (created in GFCommandStack) to the internal execution of a * feature. Could be created here as well, but then there would * potentially be different instances of features and contexts. */ if (options != null) { Object object = options.get(GFCommandStack.OPTION_EXECUTION_INFO); if (object instanceof IExecutionInfo) { executionInfo = (IExecutionInfo) object; options.remove(GFCommandStack.OPTION_EXECUTION_INFO); } } if (topLevelCommand) { topLevelCommand = false; try { super.execute(command, options); /* * Add the execution info to the stack; in case it is not * provided, create it first. Must happen here and not in the * else branch below because the super.execute call add the EMF * command to the history and therefore to the command stack * (thus is the entry relevant for undo) */ if (executionInfo == null) { executionInfo = new DefaultExecutionInfo(); GraphitiUiInternal.getCommandService().completeExecutionInfo((DefaultExecutionInfo) executionInfo, GraphitiUiInternal.getCommandService().transformFromEmfToGefCommand(command)); } /* * Remove the feature and context combinations from the * execution list whose features did not do any changes. The * commands for those features are not placed on the editor * command stack and must also not appear in the stack for * additional undo steps. See Bugzilla 389380 for details. In * case no entry is left in the execution info, it must not be * written to the stack in order to keep the standard and the * additional undo stack in sync. */ executionInfo = GraphitiUiInternal.getCommandService().removeFeaturesWithoutChanges(executionInfo); if (executionInfo.getExecutionList().length > 0) { undoStackForExecutionInfo.push(executionInfo); } } finally { topLevelCommand = true; } } else { command.execute(); if (getMostRecentCommand() != null) { getMostRecentCommand().chain(command); } } } @Override public void dispose() { super.dispose(); redoStackForExecutionInfo.clear(); redoStackForExecutionInfo = null; undoStackForExecutionInfo.clear(); undoStackForExecutionInfo = null; } @Override public void flush() { super.flush(); undoStackForExecutionInfo.clear(); redoStackForExecutionInfo.clear(); } @Override public void redo() { // Care about EMF redo super.redo(); // Check if non-EMF redo is needed and care about it if (!redoStackForExecutionInfo.isEmpty()) { IExecutionInfo ei = redoStackForExecutionInfo.pop(); undoStackForExecutionInfo.push(ei); IFeatureAndContext[] executionList = ei.getExecutionList(); // Traverse operation forwards for (int i = 0; i < executionList.length; i++) { IFeature feature = executionList[i].getFeature(); IContext context = executionList[i].getContext(); if (feature instanceof ICustomUndoableFeature) { ICustomUndoableFeature undoableFeature = (ICustomUndoableFeature) feature; if (undoableFeature.canRedo(context)) { undoableFeature.redo(context); } } } } } @Override public void undo() { // Care about EMF undo super.undo(); // Check if non-EMF undo is needed and care about it if (!undoStackForExecutionInfo.isEmpty()) { IExecutionInfo ei = undoStackForExecutionInfo.pop(); redoStackForExecutionInfo.push(ei); IFeatureAndContext[] executionList = ei.getExecutionList(); // Traverse operations backwards for (int i = executionList.length - 1; i >= 0; i--) { IFeature feature = executionList[i].getFeature(); IContext context = executionList[i].getContext(); if (feature instanceof ICustomUndoableFeature) { ICustomUndoableFeature undoableFeature = (ICustomUndoableFeature) feature; if (undoableFeature.canUndo(context)) { undoableFeature.undo(context); } } } } } }