blob: cb6b02ed5730caf059700c8f07c111186ef67423 [file] [log] [blame]
/*
* Copyright (c) 2008, 2015, 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.LayoutGraph;
import com.sun.hotspot.igv.layout.LayoutManager;
import com.sun.hotspot.igv.layout.Link;
import com.sun.hotspot.igv.layout.Vertex;
import java.awt.Dimension;
import java.awt.Point;
import java.util.*;
/**
*
* @author Thomas Wuerthinger
*/
public class HierarchicalLayoutManager implements LayoutManager {
public static final boolean TRACE = false;
public static final boolean CHECK = false;
public static final int SWEEP_ITERATIONS = 1;
public static final int CROSSING_ITERATIONS = 2;
public static final int DUMMY_HEIGHT = 1;
public static final int DUMMY_WIDTH = 1;
public static final int X_OFFSET = 8;
public static final int LAYER_OFFSET = 8;
public static final int MAX_LAYER_LENGTH = -1;
public static final int MIN_LAYER_DIFFERENCE = 1;
public static final int VIP_BONUS = 10;
public enum Combine {
NONE,
SAME_INPUTS,
SAME_OUTPUTS
}
// Options
private Combine combine;
private int dummyWidth;
private int dummyHeight;
private int xOffset;
private int layerOffset;
private int maxLayerLength;
private int minLayerDifference;
// Algorithm global datastructures
private Set<Link> reversedLinks;
private List<LayoutNode> nodes;
private HashMap<Vertex, LayoutNode> vertexToLayoutNode;
private HashMap<Link, List<Point>> reversedLinkStartPoints;
private HashMap<Link, List<Point>> reversedLinkEndPoints;
private HashMap<LayoutEdge, LayoutEdge> bottomEdgeHash;
private HashMap<Link, List<Point>> splitStartPoints;
private HashMap<Link, List<Point>> splitEndPoints;
private LayoutGraph graph;
private List<LayoutNode>[] layers;
private int layerCount;
private Set<? extends Link> importantLinks;
private Set<Link> linksToFollow;
private class LayoutNode {
public int x;
public int y;
public int width;
public int height;
public int layer = -1;
public int xOffset;
public int yOffset;
public int bottomYOffset;
public Vertex vertex; // Only used for non-dummy nodes, otherwise null
public List<LayoutEdge> preds = new ArrayList<>();
public List<LayoutEdge> succs = new ArrayList<>();
public HashMap<Integer, Integer> outOffsets = new HashMap<>();
public HashMap<Integer, Integer> inOffsets = new HashMap<>();
public int pos = -1; // Position within layer
public int crossingNumber;
@Override
public String toString() {
return "Node " + vertex;
}
}
private class LayoutEdge {
public LayoutNode from;
public LayoutNode to;
public int relativeFrom;
public int relativeTo;
public Link link;
public boolean vip;
}
private abstract class AlgorithmPart {
public void start() {
if (CHECK) {
preCheck();
}
long start = 0;
if (TRACE) {
System.out.println("##################################################");
System.out.println("Starting part " + this.getClass().getName());
start = System.currentTimeMillis();
}
run();
if (TRACE) {
System.out.println("Timing for " + this.getClass().getName() + " is " + (System.currentTimeMillis() - start));
printStatistics();
}
if (CHECK) {
postCheck();
}
}
protected abstract void run();
protected void printStatistics() {
}
protected void postCheck() {
}
protected void preCheck() {
}
}
public HierarchicalLayoutManager() {
this(Combine.NONE);
}
public HierarchicalLayoutManager(Combine b) {
this.combine = b;
this.dummyWidth = DUMMY_WIDTH;
this.dummyHeight = DUMMY_HEIGHT;
this.xOffset = X_OFFSET;
this.layerOffset = LAYER_OFFSET;
this.maxLayerLength = MAX_LAYER_LENGTH;
this.minLayerDifference = MIN_LAYER_DIFFERENCE;
this.linksToFollow = new HashSet<>();
}
public int getMaxLayerLength() {
return maxLayerLength;
}
public void setMaxLayerLength(int v) {
maxLayerLength = v;
}
public void setMinLayerDifference(int v) {
minLayerDifference = v;
}
@Override
public void doLayout(LayoutGraph graph) {
doLayout(graph, new HashSet<Link>());
}
@Override
public void doLayout(LayoutGraph graph, Set<? extends Link> importantLinks) {
this.importantLinks = importantLinks;
this.graph = graph;
vertexToLayoutNode = new HashMap<>();
reversedLinks = new HashSet<>();
reversedLinkStartPoints = new HashMap<>();
reversedLinkEndPoints = new HashMap<>();
bottomEdgeHash = new HashMap<>();
nodes = new ArrayList<>();
splitStartPoints = new HashMap<>();
splitEndPoints = new HashMap<>();
// #############################################################
// Step 1: Build up data structure
new BuildDatastructure().start();
// #############################################################
// STEP 2: Reverse edges, handle backedges
new ReverseEdges().start();
for (LayoutNode n : nodes) {
ArrayList<LayoutEdge> tmpArr = new ArrayList<>();
for (LayoutEdge e : n.succs) {
if (importantLinks.contains(e.link)) {
tmpArr.add(e);
}
}
for (LayoutEdge e : tmpArr) {
e.from.succs.remove(e);
e.to.preds.remove(e);
}
}
// #############################################################
// STEP 3: Assign layers
new AssignLayers().start();
// #############################################################
// STEP 4: Create dummy nodes
new CreateDummyNodes().start();
// #############################################################
// STEP 5: Crossing Reduction
new CrossingReduction().start();
// #############################################################
// STEP 7: Assign X coordinates
new AssignXCoordinates().start();
// #############################################################
// STEP 6: Assign Y coordinates
new AssignYCoordinates().start();
// #############################################################
// STEP 8: Write back to interface
new WriteResult().start();
}
private class WriteResult extends AlgorithmPart {
private int pointCount;
@Override
protected void run() {
HashMap<Vertex, Point> vertexPositions = new HashMap<>();
HashMap<Link, List<Point>> linkPositions = new HashMap<>();
for (Vertex v : graph.getVertices()) {
LayoutNode n = vertexToLayoutNode.get(v);
assert !vertexPositions.containsKey(v);
vertexPositions.put(v, new Point(n.x + n.xOffset, n.y + n.yOffset));
}
for (LayoutNode n : nodes) {
for (LayoutEdge e : n.preds) {
if (e.link != null) {
ArrayList<Point> points = new ArrayList<>();
Point p = new Point(e.to.x + e.relativeTo, e.to.y + e.to.yOffset + e.link.getTo().getRelativePosition().y);
points.add(p);
if (e.to.inOffsets.containsKey(e.relativeTo)) {
points.add(new Point(p.x, p.y + e.to.inOffsets.get(e.relativeTo) + e.link.getTo().getRelativePosition().y));
}
LayoutNode cur = e.from;
LayoutNode other = e.to;
LayoutEdge curEdge = e;
while (cur.vertex == null && cur.preds.size() != 0) {
if (points.size() > 1 && points.get(points.size() - 1).x == cur.x + cur.width / 2 && points.get(points.size() - 2).x == cur.x + cur.width / 2) {
points.remove(points.size() - 1);
}
points.add(new Point(cur.x + cur.width / 2, cur.y + cur.height));
if (points.size() > 1 && points.get(points.size() - 1).x == cur.x + cur.width / 2 && points.get(points.size() - 2).x == cur.x + cur.width / 2) {
points.remove(points.size() - 1);
}
points.add(new Point(cur.x + cur.width / 2, cur.y));
assert cur.preds.size() == 1;
curEdge = cur.preds.get(0);
cur = curEdge.from;
}
p = new Point(cur.x + curEdge.relativeFrom, cur.y + cur.height - cur.bottomYOffset + (curEdge.link == null ? 0 : curEdge.link.getFrom().getRelativePosition().y));
if (curEdge.from.outOffsets.containsKey(curEdge.relativeFrom)) {
points.add(new Point(p.x, p.y + curEdge.from.outOffsets.get(curEdge.relativeFrom) + (curEdge.link == null ? 0 : curEdge.link.getFrom().getRelativePosition().y)));
}
points.add(p);
Collections.reverse(points);
if (cur.vertex == null && cur.preds.size() == 0) {
if (reversedLinkEndPoints.containsKey(e.link)) {
for (Point p1 : reversedLinkEndPoints.get(e.link)) {
points.add(new Point(p1.x + e.to.x, p1.y + e.to.y));
}
}
if (splitStartPoints.containsKey(e.link)) {
points.add(0, null);
points.addAll(0, splitStartPoints.get(e.link));
//checkPoints(points);
if (reversedLinks.contains(e.link)) {
Collections.reverse(points);
}
assert !linkPositions.containsKey(e.link);
linkPositions.put(e.link, points);
} else {
splitEndPoints.put(e.link, points);
}
} else {
if (reversedLinks.contains(e.link)) {
Collections.reverse(points);
}
if (reversedLinkStartPoints.containsKey(e.link)) {
for (Point p1 : reversedLinkStartPoints.get(e.link)) {
points.add(new Point(p1.x + cur.x, p1.y + cur.y));
}
}
if (reversedLinkEndPoints.containsKey(e.link)) {
for (Point p1 : reversedLinkEndPoints.get(e.link)) {
points.add(0, new Point(p1.x + other.x, p1.y + other.y));
}
}
assert !linkPositions.containsKey(e.link);
linkPositions.put(e.link, points);
}
pointCount += points.size();
// No longer needed!
e.link = null;
}
}
for (LayoutEdge e : n.succs) {
if (e.link != null) {
ArrayList<Point> points = new ArrayList<>();
Point p = new Point(e.from.x + e.relativeFrom, e.from.y + e.from.height - e.from.bottomYOffset + e.link.getFrom().getRelativePosition().y);
points.add(p);
if (e.from.outOffsets.containsKey(e.relativeFrom)) {
points.add(new Point(p.x, p.y + e.from.outOffsets.get(e.relativeFrom) + e.link.getFrom().getRelativePosition().y));
}
LayoutNode cur = e.to;
LayoutNode other = e.from;
LayoutEdge curEdge = e;
while (cur.vertex == null && !cur.succs.isEmpty()) {
if (points.size() > 1 && points.get(points.size() - 1).x == cur.x + cur.width / 2 && points.get(points.size() - 2).x == cur.x + cur.width / 2) {
points.remove(points.size() - 1);
}
points.add(new Point(cur.x + cur.width / 2, cur.y));
if (points.size() > 1 && points.get(points.size() - 1).x == cur.x + cur.width / 2 && points.get(points.size() - 2).x == cur.x + cur.width / 2) {
points.remove(points.size() - 1);
}
points.add(new Point(cur.x + cur.width / 2, cur.y + cur.height));
if (cur.succs.isEmpty()) {
break;
}
assert cur.succs.size() == 1;
curEdge = cur.succs.get(0);
cur = curEdge.to;
}
p = new Point(cur.x + curEdge.relativeTo, cur.y + cur.yOffset + ((curEdge.link == null) ? 0 : curEdge.link.getTo().getRelativePosition().y));
points.add(p);
if (curEdge.to.inOffsets.containsKey(curEdge.relativeTo)) {
points.add(new Point(p.x, p.y + curEdge.to.inOffsets.get(curEdge.relativeTo) + ((curEdge.link == null) ? 0 : curEdge.link.getTo().getRelativePosition().y)));
}
if (cur.succs.isEmpty() && cur.vertex == null) {
if (reversedLinkStartPoints.containsKey(e.link)) {
for (Point p1 : reversedLinkStartPoints.get(e.link)) {
points.add(0, new Point(p1.x + other.x, p1.y + other.y));
}
}
if (splitEndPoints.containsKey(e.link)) {
points.add(null);
points.addAll(splitEndPoints.get(e.link));
//checkPoints(points);
if (reversedLinks.contains(e.link)) {
Collections.reverse(points);
}
assert !linkPositions.containsKey(e.link);
linkPositions.put(e.link, points);
} else {
splitStartPoints.put(e.link, points);
}
} else {
if (reversedLinkStartPoints.containsKey(e.link)) {
for (Point p1 : reversedLinkStartPoints.get(e.link)) {
points.add(0, new Point(p1.x + other.x, p1.y + other.y));
}
}
if (reversedLinkEndPoints.containsKey(e.link)) {
for (Point p1 : reversedLinkEndPoints.get(e.link)) {
points.add(new Point(p1.x + cur.x, p1.y + cur.y));
}
}
if (reversedLinks.contains(e.link)) {
Collections.reverse(points);
}
//checkPoints(points);
assert !linkPositions.containsKey(e.link);
linkPositions.put(e.link, points);
}
pointCount += points.size();
e.link = null;
}
}
}
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
for (Vertex v : vertexPositions.keySet()) {
Point p = vertexPositions.get(v);
minX = Math.min(minX, p.x);
minY = Math.min(minY, p.y);
}
for (Link l : linkPositions.keySet()) {
List<Point> points = linkPositions.get(l);
for (Point p : points) {
if (p != null) {
minX = Math.min(minX, p.x);
minY = Math.min(minY, p.y);
}
}
}
for (Vertex v : vertexPositions.keySet()) {
Point p = vertexPositions.get(v);
p.x -= minX;
p.y -= minY;
v.setPosition(p);
}
for (Link l : linkPositions.keySet()) {
List<Point> points = linkPositions.get(l);
for (Point p : points) {
if (p != null) {
p.x -= minX;
p.y -= minY;
}
}
l.setControlPoints(points);
}
}
@Override
protected void printStatistics() {
System.out.println("Number of nodes: " + nodes.size());
int edgeCount = 0;
for (LayoutNode n : nodes) {
edgeCount += n.succs.size();
}
System.out.println("Number of edges: " + edgeCount);
System.out.println("Number of points: " + pointCount);
}
}
private static class Segment {
public float d;
public int orderNumber = -1;
public ArrayList<LayoutNode> nodes = new ArrayList<>();
public HashSet<Segment> succs = new HashSet<>();
public HashSet<Segment> preds = new HashSet<>();
public Region region;
}
private static final Comparator<Segment> segmentComparator = new Comparator<Segment>() {
@Override
public int compare(Segment s1, Segment s2) {
return s1.orderNumber - s2.orderNumber;
}
};
private static class Region {
public float d;
public int minOrderNumber;
public SortedSet<Segment> segments = new TreeSet<>(segmentComparator);
public HashSet<Region> succs = new HashSet<>(4);
public HashSet<Region> preds = new HashSet<>(4);
}
private static final Comparator<Region> regionComparator = new Comparator<Region>() {
@Override
public int compare(Region r1, Region r2) {
return r1.minOrderNumber - r2.minOrderNumber;
}
};
private static final Comparator<LayoutNode> nodePositionComparator = new Comparator<LayoutNode>() {
@Override
public int compare(LayoutNode n1, LayoutNode n2) {
return n1.pos - n2.pos;
}
};
private static final Comparator<LayoutNode> nodeProcessingDownComparator = new Comparator<LayoutNode>() {
@Override
public int compare(LayoutNode n1, LayoutNode n2) {
int n1VIP = 0;
for (LayoutEdge e : n1.preds) {
if (e.vip) {
n1VIP++;
}
}
int n2VIP = 0;
for (LayoutEdge e : n2.preds) {
if (e.vip) {
n2VIP++;
}
}
if (n1VIP != n2VIP) {
return n2VIP - n1VIP;
}
if (n1.vertex == null) {
if (n2.vertex == null) {
return 0;
}
return -1;
}
if (n2.vertex == null) {
return 1;
}
return n1.preds.size() - n2.preds.size();
}
};
private static final Comparator<LayoutNode> nodeProcessingUpComparator = new Comparator<LayoutNode>() {
@Override
public int compare(LayoutNode n1, LayoutNode n2) {
int n1VIP = 0;
for (LayoutEdge e : n1.succs) {
if (e.vip) {
n1VIP++;
}
}
int n2VIP = 0;
for (LayoutEdge e : n2.succs) {
if (e.vip) {
n2VIP++;
}
}
if (n1VIP != n2VIP) {
return n2VIP - n1VIP;
}
if (n1.vertex == null) {
if (n2.vertex == null) {
return 0;
}
return -1;
}
if (n2.vertex == null) {
return 1;
}
return n1.succs.size() - n2.succs.size();
}
};
private class AssignXCoordinates extends AlgorithmPart {
private ArrayList<Integer>[] space;
private ArrayList<LayoutNode>[] downProcessingOrder;
private ArrayList<LayoutNode>[] upProcessingOrder;
private void initialPositions() {
for (LayoutNode n : nodes) {
n.x = space[n.layer].get(n.pos);
}
}
@SuppressWarnings("unchecked")
private void createArrays() {
space = new ArrayList[layers.length];
downProcessingOrder = new ArrayList[layers.length];
upProcessingOrder = new ArrayList[layers.length];
}
@Override
protected void run() {
createArrays();
for (int i = 0; i < layers.length; i++) {
space[i] = new ArrayList<>();
downProcessingOrder[i] = new ArrayList<>();
upProcessingOrder[i] = new ArrayList<>();
int curX = 0;
for (LayoutNode n : layers[i]) {
space[i].add(curX);
curX += n.width + xOffset;
downProcessingOrder[i].add(n);
upProcessingOrder[i].add(n);
}
Collections.sort(downProcessingOrder[i], nodeProcessingDownComparator);
Collections.sort(upProcessingOrder[i], nodeProcessingUpComparator);
}
initialPositions();
for (int i = 0; i < SWEEP_ITERATIONS; i++) {
sweepDown();
adjustSpace();
sweepUp(false);
adjustSpace();
}
sweepDown();
adjustSpace();
sweepUp(true);
}
private void adjustSpace() {
for (int i = 0; i < layers.length; i++) {
// space[i] = new ArrayList<>();
int curX = 0;
for (LayoutNode n : layers[i]) {
space[i].add(n.x);
// curX += n.width + xOffset;
}
}
}
private int calculateOptimalDown(LayoutNode n) {
int size = n.preds.size();
if (size == 0) {
return n.x;
}
int vipCount = 0;
for (LayoutEdge e : n.preds) {
if (e.vip) {
vipCount++;
}
}
if (vipCount == 0) {
int[] values = new int[size];
for (int i = 0; i < size; i++) {
LayoutEdge e = n.preds.get(i);
values[i] = e.from.x + e.relativeFrom - e.relativeTo;
}
return median(values);
} else {
int z = 0;
int[] values = new int[vipCount];
for (int i = 0; i < size; i++) {
LayoutEdge e = n.preds.get(i);
if (e.vip) {
values[z++] = e.from.x + e.relativeFrom - e.relativeTo;
}
}
return median(values);
}
}
private int calculateOptimalBoth(LayoutNode n) {
if (n.preds.size() == n.succs.size()) {
return n.x;
}
int[] values = new int[n.preds.size() + n.succs.size()];
int i = 0;
for (LayoutEdge e : n.preds) {
values[i] = e.from.x + e.relativeFrom - e.relativeTo;
i++;
}
for (LayoutEdge e : n.succs) {
values[i] = e.to.x + e.relativeTo - e.relativeFrom;
i++;
}
return median(values);
}
private int calculateOptimalUp(LayoutNode n) {
int size = n.succs.size();
if (size == 0) {
return n.x;
}
int[] values = new int[size];
for (int i = 0; i < size; i++) {
LayoutEdge e = n.succs.get(i);
values[i] = e.to.x + e.relativeTo - e.relativeFrom;
if (e.vip) {
return values[i];
}
}
return median(values);
}
private int median(int[] values) {
Arrays.sort(values);
if (values.length % 2 == 0) {
return (values[values.length / 2 - 1] + values[values.length / 2]) / 2;
} else {
return values[values.length / 2];
}
}
private void sweepUp(boolean onlyDummies) {
for (int i = layers.length - 1; i >= 0; i--) {
NodeRow r = new NodeRow(space[i]);
for (LayoutNode n : upProcessingOrder[i]) {
int optimal = calculateOptimalUp(n);
r.insert(n, optimal);
}
}
}
private void doubleSweep() {
for (int i = layers.length - 2; i >= 0; i--) {
NodeRow r = new NodeRow(space[i]);
for (LayoutNode n : upProcessingOrder[i]) {
int optimal = calculateOptimalBoth(n);
r.insert(n, optimal);
}
}
}
private void sweepDown() {
for (int i = 1; i < layers.length; i++) {
NodeRow r = new NodeRow(space[i]);
for (LayoutNode n : downProcessingOrder[i]) {
int optimal = calculateOptimalDown(n);
r.insert(n, optimal);
}
}
}
}
private static class NodeRow {
private TreeSet<LayoutNode> treeSet;
private ArrayList<Integer> space;
public NodeRow(ArrayList<Integer> space) {
treeSet = new TreeSet<>(nodePositionComparator);
this.space = space;
}
public int offset(LayoutNode n1, LayoutNode n2) {
int v1 = space.get(n1.pos) + n1.width;
int v2 = space.get(n2.pos);
return v2 - v1;
}
public void insert(LayoutNode n, int pos) {
SortedSet<LayoutNode> headSet = treeSet.headSet(n);
LayoutNode leftNeighbor = null;
int minX = Integer.MIN_VALUE;
if (!headSet.isEmpty()) {
leftNeighbor = headSet.last();
minX = leftNeighbor.x + leftNeighbor.width + offset(leftNeighbor, n);
}
if (pos < minX) {
n.x = minX;
} else {
LayoutNode rightNeighbor = null;
SortedSet<LayoutNode> tailSet = treeSet.tailSet(n);
int maxX = Integer.MAX_VALUE;
if (!tailSet.isEmpty()) {
rightNeighbor = tailSet.first();
maxX = rightNeighbor.x - offset(n, rightNeighbor) - n.width;
}
if (pos > maxX) {
n.x = maxX;
} else {
n.x = pos;
}
assert minX <= maxX : minX + " vs " + maxX;
}
treeSet.add(n);
}
}
private static Comparator<LayoutNode> crossingNodeComparator = new Comparator<LayoutNode>() {
@Override
public int compare(LayoutNode n1, LayoutNode n2) {
return n1.crossingNumber - n2.crossingNumber;
}
};
private class CrossingReduction extends AlgorithmPart {
@Override
public void preCheck() {
for (LayoutNode n : nodes) {
assert n.layer < layerCount;
}
}
@SuppressWarnings("unchecked")
private void createLayers() {
layers = new List[layerCount];
for (int i = 0; i < layerCount; i++) {
layers[i] = new ArrayList<>();
}
}
@Override
protected void run() {
createLayers();
// Generate initial ordering
HashSet<LayoutNode> visited = new HashSet<>();
for (LayoutNode n : nodes) {
if (n.layer == 0) {
layers[0].add(n);
visited.add(n);
} else if (n.preds.isEmpty()) {
layers[n.layer].add(n);
visited.add(n);
}
}
for (int i = 0; i < layers.length - 1; i++) {
for (LayoutNode n : layers[i]) {
for (LayoutEdge e : n.succs) {
if (!visited.contains(e.to)) {
visited.add(e.to);
layers[i + 1].add(e.to);
}
}
}
}
updatePositions();
initX();
// Optimize
for (int i = 0; i < CROSSING_ITERATIONS; i++) {
downSweep();
upSweep();
}
downSweep();
}
private void initX() {
for (int i = 0; i < layers.length; i++) {
updateXOfLayer(i);
}
}
private void updateXOfLayer(int index) {
int x = 0;
for (LayoutNode n : layers[index]) {
n.x = x;
x += n.width + X_OFFSET;
}
}
private void updatePositions() {
for (int i = 0; i < layers.length; i++) {
int z = 0;
for (LayoutNode n : layers[i]) {
n.pos = z;
z++;
}
}
}
private void downSweep() {
// Downsweep
for (int i = 1; i < layerCount; i++) {
for (LayoutNode n : layers[i]) {
n.crossingNumber = 0;
}
for (LayoutNode n : layers[i]) {
int sum = 0;
int count = 0;
for (LayoutEdge e : n.preds) {
int cur = e.from.x + e.relativeFrom;
int factor = 1;
if (e.vip) {
factor = VIP_BONUS;
}
sum += cur * factor;
count += factor;
}
if (count > 0) {
sum /= count;
n.crossingNumber = sum;
}
}
updateCrossingNumbers(i, true);
Collections.sort(layers[i], crossingNodeComparator);
updateXOfLayer(i);
int z = 0;
for (LayoutNode n : layers[i]) {
n.pos = z;
z++;
}
}
}
private void updateCrossingNumbers(int index, boolean down) {
for (int i = 0; i < layers[index].size(); i++) {
LayoutNode n = layers[index].get(i);
LayoutNode prev = null;
if (i > 0) {
prev = layers[index].get(i - 1);
}
LayoutNode next = null;
if (i < layers[index].size() - 1) {
next = layers[index].get(i + 1);
}
boolean cond = n.succs.isEmpty();
if (down) {
cond = n.preds.isEmpty();
}
if (cond) {
if (prev != null && next != null) {
n.crossingNumber = (prev.crossingNumber + next.crossingNumber) / 2;
} else if (prev != null) {
n.crossingNumber = prev.crossingNumber;
} else if (next != null) {
n.crossingNumber = next.crossingNumber;
}
}
}
}
private void upSweep() {
// Upsweep
for (int i = layerCount - 2; i >= 0; i--) {
for (LayoutNode n : layers[i]) {
n.crossingNumber = 0;
}
for (LayoutNode n : layers[i]) {
int count = 0;
int sum = 0;
for (LayoutEdge e : n.succs) {
int cur = e.to.x + e.relativeTo;
int factor = 1;
if (e.vip) {
factor = VIP_BONUS;
}
sum += cur * factor;
count += factor;
}
if (count > 0) {
sum /= count;
n.crossingNumber = sum;
}
}
updateCrossingNumbers(i, false);
Collections.sort(layers[i], crossingNodeComparator);
updateXOfLayer(i);
int z = 0;
for (LayoutNode n : layers[i]) {
n.pos = z;
z++;
}
}
}
@Override
public void postCheck() {
HashSet<LayoutNode> visited = new HashSet<>();
for (int i = 0; i < layers.length; i++) {
for (LayoutNode n : layers[i]) {
assert !visited.contains(n);
assert n.layer == i;
visited.add(n);
}
}
}
}
private class AssignYCoordinates extends AlgorithmPart {
@Override
protected void run() {
int curY = 0;
for (int i = 0; i < layers.length; i++) {
int maxHeight = 0;
int baseLine = 0;
int bottomBaseLine = 0;
for (LayoutNode n : layers[i]) {
maxHeight = Math.max(maxHeight, n.height - n.yOffset - n.bottomYOffset);
baseLine = Math.max(baseLine, n.yOffset);
bottomBaseLine = Math.max(bottomBaseLine, n.bottomYOffset);
}
int maxXOffset = 0;
for (LayoutNode n : layers[i]) {
if (n.vertex == null) {
// Dummy node
n.y = curY;
n.height = maxHeight + baseLine + bottomBaseLine;
} else {
n.y = curY + baseLine + (maxHeight - (n.height - n.yOffset - n.bottomYOffset)) / 2 - n.yOffset;
}
for (LayoutEdge e : n.succs) {
int curXOffset = Math.abs(n.x - e.to.x);
maxXOffset = Math.max(curXOffset, maxXOffset);
}
}
curY += maxHeight + baseLine + bottomBaseLine;
curY += layerOffset + ((int) (Math.sqrt(maxXOffset) * 1.5));
}
}
}
private class CreateDummyNodes extends AlgorithmPart {
private int oldNodeCount;
@Override
protected void preCheck() {
for (LayoutNode n : nodes) {
for (LayoutEdge e : n.succs) {
assert e.from != null;
assert e.from == n;
assert e.from.layer < e.to.layer;
}
for (LayoutEdge e : n.preds) {
assert e.to != null;
assert e.to == n;
}
}
}
@Override
protected void run() {
oldNodeCount = nodes.size();
if (combine == Combine.SAME_OUTPUTS) {
Comparator<LayoutEdge> comparator = new Comparator<LayoutEdge>() {
@Override
public int compare(LayoutEdge e1, LayoutEdge e2) {
return e1.to.layer - e2.to.layer;
}
};
HashMap<Integer, List<LayoutEdge>> portHash = new HashMap<>();
ArrayList<LayoutNode> currentNodes = new ArrayList<>(nodes);
for (LayoutNode n : currentNodes) {
portHash.clear();
ArrayList<LayoutEdge> succs = new ArrayList<>(n.succs);
HashMap<Integer, LayoutNode> topNodeHash = new HashMap<>();
HashMap<Integer, HashMap<Integer, LayoutNode>> bottomNodeHash = new HashMap<>();
for (LayoutEdge e : succs) {
assert e.from.layer < e.to.layer;
if (e.from.layer != e.to.layer - 1) {
if (maxLayerLength != -1 && e.to.layer - e.from.layer > maxLayerLength/* && e.to.preds.size() > 1 && e.from.succs.size() > 1*/) {
assert maxLayerLength > 2;
e.to.preds.remove(e);
e.from.succs.remove(e);
LayoutEdge topEdge = null;
if (combine == Combine.SAME_OUTPUTS && topNodeHash.containsKey(e.relativeFrom)) {
LayoutNode topNode = topNodeHash.get(e.relativeFrom);
topEdge = new LayoutEdge();
topEdge.relativeFrom = e.relativeFrom;
topEdge.from = e.from;
topEdge.relativeTo = topNode.width / 2;
topEdge.to = topNode;
topEdge.link = e.link;
topEdge.vip = e.vip;
e.from.succs.add(topEdge);
topNode.preds.add(topEdge);
} else {
LayoutNode topNode = new LayoutNode();
topNode.layer = e.from.layer + 1;
topNode.width = DUMMY_WIDTH;
topNode.height = DUMMY_HEIGHT;
nodes.add(topNode);
topEdge = new LayoutEdge();
topEdge.relativeFrom = e.relativeFrom;
topEdge.from = e.from;
topEdge.relativeTo = topNode.width / 2;
topEdge.to = topNode;
topEdge.link = e.link;
topEdge.vip = e.vip;
e.from.succs.add(topEdge);
topNode.preds.add(topEdge);
topNodeHash.put(e.relativeFrom, topNode);
bottomNodeHash.put(e.relativeFrom, new HashMap<Integer, LayoutNode>());
}
HashMap<Integer, LayoutNode> hash = bottomNodeHash.get(e.relativeFrom);
LayoutNode bottomNode = null;
if (hash.containsKey(e.to.layer)) {
bottomNode = hash.get(e.to.layer);
} else {
bottomNode = new LayoutNode();
bottomNode.layer = e.to.layer - 1;
bottomNode.width = DUMMY_WIDTH;
bottomNode.height = DUMMY_HEIGHT;
nodes.add(bottomNode);
hash.put(e.to.layer, bottomNode);
}
LayoutEdge bottomEdge = new LayoutEdge();
bottomEdge.relativeTo = e.relativeTo;
bottomEdge.to = e.to;
bottomEdge.relativeFrom = bottomNode.width / 2;
bottomEdge.from = bottomNode;
bottomEdge.link = e.link;
bottomEdge.vip = e.vip;
e.to.preds.add(bottomEdge);
bottomEdgeHash.put(topEdge, bottomEdge);
bottomNode.succs.add(bottomEdge);
} else {
Integer i = e.relativeFrom;
if (!portHash.containsKey(i)) {
portHash.put(i, new ArrayList<LayoutEdge>());
}
portHash.get(i).add(e);
}
}
}
succs = new ArrayList<>(n.succs);
for (LayoutEdge e : succs) {
Integer i = e.relativeFrom;
if (portHash.containsKey(i)) {
List<LayoutEdge> list = portHash.get(i);
Collections.sort(list, comparator);
if (list.size() == 1) {
processSingleEdge(list.get(0));
} else {
int maxLayer = list.get(0).to.layer;
for (LayoutEdge curEdge : list) {
maxLayer = Math.max(maxLayer, curEdge.to.layer);
}
int cnt = maxLayer - n.layer - 1;
LayoutEdge[] edges = new LayoutEdge[cnt];
LayoutNode[] nodes = new LayoutNode[cnt];
edges[0] = new LayoutEdge();
edges[0].from = n;
edges[0].relativeFrom = i;
edges[0].vip = e.vip;
n.succs.add(edges[0]);
nodes[0] = new LayoutNode();
nodes[0].width = dummyWidth;
nodes[0].height = dummyHeight;
nodes[0].layer = n.layer + 1;
nodes[0].preds.add(edges[0]);
edges[0].to = nodes[0];
edges[0].relativeTo = nodes[0].width / 2;
for (int j = 1; j < cnt; j++) {
edges[j] = new LayoutEdge();
edges[j].vip = e.vip;
edges[j].from = nodes[j - 1];
edges[j].relativeFrom = nodes[j - 1].width / 2;
nodes[j - 1].succs.add(edges[j]);
nodes[j] = new LayoutNode();
nodes[j].width = dummyWidth;
nodes[j].height = dummyHeight;
nodes[j].layer = n.layer + j + 1;
nodes[j].preds.add(edges[j]);
edges[j].to = nodes[j];
edges[j].relativeTo = nodes[j].width / 2;
}
for (LayoutEdge curEdge : list) {
assert curEdge.to.layer - n.layer - 2 >= 0;
assert curEdge.to.layer - n.layer - 2 < cnt;
LayoutNode anchor = nodes[curEdge.to.layer - n.layer - 2];
anchor.succs.add(curEdge);
curEdge.from = anchor;
curEdge.relativeFrom = anchor.width / 2;
n.succs.remove(curEdge);
}
}
portHash.remove(i);
}
}
}
} else if (combine == Combine.SAME_INPUTS) {
throw new UnsupportedOperationException("Currently not supported");
} else {
ArrayList<LayoutNode> currentNodes = new ArrayList<>(nodes);
for (LayoutNode n : currentNodes) {
for (LayoutEdge e : n.succs) {
processSingleEdge(e);
}
}
}
}
private void processSingleEdge(LayoutEdge e) {
LayoutNode n = e.from;
if (e.to.layer > n.layer + 1) {
LayoutEdge last = e;
for (int i = n.layer + 1; i < last.to.layer; i++) {
last = addBetween(last, i);
}
}
}
private LayoutEdge addBetween(LayoutEdge e, int layer) {
LayoutNode n = new LayoutNode();
n.width = dummyWidth;
n.height = dummyHeight;
n.layer = layer;
n.preds.add(e);
nodes.add(n);
LayoutEdge result = new LayoutEdge();
result.vip = e.vip;
n.succs.add(result);
result.from = n;
result.relativeFrom = n.width / 2;
result.to = e.to;
result.relativeTo = e.relativeTo;
e.relativeTo = n.width / 2;
e.to.preds.remove(e);
e.to.preds.add(result);
e.to = n;
return result;
}
@Override
public void printStatistics() {
System.out.println("Dummy nodes created: " + (nodes.size() - oldNodeCount));
}
@Override
public void postCheck() {
ArrayList<LayoutNode> currentNodes = new ArrayList<>(nodes);
for (LayoutNode n : currentNodes) {
for (LayoutEdge e : n.succs) {
assert e.from.layer == e.to.layer - 1;
}
}
for (int i = 0; i < layers.length; i++) {
assert layers[i].size() > 0;
for (LayoutNode n : layers[i]) {
assert n.layer == i;
}
}
}
}
private class AssignLayers extends AlgorithmPart {
@Override
public void preCheck() {
for (LayoutNode n : nodes) {
assert n.layer == -1;
}
}
@Override
protected void run() {
assignLayerDownwards();
assignLayerUpwards();
}
private void assignLayerDownwards() {
ArrayList<LayoutNode> hull = new ArrayList<>();
for (LayoutNode n : nodes) {
if (n.preds.isEmpty()) {
hull.add(n);
n.layer = 0;
}
}
int z = minLayerDifference;
while (!hull.isEmpty()) {
ArrayList<LayoutNode> newSet = new ArrayList<>();
for (LayoutNode n : hull) {
for (LayoutEdge se : n.succs) {
LayoutNode s = se.to;
if (s.layer != -1) {
// This node was assigned before.
} else {
boolean unassignedPred = false;
for (LayoutEdge pe : s.preds) {
LayoutNode p = pe.from;
if (p.layer == -1 || p.layer >= z) {
// This now has an unscheduled successor or a successor that was scheduled only in this round.
unassignedPred = true;
break;
}
}
if (unassignedPred) {
// This successor node can not yet be assigned.
} else {
s.layer = z;
newSet.add(s);
}
}
}
}
hull = newSet;
z += minLayerDifference;
}
layerCount = z - minLayerDifference;
for (LayoutNode n : nodes) {
n.layer = (layerCount - 1 - n.layer);
}
}
private void assignLayerUpwards() {
ArrayList<LayoutNode> hull = new ArrayList<>();
for (LayoutNode n : nodes) {
if (n.succs.isEmpty()) {
hull.add(n);
} else {
n.layer = -1;
}
}
int z = minLayerDifference;
while (!hull.isEmpty()) {
ArrayList<LayoutNode> newSet = new ArrayList<>();
for (LayoutNode n : hull) {
if (n.layer < z) {
for (LayoutEdge se : n.preds) {
LayoutNode s = se.from;
if (s.layer != -1) {
// This node was assigned before.
} else {
boolean unassignedSucc = false;
for (LayoutEdge pe : s.succs) {
LayoutNode p = pe.to;
if (p.layer == -1 || p.layer >= z) {
// This now has an unscheduled successor or a successor that was scheduled only in this round.
unassignedSucc = true;
break;
}
}
if (unassignedSucc) {
// This predecessor node can not yet be assigned.
} else {
s.layer = z;
newSet.add(s);
}
}
}
} else {
newSet.add(n);
}
}
hull = newSet;
z += minLayerDifference;
}
layerCount = z - minLayerDifference;
for (LayoutNode n : nodes) {
n.layer = (layerCount - 1 - n.layer);
}
}
@Override
public void postCheck() {
for (LayoutNode n : nodes) {
assert n.layer >= 0;
assert n.layer < layerCount;
for (LayoutEdge e : n.succs) {
assert e.from.layer < e.to.layer;
}
}
}
}
private class ReverseEdges extends AlgorithmPart {
private HashSet<LayoutNode> visited;
private HashSet<LayoutNode> active;
@Override
protected void run() {
// Remove self-edges
for (LayoutNode node : nodes) {
ArrayList<LayoutEdge> succs = new ArrayList<>(node.succs);
for (LayoutEdge e : succs) {
assert e.from == node;
if (e.to == node) {
node.succs.remove(e);
node.preds.remove(e);
}
}
}
// Reverse inputs of roots
for (LayoutNode node : nodes) {
if (node.vertex.isRoot()) {
boolean ok = true;
for (LayoutEdge e : node.preds) {
if (e.from.vertex.isRoot()) {
ok = false;
break;
}
}
if (ok) {
reverseAllInputs(node);
}
}
}
// Start DFS and reverse back edges
visited = new HashSet<>();
active = new HashSet<>();
for (LayoutNode node : nodes) {
DFS(node);
}
for (LayoutNode node : nodes) {
SortedSet<Integer> reversedDown = new TreeSet<>();
for (LayoutEdge e : node.succs) {
if (reversedLinks.contains(e.link)) {
reversedDown.add(e.relativeFrom);
}
}
SortedSet<Integer> reversedUp = null;
if (reversedDown.size() == 0) {
reversedUp = new TreeSet<>(Collections.reverseOrder());
} else {
reversedUp = new TreeSet<>();
}
for (LayoutEdge e : node.preds) {
if (reversedLinks.contains(e.link)) {
reversedUp.add(e.relativeTo);
}
}
final int offset = X_OFFSET + DUMMY_WIDTH;
int curX = 0;
int curWidth = node.width + reversedDown.size() * offset;
for (int pos : reversedDown) {
ArrayList<LayoutEdge> reversedSuccs = new ArrayList<>();
for (LayoutEdge e : node.succs) {
if (e.relativeFrom == pos && reversedLinks.contains(e.link)) {
reversedSuccs.add(e);
e.relativeFrom = curWidth;
}
}
ArrayList<Point> startPoints = new ArrayList<>();
startPoints.add(new Point(curWidth, curX));
startPoints.add(new Point(pos, curX));
startPoints.add(new Point(pos, reversedDown.size() * offset));
for (LayoutEdge e : reversedSuccs) {
reversedLinkStartPoints.put(e.link, startPoints);
}
node.inOffsets.put(pos, -curX);
curX += offset;
node.height += offset;
node.yOffset += offset;
curWidth -= offset;
}
node.width += reversedDown.size() * offset;
if (reversedDown.size() == 0) {
curX = offset;
} else {
curX = -offset;
}
curX = 0;
int minX = 0;
if (reversedDown.size() != 0) {
minX = -offset * reversedUp.size();
}
int oldNodeHeight = node.height;
for (int pos : reversedUp) {
ArrayList<LayoutEdge> reversedPreds = new ArrayList<>();
for (LayoutEdge e : node.preds) {
if (e.relativeTo == pos && reversedLinks.contains(e.link)) {
if (reversedDown.size() == 0) {
e.relativeTo = node.width + offset;
} else {
e.relativeTo = curX - offset;
}
reversedPreds.add(e);
}
}
node.height += offset;
ArrayList<Point> endPoints = new ArrayList<>();
if (reversedDown.size() == 0) {
curX += offset;
node.width += offset;
endPoints.add(new Point(node.width, node.height));
} else {
curX -= offset;
node.width += offset;
endPoints.add(new Point(curX, node.height));
}
node.outOffsets.put(pos - minX, curX);
curX += offset;
node.bottomYOffset += offset;
endPoints.add(new Point(pos, node.height));
endPoints.add(new Point(pos, oldNodeHeight));
for (LayoutEdge e : reversedPreds) {
reversedLinkEndPoints.put(e.link, endPoints);
}
}
if (minX < 0) {
for (LayoutEdge e : node.preds) {
e.relativeTo -= minX;
}
for (LayoutEdge e : node.succs) {
e.relativeFrom -= minX;
}
node.xOffset = -minX;
node.width += -minX;
}
}
}
private void DFS(LayoutNode startNode) {
if (visited.contains(startNode)) {
return;
}
Stack<LayoutNode> stack = new Stack<>();
stack.push(startNode);
while (!stack.empty()) {
LayoutNode node = stack.pop();
if (visited.contains(node)) {
// Node no longer active
active.remove(node);
continue;
}
// Repush immediately to know when no longer active
stack.push(node);
visited.add(node);
active.add(node);
ArrayList<LayoutEdge> succs = new ArrayList<>(node.succs);
for (LayoutEdge e : succs) {
if (active.contains(e.to)) {
assert visited.contains(e.to);
// Encountered back edge
reverseEdge(e);
} else if (!visited.contains(e.to) && (linksToFollow.size() == 0 || linksToFollow.contains(e.link))) {
stack.push(e.to);
}
}
}
}
private void reverseAllInputs(LayoutNode node) {
for (LayoutEdge e : node.preds) {
assert !reversedLinks.contains(e.link);
reversedLinks.add(e.link);
node.succs.add(e);
e.from.preds.add(e);
e.from.succs.remove(e);
int oldRelativeFrom = e.relativeFrom;
int oldRelativeTo = e.relativeTo;
e.to = e.from;
e.from = node;
e.relativeFrom = oldRelativeTo;
e.relativeTo = oldRelativeFrom;
}
node.preds.clear();
}
private void reverseEdge(LayoutEdge e) {
assert !reversedLinks.contains(e.link);
reversedLinks.add(e.link);
LayoutNode oldFrom = e.from;
LayoutNode oldTo = e.to;
int oldRelativeFrom = e.relativeFrom;
int oldRelativeTo = e.relativeTo;
e.from = oldTo;
e.to = oldFrom;
e.relativeFrom = oldRelativeTo;
e.relativeTo = oldRelativeFrom;
oldFrom.succs.remove(e);
oldFrom.preds.add(e);
oldTo.preds.remove(e);
oldTo.succs.add(e);
}
@Override
public void postCheck() {
for (LayoutNode n : nodes) {
HashSet<LayoutNode> curVisited = new HashSet<>();
Queue<LayoutNode> queue = new LinkedList<>();
for (LayoutEdge e : n.succs) {
LayoutNode s = e.to;
queue.add(s);
curVisited.add(s);
}
while (!queue.isEmpty()) {
LayoutNode curNode = queue.remove();
for (LayoutEdge e : curNode.succs) {
assert e.to != n;
if (!curVisited.contains(e.to)) {
queue.add(e.to);
curVisited.add(e.to);
}
}
}
}
}
}
private Comparator<Link> linkComparator = new Comparator<Link>() {
@Override
public int compare(Link l1, Link l2) {
if (l1.isVIP() && !l2.isVIP()) {
return -1;
}
if (!l1.isVIP() && l2.isVIP()) {
return 1;
}
int result = l1.getFrom().getVertex().compareTo(l2.getFrom().getVertex());
if (result != 0) {
return result;
}
result = l1.getTo().getVertex().compareTo(l2.getTo().getVertex());
if (result != 0) {
return result;
}
result = l1.getFrom().getRelativePosition().x - l2.getFrom().getRelativePosition().x;
if (result != 0) {
return result;
}
result = l1.getTo().getRelativePosition().x - l2.getTo().getRelativePosition().x;
return result;
}
};
private class BuildDatastructure extends AlgorithmPart {
@Override
protected void run() {
// Set up nodes
List<Vertex> vertices = new ArrayList<>(graph.getVertices());
Collections.sort(vertices);
for (Vertex v : vertices) {
LayoutNode node = new LayoutNode();
Dimension size = v.getSize();
node.width = (int) size.getWidth();
node.height = (int) size.getHeight();
node.vertex = v;
nodes.add(node);
vertexToLayoutNode.put(v, node);
}
// Set up edges
List<Link> links = new ArrayList<>(graph.getLinks());
Collections.sort(links, linkComparator);
for (Link l : links) {
LayoutEdge edge = new LayoutEdge();
assert vertexToLayoutNode.containsKey(l.getFrom().getVertex());
assert vertexToLayoutNode.containsKey(l.getTo().getVertex());
edge.from = vertexToLayoutNode.get(l.getFrom().getVertex());
edge.to = vertexToLayoutNode.get(l.getTo().getVertex());
edge.relativeFrom = l.getFrom().getRelativePosition().x;
edge.relativeTo = l.getTo().getRelativePosition().x;
edge.link = l;
edge.vip = l.isVIP();
edge.from.succs.add(edge);
edge.to.preds.add(edge);
//assert edge.from != edge.to; // No self-loops allowed
}
for (Link l : importantLinks) {
if (!vertexToLayoutNode.containsKey(l.getFrom().getVertex())
|| vertexToLayoutNode.containsKey(l.getTo().getVertex())) {
continue;
}
LayoutNode from = vertexToLayoutNode.get(l.getFrom().getVertex());
LayoutNode to = vertexToLayoutNode.get(l.getTo().getVertex());
for (LayoutEdge e : from.succs) {
if (e.to == to) {
linksToFollow.add(e.link);
}
}
}
}
@Override
public void postCheck() {
assert vertexToLayoutNode.keySet().size() == nodes.size();
assert nodes.size() == graph.getVertices().size();
for (Vertex v : graph.getVertices()) {
LayoutNode node = vertexToLayoutNode.get(v);
assert node != null;
for (LayoutEdge e : node.succs) {
assert e.from == node;
}
for (LayoutEdge e : node.preds) {
assert e.to == node;
}
}
}
}
@Override
public void doRouting(LayoutGraph graph) {
// Do nothing for now
}
}