From a523a1589c4725f5ecb77ecd28f52520894da43e Mon Sep 17 00:00:00 2001 From: Camille Letavernier Date: Fri, 25 May 2018 09:54:19 +0200 Subject: Bug 535097: [Sequence Diagram] Semantic coverage of Operands must be consistent with visuals https://bugs.eclipse.org/bugs/show_bug.cgi?id=535097 Signed-off-by: Camille Letavernier Change-Id: I3d97ee1819eb3f374a3d4b2de3644d78f11469d1 --- .../edit/policies/CombinedCreationEditPolicy.java | 11 +- .../policies/CombinedFragmentResizeEditPolicy.java | 41 +- .../ConnectRectangleToGridEditPolicy.java | 16 +- .../ResizeOperandEditPolicy.java | 2 +- .../resource/bugs/bug535097-OperandsSemantic.di | 2 + .../bugs/bug535097-OperandsSemantic.notation | 108 +++++ .../resource/bugs/bug535097-OperandsSemantic.uml | 31 ++ .../uml/diagram/sequence/tests/bug/BugTests.java | 1 + .../tests/bug/TestCFOperandsSemanticCoverage.java | 482 +++++++++++++++++++++ 9 files changed, 667 insertions(+), 27 deletions(-) create mode 100644 tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug535097-OperandsSemantic.di create mode 100644 tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug535097-OperandsSemantic.notation create mode 100644 tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug535097-OperandsSemantic.uml create mode 100644 tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/src/org/eclipse/papyrus/uml/diagram/sequence/tests/bug/TestCFOperandsSemanticCoverage.java diff --git a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/edit/policies/CombinedCreationEditPolicy.java b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/edit/policies/CombinedCreationEditPolicy.java index 25c0fab4fb9..f130979a26b 100644 --- a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/edit/policies/CombinedCreationEditPolicy.java +++ b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/edit/policies/CombinedCreationEditPolicy.java @@ -136,8 +136,10 @@ public class CombinedCreationEditPolicy extends DefaultCreationEditPolicy { // We get the size from the mouse cursor location to the bottom of the existing operand int height = targetOperandBounds.getBottom().y() - locationToOperand.y(); + int width = compartmentFigure.getBounds().width(); + int distanceToCompartmentTop = compartmentFigure.getBounds().getTopLeft().getNegated().translate(locationToCompartment).y; - Rectangle bounds = new Rectangle(0, distanceToCompartmentTop, -1, height); + Rectangle bounds = new Rectangle(0, distanceToCompartmentTop, width, height); ICommand setBoundsCommand = new SetResizeAndLocationCommand(editingDomain, "Set dimension", descriptor, bounds); // Also reduce the size of the existing operand, to avoid shifting the entire operands stack @@ -145,16 +147,15 @@ public class CombinedCreationEditPolicy extends DefaultCreationEditPolicy { int siblingHeight = targetOperandPart.getFigure().getBounds().height(); - Dimension siblingDimension = new Dimension(-1, siblingHeight - height); + Dimension siblingDimension = new Dimension(width, siblingHeight - height); ICommand reduceSiblingSizeCommand = new SetResizeCommand(editingDomain, "Set dimension", new NotationAndTypeAdapter(view.getElement(), view), siblingDimension); return setBoundsCommand.compose(reduceSiblingSizeCommand); } // Shouldn't happen in a well-formed diagram, since a CF should always have at least one operand. - // If this happens, simply take all available height + // If this happens, simply take all available size Rectangle clientArea = compartmentFigure.getClientArea(); - int height = clientArea.height(); - Dimension size = new Dimension(-1, height); + Dimension size = new Dimension(clientArea.getSize()); ICommand setBoundsCommand = new SetResizeCommand(editingDomain, "Set dimension", descriptor, size); return setBoundsCommand; } diff --git a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/edit/policies/CombinedFragmentResizeEditPolicy.java b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/edit/policies/CombinedFragmentResizeEditPolicy.java index f2481a6d22c..cf7b16985c6 100644 --- a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/edit/policies/CombinedFragmentResizeEditPolicy.java +++ b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/edit/policies/CombinedFragmentResizeEditPolicy.java @@ -139,31 +139,38 @@ public class CombinedFragmentResizeEditPolicy extends ResizableEditPolicyEx { int direction = cbr.getResizeDirection(); List operands = getOperands(); - if (!operands.isEmpty() && ((direction & PositionConstants.NORTH) != 0 || (direction & PositionConstants.SOUTH) != 0)) { - ChangeBoundsRequest resizeFirstOrLastOperand = new ChangeBoundsRequest(); + if (!operands.isEmpty()) { + ChangeBoundsRequest resizeOperand = new ChangeBoundsRequest(); GraphicalEditPart operand; + int firstOrLastOperandResizeDirection; if ((direction & PositionConstants.NORTH) != 0) { operand = operands.get(0); - resizeFirstOrLastOperand.setResizeDirection(PositionConstants.NORTH); + firstOrLastOperandResizeDirection = PositionConstants.NORTH; } else { operand = operands.get(operands.size() - 1); - resizeFirstOrLastOperand.setResizeDirection(PositionConstants.SOUTH); + firstOrLastOperandResizeDirection = PositionConstants.SOUTH; } - resizeFirstOrLastOperand.setMoveDelta(cbr.getMoveDelta()); - resizeFirstOrLastOperand.setLocation(cbr.getLocation()); - resizeFirstOrLastOperand.setEditParts(operand); - resizeFirstOrLastOperand.setSizeDelta(new Dimension(0, cbr.getSizeDelta().height)); - resizeFirstOrLastOperand.setType(RequestConstants.REQ_RESIZE); - commands.add(operand.getCommand(resizeFirstOrLastOperand)); - } else { - // Width change only; nothing is required. - // XXX Optionally, we may force the new width to all operands. That's not required, as the layout - // will take care of that anyway. - return resizeCFCommand; - } + resizeOperand.setMoveDelta(cbr.getMoveDelta()); + resizeOperand.setLocation(cbr.getLocation()); + resizeOperand.setType(RequestConstants.REQ_RESIZE); + + for (GraphicalEditPart operandPart : operands) { + resizeOperand.setEditParts(operand); + if (operandPart == operand) { + // Give all the delta (Height and width) to either the first or last operand + resizeOperand.setSizeDelta(new Dimension(cbr.getSizeDelta())); + resizeOperand.setResizeDirection(firstOrLastOperandResizeDirection); + } else { + // Give only the width delta to other operands + resizeOperand.setSizeDelta(new Dimension(cbr.getSizeDelta().width(), 0)); + resizeOperand.setResizeDirection(PositionConstants.EAST); + } + commands.add(operandPart.getCommand(resizeOperand)); + } - return command; + return command; + } } return resizeCFCommand; diff --git a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/ConnectRectangleToGridEditPolicy.java b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/ConnectRectangleToGridEditPolicy.java index 1b5f4154fb1..bba8bc51415 100644 --- a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/ConnectRectangleToGridEditPolicy.java +++ b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence/custom-src/org/eclipse/papyrus/uml/diagram/sequence/referencialgrilling/ConnectRectangleToGridEditPolicy.java @@ -365,6 +365,10 @@ public class ConnectRectangleToGridEditPolicy extends ConnectToGridEditPolicy im protected void updateColumnStartFromXNotification(PrecisionRectangle bounds) { int newX = bounds.x(); updatePositionGridAxis(columnStart, newX, 0); + if (columnFinish != null) { + newX = bounds.x() + bounds.width(); + updatePositionGridAxis(columnFinish, newX, 0); + } UMLDiagramEditorPlugin.log.trace(LogOptions.SEQUENCE_DEBUG_REFERENCEGRID, "+---->ACTION: modifiy AXIS START to x=" + newX);//$NON-NLS-1$ } @@ -402,10 +406,14 @@ public class ConnectRectangleToGridEditPolicy extends ConnectToGridEditPolicy im /** * This allows to update the position of anchor after the move. * - * @param anchor The anchor to recalculate. - * @param node The moved node. - * @param oldY The old Y position. - * @param newY The new Y position. + * @param anchor + * The anchor to recalculate. + * @param node + * The moved node. + * @param oldY + * The old Y position. + * @param newY + * The new Y position. */ protected void updateAnchorFromY(IdentityAnchor anchor, Node node, int oldY, int newY) { if (null != anchor && !anchor.getId().trim().equals("")) { //$NON-NLS-1$ 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 8a87ae6ea46..7f4cf8f0054 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 @@ -124,7 +124,7 @@ public class ResizeOperandEditPolicy extends GraphicalEditPolicy { operandFigure.translateToRelative(bounds); // Set the new bounds, relative to the parent (CombinedFragment), to make - // sure the notation is consistent with the visuals. We should get x = 0; y = sizeOf(Cf_Label) + Sum(sizeOf(previousOperands)) + // sure the notation is consistent with the visuals. We should get x = 0; y = Sum(sizeOf(previousOperands)); width = width(CF) IFigure cfFigure = ((IGraphicalEditPart) operandPart.getParent()).getFigure(); bounds.translate(cfFigure.getBounds().getTopLeft().getNegated()); diff --git a/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug535097-OperandsSemantic.di b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug535097-OperandsSemantic.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/bug535097-OperandsSemantic.di @@ -0,0 +1,2 @@ + + diff --git a/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug535097-OperandsSemantic.notation b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug535097-OperandsSemantic.notation new file mode 100644 index 00000000000..0d9d5c5c8b2 --- /dev/null +++ b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug535097-OperandsSemantic.notation @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug535097-OperandsSemantic.uml b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug535097-OperandsSemantic.uml new file mode 100644 index 00000000000..9eaac2b1fef --- /dev/null +++ b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/resource/bugs/bug535097-OperandsSemantic.uml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/src/org/eclipse/papyrus/uml/diagram/sequence/tests/bug/BugTests.java b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/src/org/eclipse/papyrus/uml/diagram/sequence/tests/bug/BugTests.java index 700c672586a..875fb41e54c 100644 --- a/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/src/org/eclipse/papyrus/uml/diagram/sequence/tests/bug/BugTests.java +++ b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/src/org/eclipse/papyrus/uml/diagram/sequence/tests/bug/BugTests.java @@ -36,6 +36,7 @@ import org.junit.runners.Suite.SuiteClasses; CombinedFragmentRegressionTest.class, TestCombinedFragmentOperandsLayout.class, TestCFOperandsCoveredNodes.class, + TestCFOperandsSemanticCoverage.class, }) public class BugTests { diff --git a/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/src/org/eclipse/papyrus/uml/diagram/sequence/tests/bug/TestCFOperandsSemanticCoverage.java b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/src/org/eclipse/papyrus/uml/diagram/sequence/tests/bug/TestCFOperandsSemanticCoverage.java new file mode 100644 index 00000000000..81c7802b81a --- /dev/null +++ b/tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.sequence.tests/src/org/eclipse/papyrus/uml/diagram/sequence/tests/bug/TestCFOperandsSemanticCoverage.java @@ -0,0 +1,482 @@ +/***************************************************************************** + * Copyright (c) 2018 EclipseSource 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: + * EclipseSource - Initial API and implementation + * + *****************************************************************************/ +package org.eclipse.papyrus.uml.diagram.sequence.tests.bug; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +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.emf.ecore.EObject; +import org.eclipse.gef.EditPart; +import org.eclipse.gef.RequestConstants; +import org.eclipse.gef.requests.ChangeBoundsRequest; +import org.eclipse.gmf.runtime.diagram.ui.editparts.GraphicalEditPart; +import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart; +import org.eclipse.gmf.runtime.diagram.ui.requests.CreateUnspecifiedTypeRequest; +import org.eclipse.gmf.runtime.diagram.ui.requests.CreateViewRequest.ViewDescriptor; +import org.eclipse.gmf.runtime.notation.View; +import org.eclipse.papyrus.infra.gmfdiag.common.utils.DiagramEditPartsUtil; +import org.eclipse.papyrus.junit.utils.rules.ActiveDiagram; +import org.eclipse.papyrus.junit.utils.rules.PapyrusEditorFixture; +import org.eclipse.papyrus.junit.utils.rules.PluginResource; +import org.eclipse.papyrus.uml.diagram.sequence.providers.UMLElementTypes; +import org.eclipse.papyrus.uml.diagram.sequence.requests.MoveSeparatorRequest; +import org.eclipse.uml2.uml.ActionExecutionSpecification; +import org.eclipse.uml2.uml.CombinedFragment; +import org.eclipse.uml2.uml.ExecutionSpecification; +import org.eclipse.uml2.uml.InteractionFragment; +import org.eclipse.uml2.uml.InteractionOperand; +import org.eclipse.uml2.uml.Message; +import org.eclipse.uml2.uml.MessageEnd; +import org.eclipse.uml2.uml.MessageOccurrenceSpecification; +import org.eclipse.uml2.uml.util.UMLSwitch; +import org.hamcrest.core.IsInstanceOf; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; + +/** + *

+ * Test the semantic coverage update when the CombinedFragment and/or operands + * are updated visually (Creation/Deletion/Resize/Move). + *

+ */ +// Test class for Bug 535097 +@PluginResource({ "resource/bugs/bug535097-OperandsSemantic.di", "resource/bugs/style.css" }) +@ActiveDiagram("SemanticCoverageTest") +public class TestCFOperandsSemanticCoverage { + + @Rule + public final PapyrusEditorFixture editor = new PapyrusEditorFixture(); + + private IGraphicalEditPart cfPart; + private IGraphicalEditPart operandPart; + + private IGraphicalEditPart message10; + private IGraphicalEditPart message11; + private IGraphicalEditPart message12; + + private IGraphicalEditPart exec1; + private IGraphicalEditPart exec2; + + @Before + public void initParts() { + cfPart = (IGraphicalEditPart) editor.findEditPart("TestFragment", CombinedFragment.class); + operandPart = (IGraphicalEditPart) editor.findEditPart("InteractionOperand0", InteractionOperand.class); + + message10 = (IGraphicalEditPart) editor.findEditPart("Message10", Message.class); + message11 = (IGraphicalEditPart) editor.findEditPart("Message11", Message.class); + message12 = (IGraphicalEditPart) editor.findEditPart("Message12", Message.class); + + exec1 = (IGraphicalEditPart) editor.findEditPart("Exec1", ActionExecutionSpecification.class); + exec2 = (IGraphicalEditPart) editor.findEditPart("Exec2", ActionExecutionSpecification.class); + } + + // The coverage is already set properly in the test model. + // This test is here to make sure that opening the diagram doesn't + // break the current values by incorrectly changing them. + @Test + public void testInitialCoverage() { + assertCovered(message10, operandPart); + assertCovered(message11, operandPart); + assertCovered(exec1, operandPart); + + assertNotCovered(message12, operandPart); + assertNotCovered(exec2, operandPart); + } + + @Test + public void testExpandCF() { + ChangeBoundsRequest request = new ChangeBoundsRequest(RequestConstants.REQ_RESIZE); + request.setEditParts(cfPart); + request.setResizeDirection(PositionConstants.SOUTH_EAST); + + // Expand the Fragment (South-East) + request.setSizeDelta(new Dimension(40, 160)); // Cover Exec2, and Message12#start + + editor.execute(cfPart.getCommand(request)); + + assertCovered(message10, operandPart); + assertCovered(message11, operandPart); + assertCovered(exec1, operandPart); + assertCovered(exec2, operandPart); + + assertCoverage(getSend(message12), getOperand(operandPart), true); + assertCoverage(getReceive(message12), getOperand(operandPart), false); + } + + @Test + @Ignore + public void testExpandCFWidth() { + ChangeBoundsRequest request = new ChangeBoundsRequest(RequestConstants.REQ_RESIZE); + request.setEditParts(cfPart); + request.setResizeDirection(PositionConstants.SOUTH_EAST); + + // Expand the Fragment (South-East) + request.setSizeDelta(new Dimension(200, 160)); + + editor.execute(cfPart.getCommand(request)); + + // TODO Assert + + // assertCovered(message10, operandPart); + // assertCovered(message11, operandPart); + // assertCovered(exec1, operandPart); + // assertCovered(exec2, operandPart); + // + // assertCoverage(getSend(message12), getOperand(operandPart), true); + // assertCoverage(getReceive(message12), getOperand(operandPart), false); + } + + @Test + public void testShrinkCF() { + ChangeBoundsRequest request = new ChangeBoundsRequest(RequestConstants.REQ_RESIZE); + request.setEditParts(cfPart); + request.setResizeDirection(PositionConstants.SOUTH_EAST); + + // Shrink the Fragment (South-East) + request.setSizeDelta(new Dimension(0, -100)); // Between Message10 and Message11, only partially covers Exec1 + + editor.execute(cfPart.getCommand(request)); + + assertCovered(message10, operandPart); + assertNotCovered(message11, operandPart); + + assertCoverage(getStart(exec1), getOperand(operandPart), true); + assertCoverage(getFinish(exec1), getOperand(operandPart), false); + + assertNotCovered(exec2, operandPart); + assertNotCovered(message12, operandPart); + } + + @Test + @Ignore + public void testCreateCombinedFragmentSpecificSize() { + // Create a fragment with a predefined size + + } + + @Test + public void testCreateOperand() { + IGraphicalEditPart operand2Part = createOperand(operandPart, at(200, 100, cfPart)); // Between Message10 and Message11 + + // Covered by the first operand + assertCovered(message10, operandPart); + assertCoverage(getStart(exec1), getOperand(operandPart), true); + + // Covered by the second operand + assertCovered(message11, operand2Part); + assertCoverage(getFinish(exec1), getOperand(operand2Part), true); + + // Not covered + assertNotCovered(message12, operandPart); + assertNotCovered(message12, operand2Part); + assertNotCovered(exec2, operandPart); + assertNotCovered(exec2, operand2Part); + } + + @Test + public void testMoveCF() { + ChangeBoundsRequest request = new ChangeBoundsRequest(RequestConstants.REQ_MOVE); + request.setEditParts(cfPart); + + // Move to the bottom + request.setMoveDelta(new Point(0, 180)); // Move over Exec2 and start of Message12 + editor.execute(cfPart.getCommand(request)); + + assertNotCovered(exec1, operandPart); + assertNotCovered(message10, operandPart); + assertNotCovered(message11, operandPart); + + assertCovered(exec2, operandPart); + + assertCoverage(getSend(message12), getOperand(operandPart), true); + assertCoverage(getReceive(message12), getOperand(operandPart), false); + + // Move again, to the right + + request.setMoveDelta(new Point(280, 0)); // Move fully over Message12 + editor.execute(cfPart.getCommand(request)); + + assertNotCovered(exec1, operandPart); + assertNotCovered(message10, operandPart); + assertNotCovered(message11, operandPart); + assertNotCovered(exec2, operandPart); + + assertCovered(message12, operandPart); + } + + @Test + public void testDeleteOperand() { + // Create a new operand, delete the first one, and check that the new + // one covers everything that was initially covered by the first + IGraphicalEditPart operand2Part = createOperand(operandPart, at(200, 100, cfPart)); // Between Message10 and Message11 + editor.delete(operandPart); + + assertCovered(message10, operand2Part); + assertCovered(message11, operand2Part); + assertCovered(exec1, operand2Part); + + assertNotCovered(message12, operand2Part); + assertNotCovered(exec2, operand2Part); + + // Same idea, with more operands. We'll need a bigger CF... + ChangeBoundsRequest request = new ChangeBoundsRequest(RequestConstants.REQ_RESIZE); + request.setEditParts(cfPart); + request.setResizeDirection(PositionConstants.SOUTH_EAST); + request.setSizeDelta(new Dimension(40, 160)); // Cover Exec2, and Message12#start + editor.execute(cfPart.getCommand(request)); + + // Create some operands every 100px + IGraphicalEditPart operand3Part = createOperand(operand2Part, at(200, 100, cfPart)); // Between Message10 and Message11 + IGraphicalEditPart operand4Part = createOperand(operand3Part, at(150, 200, cfPart)); // Between Exec1 and Exec2 + IGraphicalEditPart operand5Part = createOperand(operand4Part, at(75, 300, cfPart)); // Middle of exec 2, below Message12 + + // Before deleting, make sure everything is what we expect... + assertCovered(message10, operand2Part); + assertCoverage(getStart(exec1), getOperand(operand2Part), true); + + assertCovered(message11, operand3Part); + assertCoverage(getFinish(exec1), getOperand(operand3Part), true); + + assertCoverage(getStart(exec2), getOperand(operand4Part), true); + assertCoverage(getSend(message12), getOperand(operand4Part), true); + + assertCoverage(getFinish(exec2), getOperand(operand5Part), true); + + // Now, start deleting some operands... + editor.delete(operand2Part); // Delete the top operand. Give its fragments to Operand3 + + assertCovered(message10, operand3Part); + assertCovered(message11, operand3Part); + assertCovered(exec1, operand3Part); + + // 4 and 5 still expect the same coverage + assertCoverage(getStart(exec2), getOperand(operand4Part), true); + assertCoverage(getSend(message12), getOperand(operand4Part), true); + assertCoverage(getFinish(exec2), getOperand(operand5Part), true); + + editor.delete(operand4Part); // Delete the middle operand. Give its fragments to Operand3 + + assertCovered(message10, operand3Part); + assertCovered(message11, operand3Part); + assertCovered(exec1, operand3Part); + assertCoverage(getStart(exec2), getOperand(operand3Part), true); + assertCoverage(getSend(message12), getOperand(operand3Part), true); + + // 5 still expects the same coverage + assertCoverage(getFinish(exec2), getOperand(operand5Part), true); + + editor.delete(operand5Part); // Delete the bottom operand. Give its fragments to Operand3 + + assertCovered(message10, operand3Part); + assertCovered(message11, operand3Part); + assertCovered(exec1, operand3Part); + assertCoverage(getStart(exec2), getOperand(operand3Part), true); + assertCoverage(getSend(message12), getOperand(operand3Part), true); + assertCoverage(getFinish(exec2), getOperand(operand3Part), true); + } + + @Test + public void testMoveOperandSeparator() { + // First, expand the CF... + ChangeBoundsRequest request = new ChangeBoundsRequest(RequestConstants.REQ_RESIZE); + request.setEditParts(cfPart); + request.setResizeDirection(PositionConstants.SOUTH_EAST); + request.setSizeDelta(new Dimension(40, 160)); // Cover Exec2, and Message12#start + editor.execute(cfPart.getCommand(request)); + + // ...and create some operands... + IGraphicalEditPart operand2Part = createOperand(operandPart, at(200, 100, cfPart)); // Between Message10 and Message11 + IGraphicalEditPart operand3Part = createOperand(operand2Part, at(150, 200, cfPart)); // Between Exec1 and Exec2 + IGraphicalEditPart operand4Part = createOperand(operand3Part, at(75, 300, cfPart)); // Middle of exec 2, below Message12 + + // ...Check the initial state... + assertCovered(message10, operandPart); + assertCoverage(getStart(exec1), getOperand(operandPart), true); + + assertCovered(message11, operand2Part); + assertCoverage(getFinish(exec1), getOperand(operand2Part), true); + + assertCoverage(getStart(exec2), getOperand(operand3Part), true); + assertCoverage(getSend(message12), getOperand(operand3Part), true); + + assertCoverage(getFinish(exec2), getOperand(operand4Part), true); + + // Prepare the request to move the first separator... + MoveSeparatorRequest resizeOperands = new MoveSeparatorRequest(0); + resizeOperands.setLocation(at(100, 0, operandPart)); + resizeOperands.setEditParts(Collections.singletonList(cfPart)); + + // Move the first separator down + resizeOperands.setMoveDelta(new Point(0, 80)); // Below Message 11 and Exec1 + editor.execute(cfPart.getCommand(resizeOperands)); + + assertCovered(message10, operandPart); + assertCovered(message11, operandPart); + assertCovered(exec1, operandPart); + + // Move the first separator up + resizeOperands.setMoveDelta(new Point(0, -150)); // Above message 10 and exec1 + editor.execute(cfPart.getCommand(resizeOperands)); + + assertCovered(message10, operand2Part); + assertCovered(message11, operand2Part); + assertCovered(exec1, operand2Part); + + // Last separator... + resizeOperands = new MoveSeparatorRequest(2); + resizeOperands.setLocation(at(100, 0, operand4Part)); + resizeOperands.setEditParts(Collections.singletonList(cfPart)); + + // Move the last separator down + resizeOperands.setMoveDelta(new Point(0, 45)); // Below Exec2 + editor.execute(cfPart.getCommand(resizeOperands)); + + assertCovered(exec2, operand3Part); + assertCoverage(getSend(message12), getOperand(operand3Part), true); + + // Move the last separator up + resizeOperands.setMoveDelta(new Point(0, -100)); // Between start of Exec2 and start of message12 + editor.execute(cfPart.getCommand(resizeOperands)); + + assertCoverage(getStart(exec2), getOperand(operand3Part), true); + // --- separator is here --- + assertCoverage(getSend(message12), getOperand(operand4Part), true); + assertCoverage(getFinish(exec2), getOperand(operand4Part), true); + } + + private void assertCovered(IGraphicalEditPart coveredPart, IGraphicalEditPart operandPart) { + assertCoverage(coveredPart, operandPart, true); + } + + private void assertNotCovered(IGraphicalEditPart coveredPart, IGraphicalEditPart operandPart) { + assertCoverage(coveredPart, operandPart, false); + } + + private void assertCoverage(IGraphicalEditPart coveredPart, IGraphicalEditPart operandPart, boolean expectedCoverage) { + EObject semantic = coveredPart.getNotationView().getElement(); + InteractionOperand operand = getOperand(operandPart); + + assertCoverage(semantic, operand, expectedCoverage); + } + + private InteractionOperand getOperand(IGraphicalEditPart operandPart) { + return (InteractionOperand) operandPart.getNotationView().getElement(); + } + + private void assertCoverage(EObject semantic, InteractionOperand operand, boolean expectedCoverage) { + new UMLSwitch() { + @Override + public Void caseMessage(Message object) { + assertCoverage(object, operand, expectedCoverage); + return null; + } + + @Override + public Void caseExecutionSpecification(ExecutionSpecification object) { + assertCoverage(object, operand, expectedCoverage); + return null; + } + }.doSwitch(semantic); + } + + private MessageEnd getSend(IGraphicalEditPart messageEditPart) { + return ((Message) messageEditPart.getNotationView().getElement()).getSendEvent(); + } + + private MessageEnd getReceive(IGraphicalEditPart messageEditPart) { + return ((Message) messageEditPart.getNotationView().getElement()).getReceiveEvent(); + } + + private InteractionFragment getStart(IGraphicalEditPart execSpecEditPart) { + return ((ExecutionSpecification) execSpecEditPart.getNotationView().getElement()).getStart(); + } + + private InteractionFragment getFinish(IGraphicalEditPart execSpecEditPart) { + return ((ExecutionSpecification) execSpecEditPart.getNotationView().getElement()).getFinish(); + } + + private void assertCoverage(Message message, InteractionOperand operand, boolean expectedCoverage) { + assertCoverage(message.getSendEvent(), operand, expectedCoverage); + assertCoverage(message.getReceiveEvent(), operand, expectedCoverage); + } + + private void assertCoverage(MessageEnd messageEnd, InteractionOperand operand, boolean expectedCoverage) { + Assert.assertThat(messageEnd, IsInstanceOf.instanceOf(MessageOccurrenceSpecification.class)); + assertCoverage((InteractionFragment) messageEnd, operand, expectedCoverage); + } + + private void assertCoverage(ExecutionSpecification exec, InteractionOperand operand, boolean expectedCoverage) { + assertCoverage(exec.getStart(), operand, expectedCoverage); + assertCoverage(exec.getFinish(), operand, expectedCoverage); + } + + private void assertCoverage(InteractionFragment fragment, InteractionOperand operand, boolean expectedCoverage) { + Assert.assertEquals(expectedCoverage, operand.getFragments().contains(fragment)); + } + + // Don't use editor.createShape(), because we need a special type of request to create operands. + // The "InsertAt" behavior will only be computed if we use a CreateUnspecifiedTypeRequest (From the palette) + // and target an Operand. The Operand will then be responsible for setting the InsertAt parameter + // and delegate to the CombinedFragment compartment for the actual creation + private GraphicalEditPart createOperand(IGraphicalEditPart targetVisualPart, Point location) { + CreateUnspecifiedTypeRequest request = new CreateUnspecifiedTypeRequest(Collections.singletonList(UMLElementTypes.InteractionOperand_Shape), targetVisualPart.getDiagramPreferencesHint()); + + request.setLocation(location); + + EditPart target = targetVisualPart.getTargetEditPart(request); + assertThat("No target edit part", target, notNullValue()); + org.eclipse.gef.commands.Command command = target.getCommand(request); + editor.execute(command); + + // Find the new edit-part + Object result = request.getNewObject(); + Assert.assertThat(result, instanceOf(Collection.class)); + Collection results = (Collection) result; + return results.stream() + .filter(ViewDescriptor.class::isInstance).map(ViewDescriptor.class::cast) + .map(desc -> desc.getAdapter(View.class)).map(View.class::cast) + .filter(Objects::nonNull) + .map(view -> DiagramEditPartsUtil.getEditPartFromView(view, targetVisualPart)) + .filter(GraphicalEditPart.class::isInstance).map(GraphicalEditPart.class::cast) + .filter(Objects::nonNull) + .findAny().orElseThrow(() -> new IllegalStateException("Could not find new shape edit-part")); + } + + // Convert a point that is relative to the given part to a point relative to the current Viewport (Taking zoom & translate into account). + // This can be used to get a "Mouse Location" to configure Requests + private static Point at(int x, int y, IGraphicalEditPart relativeTo) { + Point at = new Point(x, y); + + IFigure figure = relativeTo.getContentPane(); + Point layoutOrigin = figure.getClientArea().getLocation(); + + at.performTranslate(layoutOrigin.x, layoutOrigin.y); + figure.translateToParent(at); + figure.translateToAbsolute(at); + + return at; + } + +} -- cgit v1.2.3