| /******************************************************************************* |
| * 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); |
| } |
| } |
| |
| } |