/****************************************************************************** * 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.emf.commands.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.Map; import org.eclipse.core.commands.operations.IOperationApprover; import org.eclipse.core.commands.operations.IUndoableOperation; import org.eclipse.core.commands.operations.OperationHistoryFactory; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.IStatus; import org.eclipse.emf.transaction.TransactionalEditingDomain; import org.eclipse.emf.workspace.AbstractEMFOperation; import org.eclipse.emf.workspace.CompositeEMFOperation; import org.eclipse.gmf.runtime.common.core.command.CommandResult; import org.eclipse.gmf.runtime.common.core.command.ICommand; import org.eclipse.gmf.runtime.common.core.command.ICompositeCommand; import org.eclipse.gmf.runtime.common.core.internal.command.ICommandWithSettableResult; import org.eclipse.gmf.runtime.common.core.util.StringStatics; /** * An undoable operation that is composed of child {@link IUndoableOperation}s * that are expected to modify EMF model resources. * <p> * The operation provides a list of {@link IFile}s that are expected to be * modified when the operation is executed, undone or redone. An * {@link IOperationApprover} is registered with the * {@link OperationHistoryFactory#getOperationHistory()} to validate the * modification to these resources. * <P> * This class is meant to be instantiated by clients. * * @author ldamus */ public class CompositeTransactionalCommand extends CompositeEMFOperation implements ICompositeCommand, ICommandWithSettableResult { private CommandResult commandResult; /** * Initializes me with the editing domain in which I am making model changes * and a label. * * @param domain * my editing domain * @param label * my user-readable label, should never be <code>null</code>. */ public CompositeTransactionalCommand(TransactionalEditingDomain domain, String label) { super(domain, (label == null) ? StringStatics.BLANK : label); } /** * Initializes me with the editing domain, a label, and transaction options. * * @param domain * my editing domain * @param label * my user-readable label, should never be <code>null</code>. * @param options * for the transaction in which I execute myself, or * <code>null</code> for the default options */ public CompositeTransactionalCommand(TransactionalEditingDomain domain, String label, Map options) { super(domain, (label == null) ? StringStatics.BLANK : label, options); } /** * Initializes me with the editing domain, a label, and child operations. * * @param domain * my editing domain * @param label * my user-readable label, should never be <code>null</code>. * @param children * a list of operations to compose */ public CompositeTransactionalCommand(TransactionalEditingDomain domain, String label, List children) { super(domain, (label == null) ? StringStatics.BLANK : label, children); } /** * Initializes me with the editing domain, a label, and child operations, * and transaction options. * * @param domain * my editing domain * @param label * my user-readable label, should never be <code>null</code>. * @param children * a list of operations to compose * @param options * for the transaction in which I execute myself, or * <code>null</code> for the default options */ public CompositeTransactionalCommand(TransactionalEditingDomain domain, String label, List children, Map options) { super(domain, (label == null) ? StringStatics.BLANK : label, children, options); } /** * Returns the {@link IFile}s for resources that may be modified when the * operation is executed, undone or redone. */ 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); } public CommandResult 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(super.aggregateStatuses(statusList), getReturnValues()); } return commandResult; } /** * Sets the command result. * * @param result * the new result for this command. */ protected void setResult(CommandResult result) { this.commandResult = result; } /** * 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; } /** * Overrides the superclass implementation to set the command result. */ protected IStatus aggregateStatuses(List statuses) { IStatus aggregate = super.aggregateStatuses(statuses); setResult(new CommandResult(aggregate, getReturnValues())); return aggregate; } // Documentation copied from the interface public final ICommand compose(IUndoableOperation operation) { if (operation != null) { add(operation); } return this; } /** * 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 0: return this; case 1: IUndoableOperation child = (IUndoableOperation) iterator() .next(); if (child instanceof ICommand && child instanceof AbstractEMFOperation) { // return the single command if is a kind of EMF operation; // otherwise this composite will be returned to preserve the // EMF transaction behaviour. return ((ICommand) child).reduce(); } default: if (!isTransactionNestingEnabled()) { List children = getChildren(); IUndoableOperation[] opChildren = (IUndoableOperation[]) children .toArray(new IUndoableOperation[children.size()]); children.clear(); for (int i = 0; i < opChildren.length; ++i) { doReduce(opChildren[i], children); } } } return this; } private void doReduce(IUndoableOperation operation, List children) { if (operation instanceof CompositeEMFOperation) { for (Iterator i = ((CompositeEMFOperation) operation).iterator(); i.hasNext();) { doReduce((IUndoableOperation) i.next(), children); } } else { children.add(operation); } } /** * 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; } /** * I can execute if I am not empty and all of my children can execute. */ public boolean canExecute() { return !isEmpty() && super.canExecute(); } /** * I can redo if I am not empty and all my children can all be redone. */ public boolean canRedo() { return !isEmpty() && super.canRedo(); } /** * I can undo if I am not empty and all my children can all be undone. */ public boolean canUndo() { return !isEmpty() && super.canUndo(); } /** * Internal method to set the command result. * * @param result CommandResult to set * @deprecated internal API */ public void internalSetResult(CommandResult result) { this.commandResult = result; } }