diff --git a/plugins/de.cau.cs.kieler.klighd.incremental/src/de/cau/cs/kieler/klighd/incremental/diff/KComparison.java b/plugins/de.cau.cs.kieler.klighd.incremental/src/de/cau/cs/kieler/klighd/incremental/diff/KComparison.java index 08ea83d2c..3bdd57e32 100644 --- a/plugins/de.cau.cs.kieler.klighd.incremental/src/de/cau/cs/kieler/klighd/incremental/diff/KComparison.java +++ b/plugins/de.cau.cs.kieler.klighd.incremental/src/de/cau/cs/kieler/klighd/incremental/diff/KComparison.java @@ -38,6 +38,7 @@ public class KComparison { private UIDAdapter baseAdapter; private UIDAdapter newAdapter; private MapDifference nodeDifference; + private MapDifference edgeDifference; /** * Create new comparison. @@ -51,6 +52,7 @@ public KComparison(final UIDAdapter baseAdapter, final UIDAdapter newAdapter) { this.baseAdapter = baseAdapter; this.newAdapter = newAdapter; nodeDifference = Maps.difference(baseAdapter.getNodeMap(), newAdapter.getNodeMap()); + edgeDifference = Maps.difference(baseAdapter.getEdgeMap(), newAdapter.getEdgeMap()); } /** @@ -185,5 +187,32 @@ public Collection getRemovedNodes() { public Collection> getMatchedNodes() { return nodeDifference.entriesDiffering().values(); } + + /** + * Get newly added edges. + * + * @return the newly added edges. + */ + public Collection getAddedEdges() { + return edgeDifference.entriesOnlyOnRight().values(); + } + + /** + * Get removed edges. + * + * @return removed edges. + */ + public Collection getRemovedEdges() { + return edgeDifference.entriesOnlyOnLeft().values(); + } + + /** + * Get matched edges, that are present in both models. + * + * @return pairs of matched edges. + */ + public Collection> getMatchedEdges() { + return edgeDifference.entriesDiffering().values(); + } } diff --git a/plugins/de.cau.cs.kieler.klighd.incremental/src/de/cau/cs/kieler/klighd/incremental/merge/KGraphMerger.java b/plugins/de.cau.cs.kieler.klighd.incremental/src/de/cau/cs/kieler/klighd/incremental/merge/KGraphMerger.java index 55a877af2..3d1e9a538 100644 --- a/plugins/de.cau.cs.kieler.klighd.incremental/src/de/cau/cs/kieler/klighd/incremental/merge/KGraphMerger.java +++ b/plugins/de.cau.cs.kieler.klighd.incremental/src/de/cau/cs/kieler/klighd/incremental/merge/KGraphMerger.java @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2016,2020 by + * Copyright 2016-2023 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group @@ -36,7 +36,6 @@ import com.google.common.collect.MapDifference.ValueDifference; import com.google.common.collect.Sets; -import de.cau.cs.kieler.klighd.KlighdDataManager; import de.cau.cs.kieler.klighd.incremental.diff.KComparison; import de.cau.cs.kieler.klighd.kgraph.KEdge; import de.cau.cs.kieler.klighd.kgraph.KGraphData; @@ -88,9 +87,54 @@ public void merge() { handleRemovedNodes(); handleAddedNodes(); handleMatchedNodes(); + + // handle edges after everything else is done, because they are contained in their source, therefore we can't be + // sure the target exists until we have gone through the entire model + handleRemovedEdges(); + handleAddedEdges(); + handleMatchedEdges(); + updatePositions(); } + + /** + * Removes edges from the base model. + */ + private void handleRemovedEdges() { + for (KEdge edge : comparison.getRemovedEdges()) { + removeEdge(edge); + } + } + + /** + * Removes the edge from the graph it is contained in. + */ + private void removeEdge(KEdge edge) { + edge.setSource(null); + edge.setTarget(null); + edge.setSourcePort(null); + edge.setTargetPort(null); + } + /** + * Adds edges from the new model to the base model. + */ + private void handleAddedEdges() { + Stream newEdges = comparison.getAddedEdges().stream(); + newEdges.forEach( + (KEdge edge) -> handleAddedEdge(edge) + ); + } + + /** + * Handles edges that are present in both the base model and the new model. + */ + private void handleMatchedEdges() { + for (ValueDifference diff : comparison.getMatchedEdges()) { + handleMatchedEdge(diff.rightValue()); + } + } + /** * Remove nodes from the base model that are not longer present in the new model. */ @@ -142,10 +186,6 @@ private void handleAddedNodes() { )).forEachOrdered( (KNode node) -> addNode(node) ); - // Add edges after adding the nodes to ensure that all targets are available. - for (KNode node : comparison.getAddedNodes()) { - handleEdges(comparison.lookupBaseNode(node), node); - } } /** @@ -161,7 +201,10 @@ private void addNode(final KNode node) { // and just copy the node and add it to the base adapter, without putting it into the base model directly. if (comparison.lookupBaseNode(node) == null) { KNode copiedNode = EcoreUtil.copy(node); - comparison.getBaseAdapter().generateIDs(copiedNode); + // Edges from this node are handled later individually when the target is guaranteed to be added already, + // remove them for now. + allNewEdgesForCopiedNode(copiedNode).forEach(edge -> removeEdge(edge)); + comparison.getBaseAdapter().generateIDs(copiedNode, true, -1); } return; } @@ -180,11 +223,35 @@ private void addNode(final KNode node) { if (comparison.lookupBaseNode(node) == null) { int oldPosition = node.getParent().getChildren().indexOf(node); KNode copiedNode = EcoreUtil.copy(node); + // Edges from this node are handled later individually when the target is guaranteed to be added already, + // remove them for now. + allNewEdgesForCopiedNode(copiedNode).forEach(edge -> removeEdge(edge)); baseParent.getChildren().add(oldPosition, copiedNode); - comparison.getBaseAdapter().generateIDs(copiedNode); + comparison.getBaseAdapter().generateIDs(copiedNode, true, oldPosition); } } } + + /** + * Returns all edges of this copied node, that are copied alongside it. Includes all outgoing edges, also of potential + * children of this node. + */ + private List allNewEdgesForCopiedNode(KNode node) { + List allEdges = new ArrayList(); + allNewEdgesForCopiedNode(node, allEdges); + return allEdges; + } + + /** + * Adds all edges of this copied node, that are copied alongside it to the {@code allEdges} list. + * Includes all outgoing edges, also of potential children of this node. Adds the result to the given list. + */ + private void allNewEdgesForCopiedNode(KNode node, List allEdges) { + allEdges.addAll(node.getOutgoingEdges()); + for (KNode childNode : node.getChildren()) { + allNewEdgesForCopiedNode(childNode, allEdges); + } + } /** * Update nodes that are present in both the base and new model. @@ -194,28 +261,6 @@ private void handleMatchedNodes() { updateKnode(diff.leftValue(), diff.rightValue()); } } - - /** - * Updates the positions of all nodes, edges, ports and labels in their containment and reference lists to match the - * new model. - */ - private void updatePositions() { - for (Entry entry : updatedElements.entrySet()) { - if (entry.getKey() instanceof KNode) { - updatePosition((KNode) entry.getKey(), (KNode) entry.getValue()); - } - if (entry.getKey() instanceof KEdge) { - updatePosition((KEdge) entry.getKey(), (KEdge) entry.getValue()); - } - if (entry.getKey() instanceof KPort) { - updatePosition((KPort) entry.getKey(), (KPort) entry.getValue()); - } - if (entry.getKey() instanceof KLabel) { - updatePosition((KLabel) entry.getKey(), (KLabel) entry.getValue()); - } - } - updatedElements.clear(); - } /** * Update the given node pair. @@ -231,43 +276,32 @@ private void updateKnode(final KNode baseNode, final KNode newNode) { copyInsets(newNode.getInsets(), baseNode.getInsets()); handleLabels(baseNode, newNode); handlePorts(baseNode, newNode); - handleEdges(baseNode, newNode); + // edges are handled after all possible sources and targets have been handled updatedElements.put(baseNode, newNode); } /** - * Update all outgoing edges of the given node pair. Edges are removed, copied from the new - * model or updated. + * Update the matched edge. * - * @param baseNode - * the node to update to. - * @param newNode - * the node to update from. + * @param matchedEdge the matched edge. */ - private void handleEdges(final KNode baseNode, final KNode newNode) { - Set oldEdges = null; - if (baseNode != null) { - oldEdges = new HashSet(baseNode.getOutgoingEdges()); - } - for (KEdge newEdge : Lists.newLinkedList(newNode.getOutgoingEdges())) { - KEdge baseEdge = comparison.lookupBaseEdge(newEdge); - if (baseEdge == null || baseEdge.getTarget() == null) { - baseEdge = EcoreUtil.copy(newEdge); - updateEdge(baseEdge, newEdge); - } else { - if (oldEdges != null) { - oldEdges.remove(baseEdge); - } - updateEdge(baseEdge, newEdge); - } + private void handleMatchedEdge(final KEdge matchedEdge) { + KEdge baseEdge = comparison.lookupBaseEdge(matchedEdge); + if (baseEdge != null && baseEdge.getTarget() != null) { + updateEdge(baseEdge, matchedEdge); } - if (baseNode != null) { - for (KEdge oldEdge : oldEdges) { - oldEdge.setSource(null); - oldEdge.setTarget(null); - oldEdge.setSourcePort(null); - oldEdge.setTargetPort(null); - } + } + + /** + * Update the added edge. + * + * @param newEdge the added edge. + */ + private void handleAddedEdge(final KEdge newEdge) { + KEdge baseEdge = comparison.lookupBaseEdge(newEdge); + if (baseEdge == null || baseEdge.getTarget() == null) { + baseEdge = EcoreUtil.copy(newEdge); + updateEdge(baseEdge, newEdge); } } @@ -374,7 +408,8 @@ private void updateLabel(final KLabel baseLabel, final KLabel newLabel) { updateShapeLayout(baseLabel, newLabel); baseLabel.setText(newLabel.getText()); copyInsets(newLabel.getInsets(), baseLabel.getInsets()); - comparison.getBaseAdapter().generateIDs(baseLabel); + int newPosition = newLabel.getParent().getLabels().indexOf(newLabel); + comparison.getBaseAdapter().generateIDs(baseLabel, newPosition); updatedElements.put(baseLabel, newLabel); } @@ -428,7 +463,8 @@ private void updatePort(final KPort basePort, final KPort newPort) { updateGraphElement(basePort, newPort); updateShapeLayout(basePort, newPort); copyInsets(newPort.getInsets(), basePort.getInsets()); - comparison.getBaseAdapter().generateIDs(basePort); + int newPosition = newPort.getNode().getPorts().indexOf(newPort); + comparison.getBaseAdapter().generateIDs(basePort, newPosition); handleLabels(basePort, newPort); updatedElements.put(basePort, newPort); } @@ -458,6 +494,28 @@ private void updateGraphElement(final KGraphElement baseElement, baseProperties.removeKey(property); } } + + /** + * Updates the positions of all nodes, edges, ports and labels in their containment and reference lists to match the + * new model. + */ + private void updatePositions() { + for (Entry entry : updatedElements.entrySet()) { + if (entry.getKey() instanceof KNode) { + updatePosition((KNode) entry.getKey(), (KNode) entry.getValue()); + } + if (entry.getKey() instanceof KEdge) { + updatePosition((KEdge) entry.getKey(), (KEdge) entry.getValue()); + } + if (entry.getKey() instanceof KPort) { + updatePosition((KPort) entry.getKey(), (KPort) entry.getValue()); + } + if (entry.getKey() instanceof KLabel) { + updatePosition((KLabel) entry.getKey(), (KLabel) entry.getValue()); + } + } + updatedElements.clear(); + } /** * Updates the position of this node in the child list of its parent. diff --git a/plugins/de.cau.cs.kieler.klighd.incremental/src/de/cau/cs/kieler/klighd/incremental/util/UIDAdapter.java b/plugins/de.cau.cs.kieler.klighd.incremental/src/de/cau/cs/kieler/klighd/incremental/util/UIDAdapter.java index 0f926ca3b..684087979 100644 --- a/plugins/de.cau.cs.kieler.klighd.incremental/src/de/cau/cs/kieler/klighd/incremental/util/UIDAdapter.java +++ b/plugins/de.cau.cs.kieler.klighd.incremental/src/de/cau/cs/kieler/klighd/incremental/util/UIDAdapter.java @@ -320,9 +320,11 @@ protected void handleContainment(final Notification notification) { * * @param node * the node to add. + * @param position the position in its parent reference list where this element will be placed. + * Used for creating a consistent and unique ID if the element has no identifier. * @return the new or existing id, or {@code null} if the id is already taken. */ - private String addId(final KNode node) { + private String addId(final KNode node, int position) { String id = getId(node); if (id != null) { return id; @@ -334,7 +336,7 @@ private String addId(final KNode node) { parentId = ""; localId = "root"; } else { - parentId = addId(parent); + parentId = addId(parent, -1); // parent should already be generated. KIdentifier identifier = node.getData(KIdentifier.class); if (identifier != null) { localId = identifier.getId(); @@ -343,7 +345,7 @@ private String addId(final KNode node) { localId += label.getText(); } } else { - localId = "N" + parent.getChildren().indexOf(node); + localId = "N" + position; } } id = parentId + ID_SEPARATOR + localId; @@ -383,19 +385,19 @@ private String addId(final KEdge edge) { localId = "E"; KPort sourcePort = edge.getSourcePort(); if (sourcePort != null) { - localId += addId(sourcePort); + localId += addId(sourcePort, -1); // port should already be generated. } localId += "->"; KNode targetNode = edge.getTargetPort() == null ? edge.getTarget() : edge.getTargetPort().getNode(); if (targetNode != null) { - localId += addId(targetNode); + localId += addId(targetNode, -1); // node should already be generated. } localId += ":"; KPort targetPort = edge.getTargetPort(); if (targetPort != null) { - localId += addId(targetPort); + localId += addId(targetPort, -1); // port should already be generated. } } id = parentId + ID_SEPARATOR + localId; @@ -411,9 +413,11 @@ private String addId(final KEdge edge) { * * @param label * the label to add. + * @param position the position in its parent reference list where this element will be placed. + * Used for creating a consistent and unique ID if the element has no identifier. * @return the new or existing id, or {@code null} if the id is already taken. */ - private String addId(final KLabel label) { + private String addId(final KLabel label, int position) { String id = getId(label); if (id != null) { return id; @@ -436,7 +440,7 @@ private String addId(final KLabel label) { if (identifier != null) { localId = identifier.getId(); } else { - localId = "L" + parent.getLabels().indexOf(label); + localId = "L" + position; } id = parentId + ID_SEPARATOR + localId; } @@ -451,9 +455,11 @@ private String addId(final KLabel label) { * * @param port * the port to add. + * @param position the position in its parent reference list where this element will be placed. + * Used for creating a consistent and unique ID if the element has no identifier. * @return the new or existing id, or {@code null} if the id is already taken. */ - private String addId(final KPort port) { + private String addId(final KPort port, int position) { String id = getId(port); if (id != null) { return id; @@ -463,13 +469,13 @@ private String addId(final KPort port) { // This is a dangling element and should not be included in the graph. Give it a unique ID anyway. id = DANGLING_ELEMENT + port.hashCode(); } else { - String parentId = addId(parent); + String parentId = addId(parent, -1); // parent should already be generated. String localId = ""; KIdentifier identifier = port.getData(KIdentifier.class); if (identifier != null) { localId = identifier.getId(); } else { - localId = "P" + parent.getPorts().indexOf(port); + localId = "P" + position; } id = parentId + ID_SEPARATOR + localId; } @@ -543,21 +549,34 @@ private void removeId(final KPort port) { * Generate IDs recursively for this {@link KNode} and all its child {@link KGraphElement}s. * * @param node the node to start generating IDs from. + * @param skipEdges if the ID generation of the edges should be skipped. + * @param position the position in its parent reference list where this element will be placed. + * Used for creating a consistent and unique ID if the element has no identifier. */ - public void generateIDs(final KNode node) { - addId(node); + public void generateIDs(final KNode node, boolean skipEdges, int position) { + internalGenerateIDs(node, true, position); + // Make sure that the edges are always generated last after all connected elements are ready. + if (!skipEdges) { + internalGenerateIDs(node, false, position); + } + } + + private void internalGenerateIDs(final KNode node, boolean skipEdges, int position) { + addId(node, position); - for (KNode childNode : node.getChildren()) { - generateIDs(childNode); + for (int i = 0; i < node.getChildren().size(); ++i) { + internalGenerateIDs(node.getChildren().get(i), skipEdges, i); } - for (KPort port : node.getPorts()) { - generateIDs(port); + for (int i = 0; i < node.getPorts().size(); ++i) { + generateIDs(node.getPorts().get(i), i); } - for (KLabel label : node.getLabels()) { - generateIDs(label); + for (int i = 0; i < node.getLabels().size(); ++i) { + generateIDs(node.getLabels().get(i), i); } - for (KEdge edge : node.getOutgoingEdges()) { - generateIDs(edge); + if (!skipEdges) { + for (KEdge edge : node.getOutgoingEdges()) { + generateIDs(edge); + } } } @@ -565,12 +584,14 @@ public void generateIDs(final KNode node) { * Generate IDs recursively for this {@link KPort} and all its child {@link KGraphElement}s. * * @param port the port to start generating IDs from. + * @param position the position in its parent reference list where this element will be placed. + * Used for creating a consistent and unique ID if the element has no identifier. */ - public void generateIDs(final KPort port) { - addId(port); - - for (KLabel label : port.getLabels()) { - generateIDs(label); + public void generateIDs(final KPort port, int position) { + addId(port, position); + + for (int i = 0; i < port.getLabels().size(); ++i) { + generateIDs(port.getLabels().get(i), i); } } @@ -578,9 +599,11 @@ public void generateIDs(final KPort port) { * Generate IDs recursively for this {@link KLabel} and all its child {@link KGraphElement}s. * * @param label the label to start generating IDs from. + * @param position the position in its parent reference list where this element will be placed. + * Used for creating a consistent and unique ID if the element has no identifier. */ - public void generateIDs(final KLabel label) { - addId(label); + public void generateIDs(final KLabel label, int position) { + addId(label, position); } /** @@ -590,9 +613,9 @@ public void generateIDs(final KLabel label) { */ public void generateIDs(final KEdge edge) { addId(edge); - - for (KLabel label : edge.getLabels()) { - generateIDs(label); + + for (int i = 0; i < edge.getLabels().size(); ++i) { + generateIDs(edge.getLabels().get(i), i); } } diff --git a/plugins/de.cau.cs.kieler.klighd.incremental/src/de/cau/cs/kieler/klighd/incremental/util/UIDAdapters.java b/plugins/de.cau.cs.kieler.klighd.incremental/src/de/cau/cs/kieler/klighd/incremental/util/UIDAdapters.java index 867777397..718850fa4 100644 --- a/plugins/de.cau.cs.kieler.klighd.incremental/src/de/cau/cs/kieler/klighd/incremental/util/UIDAdapters.java +++ b/plugins/de.cau.cs.kieler.klighd.incremental/src/de/cau/cs/kieler/klighd/incremental/util/UIDAdapters.java @@ -58,7 +58,7 @@ public static UIDAdapter retrieveAdapter(final KNode node) { UIDAdapter newAdapter = new UIDAdapter(); node.eAdapters().add(newAdapter); adapters.put(node, new WeakReference(newAdapter)); - newAdapter.generateIDs(node); + newAdapter.generateIDs(node, false, 0); return newAdapter; } diff --git a/test/de.cau.cs.kieler.klighd.test/src/de/cau/cs/kieler/klighd/test/IncrementalUpdateTest.java b/test/de.cau.cs.kieler.klighd.test/src/de/cau/cs/kieler/klighd/test/IncrementalUpdateTest.java index 7e8d0e492..4e266b958 100644 --- a/test/de.cau.cs.kieler.klighd.test/src/de/cau/cs/kieler/klighd/test/IncrementalUpdateTest.java +++ b/test/de.cau.cs.kieler.klighd.test/src/de/cau/cs/kieler/klighd/test/IncrementalUpdateTest.java @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2020 by + * Copyright 2020-2023 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group @@ -51,6 +51,34 @@ private ViewContext createViewContext() { return new ViewContext((IDiagramWorkbenchPart) null, null); } + /* + * method for programatically creating the following KGraph in kgt notion: + * + * kgraph nodeRoot + * + * knode nodeA { + * kport portA1 + * kport portA2 + * + * klabel labelA1 "labelA1" + * klabel labelA2 "labelA2" + * + * kedge (->nodeB) // edgeAB + * kedge (->nodeC) // edgeAC + * + * kedge (:portA1->nodeB:portB1) // edgeA1B1 + * kedge (:portA1->nodeB:portB1) // edgeA1B1_2 + * } + * knode nodeB { + * kport portB1 + * + * kedge (->nodeC) // edgeBC + * } + * knode nodeC { + * kedge (->nodeB) // edgeCB + * } + * + */ private KNode createTestGraph() { final KNode nodeRoot = KGraphUtil.createInitializedNode(); addIdentifier(nodeRoot, "nodeRoot"); @@ -558,6 +586,93 @@ public void testAddEdgeOnPort() { Assert.assertSame(newEdgeTargetPosition, baseEdgeTargetPosition); } + /** + * Tests adding an edge to an added port of an added node. Checks if the elements are added and if the edge is + * connected to the new port at the correct positions in various reference lists. + */ + @Test + public void testAddEdgeNewNodeToNewTargetPort() { + final KNode baseGraph = createTestGraph(); + final KNode newGraph = createTestGraph(); + + // Create a new source node on index 0. + final KNode newSourceNode = KGraphUtil.createInitializedNode(); + final EObject newSourceNodeSource = new EObjectImpl() { }; + newSourceNode.setProperty(KlighdInternalProperties.MODEL_ELEMEMT, newSourceNodeSource); + final int newSourceNodePosition = 0; + + newGraph.getChildren().add(newSourceNodePosition, newSourceNode); + + // Create the new target port on another node with index 1. + final KPort newTargetPort = KGraphUtil.createInitializedPort(); + final EObject newTargetPortSource = new EObjectImpl() { }; + newTargetPort.setProperty(KlighdInternalProperties.MODEL_ELEMEMT, newTargetPortSource); + final int targetNodePosition = 1; + final int newTargetPortPosition = 0; + + final KNode targetNode = newGraph.getChildren().get(targetNodePosition); + targetNode.getPorts().add(newTargetPortPosition, newTargetPort); + + // Create a new edge from the new source node to the new target port. + final KEdge newEdge = KGraphUtil.createInitializedEdge(); + final EObject newEdgeSource = new EObjectImpl() { }; + newEdge.setProperty(KlighdInternalProperties.MODEL_ELEMEMT, newEdgeSource); + + newEdge.setSource(newSourceNode); + newEdge.setTargetPort(newTargetPort); + newEdge.setTarget(targetNode); + final int newEdgeSourcePosition = 0; + final int newEdgeTargetPortPosition = 0; + final int newEdgeTargetPosition = 0; + newSourceNode.getOutgoingEdges().move(newEdgeSourcePosition, newEdge); + newTargetPort.getEdges().move(newEdgeTargetPortPosition, newEdge); + targetNode.getIncomingEdges().move(newEdgeTargetPosition, newEdge); + + final ViewContext viewContext = createViewContext(); + // Initialize the view context with the base graph. + INCREMENTAL_UPDATE_STRATEGY.update(viewContext.getViewModel(), baseGraph, viewContext); + // Update with the new graph. + INCREMENTAL_UPDATE_STRATEGY.update(viewContext.getViewModel(), newGraph, viewContext); + + // Assert the new node is in the updated base model. + EObject baseNewNode = viewContext.getTargetElements(newSourceNodeSource).stream().findFirst().orElse(null); + Assert.assertNotNull(baseNewNode); + Assert.assertTrue(baseNewNode instanceof KNode); + + // Assert the new port is in the updated base model. + EObject baseNewPort = viewContext.getTargetElements(newTargetPortSource).stream().findFirst().orElse(null); + Assert.assertNotNull(baseNewPort); + Assert.assertTrue(baseNewPort instanceof KPort); + + // Assert the new edge is in the updated base model. + EObject baseNewEdge = viewContext.getTargetElements(newEdgeSource).stream().findFirst().orElse(null); + Assert.assertNotNull(baseNewEdge); + Assert.assertTrue(baseNewEdge instanceof KEdge); + // Assert the new edge also connects to the new port correctly. + Assert.assertSame(baseNewPort, ((KEdge) baseNewEdge).getTargetPort()); + + + // Assert that the new node is in the same positions in the updated base graph as it is in the new graph. + int baseNodePosition = viewContext.getViewModel().getChildren().indexOf(baseNewNode); + Assert.assertSame(newSourceNodePosition, baseNodePosition); + + // Assert that the new port is in the same positions in the updated base graph as it is in the new graph. + int basePortPosition = viewContext.getViewModel().getChildren().get(targetNodePosition).getPorts() + .indexOf(baseNewPort); + Assert.assertSame(newTargetPortPosition, basePortPosition); + + // Assert that the new edge is in the same positions in the updated base graph as it is in the new graph. + int baseEdgeSourcePosition = viewContext.getViewModel().getChildren().get(newSourceNodePosition) + .getOutgoingEdges().indexOf(baseNewEdge); + Assert.assertSame(newEdgeSourcePosition, baseEdgeSourcePosition); + int baseEdgeTargetPosition = viewContext.getViewModel().getChildren().get(targetNodePosition).getIncomingEdges() + .indexOf(baseNewEdge); + Assert.assertSame(newEdgeTargetPosition, baseEdgeTargetPosition); + int baseEdgeTargetPortPosition = viewContext.getViewModel().getChildren().get(targetNodePosition).getPorts() + .get(newTargetPortPosition).getEdges().indexOf(baseNewEdge); + Assert.assertSame(newEdgeTargetPortPosition, baseEdgeTargetPortPosition); + } + /** * Tests removing an edge plainly on a node. */