| /******************************************************************************* |
| * 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; |
| |
| /** |
| * This visitor eliminates cycles in the graph via a modified implementation of |
| * the greedy cycle removal algorithm for directed graphs. The algorithm has |
| * been modified to handle the presence of Subgraphs and compound cycles which |
| * may result. This algorithm determines a set of edges which can be inverted |
| * and result in a graph without compound cycles. |
| * |
| * @author Daniel Lee |
| * @author Randy Hudson |
| * @since 2.1.2 |
| */ |
| class CompoundBreakCycles extends GraphVisitor { |
| |
| /* |
| * Caches all nodes in the graph. Used in identifying cycles and in cycle |
| * removal. Flag field indicates "presence". If true, the node has been |
| * removed from the list. |
| */ |
| private NodeList graphNodes; |
| private NodeList sL = new NodeList(); |
| |
| private boolean allFlagged(NodeList nodes) { |
| for (int i = 0; i < nodes.size(); i++) { |
| if (nodes.getNode(i).flag == false) |
| return false; |
| } |
| return true; |
| } |
| |
| private int buildNestingTreeIndices(NodeList nodes, int base) { |
| for (int i = 0; i < nodes.size(); i++) { |
| Node node = (Node) nodes.get(i); |
| if (node instanceof Subgraph) { |
| Subgraph s = (Subgraph) node; |
| s.nestingTreeMin = base; |
| base = buildNestingTreeIndices(s.members, base); |
| } |
| node.nestingIndex = base++; |
| } |
| return base++; |
| } |
| |
| private boolean canBeRemoved(Node n) { |
| return !n.flag && getChildCount(n) == 0; |
| } |
| |
| private boolean changeInDegree(Node n, int delta) { |
| return (n.workingInts[1] += delta) == 0; |
| } |
| |
| private boolean changeOutDegree(Node n, int delta) { |
| return (n.workingInts[2] += delta) == 0; |
| } |
| |
| /* |
| * Execution of the modified greedy cycle removal algorithm. |
| */ |
| private void cycleRemove(NodeList children) { |
| NodeList sR = new NodeList(); |
| do { |
| findSinks(children, sR); |
| findSources(children); |
| |
| // all sinks and sources added, find node with highest |
| // outDegree - inDegree |
| Node max = findNodeWithMaxDegree(children); |
| if (max != null) { |
| for (int i = 0; i < children.size(); i++) { |
| Node child = (Node) children.get(i); |
| if (child.flag) |
| continue; |
| if (child == max) |
| restoreSinks(max, sR); |
| else |
| restoreSources(child); |
| } |
| remove(max); |
| } |
| } while (!allFlagged(children)); |
| while (!sR.isEmpty()) |
| sL.add(sR.remove(sR.size() - 1)); |
| } |
| |
| private void findInitialSinks(NodeList children, NodeList sinks) { |
| for (int i = 0; i < children.size(); i++) { |
| Node node = children.getNode(i); |
| if (node.flag) |
| continue; |
| if (isSink(node) && canBeRemoved(node)) { |
| sinks.add(node); |
| node.flag = true; |
| } |
| if (node instanceof Subgraph) |
| findInitialSinks(((Subgraph) node).members, sinks); |
| } |
| } |
| |
| private void findInitialSources(NodeList children, NodeList sources) { |
| for (int i = 0; i < children.size(); i++) { |
| Node node = children.getNode(i); |
| if (isSource(node) && canBeRemoved(node)) { |
| sources.add(node); |
| node.flag = true; |
| } |
| if (node instanceof Subgraph) |
| findInitialSources(((Subgraph) node).members, sources); |
| } |
| } |
| |
| private Node findNodeWithMaxDegree(NodeList nodes) { |
| int max = Integer.MIN_VALUE; |
| Node maxNode = null; |
| |
| for (int i = 0; i < nodes.size(); i++) { |
| Node node = nodes.getNode(i); |
| if (node.flag) |
| continue; |
| int degree = getNestedOutDegree(node) - getNestedInDegree(node); |
| if (degree >= max && node.flag == false) { |
| max = degree; |
| maxNode = node; |
| } |
| } |
| return maxNode; |
| } |
| |
| /* |
| * Finds all sinks in graphNodes and adds them to the passed NodeList |
| */ |
| private void findSinks(NodeList children, NodeList rightList) { |
| // NodeList rightList = new NodeList(); |
| NodeList sinks = new NodeList(); |
| findInitialSinks(children, sinks); |
| while (!sinks.isEmpty()) { |
| Node sink = sinks.getNode(sinks.size() - 1); |
| rightList.add(sink); |
| sinks.remove(sink); |
| removeSink(sink, sinks); |
| |
| // Check to see if the removal has made the parent node a sink |
| if (sink.getParent() != null) { |
| Node parent = sink.getParent(); |
| setChildCount(parent, getChildCount(parent) - 1); |
| if (isSink(parent) && canBeRemoved(parent)) { |
| sinks.add(parent); |
| parent.flag = true; |
| } |
| } |
| } |
| } |
| |
| /* |
| * Finds all sources in graphNodes and adds them to the sL NodeList. |
| */ |
| private void findSources(NodeList children) { |
| NodeList sources = new NodeList(); |
| findInitialSources(children, sources); |
| while (!sources.isEmpty()) { |
| Node source = sources.getNode(sources.size() - 1); |
| sL.add(source); |
| sources.remove(source); |
| removeSource(source, sources); |
| |
| // Check to see if the removal has made the parent node a source |
| if (source.getParent() != null) { |
| Node parent = source.getParent(); |
| setChildCount(parent, getChildCount(parent) - 1); |
| if (isSource(parent) && canBeRemoved(parent)) { |
| sources.add(parent); |
| parent.flag = true; |
| } |
| } |
| } |
| } |
| |
| private int getChildCount(Node n) { |
| return n.workingInts[3]; |
| } |
| |
| private int getInDegree(Node n) { |
| return n.workingInts[1]; |
| } |
| |
| private int getNestedInDegree(Node n) { |
| int result = getInDegree(n); |
| if (n instanceof Subgraph) { |
| Subgraph s = (Subgraph) n; |
| for (int i = 0; i < s.members.size(); i++) |
| if (!s.members.getNode(i).flag) |
| result += getInDegree(s.members.getNode(i)); |
| } |
| return result; |
| } |
| |
| private int getNestedOutDegree(Node n) { |
| int result = getOutDegree(n); |
| if (n instanceof Subgraph) { |
| Subgraph s = (Subgraph) n; |
| for (int i = 0; i < s.members.size(); i++) |
| if (!s.members.getNode(i).flag) |
| result += getOutDegree(s.members.getNode(i)); |
| } |
| return result; |
| } |
| |
| private int getOrderIndex(Node n) { |
| return n.workingInts[0]; |
| } |
| |
| private int getOutDegree(Node n) { |
| return n.workingInts[2]; |
| } |
| |
| private void initializeDegrees(DirectedGraph g) { |
| g.nodes.resetFlags(); |
| g.edges.resetFlags(false); |
| for (int i = 0; i < g.nodes.size(); i++) { |
| Node n = g.nodes.getNode(i); |
| setInDegree(n, n.incoming.size()); |
| setOutDegree(n, n.outgoing.size()); |
| if (n instanceof Subgraph) |
| setChildCount(n, ((Subgraph) n).members.size()); |
| else |
| setChildCount(n, 0); |
| } |
| } |
| |
| private void invertEdges(DirectedGraph g) { |
| // Assign order indices |
| int orderIndex = 0; |
| for (int i = 0; i < sL.size(); i++) { |
| setOrderIndex(sL.getNode(i), orderIndex++); |
| } |
| // Invert edges that are causing a cycle |
| for (int i = 0; i < g.edges.size(); i++) { |
| Edge e = g.edges.getEdge(i); |
| if (getOrderIndex(e.source) > getOrderIndex(e.target) |
| && !e.source.isNested(e.target) |
| && !e.target.isNested(e.source)) { |
| e.invert(); |
| e.isFeedback = true; |
| } |
| } |
| } |
| |
| /** |
| * Removes all edges connecting the given subgraph to other nodes outside of |
| * it. |
| * |
| * @param s |
| * @param n |
| */ |
| private void isolateSubgraph(Subgraph subgraph, Node member) { |
| Edge edge = null; |
| for (int i = 0; i < member.incoming.size(); i++) { |
| edge = member.incoming.getEdge(i); |
| if (!subgraph.isNested(edge.source) && !edge.flag) |
| removeEdge(edge); |
| } |
| for (int i = 0; i < member.outgoing.size(); i++) { |
| edge = member.outgoing.getEdge(i); |
| if (!subgraph.isNested(edge.target) && !edge.flag) |
| removeEdge(edge); |
| } |
| if (member instanceof Subgraph) { |
| NodeList members = ((Subgraph) member).members; |
| for (int i = 0; i < members.size(); i++) |
| isolateSubgraph(subgraph, members.getNode(i)); |
| } |
| } |
| |
| private boolean isSink(Node n) { |
| return getOutDegree(n) == 0 |
| && (n.getParent() == null || isSink(n.getParent())); |
| } |
| |
| private boolean isSource(Node n) { |
| return getInDegree(n) == 0 |
| && (n.getParent() == null || isSource(n.getParent())); |
| } |
| |
| private void remove(Node n) { |
| n.flag = true; |
| if (n.getParent() != null) |
| setChildCount(n.getParent(), getChildCount(n.getParent()) - 1); |
| removeSink(n, null); |
| removeSource(n, null); |
| sL.add(n); |
| if (n instanceof Subgraph) { |
| Subgraph s = (Subgraph) n; |
| isolateSubgraph(s, s); |
| cycleRemove(s.members); |
| } |
| } |
| |
| private boolean removeEdge(Edge e) { |
| if (e.flag) |
| return false; |
| e.flag = true; |
| changeOutDegree(e.source, -1); |
| changeInDegree(e.target, -1); |
| return true; |
| } |
| |
| /** |
| * Removes all edges between a parent and any of its children or |
| * descendants. |
| */ |
| private void removeParentChildEdges(DirectedGraph g) { |
| for (int i = 0; i < g.edges.size(); i++) { |
| Edge e = g.edges.getEdge(i); |
| if (e.source.isNested(e.target) || e.target.isNested(e.source)) |
| removeEdge(e); |
| } |
| } |
| |
| private void removeSink(Node sink, NodeList allSinks) { |
| for (int i = 0; i < sink.incoming.size(); i++) { |
| Edge e = sink.incoming.getEdge(i); |
| if (!e.flag) { |
| removeEdge(e); |
| Node source = e.source; |
| if (allSinks != null && isSink(source) && canBeRemoved(source)) { |
| allSinks.add(source); |
| source.flag = true; |
| } |
| } |
| } |
| } |
| |
| private void removeSource(Node n, NodeList allSources) { |
| for (int i = 0; i < n.outgoing.size(); i++) { |
| Edge e = n.outgoing.getEdge(i); |
| if (!e.flag) { |
| e.flag = true; |
| changeInDegree(e.target, -1); |
| changeOutDegree(e.source, -1); |
| |
| Node target = e.target; |
| if (allSources != null && isSource(target) |
| && canBeRemoved(target)) { |
| allSources.add(target); |
| target.flag = true; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Restores an edge if it has been removed, and both of its nodes are not |
| * removed. |
| * |
| * @param e |
| * the edge |
| * @return <code>true</code> if the edge was restored |
| */ |
| private boolean restoreEdge(Edge e) { |
| if (!e.flag || e.source.flag || e.target.flag) |
| return false; |
| e.flag = false; |
| changeOutDegree(e.source, 1); |
| changeInDegree(e.target, 1); |
| return true; |
| } |
| |
| /** |
| * Brings back all nodes nested in the given node. |
| * |
| * @param node |
| * the node to restore |
| * @param sr |
| * current sinks |
| */ |
| private void restoreSinks(Node node, NodeList sR) { |
| if (node.flag && sR.contains(node)) { |
| node.flag = false; |
| if (node.getParent() != null) |
| setChildCount(node.getParent(), |
| getChildCount(node.getParent()) + 1); |
| sR.remove(node); |
| for (int i = 0; i < node.incoming.size(); i++) { |
| Edge e = node.incoming.getEdge(i); |
| restoreEdge(e); |
| } |
| for (int i = 0; i < node.outgoing.size(); i++) { |
| Edge e = node.outgoing.getEdge(i); |
| restoreEdge(e); |
| } |
| } |
| if (node instanceof Subgraph) { |
| Subgraph s = (Subgraph) node; |
| for (int i = 0; i < s.members.size(); i++) { |
| Node member = s.members.getNode(i); |
| restoreSinks(member, sR); |
| } |
| } |
| } |
| |
| /** |
| * Brings back all nodes nested in the given node. |
| * |
| * @param node |
| * the node to restore |
| * @param sr |
| * current sinks |
| */ |
| private void restoreSources(Node node) { |
| if (node.flag && sL.contains(node)) { |
| node.flag = false; |
| if (node.getParent() != null) |
| setChildCount(node.getParent(), |
| getChildCount(node.getParent()) + 1); |
| sL.remove(node); |
| for (int i = 0; i < node.incoming.size(); i++) { |
| Edge e = node.incoming.getEdge(i); |
| restoreEdge(e); |
| } |
| for (int i = 0; i < node.outgoing.size(); i++) { |
| Edge e = node.outgoing.getEdge(i); |
| restoreEdge(e); |
| } |
| } |
| if (node instanceof Subgraph) { |
| Subgraph s = (Subgraph) node; |
| for (int i = 0; i < s.members.size(); i++) { |
| Node member = s.members.getNode(i); |
| restoreSources(member); |
| } |
| } |
| } |
| |
| 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(); |
| } |
| } |
| |
| private void setChildCount(Node n, int count) { |
| n.workingInts[3] = count; |
| } |
| |
| private void setInDegree(Node n, int deg) { |
| n.workingInts[1] = deg; |
| } |
| |
| private void setOrderIndex(Node n, int index) { |
| n.workingInts[0] = index; |
| } |
| |
| private void setOutDegree(Node n, int deg) { |
| n.workingInts[2] = deg; |
| } |
| |
| /** |
| * @see GraphVisitor#visit(org.eclipse.draw2d.graph.DirectedGraph) |
| */ |
| public void visit(DirectedGraph g) { |
| initializeDegrees(g); |
| graphNodes = g.nodes; |
| |
| NodeList roots = new NodeList(); |
| for (int i = 0; i < graphNodes.size(); i++) { |
| if (graphNodes.getNode(i).getParent() == null) |
| roots.add(graphNodes.getNode(i)); |
| } |
| buildNestingTreeIndices(roots, 0); |
| removeParentChildEdges(g); |
| cycleRemove(roots); |
| invertEdges(g); |
| } |
| |
| } |