blob: 39cd1d8ed0692355ec5e60f872d187a57d7c6004 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.draw2d.graph;
import java.util.ArrayList;
import java.util.List;
/**
* This visitor eliminates cycles in the graph using a "greedy" heuristic. Nodes
* which are sources and sinks are marked and placed in a source and sink list,
* leaving only nodes involved in cycles. A remaining node with the highest
* (outgoing-incoming) edges score is then chosen greedily as if it were a
* source. The process is repeated until all nodes have been marked and placed
* in a list. The lists are then concatenated, and any edges which go backwards
* in this list will be inverted during the layout procedure.
*
* @author Daniel Lee
* @since 2.1.2
*/
class BreakCycles extends GraphVisitor {
// Used in identifying cycles and in cycle removal.
// Flag field indicates "presence". If true, the node has been removed from
// the list.
NodeList graphNodes = new NodeList();
private boolean allNodesFlagged() {
for (int i = 0; i < graphNodes.size(); i++) {
if (graphNodes.getNode(i).flag == false)
return false;
}
return true;
}
private void breakCycles(DirectedGraph g) {
initializeDegrees(g);
greedyCycleRemove(g);
invertEdges(g);
}
/*
* Returns true if g contains cycles, false otherwise
*/
private boolean containsCycles(DirectedGraph g) {
List noLefts = new ArrayList();
// Identify all initial nodes for removal
for (int i = 0; i < graphNodes.size(); i++) {
Node node = graphNodes.getNode(i);
if (getIncomingCount(node) == 0)
sortedInsert(noLefts, node);
}
while (noLefts.size() > 0) {
Node node = (Node) noLefts.remove(noLefts.size() - 1);
node.flag = true;
for (int i = 0; i < node.outgoing.size(); i++) {
Node right = node.outgoing.getEdge(i).target;
setIncomingCount(right, getIncomingCount(right) - 1);
if (getIncomingCount(right) == 0)
sortedInsert(noLefts, right);
}
}
if (allNodesFlagged())
return false;
return true;
}
/*
* Returns the node in graphNodes with the largest (outgoing edge count -
* incoming edge count) value
*/
private Node findNodeWithMaxDegree() {
int max = Integer.MIN_VALUE;
Node maxNode = null;
for (int i = 0; i < graphNodes.size(); i++) {
Node node = graphNodes.getNode(i);
if (getDegree(node) >= max && node.flag == false) {
max = getDegree(node);
maxNode = node;
}
}
return maxNode;
}
private int getDegree(Node n) {
return n.workingInts[3];
}
private int getIncomingCount(Node n) {
return n.workingInts[0];
}
private int getInDegree(Node n) {
return n.workingInts[1];
}
private int getOrderIndex(Node n) {
return n.workingInts[0];
}
private int getOutDegree(Node n) {
return n.workingInts[2];
}
private void greedyCycleRemove(DirectedGraph g) {
NodeList sL = new NodeList();
NodeList sR = new NodeList();
do {
// Add all sinks and isolated nodes to sR
boolean hasSink;
do {
hasSink = false;
for (int i = 0; i < graphNodes.size(); i++) {
Node node = graphNodes.getNode(i);
if (getOutDegree(node) == 0 && node.flag == false) {
hasSink = true;
node.flag = true;
updateIncoming(node);
sR.add(node);
break;
}
}
} while (hasSink);
// Add all sources to sL
boolean hasSource;
do {
hasSource = false;
for (int i = 0; i < graphNodes.size(); i++) {
Node node = graphNodes.getNode(i);
if (getInDegree(node) == 0 && node.flag == false) {
hasSource = true;
node.flag = true;
updateOutgoing(node);
sL.add(node);
break;
}
}
} while (hasSource);
// When all sinks and sources are removed, choose a node with the
// maximum degree (outDegree - inDegree) and add it to sL
Node max = findNodeWithMaxDegree();
if (max != null) {
sL.add(max);
max.flag = true;
updateIncoming(max);
updateOutgoing(max);
}
} while (!allNodesFlagged());
// Assign order indexes
int orderIndex = 0;
for (int i = 0; i < sL.size(); i++) {
setOrderIndex(sL.getNode(i), orderIndex++);
}
for (int i = sR.size() - 1; i >= 0; i--) {
setOrderIndex(sR.getNode(i), orderIndex++);
}
}
private void initializeDegrees(DirectedGraph g) {
graphNodes.resetFlags();
for (int i = 0; i < g.nodes.size(); i++) {
Node n = graphNodes.getNode(i);
setInDegree(n, n.incoming.size());
setOutDegree(n, n.outgoing.size());
setDegree(n, n.outgoing.size() - n.incoming.size());
}
}
private void invertEdges(DirectedGraph g) {
for (int i = 0; i < g.edges.size(); i++) {
Edge e = g.edges.getEdge(i);
if (getOrderIndex(e.source) > getOrderIndex(e.target)) {
e.invert();
e.isFeedback = true;
}
}
}
private void setDegree(Node n, int deg) {
n.workingInts[3] = deg;
}
private void setIncomingCount(Node n, int count) {
n.workingInts[0] = count;
}
private void setInDegree(Node n, int deg) {
n.workingInts[1] = deg;
}
private void setOutDegree(Node n, int deg) {
n.workingInts[2] = deg;
}
private void setOrderIndex(Node n, int index) {
n.workingInts[0] = index;
}
private void sortedInsert(List list, Node node) {
int insert = 0;
while (insert < list.size()
&& ((Node) list.get(insert)).sortValue > node.sortValue)
insert++;
list.add(insert, node);
}
/*
* Called after removal of n. Updates the degree values of n's incoming
* nodes.
*/
private void updateIncoming(Node n) {
for (int i = 0; i < n.incoming.size(); i++) {
Node in = n.incoming.getEdge(i).source;
if (in.flag == false) {
setOutDegree(in, getOutDegree(in) - 1);
setDegree(in, getOutDegree(in) - getInDegree(in));
}
}
}
/*
* Called after removal of n. Updates the degree values of n's outgoing
* nodes.
*/
private void updateOutgoing(Node n) {
for (int i = 0; i < n.outgoing.size(); i++) {
Node out = n.outgoing.getEdge(i).target;
if (out.flag == false) {
setInDegree(out, getInDegree(out) - 1);
setDegree(out, getOutDegree(out) - getInDegree(out));
}
}
}
public void revisit(DirectedGraph g) {
for (int i = 0; i < g.edges.size(); i++) {
Edge e = g.edges.getEdge(i);
if (e.isFeedback())
e.invert();
}
}
/**
* @see GraphVisitor#visit(org.eclipse.draw2d.graph.DirectedGraph)
*/
public void visit(DirectedGraph g) {
// put all nodes in list, initialize index
graphNodes.resetFlags();
for (int i = 0; i < g.nodes.size(); i++) {
Node n = g.nodes.getNode(i);
setIncomingCount(n, n.incoming.size());
graphNodes.add(n);
}
if (containsCycles(g)) {
breakCycles(g);
}
}
}