Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian W. Damus2018-05-08 14:42:26 +0000
committerNicolas FAUVERGUE2018-06-05 09:31:15 +0000
commit193b89ef7c445558bd1af39025906ea6743dca77 (patch)
treebe1238bece6665363fb2660b2ce282dd27a92775
parent9ff5fff7785695f43ee84facab4ae4f02800bb6f (diff)
downloadorg.eclipse.papyrus-193b89ef7c445558bd1af39025906ea6743dca77.tar.gz
org.eclipse.papyrus-193b89ef7c445558bd1af39025906ea6743dca77.tar.xz
org.eclipse.papyrus-193b89ef7c445558bd1af39025906ea6743dca77.zip
Bug 533679: Creation of CFrag should not graphically move elements
Fix the handling of operand ownership for execution specifications to account for (a) that they are not started/finished only by ExecutionOccurrenceSpecifications specifically and (b) that they are not always provided by the rows of the grid model. Since the updating of the rows and columns and fragment ownership is so often done in a read-only context (following operation execution on the history) we can avoid redundant calculations by just always posting the update asynchronously and throttling multiple pending calculations. Altogether this results in the calculation of operand ownership upon initial creation of a combined fragment not being immediately overridden by an incorrect calculation that puts all of the fragments back into the root interaction. https://bugs.eclipse.org/bugs/show_bug.cgi?id=533679 Change-Id: I2abb81a920e2a8a7f20d3a6b702d3194d8e7ae8e Signed-off-by: Christian W. Damus <give.a.damus@gmail.com>
-rw-r--r--plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/OneShotExecutor.java79
-rw-r--r--plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/TransactionHelper.java25
-rw-r--r--plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/TransactionPrecommitExecutor.java23
-rw-r--r--plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/command/AsynchronousCommand.java59
-rw-r--r--plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/ComputeOwnerHelper.java73
-rw-r--r--plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/GridManagementEditPolicy.java39
-rw-r--r--plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/ResizeOperandEditPolicy.java1
-rw-r--r--plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/util/ExecutionSpecificationUtil.java28
-rw-r--r--plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/util/RetryingDeferredAction.java87
-rw-r--r--plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/validation/AsyncValidateCommand.java11
-rw-r--r--tests/junit/framework/org.eclipse.papyrus.junit.utils/src/org/eclipse/papyrus/junit/utils/rules/PapyrusEditorFixture.java107
-rw-r--r--tests/junit/plugins/infra/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/utils/OneShotExecutorTest.java90
-rw-r--r--tests/junit/plugins/infra/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/utils/TransactionHelperTest.java94
-rw-r--r--tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug533679.di2
-rw-r--r--tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug533679.notation129
-rw-r--r--tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug533679.uml29
-rw-r--r--tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/src/org/eclipse/papyrus/uml/diagram/sequence/tests/bug/CombinedFragmentRegressionTest.java96
17 files changed, 909 insertions, 63 deletions
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/OneShotExecutor.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/OneShotExecutor.java
new file mode 100644
index 00000000000..3e69e6d6799
--- /dev/null
+++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/OneShotExecutor.java
@@ -0,0 +1,79 @@
+/*****************************************************************************
+ * Copyright (c) 2018 Christian W. Damus 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.core.utils;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A wrapper for an {@link Executor} that permits only a single
+ * {@link Runnable} to be pending execution at any time. If while
+ * a task is pending additional tasks are requested for execution,
+ * the last requested task will supersede the pending task so that
+ * when some task eventually is executed, it will only be one
+ * task. So, it is best to use this for asynchronous execution of
+ * runnables that all do the same thing, where the aim is just to
+ * avoid redundant execution.
+ *
+ * @since 3.0
+ */
+public final class OneShotExecutor implements Executor {
+ private final AtomicReference<Runnable> pending = new AtomicReference<>();
+ private final Executor delegate;
+
+ /**
+ * Initializes me with the {@code executor} on which to submit
+ * tasks.
+ *
+ * @param executor
+ * the executor to which I delegate execution of tasks
+ */
+ public OneShotExecutor(Executor executor) {
+ super();
+
+ this.delegate = executor;
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ delegate.execute(new Task(command));
+ }
+
+ //
+ // Nested types
+ //
+
+ /**
+ * A one-shot task that replaces any pending task and executes
+ * its delegate if and only if it is still the pending task
+ * when its time to execute arrives.
+ */
+ private final class Task implements Runnable {
+ private final Runnable delegate;
+
+ Task(Runnable command) {
+ super();
+
+ this.delegate = command;
+ pending.set(this);
+ }
+
+ @Override
+ public void run() {
+ if (pending.compareAndSet(this, null)) {
+ delegate.run();
+ }
+ }
+ }
+}
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/TransactionHelper.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/TransactionHelper.java
index 90315f531b5..42e4a220ae8 100644
--- a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/TransactionHelper.java
+++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/TransactionHelper.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2014, 2016 CEA LIST, Christian W. Damus, and others.
+ * Copyright (c) 2014, 2018 CEA LIST, Christian W. Damus, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -9,13 +9,14 @@
* Contributors:
* Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation
* Christian W. Damus (CEA) - bugs 429826, 408491, 433320
- * Christian W. Damus - bugs 451557, 457560, 461629, 463564, 466997, 465416, 485220, 498140
+ * Christian W. Damus - bugs 451557, 457560, 461629, 463564, 466997, 465416, 485220, 498140, 533679
*
*****************************************************************************/
package org.eclipse.papyrus.infra.core.utils;
import java.util.Collections;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
@@ -474,6 +475,26 @@ public class TransactionHelper {
}
/**
+ * Disposes of a {@linkplain #createTransactionExecutor(TransactionalEditingDomain, Executor) transaction executor}
+ * that is no longer needed on an editing domain that is still in use.
+ *
+ * @param executor
+ * a transaction executor to dispose
+ *
+ * @throws IllegalArgumentException
+ * if the {@code executor} is not a transaction executor
+ * @throws NullPointerException
+ * if the {@code executor} is {@code null}
+ * @since 3.0
+ */
+ public static void disposeTransactionExecutor(Executor executor) {
+ if (!(Objects.requireNonNull(executor) instanceof TransactionPrecommitExecutor)) {
+ throw new IllegalArgumentException("executor"); //$NON-NLS-1$
+ }
+ ((TransactionPrecommitExecutor) executor).dispose();
+ }
+
+ /**
* <p>
* Create a privileged progress runnable, which is like a regular {@linkplain TransactionalEditingDomain#createPrivilegedRunnable(Runnable)
* privileged runnable} except that it is given a progress monitor for progress reporting.
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/TransactionPrecommitExecutor.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/TransactionPrecommitExecutor.java
index 75df6652d6c..bbb1592ed88 100644
--- a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/TransactionPrecommitExecutor.java
+++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/TransactionPrecommitExecutor.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2015 Christian W. Damus and others.
+ * Copyright (c) 2015, 2018 Christian W. Damus and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -39,6 +39,7 @@ import com.google.common.collect.Maps;
* write transaction is active.
*/
class TransactionPrecommitExecutor implements Executor, TransactionalEditingDomainListener {
+ private final TransactionalEditingDomain editingDomain;
private final Executor fallback;
private final AtomicBoolean writeActive = new AtomicBoolean();
@@ -46,9 +47,12 @@ class TransactionPrecommitExecutor implements Executor, TransactionalEditingDoma
private final IExecutorPolicy policy;
private final Map<?, ?> options;
+ private final AtomicBoolean disposed = new AtomicBoolean();
+
TransactionPrecommitExecutor(TransactionalEditingDomain domain, Executor fallback, IExecutorPolicy policy, Map<?, ?> options) {
super();
+ this.editingDomain = domain;
this.fallback = fallback;
this.policy = (policy == null) ? IExecutorPolicy.NULL : policy;
this.options = ((options != null) && options.isEmpty()) ? null : options;
@@ -56,8 +60,25 @@ class TransactionPrecommitExecutor implements Executor, TransactionalEditingDoma
TransactionUtil.getAdapter(domain, TransactionalEditingDomain.Lifecycle.class).addTransactionalEditingDomainListener(this);
}
+ /**
+ * Disposes me. This entails at least desisting in listening to lifecycle changes in
+ * my editing domain.
+ */
+ public void dispose() {
+ if (disposed.compareAndSet(false, true)) {
+ TransactionalEditingDomain.Lifecycle lifecycle = TransactionUtil.getAdapter(editingDomain, TransactionalEditingDomain.Lifecycle.class);
+ if (lifecycle != null) {
+ lifecycle.removeTransactionalEditingDomainListener(this);
+ }
+ }
+ }
+
@Override
public void execute(Runnable command) {
+ if (disposed.get()) {
+ return;
+ }
+
if (writeActive.get() && selectSelf(command)) {
queue.offer(command);
} else {
diff --git a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/command/AsynchronousCommand.java b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/command/AsynchronousCommand.java
index a82671e62f1..91ba0cb0aba 100644
--- a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/command/AsynchronousCommand.java
+++ b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/command/AsynchronousCommand.java
@@ -14,6 +14,7 @@
package org.eclipse.papyrus.uml.diagram.sequence.command;
import java.util.List;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
import org.eclipse.core.commands.ExecutionException;
@@ -28,6 +29,7 @@ import org.eclipse.gmf.runtime.common.core.command.UnexecutableCommand;
import org.eclipse.papyrus.infra.emf.gmf.util.GMFUnsafe;
import org.eclipse.papyrus.uml.diagram.sequence.part.UMLDiagramEditorPlugin;
import org.eclipse.papyrus.uml.diagram.sequence.util.RetryingDeferredAction;
+import org.eclipse.swt.widgets.Display;
/**
* A command that posts its execution asynchronously on the UI thread
@@ -39,11 +41,12 @@ import org.eclipse.papyrus.uml.diagram.sequence.util.RetryingDeferredAction;
public class AsynchronousCommand extends AbstractCommand {
private final TransactionalEditingDomain editingDomain;
+ private final Executor executor;
private Supplier<ICommand> futureCommand;
private ICommand actualCommand;
/**
- * Initializes me with my label and an asynchronous supplier of the command
+ * Initializes me with my {@code label} and an asynchronous supplier of the command
* to be executed.
*
* @param label
@@ -58,15 +61,58 @@ public class AsynchronousCommand extends AbstractCommand {
* up to three times
*/
public AsynchronousCommand(String label, TransactionalEditingDomain editingDomain, Supplier<ICommand> futureCommand) {
+ this(label, editingDomain, futureCommand, Display.getDefault()::asyncExec);
+ }
+
+ /**
+ * Initializes me with my {@code label}, affected files, and an asynchronous supplier of the command
+ * to be executed.
+ *
+ * @param label
+ * my label
+ * @param affectedFiles
+ * my affected files
+ * @param editingDomain
+ * my contextual editing domain
+ * @param futureCommand
+ * a supplier of the command. It will be invoked later on
+ * the UI thread to compute the command to be executed then and captured
+ * back into the original context of this GMF command. If the supplied
+ * command is {@code null}, then it will be asynchronously re-tried
+ * up to three times
+ */
+ public AsynchronousCommand(String label, @SuppressWarnings("rawtypes") List affectedFiles, TransactionalEditingDomain editingDomain, Supplier<ICommand> futureCommand) {
+ this(label, affectedFiles, editingDomain, futureCommand, Display.getDefault()::asyncExec);
+ }
+
+ /**
+ * Initializes me with my {@code label}, an asynchronous supplier of the command
+ * to be executed, and an {@code executor} on which to run.
+ *
+ * @param label
+ * my label
+ * @param editingDomain
+ * my contextual editing domain
+ * @param futureCommand
+ * a supplier of the command. It will be invoked later on
+ * the UI thread to compute the command to be executed then and captured
+ * back into the original context of this GMF command. If the supplied
+ * command is {@code null}, then it will be asynchronously re-tried
+ * up to three times
+ * @param executor
+ * the executor on which to run myself
+ */
+ public AsynchronousCommand(String label, TransactionalEditingDomain editingDomain, Supplier<ICommand> futureCommand, Executor executor) {
super(label);
this.editingDomain = editingDomain;
this.futureCommand = futureCommand;
+ this.executor = executor;
}
/**
- * Initializes me with my label, affected filesm, and an asynchronous supplier of the command
- * to be executed.
+ * Initializes me with my {@code label}, affected files, an asynchronous supplier of the command
+ * to be executed, and an {@code executor} on which to run.
*
* @param label
* my label
@@ -80,17 +126,20 @@ public class AsynchronousCommand extends AbstractCommand {
* back into the original context of this GMF command. If the supplied
* command is {@code null}, then it will be asynchronously re-tried
* up to three times
+ * @param executor
+ * the executor on which to run myself
*/
- public AsynchronousCommand(String label, @SuppressWarnings("rawtypes") List affectedFiles, TransactionalEditingDomain editingDomain, Supplier<ICommand> futureCommand) {
+ public AsynchronousCommand(String label, @SuppressWarnings("rawtypes") List affectedFiles, TransactionalEditingDomain editingDomain, Supplier<ICommand> futureCommand, Executor executor) {
super(label, affectedFiles);
this.editingDomain = editingDomain;
this.futureCommand = futureCommand;
+ this.executor = executor;
}
@Override
protected CommandResult doExecuteWithResult(IProgressMonitor progressMonitor, IAdaptable info) throws ExecutionException {
- RetryingDeferredAction.defer(this::captureCommand);
+ RetryingDeferredAction.defer(executor, this::captureCommand);
return CommandResult.newOKCommandResult();
}
diff --git a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/ComputeOwnerHelper.java b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/ComputeOwnerHelper.java
index 3614b761a51..e85d91dee02 100644
--- a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/ComputeOwnerHelper.java
+++ b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/ComputeOwnerHelper.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2017 CEA LIST and others.
+ * Copyright (c) 2017, 2018 CEA LIST, Christian W. Damus, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -9,20 +9,26 @@
* Contributors:
* CEA LIST - Initial API and implementation
* Mickaƫl ADAM (ALL4TEC) mickael.adam@all4tec.net - Bug 525369
+ * Christian W. Damus - bug 533679
*****************************************************************************/
package org.eclipse.papyrus.uml.diagram.sequence.referencialgrilling;
+import static org.eclipse.papyrus.uml.diagram.sequence.util.ExecutionSpecificationUtil.getStartedExecution;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Optional;
+import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.edit.command.SetCommand;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.gmf.runtime.notation.DecorationNode;
import org.eclipse.papyrus.uml.diagram.sequence.part.UMLDiagramEditorPlugin;
import org.eclipse.papyrus.uml.diagram.sequence.util.LogOptions;
+import org.eclipse.papyrus.uml.diagram.sequence.validation.AsyncValidateCommand;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.ExecutionOccurrenceSpecification;
import org.eclipse.uml2.uml.ExecutionSpecification;
@@ -30,6 +36,7 @@ import org.eclipse.uml2.uml.Interaction;
import org.eclipse.uml2.uml.InteractionFragment;
import org.eclipse.uml2.uml.InteractionOperand;
import org.eclipse.uml2.uml.Lifeline;
+import org.eclipse.uml2.uml.OccurrenceSpecification;
import org.eclipse.uml2.uml.UMLPackage;
/**
@@ -51,7 +58,7 @@ public class ComputeOwnerHelper implements IComputeOwnerHelper {
}
}
if (column.getElement() instanceof Lifeline) {
- HorizontalLifeLinetoOperand.put((Lifeline) column.getElement(), (ArrayList<InteractionOperand>) interactionOperandStack.clone());
+ HorizontalLifeLinetoOperand.put((Lifeline) column.getElement(), new ArrayList<>(interactionOperandStack));
}
@@ -70,7 +77,7 @@ public class ComputeOwnerHelper implements IComputeOwnerHelper {
interactionOperandStack.add((InteractionOperand) row.getElement());
}
} else if (row.getElement() instanceof Element) {
- verticalElementToOperand.put((Element) row.getElement(), (ArrayList<InteractionOperand>) interactionOperandStack.clone());
+ verticalElementToOperand.put((Element) row.getElement(), new ArrayList<>(interactionOperandStack));
}
}
@@ -92,8 +99,8 @@ public class ComputeOwnerHelper implements IComputeOwnerHelper {
ArrayList<InteractionFragment> elementForInteraction = new ArrayList<>();
// list of element for the interactionOperand
- HashMap<InteractionOperand, ArrayList<InteractionFragment>> elementForIneractionOp = new HashMap<>();
- Iterator elementInteraction = interaction.eAllContents();
+ HashMap<InteractionOperand, ArrayList<InteractionFragment>> elementForInteractionOp = new HashMap<>();
+ Iterator<EObject> elementInteraction = interaction.eAllContents();
while (elementInteraction.hasNext()) {
Element element = (Element) elementInteraction.next();
if (element instanceof InteractionFragment) {
@@ -107,19 +114,21 @@ public class ComputeOwnerHelper implements IComputeOwnerHelper {
if (potentialoperand.size() >= 1) {
simplifyOwnerInteractionOperand(potentialoperand);
if (potentialoperand.size() == 1) {
- if (elementForIneractionOp.get(potentialoperand.get(0)) == null) {
- elementForIneractionOp.put(potentialoperand.get(0), new ArrayList<InteractionFragment>());
+ if (elementForInteractionOp.get(potentialoperand.get(0)) == null) {
+ elementForInteractionOp.put(potentialoperand.get(0), new ArrayList<InteractionFragment>());
}
- elementForIneractionOp.get(potentialoperand.get(0)).add(aFragment);
- if (aFragment instanceof ExecutionOccurrenceSpecification) {
- elementForIneractionOp.get(potentialoperand.get(0)).add(((ExecutionOccurrenceSpecification) aFragment).getExecution());
+ elementForInteractionOp.get(potentialoperand.get(0)).add(aFragment);
+ if (aFragment instanceof OccurrenceSpecification) {
+ Optional<ExecutionSpecification> exec = getStartedExecution((OccurrenceSpecification) aFragment);
+ exec.ifPresent(elementForInteractionOp.get(potentialoperand.get(0))::add);
}
}
} else {
if (!(aFragment instanceof InteractionOperand)) {
elementForInteraction.add(aFragment);
if (aFragment instanceof ExecutionOccurrenceSpecification) {
- elementForInteraction.add(((ExecutionOccurrenceSpecification) aFragment).getExecution());
+ Optional<ExecutionSpecification> exec = getStartedExecution((OccurrenceSpecification) aFragment);
+ exec.ifPresent(elementForInteraction::add);
}
}
}
@@ -133,10 +142,10 @@ public class ComputeOwnerHelper implements IComputeOwnerHelper {
}
// update fragments of interaction operrands
- Iterator<InteractionOperand> iterator = elementForIneractionOp.keySet().iterator();
+ Iterator<InteractionOperand> iterator = elementForInteractionOp.keySet().iterator();
while (iterator.hasNext()) {
InteractionOperand interactionOperand = iterator.next();
- ArrayList<InteractionFragment> elements = elementForIneractionOp.get(interactionOperand);
+ ArrayList<InteractionFragment> elements = elementForInteractionOp.get(interactionOperand);
if (elements.size() != 0) {
// sort list bu taking
ArrayList<InteractionFragment> existedFragments = new ArrayList<>();
@@ -144,6 +153,9 @@ public class ComputeOwnerHelper implements IComputeOwnerHelper {
existedFragments.addAll(sorted);
existedFragments.addAll(interactionOperand.getFragments());
grid.execute(new SetCommand(domain, interactionOperand, UMLPackage.eINSTANCE.getInteractionOperand_Fragment(), existedFragments));
+
+ AsyncValidateCommand.get(interactionOperand)
+ .ifPresent(grid::execute);
}
}
@@ -188,7 +200,18 @@ public class ComputeOwnerHelper implements IComputeOwnerHelper {
DecorationNode row = iteratorRow.next();
if (fragments.contains(row.getElement())) {
if (!sortedList.contains(row.getElement())) {
- sortedList.add((InteractionFragment) row.getElement());
+ InteractionFragment fragment = (InteractionFragment) row.getElement();
+ sortedList.add(fragment);
+
+ if (fragment instanceof OccurrenceSpecification) {
+ // These (often) aren't in the rows
+ Optional<ExecutionSpecification> execSpec = getStartedExecution((OccurrenceSpecification) fragment);
+ execSpec.ifPresent(exec -> {
+ if (!sortedList.contains(exec)) {
+ sortedList.add(exec);
+ }
+ });
+ }
}
}
@@ -203,17 +226,17 @@ public class ComputeOwnerHelper implements IComputeOwnerHelper {
* @param operandList
*/
protected static void simplifyOwnerInteractionOperand(ArrayList<InteractionOperand> operandList) {
-/*
- while (operandList.size() > 1) {
-
- InteractionOperand last = operandList.get(operandList.size() - 1);
- EObject parent = last.eContainer();
- while (parent != null) {
- operandList.remove(parent);
- parent = parent.eContainer();
- }
- }
-*/
+ /*
+ * while (operandList.size() > 1) {
+ *
+ * InteractionOperand last = operandList.get(operandList.size() - 1);
+ * EObject parent = last.eContainer();
+ * while (parent != null) {
+ * operandList.remove(parent);
+ * parent = parent.eContainer();
+ * }
+ * }
+ */
}
/**
diff --git a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/GridManagementEditPolicy.java b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/GridManagementEditPolicy.java
index 557f6a429a1..fffaf63c898 100644
--- a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/GridManagementEditPolicy.java
+++ b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/GridManagementEditPolicy.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2016, 2017 CEA LIST, ALL4TEC and others.
+ * Copyright (c) 2016, 2018 CEA LIST, ALL4TEC, Christian W. Damus, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -10,6 +10,7 @@
* CEA LIST - Initial API and implementation
* Mickaƫl ADAM (ALL4TEC) mickael.adam@all4tec.net - Bug 519756
* Vincent Lorenzo (CEA LIST) vincent.lorenzo@cea.fr - Bug 531936
+ * Christian W. Damus - bug 533679
*****************************************************************************/
package org.eclipse.papyrus.uml.diagram.sequence.referencialgrilling;
@@ -21,6 +22,7 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.concurrent.Executor;
import org.eclipse.core.commands.operations.OperationHistoryFactory;
import org.eclipse.draw2d.geometry.Point;
@@ -42,6 +44,8 @@ import org.eclipse.gmf.runtime.notation.Location;
import org.eclipse.gmf.runtime.notation.Node;
import org.eclipse.gmf.runtime.notation.NotationPackage;
import org.eclipse.gmf.runtime.notation.View;
+import org.eclipse.papyrus.infra.core.utils.OneShotExecutor;
+import org.eclipse.papyrus.infra.core.utils.TransactionHelper;
import org.eclipse.papyrus.infra.gmfdiag.common.editpolicies.AutomaticNotationEditPolicy;
import org.eclipse.papyrus.infra.gmfdiag.common.utils.DiagramEditPartsUtil;
import org.eclipse.papyrus.uml.diagram.sequence.command.CreateCoordinateCommand;
@@ -49,6 +53,7 @@ import org.eclipse.papyrus.uml.diagram.sequence.command.CreateGrillingStructureC
import org.eclipse.papyrus.uml.diagram.sequence.part.UMLDiagramEditorPlugin;
import org.eclipse.papyrus.uml.diagram.sequence.util.LogOptions;
import org.eclipse.papyrus.uml.diagram.sequence.util.RedirectionOperationListener;
+import org.eclipse.swt.widgets.Display;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.ExecutionOccurrenceSpecification;
import org.eclipse.uml2.uml.Interaction;
@@ -71,6 +76,9 @@ public class GridManagementEditPolicy extends GraphicalEditPolicyEx implements A
public static int threshold = 5;
+ private Executor transactionExecutor;
+ private Executor coveredUpdateExecutor;
+
/**
* @return the threshold
*/
@@ -183,6 +191,12 @@ public class GridManagementEditPolicy extends GraphicalEditPolicyEx implements A
@Override
public void activate() {
super.activate();
+
+ transactionExecutor = TransactionHelper.createTransactionExecutor(
+ ((IGraphicalEditPart) getHost()).getEditingDomain(),
+ Display.getCurrent()::asyncExec);
+ coveredUpdateExecutor = new OneShotExecutor(transactionExecutor);
+
getDiagramEventBroker().addNotificationListener(((EObject) getHost().getModel()), this);
// contentDiagramListener = new ContentDiagramListener(this);
@@ -224,8 +238,7 @@ public class GridManagementEditPolicy extends GraphicalEditPolicyEx implements A
i++;
}
// cleanUnusedRowAndColumn();
- updateRowsAndColumns();
- updateCoveredAndOwnerAfterUpdate();
+ postRowColumnCoverageUpdate();
}
/**
@@ -353,6 +366,10 @@ public class GridManagementEditPolicy extends GraphicalEditPolicyEx implements A
@Override
public void deactivate() {
getDiagramEventBroker().removeNotificationListener(((EObject) getHost().getModel()), this);
+ TransactionHelper.disposeTransactionExecutor(transactionExecutor);
+ transactionExecutor = null;
+ coveredUpdateExecutor = null;
+
if (null != this.operationHistoryListener) {
OperationHistoryFactory.getOperationHistory().removeOperationHistoryListener(this.operationHistoryListener);
}
@@ -366,11 +383,21 @@ public class GridManagementEditPolicy extends GraphicalEditPolicyEx implements A
*/
@Override
public void notifyChanged(Notification notification) {
- updateRowsAndColumns();
- updateCoveredAndOwnerAfterUpdate();
+ postRowColumnCoverageUpdate();
}
+ private void postRowColumnCoverageUpdate() {
+ Runnable update = () -> {
+ updateRowsAndColumns();
+ updateCoveredAndOwnerAfterUpdate();
+ };
+ if (coveredUpdateExecutor == null) {
+ update.run();
+ } else {
+ coveredUpdateExecutor.execute(update);
+ }
+ }
/**
* get the decoration node that represents a column from a position (absolute)
@@ -517,4 +544,4 @@ public class GridManagementEditPolicy extends GraphicalEditPolicyEx implements A
}
}
-} \ No newline at end of file
+}
diff --git a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/ResizeOperandEditPolicy.java b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/ResizeOperandEditPolicy.java
index b5a925b1ae0..8a87ae6ea46 100644
--- a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/ResizeOperandEditPolicy.java
+++ b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/ResizeOperandEditPolicy.java
@@ -31,7 +31,6 @@ import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.gmf.runtime.common.core.command.CompositeCommand;
import org.eclipse.gmf.runtime.common.core.command.ICommand;
import org.eclipse.gmf.runtime.diagram.ui.commands.ICommandProxy;
-import org.eclipse.gmf.runtime.diagram.ui.editparts.GraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart;
import org.eclipse.gmf.runtime.emf.core.util.EObjectAdapter;
import org.eclipse.gmf.runtime.notation.View;
diff --git a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/util/ExecutionSpecificationUtil.java b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/util/ExecutionSpecificationUtil.java
index 3830a3e4b67..7d9fb7130b2 100644
--- a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/util/ExecutionSpecificationUtil.java
+++ b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/util/ExecutionSpecificationUtil.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2017 CEA LIST and others.
+ * Copyright (c) 2017, 2018 CEA LIST, Christian W. Damus, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -8,6 +8,7 @@
*
* Contributors:
* Nicolas FAUVERGUE (CEA LIST) nicolas.fauvergue@cea.fr - Initial API and implementation
+ * Christian W. Damus - bug 533679
*
*****************************************************************************/
@@ -19,8 +20,11 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.function.Predicate;
import org.eclipse.draw2d.geometry.Rectangle;
+import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.gmf.runtime.diagram.ui.commands.ICommandProxy;
@@ -31,6 +35,10 @@ import org.eclipse.papyrus.uml.diagram.sequence.command.SetResizeAndLocationComm
import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.AbstractExecutionSpecificationEditPart;
import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.LifelineEditPart;
import org.eclipse.papyrus.uml.diagram.sequence.referencialgrilling.BoundForEditPart;
+import org.eclipse.uml2.common.util.CacheAdapter;
+import org.eclipse.uml2.uml.ExecutionSpecification;
+import org.eclipse.uml2.uml.OccurrenceSpecification;
+import org.eclipse.uml2.uml.UMLPackage;
/**
* This call allows to define needed methods for the exeuction specification objects.
@@ -301,4 +309,22 @@ public class ExecutionSpecificationUtil {
return result;
}
+
+ /**
+ * Query the execution, if any, that is started by an {@code occurrence}.
+ *
+ * @param occurrence
+ * an occurrence specification
+ * @return the execution specification that it starts
+ * @since 5.0
+ */
+ public static Optional<ExecutionSpecification> getStartedExecution(OccurrenceSpecification occurrence) {
+ CacheAdapter cache = CacheAdapter.getCacheAdapter(occurrence);
+ Predicate<EStructuralFeature.Setting> settingFilter = setting -> setting.getEStructuralFeature() == UMLPackage.Literals.EXECUTION_SPECIFICATION__START;
+
+ return cache.getInverseReferences(occurrence).stream()
+ .filter(settingFilter).findFirst()
+ .map(EStructuralFeature.Setting::getEObject)
+ .map(ExecutionSpecification.class::cast);
+ }
}
diff --git a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/util/RetryingDeferredAction.java b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/util/RetryingDeferredAction.java
index 6c38f53e55c..d2c5298a622 100644
--- a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/util/RetryingDeferredAction.java
+++ b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/util/RetryingDeferredAction.java
@@ -13,6 +13,7 @@
package org.eclipse.papyrus.uml.diagram.sequence.util;
+import java.util.concurrent.Executor;
import java.util.function.BooleanSupplier;
import org.eclipse.papyrus.uml.diagram.sequence.part.UMLDiagramEditorPlugin;
@@ -27,34 +28,61 @@ import org.eclipse.swt.widgets.Display;
public abstract class RetryingDeferredAction {
private static final int DEFAULT_RETRY_LIMIT = 3;
- private final Display display;
+ private final Executor executor;
private final int retryLimit;
private volatile int retries;
/**
- * Initializes me.
+ * Initializes me with the {@code executor} on which to run myself.
*
- * @param display
- * the display on which I post myself for delayed execution
+ * @param executor
+ * the executor on which to run myself
* @param retryLimit
* the number of times I may retry
*
* @throws IllegalArgumentException
* if the retry limit is non-positive
*/
- public RetryingDeferredAction(Display display, int retryLimit) {
+ public RetryingDeferredAction(Executor executor, int retryLimit) {
super();
if (retryLimit <= 0) {
throw new IllegalArgumentException("retry limit must be positive"); //$NON-NLS-1$
}
- this.display = display;
+ this.executor = executor;
this.retryLimit = retryLimit;
}
/**
- * Initializes me with the default number (three) of retries.
+ * Initializes me with the default number (three) of retries and
+ * an {@code executor} on which to run myself.
+ *
+ * @param executor
+ * the executor on which to run myself
+ */
+ public RetryingDeferredAction(Executor executor) {
+ this(executor, DEFAULT_RETRY_LIMIT);
+ }
+
+ /**
+ * Initializes me with a {@code display} on which to executor myself.
+ *
+ * @param display
+ * the display on which I post myself for delayed execution
+ * @param retryLimit
+ * the number of times I may retry
+ *
+ * @throws IllegalArgumentException
+ * if the retry limit is non-positive
+ */
+ public RetryingDeferredAction(Display display, int retryLimit) {
+ this(display::asyncExec, retryLimit);
+ }
+
+ /**
+ * Initializes me with the default number (three) of retries and
+ * a {@code display} on which to executor myself.
*
* @param display
* the display on which I post myself for delayed execution
@@ -64,7 +92,7 @@ public abstract class RetryingDeferredAction {
}
/**
- * Initializes me with the current display.
+ * Initializes me to execute myself asynchronously on the current display.
*
* @param retryLimit
* the number of times I may retry
@@ -77,7 +105,8 @@ public abstract class RetryingDeferredAction {
}
/**
- * Initializes me with the current display and the default number (three) of retries.
+ * Initializes me to execute myself asynchronously on the current display and
+ * with the default number (three) of retries.
*/
public RetryingDeferredAction() {
this(Display.getCurrent(), DEFAULT_RETRY_LIMIT);
@@ -100,7 +129,7 @@ public abstract class RetryingDeferredAction {
* if the retry limit is non-positive
*/
public static void defer(Display display, int retryLimit, BooleanSupplier action) {
- new Wrapper(display, retryLimit, action).post();
+ defer(display::asyncExec, retryLimit, action);
}
/**
@@ -116,6 +145,38 @@ public abstract class RetryingDeferredAction {
}
/**
+ * Try an {@code action} up to the given number of times, asynchronously on an {@code executor}.
+ * This is useful for the simple case where it is only necessary to attempt to perform the
+ * action and there is no need for an explicit preparation step.
+ *
+ * @param executor
+ * the executor on which to run myself
+ * @param retryLimit
+ * the maximal number of times to tr-try the {@code action}
+ * @param action
+ * the action to perform. If it returns {@code false}, then it will
+ * be re-tried (unless the limit is exceeded, of course)
+ *
+ * @throws IllegalArgumentException
+ * if the retry limit is non-positive
+ */
+ public static void defer(Executor executor, int retryLimit, BooleanSupplier action) {
+ new Wrapper(executor, retryLimit, action).post();
+ }
+
+ /**
+ * Try an {@code action} up to the default number (three) of times, asynchronously on an {@code executor}.
+ *
+ * @param executor
+ * the executor on which to run myself
+ * @param action
+ * the action to perform
+ */
+ public static void defer(Executor executor, BooleanSupplier action) {
+ defer(executor, DEFAULT_RETRY_LIMIT, action);
+ }
+
+ /**
* Try an {@code action} up to the given number of times, deferred on the current display thread.
*
* @param retryLimit
@@ -168,7 +229,7 @@ public abstract class RetryingDeferredAction {
*/
public void post() {
if (retries < retryLimit) {
- display.asyncExec(this::run);
+ executor.execute(this::run);
} else {
UMLDiagramEditorPlugin.log.warn("Retry limit exceeded for " + this); //$NON-NLS-1$
}
@@ -181,8 +242,8 @@ public abstract class RetryingDeferredAction {
private static final class Wrapper extends RetryingDeferredAction {
private final BooleanSupplier action;
- Wrapper(Display display, int retryLimit, BooleanSupplier action) {
- super(display, retryLimit);
+ Wrapper(Executor executor, int retryLimit, BooleanSupplier action) {
+ super(executor, retryLimit);
this.action = action;
}
diff --git a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/validation/AsyncValidateCommand.java b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/validation/AsyncValidateCommand.java
index 4f43cc45447..e85ba137ed2 100644
--- a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/validation/AsyncValidateCommand.java
+++ b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/validation/AsyncValidateCommand.java
@@ -16,6 +16,7 @@ package org.eclipse.papyrus.uml.diagram.sequence.validation;
import static org.eclipse.papyrus.uml.diagram.sequence.util.OccurrenceSpecificationHelper.findExecutionWith;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -29,11 +30,13 @@ import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.transaction.util.TransactionUtil;
import org.eclipse.gmf.runtime.common.core.command.ICommand;
import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.papyrus.infra.core.utils.OneShotExecutor;
import org.eclipse.papyrus.infra.emf.gmf.command.INonDirtying;
import org.eclipse.papyrus.infra.services.validation.ValidationTool;
import org.eclipse.papyrus.infra.services.validation.commands.ValidateSubtreeCommand;
import org.eclipse.papyrus.uml.diagram.sequence.command.AsynchronousCommand;
import org.eclipse.papyrus.uml.diagram.sequence.part.UMLDiagramEditorPlugin;
+import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.uml2.uml.ExecutionSpecification;
import org.eclipse.uml2.uml.InteractionOperand;
@@ -43,10 +46,13 @@ import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.OccurrenceSpecification;
import org.eclipse.uml2.uml.util.UMLSwitch;
+import com.google.common.collect.MapMaker;
+
/**
* An asynchronous validation command.
*/
public class AsyncValidateCommand extends AsynchronousCommand implements INonDirtying {
+ private static Map<EObject, OneShotExecutor> executors = new MapMaker().weakKeys().makeMap();
/**
* Initializes me with the {@code object to validate}.
@@ -55,7 +61,10 @@ public class AsyncValidateCommand extends AsynchronousCommand implements INonDir
* the object to validate later
*/
public AsyncValidateCommand(EObject object) {
- super("Validate", TransactionUtil.getEditingDomain(object), () -> validate(object));
+ super("Validate", TransactionUtil.getEditingDomain(object), () -> validate(object),
+ // Ensure that only one async validation of this object can be pending at any time
+ // and that subsequent requests just supersede any previous pending requests
+ executors.computeIfAbsent(object, __ -> new OneShotExecutor(Display.getDefault()::asyncExec)));
}
private static ICommand validate(EObject object) {
diff --git a/tests/junit/framework/org.eclipse.papyrus.junit.utils/src/org/eclipse/papyrus/junit/utils/rules/PapyrusEditorFixture.java b/tests/junit/framework/org.eclipse.papyrus.junit.utils/src/org/eclipse/papyrus/junit/utils/rules/PapyrusEditorFixture.java
index 818ac24103b..21c36251496 100644
--- a/tests/junit/framework/org.eclipse.papyrus.junit.utils/src/org/eclipse/papyrus/junit/utils/rules/PapyrusEditorFixture.java
+++ b/tests/junit/framework/org.eclipse.papyrus.junit.utils/src/org/eclipse/papyrus/junit/utils/rules/PapyrusEditorFixture.java
@@ -8,7 +8,7 @@
*
* Contributors:
* Christian W. Damus (CEA) - Initial API and implementation
- * Christian W. Damus - bugs 433206, 465416, 434983, 483721, 469188, 485220, 491542, 497865, 533673, 533682, 533676
+ * Christian W. Damus - bugs 433206, 465416, 434983, 483721, 469188, 485220, 491542, 497865, 533673, 533682, 533676, 533679
* Thanh Liem PHAN (ALL4TEC) thanhliem.phan@all4tec.net - Bug 521550
*****************************************************************************/
package org.eclipse.papyrus.junit.utils.rules;
@@ -20,10 +20,12 @@ import static org.junit.Assert.fail;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.core.commands.operations.IOperationHistory;
@@ -61,7 +63,9 @@ import org.eclipse.emf.edit.provider.IItemLabelProvider;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.workspace.IWorkspaceCommandStack;
import org.eclipse.gef.EditPart;
+import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.GraphicalEditPart;
+import org.eclipse.gef.Request;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.RootEditPart;
import org.eclipse.gef.requests.ChangeBoundsRequest;
@@ -75,6 +79,7 @@ import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramEditor;
import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramEditorWithFlyOutPalette;
import org.eclipse.gmf.runtime.diagram.ui.parts.IDiagramWorkbenchPart;
import org.eclipse.gmf.runtime.diagram.ui.requests.CreateViewRequest;
+import org.eclipse.gmf.runtime.diagram.ui.requests.CreateViewRequest.ViewDescriptor;
import org.eclipse.gmf.runtime.diagram.ui.requests.CreateViewRequestFactory;
import org.eclipse.gmf.runtime.diagram.ui.requests.EditCommandRequestWrapper;
import org.eclipse.gmf.runtime.emf.type.core.IElementType;
@@ -93,6 +98,7 @@ import org.eclipse.papyrus.infra.core.services.ServiceException;
import org.eclipse.papyrus.infra.core.services.ServicesRegistry;
import org.eclipse.papyrus.infra.core.utils.ServiceUtils;
import org.eclipse.papyrus.infra.gmfdiag.common.model.NotationModel;
+import org.eclipse.papyrus.infra.gmfdiag.common.service.palette.AspectUnspecifiedTypeCreationTool;
import org.eclipse.papyrus.infra.gmfdiag.common.utils.DiagramEditPartsUtil;
import org.eclipse.papyrus.infra.nattable.common.editor.AbstractEMFNattableEditor;
import org.eclipse.papyrus.infra.nattable.common.modelresource.PapyrusNattableModel;
@@ -109,7 +115,10 @@ import org.eclipse.papyrus.uml.tools.model.UmlModel;
import org.eclipse.papyrus.views.modelexplorer.ModelExplorerPage;
import org.eclipse.papyrus.views.modelexplorer.ModelExplorerPageBookView;
import org.eclipse.papyrus.views.modelexplorer.ModelExplorerView;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IPartListener;
@@ -1498,7 +1507,8 @@ public class PapyrusEditorFixture extends AbstractModelFixture<TransactionalEdit
* @param location
* the location (mouse pointer) at which to create the shape
* @param size
- * the size of the shape to create
+ * the size of the shape to create, or {@code null} for the default size as
+ * would be created when just clicking in the diagram
* @return the newly created shape edit-part
*
* @since 2.2
@@ -1514,13 +1524,98 @@ public class PapyrusEditorFixture extends AbstractModelFixture<TransactionalEdit
org.eclipse.gef.commands.Command command = target.getCommand(request);
execute(command);
- // Find the new edit-part
- return request.getViewDescriptors().stream()
+ return getNewEditPart(parent, request.getViewDescriptors());
+ }
+
+ private EditPart getNewEditPart(EditPart context, Collection<? extends ViewDescriptor> viewDescriptors) {
+ return viewDescriptors.stream()
.map(desc -> desc.getAdapter(View.class)).map(View.class::cast)
.filter(Objects::nonNull)
- .map(view -> DiagramEditPartsUtil.getEditPartFromView(view, parent))
+ .map(view -> DiagramEditPartsUtil.getEditPartFromView(view, context))
.filter(Objects::nonNull)
- .findAny().orElseGet(failOnAbsence("Could not find new shape edit-part"));
+ .findAny().orElseGet(failOnAbsence("Could not find newly created edit-part"));
+ }
+
+ /**
+ * Create a new shape in the current diagram by automating the creation tool.
+ * Fails if the shape cannot be created or cannot be found in the diagram after creation.
+ *
+ * @param type
+ * the type of shape to create
+ * @param location
+ * the location (mouse pointer) at which to create the shape
+ * @param size
+ * the size of the shape to create, or {@code null} for the default size as
+ * would be created when just clicking in the diagram
+ * @return the newly created shape edit-part
+ *
+ * @since 2.2
+ */
+ public EditPart createShape(IElementType type, Point location, Dimension size) {
+ class MyTool extends AspectUnspecifiedTypeCreationTool {
+ private Collection<? extends ViewDescriptor> results = Collections.emptyList();
+
+ MyTool() {
+ super(Collections.singletonList(type));
+ }
+
+ @Override
+ protected Request getTargetRequest() {
+ return super.getTargetRequest();
+ }
+
+ @Override
+ protected void selectAddedObject(EditPartViewer viewer, @SuppressWarnings("rawtypes") Collection objects) {
+ super.selectAddedObject(viewer, objects);
+
+ results = ((Collection<?>) objects).stream()
+ .filter(ViewDescriptor.class::isInstance).map(ViewDescriptor.class::cast)
+ .collect(Collectors.toList());
+ }
+
+ Collection<? extends ViewDescriptor> getResults() {
+ return results;
+ }
+ }
+
+ EditPartViewer viewer = getActiveDiagram().getViewer();
+ MyTool tool = new MyTool();
+
+ Event mouse = new Event();
+ mouse.display = editor.getSite().getShell().getDisplay();
+ mouse.widget = viewer.getControl();
+ mouse.button = 1;
+ mouse.x = location.x();
+ mouse.y = location.y();
+
+ viewer.getEditDomain().setActiveTool(tool);
+ tool.setViewer(viewer);
+ mouse.type = SWT.MouseDown;
+ tool.mouseDown(new MouseEvent(mouse), viewer);
+
+ flushDisplayEvents();
+
+ if (size == null) {
+ // Just a click
+ mouse.type = SWT.MouseUp;
+ tool.mouseUp(new MouseEvent(mouse), viewer);
+ } else {
+ // Drag and release
+ mouse.type = SWT.MouseMove;
+ mouse.x = location.x() + size.width();
+ mouse.y = location.y() + size.height();
+ tool.mouseDrag(new MouseEvent(mouse), viewer);
+
+ flushDisplayEvents();
+
+ mouse.type = SWT.MouseUp;
+ tool.mouseUp(new MouseEvent(mouse), viewer);
+ }
+
+ flushDisplayEvents();
+
+ // Find the new edit-part
+ return getNewEditPart(getActiveDiagram(), tool.getResults());
}
/**
diff --git a/tests/junit/plugins/infra/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/utils/OneShotExecutorTest.java b/tests/junit/plugins/infra/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/utils/OneShotExecutorTest.java
new file mode 100644
index 00000000000..3b7c295451e
--- /dev/null
+++ b/tests/junit/plugins/infra/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/utils/OneShotExecutorTest.java
@@ -0,0 +1,90 @@
+/*****************************************************************************
+ * Copyright (c) 2018 Christian W. Damus 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.core.utils;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.Test;
+
+/**
+ * Test cases for the {@link OneShotExecutor} class.
+ */
+public class OneShotExecutorTest {
+
+ private final TestExecutor executor = new TestExecutor();
+ private final Executor fixture = new OneShotExecutor(executor);
+
+ /**
+ * Initializes me.
+ */
+ public OneShotExecutorTest() {
+ super();
+ }
+
+ @Test
+ public void executionsPiledUp() {
+ final AtomicInteger count = new AtomicInteger();
+
+ for (int i = 0; i < 5; i++) {
+ fixture.execute(count::incrementAndGet);
+ }
+
+ executor.drain();
+
+ assertThat("Not a one-shot execution", count.get(), is(1));
+ }
+
+ @Test
+ public void executionsInSequence() {
+ final AtomicInteger count = new AtomicInteger();
+
+ for (int i = 0; i < 5; i++) {
+ fixture.execute(count::incrementAndGet);
+ executor.drain();
+ }
+
+ assertThat("Wrong number of executions", count.get(), is(5));
+ }
+
+ //
+ // Test framework
+ //
+
+ private static final class TestExecutor implements Executor {
+ Queue<Runnable> queue = new LinkedList<>();
+
+ @Override
+ public void execute(Runnable command) {
+ queue.add(command);
+ }
+
+ void drain() {
+ for (Runnable next = queue.poll(); next != null; next = queue.poll()) {
+ try {
+ next.run();
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail("Uncaught exception in test runnable: " + e.getMessage());
+ }
+ }
+ }
+ }
+}
diff --git a/tests/junit/plugins/infra/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/utils/TransactionHelperTest.java b/tests/junit/plugins/infra/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/utils/TransactionHelperTest.java
index 6499866a806..6cd7d07a866 100644
--- a/tests/junit/plugins/infra/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/utils/TransactionHelperTest.java
+++ b/tests/junit/plugins/infra/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/utils/TransactionHelperTest.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2016 Christian W. Damus and others.
+ * Copyright (c) 2016, 2018 Christian W. Damus and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -14,20 +14,31 @@
package org.eclipse.papyrus.infra.core.utils;
import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.fail;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
+import org.eclipse.emf.common.EMFPlugin;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl;
import org.eclipse.emf.transaction.RecordingCommand;
+import org.eclipse.emf.transaction.Transaction;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
+import org.eclipse.emf.transaction.impl.InternalTransactionalEditingDomain;
+import org.eclipse.emf.transaction.impl.TransactionImpl;
import org.eclipse.papyrus.infra.tools.util.IProgressCallable;
import org.eclipse.papyrus.infra.tools.util.IProgressRunnable;
import org.eclipse.papyrus.junit.utils.PrintingProgressMonitor;
@@ -133,6 +144,77 @@ public class TransactionHelperTest {
assertThat(eclass.getName(), is("Foo"));
}
+ @Test
+ public void testSimpleTransactionExecutor_readWrite() throws InterruptedException, ExecutionException {
+ Executor transactionExecutor = TransactionHelper.createTransactionExecutor(domain, exec);
+ AtomicReference<Transaction> t = new AtomicReference<>();
+
+ domain.getCommandStack().execute(new RecordingCommand(domain) {
+
+ @Override
+ protected void doExecute() {
+ transactionExecutor.execute(() -> {
+ InternalTransactionalEditingDomain iDomain = (InternalTransactionalEditingDomain) domain;
+ t.set(iDomain.getActiveTransaction());
+ });
+ }
+ });
+
+ // In case the runnable is posted to the thread
+ exec.submit(this::pass).get();
+
+ assertThat("Not executed in the transaction", t.get(), notNullValue());
+ assertThat("Not a read/write transaction", t.get().isReadOnly(), is(false));
+ assertThat("Not a trigger transaction",
+ t.get().getOptions().get(TransactionImpl.OPTION_IS_TRIGGER_TRANSACTION),
+ is(Boolean.TRUE));
+ }
+
+ @Test
+ public void testSimpleTransactionExecutor_readOnly() throws InterruptedException, ExecutionException {
+ Executor transactionExecutor = TransactionHelper.createTransactionExecutor(domain, exec);
+ AtomicReference<Transaction> t = new AtomicReference<>();
+ AtomicBoolean ran = new AtomicBoolean();
+
+ domain.runExclusive(() -> {
+ transactionExecutor.execute(() -> {
+ ran.set(true);
+
+ InternalTransactionalEditingDomain iDomain = (InternalTransactionalEditingDomain) domain;
+ t.set(iDomain.getActiveTransaction());
+ });
+ });
+
+ // In case the runnable is posted to the thread (which it should be)
+ exec.submit(this::pass).get();
+
+ assertThat("Was executed in the transaction", t.get(), nullValue());
+ assertThat("Was not executed", ran.get(), is(true));
+ }
+
+ @Test
+ public void testDisposeTransactionExecutor() throws InterruptedException, ExecutionException {
+ Executor transactionExecutor = TransactionHelper.createTransactionExecutor(domain, exec);
+ AtomicReference<Transaction> t = new AtomicReference<>();
+ AtomicBoolean ran = new AtomicBoolean();
+
+ TransactionHelper.disposeTransactionExecutor(transactionExecutor);
+ domain.runExclusive(() -> {
+ transactionExecutor.execute(() -> {
+ ran.set(true);
+
+ InternalTransactionalEditingDomain iDomain = (InternalTransactionalEditingDomain) domain;
+ t.set(iDomain.getActiveTransaction());
+ });
+ });
+
+ // In case the runnable is posted to the thread
+ exec.submit(this::pass).get();
+
+ assertThat("Was executed in the transaction", t.get(), nullValue());
+ assertThat("Was executed", ran.get(), is(false));
+ }
+
//
// Test framework
//
@@ -145,7 +227,11 @@ public class TransactionHelperTest {
@Before
public void createFixture() throws Exception {
domain = houseKeeper.createSimpleEditingDomain();
- Resource res = domain.createResource("file:bogus.ecore");
+ if (!EMFPlugin.IS_ECLIPSE_RUNNING) {
+ domain.getResourceSet().getResourceFactoryRegistry().getExtensionToFactoryMap()
+ .put("ecore", new EcoreResourceFactoryImpl());
+ }
+ Resource res = domain.createResource("file:/bogus.ecore");
TransactionHelper.run(domain, () -> {
EPackage epackage = EcoreFactory.eINSTANCE.createEPackage();
@@ -169,6 +255,10 @@ public class TransactionHelperTest {
return exec.submit(() -> runnable.run(new PrintingProgressMonitor()));
}
+ void pass() {
+ // Do nothing
+ }
+
//
// Nested types
//
diff --git a/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug533679.di b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug533679.di
new file mode 100644
index 00000000000..8c549eecdc6
--- /dev/null
+++ b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug533679.di
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<architecture:ArchitectureDescription xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:architecture="http://www.eclipse.org/papyrus/infra/core/architecture" contextId="org.eclipse.papyrus.infra.services.edit.TypeContext"/>
diff --git a/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug533679.notation b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug533679.notation
new file mode 100644
index 00000000000..0f786972ab3
--- /dev/null
+++ b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug533679.notation
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<notation:Diagram xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:notation="http://www.eclipse.org/gmf/runtime/1.0.2/notation" xmlns:style="http://www.eclipse.org/papyrus/infra/gmfdiag/style" xmlns:uml="http://www.eclipse.org/uml2/5.0.0/UML" xmi:id="_QKU0YEMwEeiEZ5Jtfh6-KA" type="PapyrusUMLSequenceDiagram" name="sequence" measurementUnit="Pixel">
+ <children xmi:type="notation:Shape" xmi:id="_QKU0YUMwEeiEZ5Jtfh6-KA" type="Interaction_Shape">
+ <children xmi:type="notation:DecorationNode" xmi:id="_QKU0YkMwEeiEZ5Jtfh6-KA" type="Interaction_NameLabel">
+ <element xmi:type="uml:Interaction" href="bug533679.uml#_POkWwEMwEeiEZ5Jtfh6-KA"/>
+ </children>
+ <children xmi:type="notation:BasicCompartment" xmi:id="_QKU0Y0MwEeiEZ5Jtfh6-KA" type="Interaction_SubfragmentCompartment">
+ <children xmi:type="notation:Shape" xmi:id="_RZ4XwEMwEeiEZ5Jtfh6-KA" type="Lifeline_Shape">
+ <children xmi:type="notation:DecorationNode" xmi:id="_RZ4-0EMwEeiEZ5Jtfh6-KA" type="Lifeline_NameLabel">
+ <element xmi:type="uml:Lifeline" href="bug533679.uml#_RZwb8EMwEeiEZ5Jtfh6-KA"/>
+ </children>
+ <children xmi:type="notation:BasicCompartment" xmi:id="_YQnmcFIgEeitkMuoPZuJ9g" type="compartment_shape_display">
+ <styles xmi:type="notation:TitleStyle" xmi:id="_YQnmcVIgEeitkMuoPZuJ9g"/>
+ <layoutConstraint xmi:type="notation:Bounds" xmi:id="_YQnmclIgEeitkMuoPZuJ9g"/>
+ </children>
+ <element xmi:type="uml:Lifeline" href="bug533679.uml#_RZwb8EMwEeiEZ5Jtfh6-KA"/>
+ <layoutConstraint xmi:type="notation:Bounds" xmi:id="_RZ4XwUMwEeiEZ5Jtfh6-KA" x="58" y="10"/>
+ </children>
+ <children xmi:type="notation:Shape" xmi:id="_R0X7YEMwEeiEZ5Jtfh6-KA" type="Lifeline_Shape">
+ <children xmi:type="notation:DecorationNode" xmi:id="_R0X7YkMwEeiEZ5Jtfh6-KA" type="Lifeline_NameLabel">
+ <element xmi:type="uml:Lifeline" href="bug533679.uml#_R0URAEMwEeiEZ5Jtfh6-KA"/>
+ </children>
+ <children xmi:type="notation:BasicCompartment" xmi:id="_R0cM0EMwEeiEZ5Jtfh6-KA" type="compartment_shape_display">
+ <styles xmi:type="notation:TitleStyle" xmi:id="_R0cM0UMwEeiEZ5Jtfh6-KA"/>
+ <element xmi:type="uml:Lifeline" href="bug533679.uml#_R0URAEMwEeiEZ5Jtfh6-KA"/>
+ <layoutConstraint xmi:type="notation:Bounds" xmi:id="_R0cM0kMwEeiEZ5Jtfh6-KA"/>
+ </children>
+ <children xmi:type="notation:Shape" xmi:id="_XgABcEMwEeiEZ5Jtfh6-KA" type="BehaviorExecutionSpecification_Shape">
+ <children xmi:type="notation:DecorationNode" xmi:id="_XgABckMwEeiEZ5Jtfh6-KA" type="BehaviorExecutionSpecification_Behavior">
+ <element xmi:type="uml:BehaviorExecutionSpecification" href="bug533679.uml#_Xf7wAEMwEeiEZ5Jtfh6-KA"/>
+ <layoutConstraint xmi:type="notation:Location" xmi:id="_XgAogEMwEeiEZ5Jtfh6-KA" x="18" y="18"/>
+ </children>
+ <element xmi:type="uml:BehaviorExecutionSpecification" href="bug533679.uml#_Xf7wAEMwEeiEZ5Jtfh6-KA"/>
+ <layoutConstraint xmi:type="notation:Bounds" xmi:id="_XgABcUMwEeiEZ5Jtfh6-KA" x="40" y="67" width="20" height="100"/>
+ </children>
+ <children xmi:type="notation:Shape" xmi:id="_vCa_wFIfEeitkMuoPZuJ9g" type="DestructionOccurrenceSpecification_Shape">
+ <element xmi:type="uml:DestructionOccurrenceSpecification" href="bug533679.uml#_vBfysFIfEeitkMuoPZuJ9g"/>
+ <layoutConstraint xmi:type="notation:Bounds" xmi:id="_vCa_wVIfEeitkMuoPZuJ9g" x="37" y="207"/>
+ </children>
+ <element xmi:type="uml:Lifeline" href="bug533679.uml#_R0URAEMwEeiEZ5Jtfh6-KA"/>
+ <layoutConstraint xmi:type="notation:Bounds" xmi:id="_R0X7YUMwEeiEZ5Jtfh6-KA" x="237" y="93" width="100" height="227"/>
+ </children>
+ <element xmi:type="uml:Interaction" href="bug533679.uml#_POkWwEMwEeiEZ5Jtfh6-KA"/>
+ <layoutConstraint xmi:type="notation:Bounds" xmi:id="_QKU0ZEMwEeiEZ5Jtfh6-KA"/>
+ </children>
+ <element xmi:type="uml:Interaction" href="bug533679.uml#_POkWwEMwEeiEZ5Jtfh6-KA"/>
+ <layoutConstraint xmi:type="notation:Bounds" xmi:id="_QKU0ZUMwEeiEZ5Jtfh6-KA"/>
+ </children>
+ <styles xmi:type="notation:StringValueStyle" xmi:id="_QKU0ZkMwEeiEZ5Jtfh6-KA" name="diagram_compatibility_version" stringValue="1.4.0"/>
+ <styles xmi:type="notation:DiagramStyle" xmi:id="_QKU0Z0MwEeiEZ5Jtfh6-KA"/>
+ <styles xmi:type="style:PapyrusDiagramStyle" xmi:id="_QKU0aEMwEeiEZ5Jtfh6-KA" diagramKindId="org.eclipse.papyrus.uml.diagram.sequence">
+ <owner xmi:type="uml:Class" href="bug533679.uml#_Jqu6wEMwEeiEZ5Jtfh6-KA"/>
+ </styles>
+ <element xmi:type="uml:Interaction" href="bug533679.uml#_POkWwEMwEeiEZ5Jtfh6-KA"/>
+ <edges xmi:type="notation:Connector" xmi:id="_Xfg5QEMwEeiEZ5Jtfh6-KA" type="Message_SynchEdge" source="_RZ4XwEMwEeiEZ5Jtfh6-KA" target="_R0X7YEMwEeiEZ5Jtfh6-KA">
+ <children xmi:type="notation:DecorationNode" xmi:id="_Xfg5Q0MwEeiEZ5Jtfh6-KA" type="Message_SynchNameLabel">
+ <element xmi:type="uml:Message" href="bug533679.uml#_XfUsAEMwEeiEZ5Jtfh6-KA"/>
+ <layoutConstraint xmi:type="notation:Location" xmi:id="_Xfg5REMwEeiEZ5Jtfh6-KA" x="1" y="-13"/>
+ </children>
+ <children xmi:type="notation:DecorationNode" xmi:id="_XfhgUEMwEeiEZ5Jtfh6-KA" type="Message_SynchStereotypeLabel">
+ <element xmi:type="uml:Message" href="bug533679.uml#_XfUsAEMwEeiEZ5Jtfh6-KA"/>
+ <layoutConstraint xmi:type="notation:Location" xmi:id="_XfhgUUMwEeiEZ5Jtfh6-KA" x="1" y="-33"/>
+ </children>
+ <styles xmi:type="notation:FontStyle" xmi:id="_Xfg5QUMwEeiEZ5Jtfh6-KA"/>
+ <styles xmi:type="notation:LineStyle" xmi:id="_XfhgUkMwEeiEZ5Jtfh6-KA"/>
+ <element xmi:type="uml:Message" href="bug533679.uml#_XfUsAEMwEeiEZ5Jtfh6-KA"/>
+ <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_Xfg5QkMwEeiEZ5Jtfh6-KA" points="[0, 0, -179, 0]$[179, 0, 0, 0]"/>
+ <sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_XftGgEMwEeiEZ5Jtfh6-KA" id="(0.5,0.21428571428571427)"/>
+ <targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_XfttkEMwEeiEZ5Jtfh6-KA" id="(0.5,0.29515418502202645)"/>
+ </edges>
+ <edges xmi:type="notation:Connector" xmi:id="_XgIkUEMwEeiEZ5Jtfh6-KA" type="Message_ReplyEdge" source="_R0X7YEMwEeiEZ5Jtfh6-KA" target="_RZ4XwEMwEeiEZ5Jtfh6-KA">
+ <children xmi:type="notation:DecorationNode" xmi:id="_XgIkU0MwEeiEZ5Jtfh6-KA" type="Message_ReplyNameLabel">
+ <element xmi:type="uml:Message" href="bug533679.uml#_XgGvIEMwEeiEZ5Jtfh6-KA"/>
+ <layoutConstraint xmi:type="notation:Location" xmi:id="_XgIkVEMwEeiEZ5Jtfh6-KA" x="1" y="-13"/>
+ </children>
+ <children xmi:type="notation:DecorationNode" xmi:id="_XgIkVUMwEeiEZ5Jtfh6-KA" type="Message_ReplyStereotypeLabel">
+ <element xmi:type="uml:Message" href="bug533679.uml#_XgGvIEMwEeiEZ5Jtfh6-KA"/>
+ <layoutConstraint xmi:type="notation:Location" xmi:id="_XgIkVkMwEeiEZ5Jtfh6-KA" x="1" y="-33"/>
+ </children>
+ <styles xmi:type="notation:FontStyle" xmi:id="_XgIkUUMwEeiEZ5Jtfh6-KA"/>
+ <styles xmi:type="notation:LineStyle" xmi:id="_XgIkV0MwEeiEZ5Jtfh6-KA"/>
+ <element xmi:type="uml:Message" href="bug533679.uml#_XgGvIEMwEeiEZ5Jtfh6-KA"/>
+ <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_XgIkUkMwEeiEZ5Jtfh6-KA" points="[0, 0, 179, 0]$[-179, 0, 0, 0]"/>
+ <sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_XgJycEMwEeiEZ5Jtfh6-KA" id="(0.5,0.73568281938326)"/>
+ <targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_XgJycUMwEeiEZ5Jtfh6-KA" id="(0.5,0.35714285714285715)"/>
+ </edges>
+ <edges xmi:type="notation:Connector" xmi:id="_m7U80FIfEeitkMuoPZuJ9g" type="Message_CreateEdge" source="_RZ4XwEMwEeiEZ5Jtfh6-KA" target="_R0X7YEMwEeiEZ5Jtfh6-KA">
+ <children xmi:type="notation:DecorationNode" xmi:id="_m7WK8FIfEeitkMuoPZuJ9g" type="Message_CreateNameLabel">
+ <layoutConstraint xmi:type="notation:Location" xmi:id="_m7WK8VIfEeitkMuoPZuJ9g" x="1" y="-13"/>
+ </children>
+ <children xmi:type="notation:DecorationNode" xmi:id="_m7WK8lIfEeitkMuoPZuJ9g" type="Message_CreateStereotypeLabel">
+ <layoutConstraint xmi:type="notation:Location" xmi:id="_m7WyAFIfEeitkMuoPZuJ9g" x="1" y="-33"/>
+ </children>
+ <styles xmi:type="notation:FontStyle" xmi:id="_m7U80VIfEeitkMuoPZuJ9g"/>
+ <styles xmi:type="notation:LineStyle" xmi:id="_m7WyAVIfEeitkMuoPZuJ9g"/>
+ <element xmi:type="uml:Message" href="bug533679.uml#_m63p0FIfEeitkMuoPZuJ9g"/>
+ <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_m7U80lIfEeitkMuoPZuJ9g" points="[0, 0, -179, 0]$[179, 0, 0, 0]"/>
+ <sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_m7vzkFIfEeitkMuoPZuJ9g" id="(0.5,0.12857142857142856)"/>
+ <targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_m7vzkVIfEeitkMuoPZuJ9g" id="(0.5,0.03083700440528634)"/>
+ </edges>
+ <edges xmi:type="notation:Connector" xmi:id="_vBnugFIfEeitkMuoPZuJ9g" type="Message_DeleteEdge" source="_RZ4XwEMwEeiEZ5Jtfh6-KA" target="_R0X7YEMwEeiEZ5Jtfh6-KA">
+ <children xmi:type="notation:DecorationNode" xmi:id="_vBnug1IfEeitkMuoPZuJ9g" type="Message_DeleteNameLabel">
+ <layoutConstraint xmi:type="notation:Location" xmi:id="_vBnuhFIfEeitkMuoPZuJ9g" x="1" y="-13"/>
+ </children>
+ <children xmi:type="notation:DecorationNode" xmi:id="_vBnuhVIfEeitkMuoPZuJ9g" type="Message_DeleteStereotypeLabel">
+ <layoutConstraint xmi:type="notation:Location" xmi:id="_vBnuhlIfEeitkMuoPZuJ9g" x="1" y="-33"/>
+ </children>
+ <styles xmi:type="notation:FontStyle" xmi:id="_vBnugVIfEeitkMuoPZuJ9g"/>
+ <styles xmi:type="notation:LineStyle" xmi:id="_vBnuh1IfEeitkMuoPZuJ9g"/>
+ <element xmi:type="uml:Message" href="bug533679.uml#_vBekkFIfEeitkMuoPZuJ9g"/>
+ <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_vBnuglIfEeitkMuoPZuJ9g" points="[0, 0, -179, 0]$[179, 0, 0, 0]"/>
+ <sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_vB-64FIfEeitkMuoPZuJ9g" id="(0.5,0.44285714285714284)"/>
+ <targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_vB-64VIfEeitkMuoPZuJ9g" id="(0.5,1.0)"/>
+ </edges>
+ <edges xmi:type="notation:Connector" xmi:id="_zvfy4FIfEeitkMuoPZuJ9g" type="Message_LostEdge" source="_RZ4XwEMwEeiEZ5Jtfh6-KA" target="_QKU0YUMwEeiEZ5Jtfh6-KA">
+ <children xmi:type="notation:DecorationNode" xmi:id="_zvfy41IfEeitkMuoPZuJ9g" type="Message_LostNameLabel">
+ <layoutConstraint xmi:type="notation:Location" xmi:id="_zvfy5FIfEeitkMuoPZuJ9g" x="1" y="-13"/>
+ </children>
+ <children xmi:type="notation:DecorationNode" xmi:id="_zvfy5VIfEeitkMuoPZuJ9g" type="Message_LostStereotypeLabel">
+ <layoutConstraint xmi:type="notation:Location" xmi:id="_zvfy5lIfEeitkMuoPZuJ9g" x="1" y="-33"/>
+ </children>
+ <styles xmi:type="notation:FontStyle" xmi:id="_zvfy4VIfEeitkMuoPZuJ9g"/>
+ <styles xmi:type="notation:LineStyle" xmi:id="_zvfy51IfEeitkMuoPZuJ9g"/>
+ <element xmi:type="uml:Message" href="bug533679.uml#_zvX3EFIfEeitkMuoPZuJ9g"/>
+ <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_zvfy4lIfEeitkMuoPZuJ9g" points="[0, 0, -106, 0]$[106, 0, 0, 0]"/>
+ <sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_zv3mUFIfEeitkMuoPZuJ9g" id="(0.5,0.5)"/>
+ <targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_zv3mUVIfEeitkMuoPZuJ9g" id="(220.0,380.0)"/>
+ </edges>
+</notation:Diagram>
diff --git a/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug533679.uml b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug533679.uml
new file mode 100644
index 00000000000..097b5898ac6
--- /dev/null
+++ b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug533679.uml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<uml:Model xmi:version="20131001" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:uml="http://www.eclipse.org/uml2/5.0.0/UML" xmi:id="_HWN6cEMwEeiEZ5Jtfh6-KA" name="bug533679">
+ <packageImport xmi:type="uml:PackageImport" xmi:id="_HeHSMEMwEeiEZ5Jtfh6-KA">
+ <importedPackage xmi:type="uml:Model" href="pathmap://UML_LIBRARIES/UMLPrimitiveTypes.library.uml#_0"/>
+ </packageImport>
+ <packagedElement xmi:type="uml:Class" xmi:id="_Jqu6wEMwEeiEZ5Jtfh6-KA" name="Foo" classifierBehavior="_POkWwEMwEeiEZ5Jtfh6-KA">
+ <ownedAttribute xmi:type="uml:Property" xmi:id="_LwEPkEMwEeiEZ5Jtfh6-KA" name="a"/>
+ <ownedAttribute xmi:type="uml:Property" xmi:id="_M6OVkEMwEeiEZ5Jtfh6-KA" name="b"/>
+ <ownedBehavior xmi:type="uml:Interaction" xmi:id="_POkWwEMwEeiEZ5Jtfh6-KA" name="DoIt">
+ <lifeline xmi:type="uml:Lifeline" xmi:id="_RZwb8EMwEeiEZ5Jtfh6-KA" name="a" represents="_LwEPkEMwEeiEZ5Jtfh6-KA" coveredBy="_m7AMsFIfEeitkMuoPZuJ9g _XfZkgEMwEeiEZ5Jtfh6-KA _XgH9QEMwEeiEZ5Jtfh6-KA _vBfLoFIfEeitkMuoPZuJ9g _zvYeIFIfEeitkMuoPZuJ9g"/>
+ <lifeline xmi:type="uml:Lifeline" xmi:id="_R0URAEMwEeiEZ5Jtfh6-KA" name="b" represents="_M6OVkEMwEeiEZ5Jtfh6-KA" coveredBy="_m7AzwFIfEeitkMuoPZuJ9g _XfaLkEMwEeiEZ5Jtfh6-KA _XgHWMEMwEeiEZ5Jtfh6-KA _vBfysFIfEeitkMuoPZuJ9g _Xf7wAEMwEeiEZ5Jtfh6-KA"/>
+ <fragment xmi:type="uml:MessageOccurrenceSpecification" xmi:id="_m7AMsFIfEeitkMuoPZuJ9g" name="create-send" covered="_RZwb8EMwEeiEZ5Jtfh6-KA" message="_m63p0FIfEeitkMuoPZuJ9g"/>
+ <fragment xmi:type="uml:MessageOccurrenceSpecification" xmi:id="_m7AzwFIfEeitkMuoPZuJ9g" name="created" covered="_R0URAEMwEeiEZ5Jtfh6-KA" message="_m63p0FIfEeitkMuoPZuJ9g"/>
+ <fragment xmi:type="uml:MessageOccurrenceSpecification" xmi:id="_XfZkgEMwEeiEZ5Jtfh6-KA" name="request-send" covered="_RZwb8EMwEeiEZ5Jtfh6-KA" message="_XfUsAEMwEeiEZ5Jtfh6-KA"/>
+ <fragment xmi:type="uml:MessageOccurrenceSpecification" xmi:id="_XfaLkEMwEeiEZ5Jtfh6-KA" name="request-recv" covered="_R0URAEMwEeiEZ5Jtfh6-KA" message="_XfUsAEMwEeiEZ5Jtfh6-KA"/>
+ <fragment xmi:type="uml:BehaviorExecutionSpecification" xmi:id="_Xf7wAEMwEeiEZ5Jtfh6-KA" name="exec" covered="_R0URAEMwEeiEZ5Jtfh6-KA" finish="_XgHWMEMwEeiEZ5Jtfh6-KA" start="_XfaLkEMwEeiEZ5Jtfh6-KA"/>
+ <fragment xmi:type="uml:MessageOccurrenceSpecification" xmi:id="_XgHWMEMwEeiEZ5Jtfh6-KA" name="reply-send" covered="_R0URAEMwEeiEZ5Jtfh6-KA" message="_XgGvIEMwEeiEZ5Jtfh6-KA"/>
+ <fragment xmi:type="uml:MessageOccurrenceSpecification" xmi:id="_XgH9QEMwEeiEZ5Jtfh6-KA" name="reply-recv" covered="_RZwb8EMwEeiEZ5Jtfh6-KA" message="_XgGvIEMwEeiEZ5Jtfh6-KA"/>
+ <fragment xmi:type="uml:MessageOccurrenceSpecification" xmi:id="_vBfLoFIfEeitkMuoPZuJ9g" name="delete-send" covered="_RZwb8EMwEeiEZ5Jtfh6-KA" message="_vBekkFIfEeitkMuoPZuJ9g"/>
+ <fragment xmi:type="uml:DestructionOccurrenceSpecification" xmi:id="_vBfysFIfEeitkMuoPZuJ9g" name="deleted" covered="_R0URAEMwEeiEZ5Jtfh6-KA" message="_vBekkFIfEeitkMuoPZuJ9g"/>
+ <fragment xmi:type="uml:MessageOccurrenceSpecification" xmi:id="_zvYeIFIfEeitkMuoPZuJ9g" name="oops-send" covered="_RZwb8EMwEeiEZ5Jtfh6-KA" message="_zvX3EFIfEeitkMuoPZuJ9g"/>
+ <message xmi:type="uml:Message" xmi:id="_XfUsAEMwEeiEZ5Jtfh6-KA" name="request" receiveEvent="_XfaLkEMwEeiEZ5Jtfh6-KA" sendEvent="_XfZkgEMwEeiEZ5Jtfh6-KA"/>
+ <message xmi:type="uml:Message" xmi:id="_XgGvIEMwEeiEZ5Jtfh6-KA" messageSort="reply" receiveEvent="_XgH9QEMwEeiEZ5Jtfh6-KA" sendEvent="_XgHWMEMwEeiEZ5Jtfh6-KA"/>
+ <message xmi:type="uml:Message" xmi:id="_m63p0FIfEeitkMuoPZuJ9g" name="create" messageSort="createMessage" receiveEvent="_m7AzwFIfEeitkMuoPZuJ9g" sendEvent="_m7AMsFIfEeitkMuoPZuJ9g"/>
+ <message xmi:type="uml:Message" xmi:id="_vBekkFIfEeitkMuoPZuJ9g" name="delete" messageSort="deleteMessage" receiveEvent="_vBfysFIfEeitkMuoPZuJ9g" sendEvent="_vBfLoFIfEeitkMuoPZuJ9g"/>
+ <message xmi:type="uml:Message" xmi:id="_zvX3EFIfEeitkMuoPZuJ9g" name="oops" messageSort="asynchCall" sendEvent="_zvYeIFIfEeitkMuoPZuJ9g"/>
+ </ownedBehavior>
+ </packagedElement>
+</uml:Model>
diff --git a/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/src/org/eclipse/papyrus/uml/diagram/sequence/tests/bug/CombinedFragmentRegressionTest.java b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/src/org/eclipse/papyrus/uml/diagram/sequence/tests/bug/CombinedFragmentRegressionTest.java
index 8a7f37f7129..4fda5bfdf62 100644
--- a/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/src/org/eclipse/papyrus/uml/diagram/sequence/tests/bug/CombinedFragmentRegressionTest.java
+++ b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/src/org/eclipse/papyrus/uml/diagram/sequence/tests/bug/CombinedFragmentRegressionTest.java
@@ -35,15 +35,20 @@ import static org.junit.Assume.assumeThat;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
+import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.validation.model.EvaluationMode;
@@ -710,6 +715,50 @@ public class CombinedFragmentRegressionTest extends AbstractPapyrusTest {
assertThat("No batch validation occurred", validationOccurred[0], is(true));
}
+ /**
+ * Verify that the creation of a combined fragment doesn't cause the interaction
+ * fragments that it encloses to move visually.
+ */
+ @Test
+ @PluginResource("resource/bugs/bug533679.di")
+ public void createCFragDoesNotMoveExistingFragments_533679() {
+ EditPart interactionEP = editor.findEditPart("DoIt", Interaction.class);
+ EditPart interactionCompartment = editor.getShapeCompartment(interactionEP);
+ Interaction interaction = interactionEP.getAdapter(Interaction.class);
+
+ // Collect the geometries of existing interaction fragments and messages
+ Map<EObject, Object> geometries = interaction.eContents().stream()
+ .collect(Collectors.toMap(Function.identity(), this::getGeometry));
+
+ editor.createShape(interactionCompartment, UMLElementTypes.CombinedFragment_Shape,
+ at(40, 60), sized(360, 360));
+
+ assumeThat(geometries.size(), greaterThanOrEqual(8));
+ geometries.forEach((element, geometry) -> assertThat(getGeometry(element), equalGeometry(geometry)));
+ }
+
+ /**
+ * Verify that the creation of a combined fragment causes all enclosed interaction
+ * fragments to be owned by the initial operand.
+ */
+ @Test
+ @PluginResource("resource/bugs/bug533679.di")
+ public void createCFragEnclosedFragments_533679() {
+ EditPart interactionEP = editor.findEditPart("DoIt", Interaction.class);
+ Interaction interaction = interactionEP.getAdapter(Interaction.class);
+
+ // Collect the interaction fragments that should move to the new operand
+ InteractionFragment[] fragments = interaction.getFragments().toArray(new InteractionFragment[0]);
+
+ EditPart cfragEP = editor.createShape(UMLElementTypes.CombinedFragment_Shape,
+ at(20, 60), sized(360, 360));
+
+ // All preƫxisting interaction fragments are contained now in the operand
+ CombinedFragment cfrag = cfragEP.getAdapter(CombinedFragment.class);
+ InteractionOperand operand = cfrag.getOperands().get(0);
+ assertThat(operand.getFragments(), hasItems(fragments));
+ }
+
//
// Test framework
//
@@ -722,4 +771,51 @@ public class CombinedFragmentRegressionTest extends AbstractPapyrusTest {
}
};
}
+
+ /**
+ * Work around the absence of an {@code equals} method in the {@link PointList} class.
+ *
+ * @param geometry
+ * a geometry to test for equality with an actual observed geometry
+ * @return the geometry matcher
+ */
+ static Matcher<Object> equalGeometry(Object geometry) {
+ return new CustomTypeSafeMatcher<Object>("equals " + geometry) {
+ @Override
+ protected boolean matchesSafely(Object item) {
+
+ return ((item instanceof PointList) && (geometry instanceof PointList))
+ ? Arrays.equals(((PointList) item).toIntArray(),
+ ((PointList) geometry).toIntArray())
+ : Objects.equals(item, geometry);
+ }
+ };
+ }
+
+ /**
+ * Query the geometry of an interaction element in the diagram.
+ *
+ * @param interactionElement
+ * an interaction element (interaction fragment or message)
+ *
+ * @return its geometry, either a {@link Rectangle}, {@link PointList}, or {@code null}
+ * for elements that have no geometry of their own but would be implied by others (such
+ * as execution occurrences)
+ */
+ Object getGeometry(EObject interactionElement) {
+ Object result = null;
+ GraphicalEditPart editPart = Optional.ofNullable(editor.findEditPart(interactionElement))
+ .filter(GraphicalEditPart.class::isInstance).map(GraphicalEditPart.class::cast)
+ .orElse(null);
+
+ // Some things don't have edit-parts, such as execution occurrences
+ if (editPart != null) {
+ IFigure figure = editPart.getFigure();
+ result = (figure instanceof Connection)
+ ? ((Connection) figure).getPoints().getCopy()
+ : figure.getBounds().getCopy();
+ }
+
+ return result;
+ }
}

Back to the top