diff options
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; + } } |