blob: d4ff5ced0450dcd321dad2372ec6ad273ab79f1e [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2006, 2010 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.diagram.core;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.OperationHistoryFactory;
import org.eclipse.emf.common.command.AbstractCommand;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.transaction.NotificationFilter;
import org.eclipse.emf.transaction.ResourceSetChangeEvent;
import org.eclipse.emf.transaction.ResourceSetListener;
import org.eclipse.emf.transaction.ResourceSetListenerImpl;
import org.eclipse.emf.transaction.RollbackException;
import org.eclipse.emf.transaction.Transaction;
import org.eclipse.emf.transaction.TransactionChangeDescription;
import org.eclipse.emf.transaction.TransactionalCommandStack;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.impl.FilterManager;
import org.eclipse.emf.transaction.impl.InternalTransaction;
import org.eclipse.emf.transaction.impl.ReadWriteValidatorImpl;
import org.eclipse.emf.transaction.impl.TransactionValidator;
import org.eclipse.emf.transaction.impl.TransactionalEditingDomainImpl;
import org.eclipse.emf.transaction.util.ConditionalRedoCommand;
import org.eclipse.emf.transaction.util.TriggerCommand;
import org.eclipse.emf.workspace.WorkspaceEditingDomainFactory;
import org.eclipse.emf.workspace.impl.WorkspaceCommandStackImpl;
import org.eclipse.gmf.runtime.diagram.core.internal.listener.NotationSemProc;
import org.eclipse.gmf.runtime.diagram.core.listener.DiagramEventBroker;
import org.eclipse.gmf.runtime.emf.core.GMFEditingDomainFactory;
/**
* Factory for {@link TransactionalEditingDomain}s that are properly configured
* to support a GMF diagram application. This factory should be preferred over
* the {@link GMFEditingDomainFactory} because it attaches a listener required
* to update the notation model after changes to the semantic model. Also, it
* handles special use cases involving the DiagramEventBroker.
*
* @author cmahoney
*/
public class DiagramEditingDomainFactory
extends GMFEditingDomainFactory {
protected static class DiagramEditingDomain extends TransactionalEditingDomainImpl {
// The following variable acts as a special latch for the DiagramEventBroker
// listener so that we can allow it to execute in a write transaction context
// while handling a post-commit event.
private InternalTransaction originatingTransaction = null;
private DiagramEventBroker deb = null;
private ResourceSetListener debWrapper = null;
public void addResourceSetListener(ResourceSetListener l) {
if (DiagramEventBroker.class.isInstance(l)) {
assert deb == null;
deb = (DiagramEventBroker)l;
debWrapper = new ResourceSetListenerImpl() {
public boolean isAggregatePrecommitListener() {
return deb.isAggregatePrecommitListener();
}
public boolean isPrecommitOnly() {
return true;
}
public Command transactionAboutToCommit(ResourceSetChangeEvent event)
throws RollbackException {
return deb.transactionAboutToCommit(event);
}
public void resourceSetChanged(ResourceSetChangeEvent event) {
deb.resourceSetChanged(event);
}
public NotificationFilter getFilter() {
return deb.getFilter();
}
public boolean isPostcommitOnly() {
return false;
}
};
super.addResourceSetListener(debWrapper);
} else {
super.addResourceSetListener(l);
}
}
public void removeResourceSetListener(ResourceSetListener l) {
if (DiagramEventBroker.class.isInstance(l)) {
assert deb != null;
deb = null;
super.removeResourceSetListener(debWrapper);
debWrapper = null;
} else {
super.removeResourceSetListener(l);
}
}
public DiagramEditingDomain(AdapterFactory adapterFactory, ResourceSet resourceSet) {
super(adapterFactory, resourceSet);
}
public DiagramEditingDomain(AdapterFactory adapterFactory, TransactionalCommandStack stack, ResourceSet resourceSet) {
super(adapterFactory, stack, resourceSet);
}
public DiagramEditingDomain(AdapterFactory adapterFactory, TransactionalCommandStack stack) {
super(adapterFactory, stack);
}
public DiagramEditingDomain(AdapterFactory adapterFactory) {
super(adapterFactory);
}
public void precommit(InternalTransaction tx) throws RollbackException {
super.precommit(tx);
if ((tx.getParent() == null) && (deb != null)) {
// ensure that when the top-level transaction commits, it
// has a self-chaining composite command as a trigger to
// insert the DiagramEventBroker's post-commit changes into,
// so that the transaction's change description and any other
// AbstractEMFOperation will get the changes automatically
Command existingTriggers = tx.getTriggers();
if (existingTriggers instanceof CompoundCommand) {
// nothing to do: already a self-chaining command
} else if (existingTriggers != null) {
// force it to be a compound by appending a no-op
tx.addTriggers(NOOP_TRIGGER);
} else {
// no triggers, yet? have to add *two* no-ops
tx.addTriggers(NOOP_TRIGGER);
tx.addTriggers(NOOP_TRIGGER);
}
}
}
public void broadcastUnbatched(Notification notification) {
super.broadcastUnbatched(notification);
final ResourceSetChangeEvent unbatchedChangeEvent =
new ResourceSetChangeEvent(this, null, Collections.singletonList(notification));
try {
runExclusive(new Runnable() {
public void run() {
try {
if (deb!=null)
deb.resourceSetChanged(unbatchedChangeEvent);
}catch (Exception e) {
// do nothing for now
}
}});
} catch (InterruptedException e) {
// do ntohing for now
}
}
protected void postcommit(InternalTransaction tx) {
try {
List notifications = getValidator().getNotificationsForPostcommit(tx);
if (deb != null && notifications != null && !notifications.isEmpty()) {
TransactionValidator originalValidator = null;
// Set the latch if it has not yet been set
if (originatingTransaction == null) {
originatingTransaction = tx;
originalValidator = getValidator();
setValidator(new ReadWriteValidatorImpl());
} else {
// In this case we must copy over the notifications and change
// descriptions to the originatingTransaction. Do this
// as a "late trigger command" because the trigger
// mechanism is already understood by some of the
// operations that need to undo/redo these changes
originatingTransaction.addTriggers(new TriggerCommand(
Collections.singletonList(
new DiagramEventBrokerCommand(
tx.getChangeDescription()))));
originatingTransaction.getNotifications().addAll(notifications);
}
try {
ArrayList cache = new ArrayList(notifications.size());
List filtered = FilterManager.getInstance().select(
notifications,
deb.getFilter(),
cache);
HashMap options = new HashMap(originatingTransaction.getOptions());
options.put(Transaction.OPTION_NO_UNDO, Boolean.FALSE);
InternalTransaction newTx = startTransaction(false, options);
try {
deb.resourceSetChanged(
new ResourceSetChangeEvent(
this,
tx,
filtered));
newTx.commit();
} catch (RuntimeException e) {
// close the internal transaction that was created.
newTx.rollback();
throw e;
}
} catch (RollbackException e) {
// Do nothing in the rollback case, we have no change descriptions
// or notifications to propagate.
} finally {
// Undo the latch if we are top-most in the recursion.
if (originatingTransaction == tx) {
originatingTransaction = null;
getValidator().dispose();
setValidator(originalValidator);
}
}
}
} catch (InterruptedException e) {
// Simply fall-through in this case and allow the post commit listeners
// to be notified.
}
// We will only call super on the top-most in the recursion.
if (originatingTransaction == null) {
super.postcommit(tx);
}
}
}
private static class DiagramEventBrokerCommand
extends AbstractCommand
implements ConditionalRedoCommand {
private final TransactionChangeDescription change;
DiagramEventBrokerCommand(TransactionChangeDescription change) {
this.change = change;
}
protected boolean prepare() {
return true;
}
public final void execute() {
// never executed
}
public boolean canUndo() {
return (change != null) && change.canApply();
}
public final void undo() {
if (change != null) {
change.applyAndReverse();
}
}
public boolean canRedo() {
return (change != null) && change.canApply();
}
public final void redo() {
if (change != null) {
change.applyAndReverse();
}
}
}
static final TriggerCommand NOOP_TRIGGER = new TriggerCommand(
Collections.singletonList(new AbstractCommand() {
protected boolean prepare() { return true; }
public void execute() {}
public boolean canUndo() { return true; }
// this command does not need to implement canRedo() because it
// is assumed to be redoable, anyway, which is what we want
public void undo() {}
public void redo() {}}));
/**
* The single shared instance of the GMF diagram editing domain factory.
*/
private static DiagramEditingDomainFactory instance = new DiagramEditingDomainFactory();
/**
* Gets the single shared instance of the GMF diagram editing domain factory.
*
* @return the editing domain factory
*/
public static WorkspaceEditingDomainFactory getInstance() {
return instance;
}
/* (non-Javadoc)
* @see org.eclipse.gmf.runtime.emf.core.GMFEditingDomainFactory#configure(org.eclipse.emf.transaction.TransactionalEditingDomain)
*/
protected void configure(TransactionalEditingDomain domain) {
super.configure(domain);
domain.addResourceSetListener(new NotationSemProc());
}
public TransactionalEditingDomain createEditingDomain() {
TransactionalEditingDomain result = createEditingDomain(OperationHistoryFactory.getOperationHistory());
return result;
}
public TransactionalEditingDomain createEditingDomain(IOperationHistory history) {
WorkspaceCommandStackImpl stack = new WorkspaceCommandStackImpl(history);
TransactionalEditingDomain result = new DiagramEditingDomain(
new ComposedAdapterFactory(
ComposedAdapterFactory.Descriptor.Registry.INSTANCE),
stack);
mapResourceSet(result);
configure(result);
return result;
}
public TransactionalEditingDomain createEditingDomain(ResourceSet rset) {
TransactionalEditingDomain result = createEditingDomain(
rset,
OperationHistoryFactory.getOperationHistory());
return result;
}
public TransactionalEditingDomain createEditingDomain(ResourceSet rset, IOperationHistory history) {
WorkspaceCommandStackImpl stack = new WorkspaceCommandStackImpl(history);
TransactionalEditingDomain result = new DiagramEditingDomain(
new ComposedAdapterFactory(
ComposedAdapterFactory.Descriptor.Registry.INSTANCE),
stack,
rset);
mapResourceSet(result);
configure(result);
return result;
}
}