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 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 links, Collection 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 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 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); }