Skip to content

Commit

Permalink
fix handling of edges by incremental update (#171)
Browse files Browse the repository at this point in the history
* fix handling of edges by incremental update

* incremental: fixed wrong handling of new/old nodes in edges. Added a
more complex test for the incremental update with multiple new elements.

Something is off with the ID generation, possibly causing further issues
(see the test and the FIXME in the KGraphMerger), but solution still
seems to work for the previous issue for now.

* incremental: fixes some issues arising when adding more complex
structures.

This includes nodes that are added together with edges and ports, which
previously could cause issues. Also fixes elements without own IDs being
inserted e.g. in the beginning of lists to not get updated correctly.

---------

Co-authored-by: Niklas Rentz <[email protected]>
  • Loading branch information
Eddykasp and NiklasRentzCAU authored Sep 8, 2023
1 parent 870286c commit f758969
Show file tree
Hide file tree
Showing 5 changed files with 320 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class KComparison {
private UIDAdapter baseAdapter;
private UIDAdapter newAdapter;
private MapDifference<String, KNode> nodeDifference;
private MapDifference<String, KEdge> edgeDifference;

/**
* Create new comparison.
Expand All @@ -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());
}

/**
Expand Down Expand Up @@ -185,5 +187,32 @@ public Collection<KNode> getRemovedNodes() {
public Collection<ValueDifference<KNode>> getMatchedNodes() {
return nodeDifference.entriesDiffering().values();
}

/**
* Get newly added edges.
*
* @return the newly added edges.
*/
public Collection<KEdge> getAddedEdges() {
return edgeDifference.entriesOnlyOnRight().values();
}

/**
* Get removed edges.
*
* @return removed edges.
*/
public Collection<KEdge> getRemovedEdges() {
return edgeDifference.entriesOnlyOnLeft().values();
}

/**
* Get matched edges, that are present in both models.
*
* @return pairs of matched edges.
*/
public Collection<ValueDifference<KEdge>> getMatchedEdges() {
return edgeDifference.entriesDiffering().values();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<KEdge> 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<KEdge> diff : comparison.getMatchedEdges()) {
handleMatchedEdge(diff.rightValue());
}
}

/**
* Remove nodes from the base model that are not longer present in the new model.
*/
Expand Down Expand Up @@ -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);
}
}

/**
Expand All @@ -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;
}
Expand All @@ -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<KEdge> allNewEdgesForCopiedNode(KNode node) {
List<KEdge> allEdges = new ArrayList<KEdge>();
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<KEdge> 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.
Expand All @@ -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<KGraphElement, KGraphElement> 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.
Expand All @@ -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<KEdge> oldEdges = null;
if (baseNode != null) {
oldEdges = new HashSet<KEdge>(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);
}
}

Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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<KGraphElement, KGraphElement> 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.
Expand Down
Loading

0 comments on commit f758969

Please sign in to comment.