diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java
index 477cdce0c5b..e2d6bf18039 100644
--- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java
+++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java
@@ -33,7 +33,7 @@ import java.util.*;
*
* @author Thomas Wuerthinger
*/
-public class HierarchicalLayoutManager extends LayoutManager {
+public class HierarchicalLayoutManager extends LayoutManager implements LayoutMover {
int maxLayerLength;
private LayoutGraph graph;
@@ -77,6 +77,62 @@ public class HierarchicalLayoutManager extends LayoutManager {
graph = layoutGraph;
}
+ @Override
+ public void moveLink(Point linkPos, int shiftX) {
+ int layerNr = graph.findLayer(linkPos.y);
+ for (LayoutNode node : graph.getLayer(layerNr)) {
+ if (node.isDummy() && linkPos.x == node.getX()) {
+ LayoutLayer layer = graph.getLayer(layerNr);
+ if (layer.contains(node)) {
+ node.setX(linkPos.x + shiftX);
+ layer.sortNodesByX();
+ break;
+ }
+ }
+ }
+ writeBack();
+ }
+
+ @Override
+ public void moveVertices(Set extends Vertex> movedVertices) {
+ for (Vertex vertex : movedVertices) {
+ moveVertex(vertex);
+ }
+ writeBack();
+ }
+
+ private void writeBack() {
+ graph.optimizeBackEdgeCrossings();
+ graph.updateLayerMinXSpacing();
+ graph.straightenEdges();
+ WriteResult.apply(graph);
+ }
+
+ @Override
+ public void moveVertex(Vertex movedVertex) {
+ Point newLoc = movedVertex.getPosition();
+ LayoutNode movedNode = graph.getLayoutNode(movedVertex);
+ assert !movedNode.isDummy();
+
+ int layerNr = graph.findLayer(newLoc.y + movedNode.getOuterHeight() / 2);
+ if (movedNode.getLayer() == layerNr) { // we move the node in the same layer
+ LayoutLayer layer = graph.getLayer(layerNr);
+ if (layer.contains(movedNode)) {
+ movedNode.setX(newLoc.x);
+ layer.sortNodesByX();
+ }
+ } else { // only remove edges if we moved the node to a new layer
+ if (maxLayerLength > 0) return; // TODO: not implemented
+ graph.removeNodeAndEdges(movedNode);
+ layerNr = graph.insertNewLayerIfNeeded(movedNode, layerNr);
+ graph.addNodeToLayer(movedNode, layerNr);
+ movedNode.setX(newLoc.x);
+ graph.getLayer(layerNr).sortNodesByX();
+ graph.removeEmptyLayers();
+ graph.addEdges(movedNode, maxLayerLength);
+ }
+ }
+
/**
* Removes self-edges from nodes in the graph. If self-edges are to be included in the layout
* (`layoutSelfEdges` is true), it stores them in the node for later processing and marks the graph
diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java
index f45e6268e2c..9c2806bab6d 100644
--- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java
+++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutEdge.java
@@ -169,6 +169,24 @@ public class LayoutEdge {
this.to = to;
}
+ /**
+ * Gets the absolute x-coordinate of the source node's connection point for this edge.
+ *
+ * @return The x-coordinate of the source node's connection point.
+ */
+ public int getFromX() {
+ return from.getX() + getRelativeFromX();
+ }
+
+ /**
+ * Gets the absolute x-coordinate of the target node's connection point for this edge.
+ *
+ * @return The x-coordinate of the target node's connection point.
+ */
+ public int getToX() {
+ return to.getX() + getRelativeToX();
+ }
+
/**
* Gets the relative horizontal position from the source node's left boundary to the edge's starting point.
*
diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java
index bd72616c2e6..64d96412467 100644
--- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java
+++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java
@@ -23,6 +23,7 @@
*/
package com.sun.hotspot.igv.hierarchicallayout;
+import static com.sun.hotspot.igv.hierarchicallayout.LayoutNode.NODE_POS_COMPARATOR;
import com.sun.hotspot.igv.layout.Link;
import com.sun.hotspot.igv.layout.Port;
import com.sun.hotspot.igv.layout.Vertex;
@@ -52,6 +53,8 @@ public class LayoutGraph {
private final Set links;
private final SortedSet vertices;
private final LinkedHashMap> inputPorts;
+ private final LinkedHashMap> outputPorts;
+ private final LinkedHashMap> portLinks;
// Layout Management: LayoutNodes and LayoutLayers
private final LinkedHashMap layoutNodes;
@@ -69,9 +72,9 @@ public class LayoutGraph {
public LayoutGraph(Collection extends Link> links, Collection extends Vertex> additionalVertices) {
this.links = new HashSet<>(links);
vertices = new TreeSet<>(additionalVertices);
- LinkedHashMap> portLinks = new LinkedHashMap<>(links.size());
+ portLinks = new LinkedHashMap<>(links.size());
inputPorts = new LinkedHashMap<>(links.size());
- LinkedHashMap> outputPorts = new LinkedHashMap<>(links.size());
+ outputPorts = new LinkedHashMap<>(links.size());
for (Link link : links) {
assert link.getFrom() != null;
@@ -175,6 +178,114 @@ public class LayoutGraph {
return Collections.unmodifiableList(allNodes);
}
+ /**
+ * Creates a new layer at the specified index in the layers list.
+ * Adjusts the layer numbers of existing nodes in layers below the inserted layer.
+ *
+ * @param layerNr The index at which to insert the new layer.
+ * @return The newly created LayoutLayer.
+ */
+ private LayoutLayer createNewLayer(int layerNr) {
+ LayoutLayer layer = new LayoutLayer();
+ layers.add(layerNr, layer);
+
+ // update layer field in nodes below layerNr
+ for (int l = layerNr + 1; l < getLayerCount(); l++) {
+ for (LayoutNode layoutNode : getLayer(l)) {
+ layoutNode.setLayer(l);
+ }
+ }
+ return layer;
+ }
+
+ /**
+ * Deletes the layer at the specified index.
+ * Adjusts the layer numbers of existing nodes in layers below the deleted layer.
+ *
+ * @param layerNr The index of the layer to delete.
+ */
+ private void deleteLayer(int layerNr) {
+ layers.remove(layerNr);
+
+ // Update the layer field in nodes below the deleted layer
+ for (int l = layerNr; l < getLayerCount(); l++) {
+ for (LayoutNode layoutNode : getLayer(l)) {
+ layoutNode.setLayer(l);
+ }
+ }
+ }
+
+ /**
+ * Ensures that no neighboring nodes of the specified node are in the same layer.
+ * If any neighbor is found in the specified layer, inserts a new layer to avoid conflicts.
+ * Returns the adjusted layer number where the node can be safely inserted.
+ *
+ * @param node The LayoutNode to check and possibly reposition.
+ * @param layerNr The proposed layer number for the node.
+ * @return The layer number where the node can be safely inserted after adjustments.
+ */
+ public int insertNewLayerIfNeeded(LayoutNode node, int layerNr) {
+ for (Link inputLink : getInputLinks(node.getVertex())) {
+ if (inputLink.getFrom().getVertex() == inputLink.getTo().getVertex()) continue;
+ LayoutNode fromNode = getLayoutNode(inputLink.getFrom().getVertex());
+ if (fromNode.getLayer() == layerNr) {
+ moveExpandLayerDown(layerNr + 1);
+ return layerNr + 1;
+ }
+ }
+ for (Link outputLink : getOutputLinks(node.getVertex())) {
+ if (outputLink.getFrom().getVertex() == outputLink.getTo().getVertex()) continue;
+ LayoutNode toNode = getLayoutNode(outputLink.getTo().getVertex());
+ if (toNode.getLayer() == layerNr) {
+ moveExpandLayerDown(layerNr);
+ return layerNr;
+ }
+ }
+ return layerNr;
+
+ }
+
+ /**
+ * Inserts a new layer at the specified index and adjusts nodes and edges accordingly.
+ * Moves existing nodes and their successors down to accommodate the new layer.
+ *
+ * @param layerNr The index at which to insert the new layer.
+ */
+ private void moveExpandLayerDown(int layerNr) {
+ LayoutLayer newLayer = createNewLayer(layerNr);
+
+ if (layerNr == 0) return;
+ LayoutLayer layerAbove = getLayer(layerNr - 1);
+
+ for (LayoutNode fromNode : layerAbove) {
+ int fromX = fromNode.getX();
+ Map> successorsByX = fromNode.groupSuccessorsByX();
+ fromNode.clearSuccessors();
+
+ for (Map.Entry> entry : successorsByX.entrySet()) {
+ Integer relativeFromX = entry.getKey();
+ List edges = entry.getValue();
+ LayoutNode dummyNode = new LayoutNode();
+ dummyNode.setX(fromX + relativeFromX);
+ dummyNode.setLayer(layerNr);
+ for (LayoutEdge edge : edges) {
+ dummyNode.addSuccessor(edge);
+ }
+ LayoutEdge dummyEdge = new LayoutEdge(fromNode, dummyNode, relativeFromX, 0, edges.get(0).getLink());
+ if (edges.get(0).isReversed()) dummyEdge.reverse();
+
+ fromNode.addSuccessor(dummyEdge);
+ dummyNode.addPredecessor(dummyEdge);
+ for (LayoutEdge edge : edges) {
+ edge.setFrom(dummyNode);
+ }
+ addDummyToLayer(dummyNode, layerNr);
+ }
+ }
+
+ newLayer.sortNodesByX();
+ }
+
/**
* Retrieves an unmodifiable list of all layers in the graph.
*
@@ -193,6 +304,31 @@ public class LayoutGraph {
return layers.size();
}
+ /**
+ * Retrieves the LayoutNode associated with the specified Vertex.
+ *
+ * @param vertex The vertex whose LayoutNode is to be retrieved.
+ * @return The LayoutNode corresponding to the given vertex, or null if not found.
+ */
+ public LayoutNode getLayoutNode(Vertex vertex) {
+ return layoutNodes.get(vertex);
+ }
+
+ /**
+ * Adds a LayoutNode to the specified layer and registers it in the graph.
+ *
+ * @param node The LayoutNode to add to the layer.
+ * @param layerNumber The index of the layer to which the node will be added.
+ */
+ public void addNodeToLayer(LayoutNode node, int layerNumber) {
+ assert !node.isDummy();
+ node.setLayer(layerNumber);
+ getLayer(layerNumber).add(node);
+ if (!layoutNodes.containsKey(node.getVertex())) {
+ layoutNodes.put(node.getVertex(), node);
+ }
+ }
+
/**
* Adds a LayoutNode to the specified layer and registers it in the graph.
*
@@ -283,6 +419,148 @@ public class LayoutGraph {
.collect(Collectors.toSet());
}
+ /**
+ * Retrieves all incoming links to the specified vertex.
+ *
+ * @param vertex The vertex whose incoming links are to be retrieved.
+ * @return A set of links that are incoming to the vertex.
+ */
+ public List getInputLinks(Vertex vertex) {
+ List inputLinks = new ArrayList<>();
+ for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) {
+ inputLinks.addAll(portLinks.getOrDefault(inputPort, Collections.emptySet()));
+ }
+ return inputLinks;
+ }
+
+ /**
+ * Retrieves all outgoing links from the specified vertex.
+ *
+ * @param vertex The vertex whose outgoing links are to be retrieved.
+ * @return A set of links that are outgoing from the vertex.
+ */
+ public List getOutputLinks(Vertex vertex) {
+ List outputLinks = new ArrayList<>();
+ for (Port outputPort : outputPorts.getOrDefault(vertex, Collections.emptySet())) {
+ outputLinks.addAll(portLinks.getOrDefault(outputPort, Collections.emptySet()));
+ }
+ return outputLinks;
+ }
+
+ public List getAllLinks(Vertex vertex) {
+ List allLinks = new ArrayList<>();
+
+ for (Port inputPort : inputPorts.getOrDefault(vertex, Collections.emptySet())) {
+ allLinks.addAll(portLinks.getOrDefault(inputPort, Collections.emptySet()));
+ }
+
+ for (Port outputPort : outputPorts.getOrDefault(vertex, Collections.emptySet())) {
+ allLinks.addAll(portLinks.getOrDefault(outputPort, Collections.emptySet()));
+ }
+
+ return allLinks;
+ }
+
+ /**
+ * Removes the specified LayoutNode and all its connected edges from the graph.
+ *
+ * @param node The LayoutNode to remove along with its edges.
+ */
+ public void removeNodeAndEdges(LayoutNode node) {
+ assert !node.isDummy();
+ removeEdges(node); // a node can only be removed together with its edges
+ int layer = node.getLayer();
+ layers.get(layer).remove(node);
+ layers.get(layer).updateNodeIndices();
+ layoutNodes.remove(node.getVertex());
+ }
+
+ /**
+ * Removes all edges connected to the specified LayoutNode.
+ * Handles the removal of associated dummy nodes if they are no longer needed.
+ * Updates the graph structure accordingly after node movement.
+ *
+ * @param node The LayoutNode whose connected edges are to be removed.
+ */
+ public void removeEdges(LayoutNode node) {
+ assert !node.isDummy();
+ for (Link link : getAllLinks(node.getVertex())) {
+ removeEdge(link);
+ }
+ }
+
+ public void removeEdge(Link link) {
+ Vertex from = link.getFrom().getVertex();
+ Vertex to = link.getTo().getVertex();
+ LayoutNode toNode = getLayoutNode(to);
+ LayoutNode fromNode = getLayoutNode(from);
+
+ if (toNode.getLayer() < fromNode.getLayer()) {
+ // Reversed edge
+ toNode = fromNode;
+ }
+
+ // Remove preds-edges bottom up, starting at "to" node
+ // Cannot start from "from" node since there might be joint edges
+ List toNodePredsEdges = List.copyOf(toNode.getPredecessors());
+ for (LayoutEdge edge : toNodePredsEdges) {
+ LayoutNode predNode = edge.getFrom();
+ LayoutEdge edgeToRemove;
+
+ if (edge.getLink() != null && edge.getLink().equals(link)) {
+ toNode.removePredecessor(edge);
+ edgeToRemove = edge;
+ } else {
+ // Wrong edge, look at next
+ continue;
+ }
+
+ if (!predNode.isDummy() && predNode.getVertex().equals(from)) {
+ // No dummy nodes inbetween 'from' and 'to' vertex
+ predNode.removeSuccessor(edgeToRemove);
+ break;
+ } else {
+ // Must remove edges between dummy nodes
+ boolean found = true;
+ LayoutNode succNode = toNode;
+ while (predNode.isDummy() && found) {
+ found = false;
+
+ if (predNode.getSuccessors().size() <= 1 && predNode.getPredecessors().size() <= 1) {
+ // Dummy node used only for this link, remove if not already removed
+ assert predNode.isDummy();
+ int layer = predNode.getLayer();
+ layers.get(layer).remove(predNode);
+ layers.get(layer).updateNodeIndices();
+ dummyNodes.remove(predNode);
+ } else {
+ // anchor node, should not be removed
+ break;
+ }
+
+ if (predNode.getPredecessors().size() == 1) {
+ predNode.removeSuccessor(edgeToRemove);
+ succNode = predNode;
+ edgeToRemove = predNode.getPredecessors().get(0);
+ predNode = edgeToRemove.getFrom();
+ found = true;
+ }
+ }
+
+ predNode.removeSuccessor(edgeToRemove);
+ succNode.removePredecessor(edgeToRemove);
+ }
+ break;
+ }
+
+ if (fromNode.getReversedLinkStartPoints().containsKey(link)) {
+ fromNode.computeReversedLinkPoints(false);
+ }
+ if (toNode.getReversedLinkStartPoints().containsKey(link)) {
+ toNode.computeReversedLinkPoints(false);
+ }
+ }
+
/**
* Retrieves the LayoutLayer at the specified index.
*
@@ -293,6 +571,30 @@ public class LayoutGraph {
return layers.get(layerNr);
}
+ /**
+ * Finds the layer closest to the given y-coordinate.
+ *
+ * @param y the y-coordinate to check
+ * @return the index of the optimal layer, or -1 if no layers are found
+ */
+ public int findLayer(int y) {
+ int optimalLayer = -1;
+ int minDistance = Integer.MAX_VALUE;
+ for (int l = 0; l < getLayerCount(); l++) {
+ // Check if y is within this layer's bounds
+ if (y >= getLayer(l).getTop() && y <= getLayer(l).getBottom()) {
+ return l;
+ }
+
+ int distance = Math.abs(getLayer(l).getCenter() - y);
+ if (distance < minDistance) {
+ minDistance = distance;
+ optimalLayer = l;
+ }
+ }
+ return optimalLayer;
+ }
+
/**
* Positions the layers vertically, calculating their heights and setting their positions.
* Centers the nodes within each layer vertically.
@@ -314,6 +616,380 @@ public class LayoutGraph {
}
}
+ /**
+ * Optimizes routing of reversed (back) edges to reduce crossings.
+ */
+ public void optimizeBackEdgeCrossings() {
+ for (LayoutNode node : getLayoutNodes()) {
+ node.optimizeBackEdgeCrossing();
+ }
+ }
+
+ /**
+ * Removes empty layers from the graph.
+ * Iteratively checks for and removes layers that contain only dummy nodes.
+ */
+ public void removeEmptyLayers() {
+ int i = 0;
+ while (i < getLayerCount()) {
+ LayoutLayer layer = getLayer(i);
+ if (layer.containsOnlyDummyNodes()) {
+ removeEmptyLayer(i);
+ } else {
+ i++; // Move to the next layer only if no removal occurred
+ }
+ }
+ }
+
+ /**
+ * Removes the layer at the specified index if it is empty or contains only dummy nodes.
+ * Adjusts the positions of nodes and edges accordingly.
+ *
+ * @param layerNr The index of the layer to remove.
+ */
+ private void removeEmptyLayer(int layerNr) {
+ LayoutLayer layer = getLayer(layerNr);
+ if (!layer.containsOnlyDummyNodes()) return;
+
+ for (LayoutNode dummyNode : layer) {
+ if (dummyNode.getSuccessors().isEmpty()) {
+ dummyNode.setLayer(layerNr + 1);
+ getLayer(layerNr + 1).add(dummyNode);
+ dummyNode.setX(dummyNode.calculateOptimalXFromPredecessors(true));
+ getLayer(layerNr + 1).sortNodesByX();
+ continue;
+ } else if (dummyNode.getPredecessors().isEmpty()) {
+ dummyNode.setLayer(layerNr - 1);
+ dummyNode.setX(dummyNode.calculateOptimalXFromSuccessors(true));
+ getLayer(layerNr - 1).add(dummyNode);
+ getLayer(layerNr - 1).sortNodesByX();
+ continue;
+ }
+ LayoutEdge layoutEdge = dummyNode.getPredecessors().get(0);
+
+ // remove the layoutEdge
+ LayoutNode fromNode = layoutEdge.getFrom();
+ fromNode.removeSuccessor(layoutEdge);
+
+ List successorEdges = dummyNode.getSuccessors();
+ for (LayoutEdge successorEdge : successorEdges) {
+ successorEdge.setRelativeFromX(layoutEdge.getRelativeFromX());
+ successorEdge.setFrom(fromNode);
+ fromNode.addSuccessor(successorEdge);
+ }
+ dummyNode.clearPredecessors();
+ dummyNode.clearSuccessors();
+ dummyNodes.remove(dummyNode);
+ }
+
+ deleteLayer(layerNr);
+ }
+
+ /**
+ * Repositions the specified LayoutNode horizontally within its layer to the new x-coordinate.
+ * Ensures no overlap with adjacent nodes and maintains minimum spacing.
+ *
+ * @param layoutNode The LayoutNode to reposition.
+ * @param newX The new x-coordinate to set for the node.
+ */
+ private void repositionLayoutNodeX(LayoutNode layoutNode, int newX) {
+ int currentX = layoutNode.getX();
+
+ // Early exit if the desired position is the same as the current position
+ if (newX == currentX) {
+ return;
+ }
+
+ LayoutLayer layer = getLayer(layoutNode.getLayer());
+ if (newX > currentX) {
+ layer.tryShiftNodeRight(layoutNode, newX);
+ } else {
+ layer.tryShiftNodeLeft(layoutNode, newX);
+ }
+ }
+
+ /**
+ * Aligns the x-coordinate of a single dummy successor node for the given LayoutNode.
+ * If the node has exactly one successor and that successor is a dummy node,
+ * sets the dummy node's x-coordinate to align with the current node or the edge's starting point.
+ *
+ * @param node The LayoutNode whose dummy successor is to be aligned.
+ */
+ private void alignSingleSuccessorDummyNodeX(LayoutNode node) {
+ // Retrieve the list of successor edges
+ List successors = node.getSuccessors();
+
+ // Proceed only if there is exactly one successor
+ if (successors.size() != 1) {
+ return;
+ }
+
+ LayoutEdge successorEdge = successors.get(0);
+ LayoutNode successorNode = successorEdge.getTo();
+
+ // Proceed only if the successor node is a dummy node
+ if (!successorNode.isDummy()) {
+ return;
+ }
+
+ // Determine the target x-coordinate based on whether the current node is a dummy
+ int targetX = node.isDummy() ? node.getX() : successorEdge.getStartX();
+
+ // Align the successor dummy node to the target x-coordinate
+ repositionLayoutNodeX(successorNode, targetX);
+ }
+
+ /**
+ * Aligns the x-coordinates of dummy successor nodes within the specified layer.
+ * Performs alignment in both forward and backward directions to ensure consistency.
+ *
+ * @param layer The LayoutLayer whose nodes' dummy successors need alignment.
+ */
+ private void alignLayerDummySuccessors(LayoutLayer layer) {
+ // Forward pass: Align dummy successors from the first node to the last.
+ for (LayoutNode node : layer) {
+ alignSingleSuccessorDummyNodeX(node);
+ }
+
+ // Backward pass: Align dummy successors from the last node to the first.
+ for (int i = layer.size() - 1; i >= 0; i--) {
+ LayoutNode node = layer.get(i);
+ alignSingleSuccessorDummyNodeX(node);
+ }
+ }
+
+ /**
+ * Straightens edges in the graph by aligning dummy nodes to reduce bends.
+ * Processes all layers to align dummy successor nodes.
+ */
+ public void straightenEdges() {
+ // Forward pass: Align dummy successors from the first layer to the last.
+ for (int i = 0; i < getLayerCount(); i++) {
+ alignLayerDummySuccessors(getLayer(i));
+ }
+
+ // Backward pass: Align dummy successors from the last layer to the first.
+ for (int i = getLayerCount() - 1; i >= 0; i--) {
+ alignLayerDummySuccessors(getLayer(i));
+ }
+ }
+
+ /**
+ * Updates the minimum X spacing for all layers in the graph.
+ */
+ public void updateLayerMinXSpacing() {
+ for (LayoutLayer layer : this.getLayers()) {
+ layer.updateMinXSpacing(false);
+ }
+ }
+
+ /**
+ * Calculates the optimal horizontal position (index) for the specified node within the given layer,
+ * aiming to minimize the number of edge crossings.
+ *
+ * @param node The node to position.
+ * @param layerNr The index of the layer in which to position the node.
+ * @return The optimal position index within the layer for the node.
+ */
+ private int optimalPosition(LayoutNode node, int layerNr) {
+ getLayer(layerNr).sort(NODE_POS_COMPARATOR);
+ int edgeCrossings = Integer.MAX_VALUE;
+ int optimalPos = -1;
+
+ // Try each possible position in the layerNr
+ for (int i = 0; i < getLayer(layerNr).size() + 1; i++) {
+ int xCoord;
+ if (i == 0) {
+ xCoord = getLayer(layerNr).get(i).getX() - node.getWidth() - 1;
+ } else {
+ xCoord = getLayer(layerNr).get(i - 1).getX() + getLayer(layerNr).get(i - 1).getWidth() + 1;
+ }
+
+ int currentCrossings = 0;
+
+ if (0 <= layerNr - 1) {
+ // For each link with an end point in vertex, check how many edges cross it
+ for (LayoutEdge edge : node.getPredecessors()) {
+ if (edge.getFrom().getLayer() == layerNr - 1) {
+ int fromNodeXCoord = edge.getFromX();
+ int toNodeXCoord = xCoord;
+ if (!node.isDummy()) {
+ toNodeXCoord += edge.getRelativeToX();
+ }
+ for (LayoutNode n : getLayer(layerNr - 1)) {
+ for (LayoutEdge e : n.getSuccessors()) {
+ if (e.getTo() == null) {
+ continue;
+ }
+ int compFromXCoord = e.getFromX();
+ int compToXCoord = e.getToX();
+ if ((fromNodeXCoord > compFromXCoord && toNodeXCoord < compToXCoord)
+ || (fromNodeXCoord < compFromXCoord
+ && toNodeXCoord > compToXCoord)) {
+ currentCrossings += 1;
+ }
+ }
+ }
+ }
+ }
+ }
+ // Edge crossings across current layerNr and layerNr below
+ if (layerNr + 1 < getLayerCount()) {
+ // For each link with an end point in vertex, check how many edges cross it
+ for (LayoutEdge edge : node.getSuccessors()) {
+ if (edge.getTo().getLayer() == layerNr + 1) {
+ int toNodeXCoord = edge.getToX();
+ int fromNodeXCoord = xCoord;
+ if (!node.isDummy()) {
+ fromNodeXCoord += edge.getRelativeFromX();
+ }
+ for (LayoutNode n : getLayer(layerNr + 1)) {
+ for (LayoutEdge e : n.getPredecessors()) {
+ if (e.getFrom() == null) {
+ continue;
+ }
+ int compFromXCoord = e.getFromX();
+ int compToXCoord = e.getToX();
+ if ((fromNodeXCoord > compFromXCoord && toNodeXCoord < compToXCoord)
+ || (fromNodeXCoord < compFromXCoord
+ && toNodeXCoord > compToXCoord)) {
+ currentCrossings += 1;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (currentCrossings <= edgeCrossings) {
+ edgeCrossings = currentCrossings;
+ optimalPos = i;
+ }
+ }
+ return optimalPos;
+ }
+
+ /**
+ * Creates layout edges for the specified node and reverses edges as needed.
+ * Reverses edges that go from lower to higher layers to maintain proper layering.
+ *
+ * @param node The LayoutNode for which to create and reverse edges.
+ */
+ public void createAndReverseLayoutEdges(LayoutNode node) {
+ List nodeLinks = new ArrayList<>(getInputLinks(node.getVertex()));
+ nodeLinks.addAll(getOutputLinks(node.getVertex()));
+ nodeLinks.sort(LINK_COMPARATOR);
+
+ List reversedLayoutNodes = new ArrayList<>();
+ for (Link link : nodeLinks) {
+ if (link.getFrom().getVertex() == link.getTo().getVertex()) continue;
+ LayoutEdge layoutEdge = createLayoutEdge(link);
+
+ LayoutNode fromNode = layoutEdge.getFrom();
+ LayoutNode toNode = layoutEdge.getTo();
+
+ if (fromNode.getLayer() > toNode.getLayer()) {
+ HierarchicalLayoutManager.ReverseEdges.reverseEdge(layoutEdge);
+ reversedLayoutNodes.add(fromNode);
+ reversedLayoutNodes.add(toNode);
+ }
+ }
+
+ // ReverseEdges
+ for (LayoutNode layoutNode : reversedLayoutNodes) {
+ layoutNode.computeReversedLinkPoints(false);
+ }
+ }
+
+ /**
+ * Inserts dummy nodes along the edges from predecessors of the specified node,
+ * for edges that span more than one layer.
+ *
+ * @param layoutNode The node for which to create predecessor dummy nodes.
+ */
+ public void createDummiesForNodePredecessor(LayoutNode layoutNode) {
+ for (LayoutEdge predEdge : layoutNode.getPredecessors()) {
+ LayoutNode fromNode = predEdge.getFrom();
+ LayoutNode toNode = predEdge.getTo();
+ if (Math.abs(toNode.getLayer() - fromNode.getLayer()) <= 1) continue;
+
+ boolean hasEdgeFromSamePort = false;
+ LayoutEdge edgeFromSamePort = new LayoutEdge(fromNode, toNode, predEdge.getLink());
+ if (predEdge.isReversed()) edgeFromSamePort.reverse();
+
+ for (LayoutEdge succEdge : fromNode.getSuccessors()) {
+ if (succEdge.getRelativeFromX() == predEdge.getRelativeFromX() && succEdge.getTo().isDummy()) {
+ edgeFromSamePort = succEdge;
+ hasEdgeFromSamePort = true;
+ break;
+ }
+ }
+
+ if (hasEdgeFromSamePort) {
+ LayoutEdge curEdge = edgeFromSamePort;
+ boolean newEdge = true;
+ while (curEdge.getTo().getLayer() < toNode.getLayer() - 1 && curEdge.getTo().isDummy() && newEdge) {
+ // Traverse down the chain of dummy nodes linking together the edges originating
+ // from the same port
+ newEdge = false;
+ if (curEdge.getTo().getSuccessors().size() == 1) {
+ curEdge = curEdge.getTo().getSuccessors().get(0);
+ newEdge = true;
+ } else {
+ for (LayoutEdge e : curEdge.getTo().getSuccessors()) {
+ if (e.getTo().isDummy()) {
+ curEdge = e;
+ newEdge = true;
+ break;
+ }
+ }
+ }
+ }
+
+ LayoutNode prevDummy;
+ if (!curEdge.getTo().isDummy()) {
+ prevDummy = curEdge.getFrom();
+ } else {
+ prevDummy = curEdge.getTo();
+ }
+
+ predEdge.setFrom(prevDummy);
+ predEdge.setRelativeFromX(prevDummy.getWidth() / 2);
+ fromNode.removeSuccessor(predEdge);
+ prevDummy.addSuccessor(predEdge);
+ }
+
+ LayoutNode layoutNode1 = predEdge.getTo();
+ if (predEdge.getTo().getLayer() - 1 > predEdge.getFrom().getLayer()) {
+ LayoutEdge prevEdge = predEdge;
+ for (int l = layoutNode1.getLayer() - 1; l > prevEdge.getFrom().getLayer(); l--) {
+ LayoutNode dummyNode = new LayoutNode();
+ dummyNode.addSuccessor(prevEdge);
+ LayoutEdge result = new LayoutEdge(prevEdge.getFrom(), dummyNode, prevEdge.getRelativeFromX(), 0, prevEdge.getLink());
+ if (prevEdge.isReversed()) result.reverse();
+ dummyNode.addPredecessor(result);
+ prevEdge.setRelativeFromX(0);
+ prevEdge.getFrom().removeSuccessor(prevEdge);
+ prevEdge.getFrom().addSuccessor(result);
+ prevEdge.setFrom(dummyNode);
+ dummyNode.setLayer(l);
+ List layerNodes = getLayer(l);
+ if (layerNodes.isEmpty()) {
+ dummyNode.setPos(0);
+ } else {
+ dummyNode.setPos(optimalPosition(dummyNode, l));
+ }
+ for (LayoutNode n : layerNodes) {
+ if (n.getPos() >= dummyNode.getPos()) {
+ n.setPos(n.getPos() + 1);
+ }
+ }
+ addDummyToLayer(dummyNode, l);
+ prevEdge = dummyNode.getPredecessors().get(0);
+ }
+ }
+ }
+ }
+
/**
* Inserts dummy nodes along the edges to successors of the specified node,
* for edges that span more than one layer.
@@ -430,4 +1106,20 @@ public class LayoutGraph {
}
}
}
+
+ /**
+ * Adds edges connected to the specified node, including any necessary dummy nodes.
+ * Handles edge reversal, dummy node insertion for both predecessors and successors,
+ * and updates node positions accordingly.
+ *
+ * @param node The LayoutNode to which edges will be added.
+ * @param maxLayerLength The maximum number of layers an edge can span without splitting it
+ */
+ public void addEdges(LayoutNode node, int maxLayerLength) {
+ assert !node.isDummy();
+ createAndReverseLayoutEdges(node);
+ createDummiesForNodeSuccessor(node, maxLayerLength);
+ createDummiesForNodePredecessor(node);
+ updatePositions();
+ }
}
diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java
index 2f87be1587d..34a8e32654b 100644
--- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java
+++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutLayer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -154,6 +154,10 @@ public class LayoutLayer extends ArrayList {
y = top;
}
+ public int getCenter() {
+ return y + height / 2;
+ }
+
/**
* Gets the bottom Y-coordinate of this layer.
*
@@ -181,6 +185,53 @@ public class LayoutLayer extends ArrayList {
this.height = height;
}
+ /**
+ * Checks if this layer contains only dummy nodes.
+ *
+ * @return true if all nodes in the layer are dummy nodes; false otherwise.
+ */
+ public boolean containsOnlyDummyNodes() {
+ for (LayoutNode node : this) {
+ if (!node.isDummy()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Sorts the nodes in this layer by their X-coordinate in increasing order.
+ * Assigns position indices to nodes based on the sorted order.
+ * Adjusts the X-coordinates of nodes to ensure minimum spacing between them.
+ */
+ public void sortNodesByX() {
+ if (isEmpty()) return;
+
+ sort(NODE_X_COMPARATOR); // Sort nodes in the layer increasingly by x
+
+ updateNodeIndices();
+ updateMinXSpacing(false);
+ }
+
+ /**
+ * Ensures nodes have minimum horizontal spacing by adjusting their X positions.
+ *
+ * @param startFromZero if true, starts positioning from X = 0; otherwise, uses the first node's current X.
+ */
+ public void updateMinXSpacing(boolean startFromZero) {
+ if (isEmpty()) {
+ return; // No nodes to adjust.
+ }
+
+ int minX = startFromZero ? 0 : this.get(0).getX();
+
+ for (LayoutNode node : this) {
+ int x = Math.max(node.getX(), minX);
+ node.setX(x);
+ minX = x + node.getOuterWidth() + NODE_OFFSET;
+ }
+ }
+
/**
* Initializes nodes' X positions with spacing.
*/
@@ -203,4 +254,60 @@ public class LayoutLayer extends ArrayList {
pos++;
}
}
+
+ /**
+ * Attempts to move the specified node to the right within the layer to the given X-coordinate.
+ * Ensures that the node does not overlap with its right neighbor by checking required spacing.
+ * If movement is possible without causing overlap, the node's X-coordinate is updated.
+ *
+ * @param layoutNode The node to move.
+ * @param newX The desired new X-coordinate for the node.
+ */
+ public void tryShiftNodeRight(LayoutNode layoutNode, int newX) {
+ int currentX = layoutNode.getX();
+ int shiftAmount = newX - currentX;
+ int rightPos = layoutNode.getPos() + 1;
+
+ if (rightPos < size()) {
+ // There is a right neighbor
+ LayoutNode rightNeighbor = get(rightPos);
+ int proposedRightEdge = layoutNode.getRight() + shiftAmount;
+ int requiredLeftEdge = rightNeighbor.getOuterLeft() - NODE_OFFSET;
+
+ if (proposedRightEdge <= requiredLeftEdge) {
+ layoutNode.setX(newX);
+ }
+ } else {
+ // No right neighbor; safe to move freely to the right
+ layoutNode.setX(newX);
+ }
+ }
+
+ /**
+ * Attempts to move the specified node to the left within the layer to the given X-coordinate.
+ * Ensures that the node does not overlap with its left neighbor by checking required spacing.
+ * If movement is possible without causing overlap, the node's X-coordinate is updated.
+ *
+ * @param layoutNode The node to move.
+ * @param newX The desired new X-coordinate for the node.
+ */
+ public void tryShiftNodeLeft(LayoutNode layoutNode, int newX) {
+ int currentX = layoutNode.getX();
+ int shiftAmount = currentX - newX;
+ int leftPos = layoutNode.getPos() - 1;
+
+ if (leftPos >= 0) {
+ // There is a left neighbor
+ LayoutNode leftNeighbor = get(leftPos);
+ int proposedLeftEdge = layoutNode.getLeft() - shiftAmount;
+ int requiredRightEdge = leftNeighbor.getOuterRight() + NODE_OFFSET;
+
+ if (requiredRightEdge <= proposedLeftEdge) {
+ layoutNode.setX(newX);
+ }
+ } else {
+ // No left neighbor; safe to move freely to the left
+ layoutNode.setX(newX);
+ }
+ }
}
diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java
new file mode 100644
index 00000000000..19f24f1f7e6
--- /dev/null
+++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+package com.sun.hotspot.igv.hierarchicallayout;
+
+import com.sun.hotspot.igv.layout.Vertex;
+import java.awt.Point;
+import java.util.Set;
+
+public interface LayoutMover {
+ /**
+ * Moves a link by shifting its position along the X-axis.
+ *
+ * @param linkPos The current position of the link.
+ * @param shiftX The amount to shift the link along the X-axis.
+ */
+ void moveLink(Point linkPos, int shiftX);
+
+ /**
+ * Moves a set of vertices.
+ *
+ * @param movedVertices A set of vertices to be moved.
+ */
+ void moveVertices(Set extends Vertex> movedVertices);
+
+ /**
+ * Moves a single vertex.
+ *
+ * @param movedVertex The vertex to be moved.
+ */
+ void moveVertex(Vertex movedVertex);
+}
+
diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java
index 0997de46430..9ad0c70b912 100644
--- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java
+++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java
@@ -69,6 +69,7 @@ public class LayoutNode {
private int rightMargin;
private int leftMargin;
private int pos = -1; // Position within its layer
+ private boolean reverseLeft = false;
private int crossingNumber = 0;
public boolean hasSelfEdge() {
@@ -250,6 +251,15 @@ public class LayoutNode {
return x + leftMargin;
}
+ /**
+ * Gets the outer left boundary (including left margin) of the node.
+ *
+ * @return The x-coordinate of the outer left boundary.
+ */
+ public int getOuterLeft() {
+ return x;
+ }
+
/**
* Gets the total width of the node, including left and right margins.
*
@@ -272,6 +282,15 @@ public class LayoutNode {
return height;
}
+ /**
+ * Gets the right boundary (excluding right margin) of the node.
+ *
+ * @return The x-coordinate of the right boundary.
+ */
+ public int getRight() {
+ return x + leftMargin + width;
+ }
+
/**
* Gets the outer right boundary (including right margin) of the node.
*
@@ -436,6 +455,50 @@ public class LayoutNode {
this.pos = pos;
}
+ /**
+ * Groups the successor edges by their relative x-coordinate from the current node.
+ *
+ * @return A map of relative x-coordinate to list of successor edges.
+ */
+ public Map> groupSuccessorsByX() {
+ Map> result = new HashMap<>();
+ for (LayoutEdge succEdge : succs) {
+ result.computeIfAbsent(succEdge.getRelativeFromX(), k -> new ArrayList<>()).add(succEdge);
+ }
+ return result;
+ }
+
+ private int getBackedgeCrossingScore() {
+ int score = 0;
+ for (LayoutEdge predEdge : preds) {
+ if (predEdge.isReversed()) {
+ List points = reversedLinkEndPoints.get(predEdge.getLink());
+ if (points != null) {
+ int x0 = points.get(points.size() - 1).x;
+ int xn = points.get(0).x;
+ int startPoint = predEdge.getStartX();
+ int endPoint = predEdge.getEndX();
+ int win = (x0 < xn) ? (startPoint - endPoint) : (endPoint - startPoint);
+ score += win;
+ }
+ }
+ }
+ for (LayoutEdge succEdge : succs) {
+ if (succEdge.isReversed()) {
+ List points = reversedLinkStartPoints.get(succEdge.getLink());
+ if (points != null) {
+ int x0 = points.get(points.size() - 1).x;
+ int xn = points.get(0).x;
+ int startPoint = succEdge.getStartX();
+ int endPoint = succEdge.getEndX();
+ int win = (x0 > xn) ? (startPoint - endPoint) : (endPoint - startPoint);
+ score += win;
+ }
+ }
+ }
+ return score;
+ }
+
private boolean computeReversedStartPoints(boolean left) {
TreeMap> sortedDownMap = left ? new TreeMap<>() : new TreeMap<>(Collections.reverseOrder());
for (LayoutEdge succEdge : succs) {
@@ -518,12 +581,28 @@ public class LayoutNode {
}
public void computeReversedLinkPoints(boolean reverseLeft) {
+ this.reverseLeft = reverseLeft;
+
initSize();
reversedLinkStartPoints.clear();
reversedLinkEndPoints.clear();
boolean hasReversedDown = computeReversedStartPoints(reverseLeft);
- computeReversedEndPoints(hasReversedDown != reverseLeft);
+ boolean hasReversedUP = computeReversedEndPoints(hasReversedDown != reverseLeft);
+ }
+
+ public boolean isReverseRight() {
+ return !reverseLeft;
+ }
+
+ public void optimizeBackEdgeCrossing() {
+ if (reversedLinkStartPoints.isEmpty() && reversedLinkEndPoints.isEmpty()) return;
+ int orig_score = getBackedgeCrossingScore();
+ computeReversedLinkPoints(isReverseRight());
+ int reverse_score = getBackedgeCrossingScore();
+ if (orig_score > reverse_score) {
+ computeReversedLinkPoints(isReverseRight());
+ }
}
public ArrayList getSelfEdgePoints() {
diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java
index 297c7d9b998..88418338f34 100644
--- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java
+++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java
@@ -81,13 +81,17 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
private final LayerWidget mainLayer;
private final LayerWidget blockLayer;
private final LayerWidget connectionLayer;
+ private final Widget shadowWidget;
+ private final Widget pointerWidget;
private final DiagramViewModel model;
private ModelState modelState;
private boolean rebuilding;
+ private final Map> outputSlotToLineWidget = new HashMap<>();
+ private final Map> inputSlotToLineWidget = new HashMap<>();
private final HierarchicalStableLayoutManager hierarchicalStableLayoutManager;
private HierarchicalLayoutManager seaLayoutManager;
-
+ private LayoutMover layoutMover;
/**
* The alpha level of partially visible figures.
@@ -341,6 +345,12 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
mainLayer.setBorder(emptyBorder);
addChild(mainLayer);
+ pointerWidget = new Widget(DiagramScene.this);
+ addChild(pointerWidget);
+
+ shadowWidget = new Widget(DiagramScene.this);
+ addChild(shadowWidget);
+
setLayout(LayoutFactory.createAbsoluteLayout());
getActions().addAction(mouseZoomAction);
getActions().addAction(ActionFactory.createPopupMenuAction((widget, localLocation) -> createPopupMenu()));
@@ -596,6 +606,163 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
}
}
+ private MoveProvider getFigureMoveProvider() {
+ return new MoveProvider() {
+
+ private boolean hasMoved = false; // Flag to track movement
+ private int startLayerY;
+
+ private void setFigureShadow(Figure f) {
+ FigureWidget fw = getWidget(f);
+ Color c = f.getColor();
+ Border border = new FigureWidget.RoundedBorder(new Color(0,0,0, 50), 1);
+ shadowWidget.setBorder(border);
+ shadowWidget.setBackground(new Color(c.getRed(), c.getGreen(), c.getBlue(), 50));
+ shadowWidget.setPreferredLocation(fw.getPreferredLocation());
+ shadowWidget.setPreferredSize(f.getSize());
+ shadowWidget.setVisible(true);
+ shadowWidget.setOpaque(true);
+ shadowWidget.revalidate();
+ shadowWidget.repaint();
+ }
+
+ private void setMovePointer(Figure f) {
+ Border border = new FigureWidget.RoundedBorder(Color.RED, 1);
+ pointerWidget.setBorder(border);
+ pointerWidget.setBackground(Color.RED);
+ pointerWidget.setPreferredBounds(new Rectangle(0, 0, 3, f.getSize().height));
+ pointerWidget.setVisible(false);
+ pointerWidget.setOpaque(true);
+ }
+
+
+ @Override
+ public void movementStarted(Widget widget) {
+ if (layoutMover == null) return; // Do nothing if layoutMover is not available
+
+ widget.bringToFront();
+ startLayerY = widget.getLocation().y;
+ hasMoved = false; // Reset the movement flag
+ Set selectedFigures = model.getSelectedFigures();
+ if (selectedFigures.size() == 1) {
+ Figure selectedFigure = selectedFigures.iterator().next();
+ setFigureShadow(selectedFigure);
+ setMovePointer(selectedFigure);
+ }
+ }
+
+ @Override
+ public void movementFinished(Widget widget) {
+ shadowWidget.setVisible(false);
+ pointerWidget.setVisible(false);
+ if (layoutMover == null || !hasMoved) return; // Do nothing if layoutMover is not available or no movement occurred
+ rebuilding = true;
+
+ Set movedFigures = new HashSet<>(model.getSelectedFigures());
+ for (Figure figure : movedFigures) {
+ FigureWidget fw = getWidget(figure);
+ figure.setPosition(new Point(fw.getLocation().x, fw.getLocation().y));
+ }
+
+ layoutMover.moveVertices(movedFigures);
+ rebuildConnectionLayer();
+
+ for (FigureWidget fw : getVisibleFigureWidgets()) {
+ fw.updatePosition();
+ }
+
+ validateAll();
+ addUndo();
+ rebuilding = false;
+ }
+
+ private static final int MAGNET_SIZE = 5;
+
+ private int magnetToStartLayerY(Widget widget, Point location) {
+ int shiftY = location.y - widget.getLocation().y;
+ if (Math.abs(location.y - startLayerY) <= MAGNET_SIZE) {
+ if (Math.abs(widget.getLocation().y - startLayerY) > MAGNET_SIZE) {
+ shiftY = startLayerY - widget.getLocation().y;
+ } else {
+ shiftY = 0;
+ }
+ }
+ return shiftY;
+ }
+
+ @Override
+ public Point getOriginalLocation(Widget widget) {
+ if (layoutMover == null) return widget.getLocation(); // default behavior
+ return ActionFactory.createDefaultMoveProvider().getOriginalLocation(widget);
+ }
+
+ @Override
+ public void setNewLocation(Widget widget, Point location) {
+ if (layoutMover == null) return; // Do nothing if layoutMover is not available
+ hasMoved = true; // Mark that a movement occurred
+
+ int shiftX = location.x - widget.getLocation().x;
+ int shiftY = magnetToStartLayerY(widget, location);
+
+ List selectedFigures = new ArrayList<>( model.getSelectedFigures());
+ selectedFigures.sort(Comparator.comparingInt(f -> f.getPosition().x));
+ for (Figure figure : selectedFigures) {
+ FigureWidget fw = getWidget(figure);
+ for (InputSlot inputSlot : figure.getInputSlots()) {
+ assert inputSlot != null;
+ if (inputSlotToLineWidget.containsKey(inputSlot)) {
+ for (LineWidget lw : inputSlotToLineWidget.get(inputSlot)) {
+ assert lw != null;
+ Point toPt = lw.getTo();
+ Point fromPt = lw.getFrom();
+ if (toPt != null && fromPt != null) {
+ int xTo = toPt.x + shiftX;
+ int yTo = toPt.y + shiftY;
+ lw.setTo(new Point(xTo, yTo));
+ lw.setFrom(new Point(fromPt.x + shiftX, fromPt.y));
+ LineWidget pred = lw.getPredecessor();
+ pred.setTo(new Point(pred.getTo().x + shiftX, pred.getTo().y));
+ pred.revalidate();
+ lw.revalidate();
+ }
+ }
+ }
+ }
+ for (OutputSlot outputSlot : figure.getOutputSlots()) {
+ assert outputSlot != null;
+ if (outputSlotToLineWidget.containsKey(outputSlot)) {
+ for (LineWidget lw : outputSlotToLineWidget.get(outputSlot)) {
+ assert lw != null;
+ Point fromPt = lw.getFrom();
+ Point toPt = lw.getTo();
+ if (toPt != null && fromPt != null) {
+ int xFrom = fromPt.x + shiftX;
+ int yFrom = fromPt.y + shiftY;
+ lw.setFrom(new Point(xFrom, yFrom));
+ lw.setTo(new Point(toPt.x + shiftX, toPt.y));
+ for (LineWidget succ : lw.getSuccessors()) {
+ succ.setFrom(new Point(succ.getFrom().x + shiftX, succ.getFrom().y));
+ succ.revalidate();
+ }
+ lw.revalidate();
+ }
+ }
+ }
+ }
+ Point newLocation = new Point(fw.getLocation().x + shiftX, fw.getLocation().y + shiftY);
+ ActionFactory.createDefaultMoveProvider().setNewLocation(fw, newLocation);
+ }
+
+ FigureWidget fw = getWidget(selectedFigures.iterator().next());
+ pointerWidget.setVisible(true);
+ Point newLocation = new Point(fw.getLocation().x + shiftX -3, fw.getLocation().y + shiftY);
+ ActionFactory.createDefaultMoveProvider().setNewLocation(pointerWidget, newLocation);
+ connectionLayer.revalidate();
+ connectionLayer.repaint();
+ }
+ };
+ }
+
private void rebuildMainLayer() {
mainLayer.removeChildren();
for (Figure figure : getModel().getDiagram().getFigures()) {
@@ -604,6 +771,7 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
figureWidget.getActions().addAction(ActionFactory.createPopupMenuAction(figureWidget));
figureWidget.getActions().addAction(selectAction);
figureWidget.getActions().addAction(hoverAction);
+ figureWidget.getActions().addAction(ActionFactory.createMoveAction(null, getFigureMoveProvider()));
addObject(figure, figureWidget);
mainLayer.addChild(figureWidget);
@@ -737,6 +905,7 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
}
private void doStableSeaLayout(Set visibleFigures, Set visibleConnections) {
+ layoutMover = null;
boolean enable = model.getCutEdges();
boolean previous = hierarchicalStableLayoutManager.getCutEdges();
hierarchicalStableLayoutManager.setCutEdges(enable);
@@ -749,17 +918,20 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
private void doSeaLayout(Set visibleFigures, Set visibleConnections) {
seaLayoutManager = new HierarchicalLayoutManager();
+ layoutMover = seaLayoutManager;
seaLayoutManager.setCutEdges(model.getCutEdges());
seaLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures));
}
private void doClusteredLayout(Set visibleFigures, Set visibleConnections) {
+ layoutMover = null;
HierarchicalClusterLayoutManager clusterLayoutManager = new HierarchicalClusterLayoutManager();
clusterLayoutManager.setCutEdges(model.getCutEdges());
clusterLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures));
}
private void doCFGLayout(Set visibleFigures, Set visibleConnections) {
+ layoutMover = null;
HierarchicalCFGLayoutManager cfgLayoutManager = new HierarchicalCFGLayoutManager(getVisibleBlockConnections(), getVisibleBlocks());
cfgLayoutManager.setCutEdges(model.getCutEdges());
cfgLayoutManager.doLayout(new LayoutGraph(visibleConnections, visibleFigures));
@@ -777,6 +949,84 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
private final Point specialNullPoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
+ private MoveProvider getFigureConnectionMoveProvider() {
+ return new MoveProvider() {
+
+ Point startLocation;
+ Point originalPosition;
+
+ @Override
+ public void movementStarted(Widget widget) {
+ if (layoutMover == null) return; // Do nothing if layoutMover is not available
+ LineWidget lw = (LineWidget) widget;
+ startLocation = lw.getClientAreaLocation();
+ originalPosition = lw.getFrom();
+ }
+
+ @Override
+ public void movementFinished(Widget widget) {
+ if (layoutMover == null) return; // Do nothing if layoutMover is not available
+ LineWidget lineWidget = (LineWidget) widget;
+ if (lineWidget.getPredecessor() == null) return;
+ if (lineWidget.getSuccessors().isEmpty()) return;
+ if (lineWidget.getFrom().x != lineWidget.getTo().x) return;
+
+ int shiftX = lineWidget.getClientAreaLocation().x - startLocation.x;
+ if (shiftX == 0) return;
+
+ rebuilding = true;
+ layoutMover.moveLink(originalPosition, shiftX);
+ rebuildConnectionLayer();
+ for (FigureWidget fw : getVisibleFigureWidgets()) {
+ fw.updatePosition();
+ }
+ validateAll();
+ addUndo();
+ rebuilding = false;
+ }
+
+ @Override
+ public Point getOriginalLocation(Widget widget) {
+ if (layoutMover == null) return widget.getLocation(); // default behavior
+ LineWidget lineWidget = (LineWidget) widget;
+ return lineWidget.getClientAreaLocation();
+ }
+
+ @Override
+ public void setNewLocation(Widget widget, Point location) {
+ if (layoutMover == null) return; // Do nothing if layoutMover is not available
+ LineWidget lineWidget = (LineWidget) widget;
+ if (lineWidget.getPredecessor() == null) return;
+ if (lineWidget.getSuccessors().isEmpty()) return;
+ if (lineWidget.getFrom().x != lineWidget.getTo().x) return;
+
+ int shiftX = location.x - lineWidget.getClientAreaLocation().x;
+ if (shiftX == 0) return;
+
+ Point oldFrom = lineWidget.getFrom();
+ Point newFrom = new Point(oldFrom.x + shiftX, oldFrom.y);
+
+ Point oldTo = lineWidget.getTo();
+ Point newTo = new Point(oldTo.x + shiftX, oldTo.y);
+
+ lineWidget.setTo(newTo);
+ lineWidget.setFrom(newFrom);
+ lineWidget.revalidate();
+
+ LineWidget predecessor = lineWidget.getPredecessor();
+ Point toPt = predecessor.getTo();
+ predecessor.setTo(new Point(toPt.x + shiftX, toPt.y));
+ predecessor.revalidate();
+
+ for (LineWidget successor : lineWidget.getSuccessors()) {
+ Point fromPt = successor.getFrom();
+ successor.setFrom(new Point(fromPt.x + shiftX, fromPt.y));
+ successor.revalidate();
+ }
+ }
+ };
+ }
+
private void processOutputSlot(OutputSlot outputSlot, List connections, int controlPointIndex, Point lastPoint, LineWidget predecessor) {
Map> pointMap = new HashMap<>(connections.size());
@@ -824,6 +1074,7 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
newPredecessor.setVisible(isVisible);
if (predecessor == null) {
+ assert outputSlot != null;
if (outputSlotToLineWidget.containsKey(outputSlot)) {
outputSlotToLineWidget.get(outputSlot).add(newPredecessor);
} else {
@@ -834,6 +1085,7 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
newWidgets.add(newPredecessor);
addObject(new ConnectionSet(connectionList), newPredecessor);
newPredecessor.getActions().addAction(hoverAction);
+ newPredecessor.getActions().addAction(ActionFactory.createMoveAction(null, getFigureConnectionMoveProvider()));
}
processOutputSlot(outputSlot, connectionList, controlPointIndex + 1, currentPoint, newPredecessor);
@@ -843,10 +1095,13 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
for (FigureConnection connection : connections) {
if (isVisibleFigureConnection(connection)) {
InputSlot inputSlot = connection.getInputSlot();
- if (inputSlotToLineWidget.containsKey(inputSlot)) {
- inputSlotToLineWidget.get(inputSlot).add(predecessor);
- } else {
- inputSlotToLineWidget.put(inputSlot, new HashSet<>(Collections.singleton(predecessor)));
+ if (predecessor != null) {
+ assert inputSlot != null;
+ if (inputSlotToLineWidget.containsKey(inputSlot)) {
+ inputSlotToLineWidget.get(inputSlot).add(predecessor);
+ } else {
+ inputSlotToLineWidget.put(inputSlot, new HashSet<>(Collections.singleton(predecessor)));
+ }
}
}
}
@@ -1212,9 +1467,6 @@ public class DiagramScene extends ObjectScene implements DiagramViewer, DoubleCl
}
}
- Map> outputSlotToLineWidget = new HashMap<>();
- Map> inputSlotToLineWidget = new HashMap<>();
-
public JPopupMenu createPopupMenu() {
JPopupMenu menu = new JPopupMenu();
diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java
index 6b9b8e548d1..24780d4d7d2 100644
--- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java
+++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java
@@ -185,6 +185,10 @@ public class FigureWidget extends Widget implements Properties.Provider, PopupMe
this.setToolTipText(PropertiesConverter.convertToHTML(f.getProperties()));
}
+ public void updatePosition() {
+ setPreferredLocation(figure.getPosition());
+ }
+
public int getFigureHeight() {
return middleWidget.getPreferredBounds().height;
}
diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java
index 1a0448c380c..93d16d35914 100644
--- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java
+++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java
@@ -60,8 +60,8 @@ public class LineWidget extends Widget implements PopupMenuProvider {
private final OutputSlot outputSlot;
private final DiagramScene scene;
private final List extends Connection> connections;
- private final Point from;
- private final Point to;
+ private Point from;
+ private Point to;
private Rectangle clientArea;
private final LineWidget predecessor;
private final List successors;
@@ -125,6 +125,10 @@ public class LineWidget extends Widget implements PopupMenuProvider {
}));
}
+ public Point getClientAreaLocation() {
+ return clientArea.getLocation();
+ }
+
private void computeClientArea() {
int minX = from.x;
int minY = from.y;
@@ -158,6 +162,16 @@ public class LineWidget extends Widget implements PopupMenuProvider {
return sb.toString();
}
+ public void setFrom(Point from) {
+ this.from = from;
+ computeClientArea();
+ }
+
+ public void setTo(Point to) {
+ this.to= to;
+ computeClientArea();
+ }
+
public Point getFrom() {
return from;
}
@@ -166,6 +180,14 @@ public class LineWidget extends Widget implements PopupMenuProvider {
return to;
}
+ public LineWidget getPredecessor() {
+ return predecessor;
+ }
+
+ public List getSuccessors() {
+ return Collections.unmodifiableList(successors);
+ }
+
private void addSuccessor(LineWidget widget) {
this.successors.add(widget);
}