non-recursive DFSTBuilder

This commit is contained in:
Alexey Kudravtsev
2015-08-21 14:03:01 +03:00
parent a4d418da34
commit 684e929dc6
6 changed files with 282 additions and 144 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -61,7 +61,6 @@ public class ArtifactSorter {
private List<JpsArtifact> doGetSortedArtifacts() {
GraphGenerator<JpsArtifact> graph = createArtifactsGraph();
DFSTBuilder<JpsArtifact> builder = new DFSTBuilder<JpsArtifact>(graph);
builder.buildDFST();
List<JpsArtifact> names = new ArrayList<JpsArtifact>();
names.addAll(graph.getNodes());
Collections.sort(names, builder.comparator());
@@ -83,7 +82,6 @@ public class ArtifactSorter {
}
final DFSTBuilder<JpsArtifact> builder = new DFSTBuilder<JpsArtifact>(graph);
builder.buildDFST();
if (builder.isAcyclic() && result.isEmpty()) return Collections.emptyMap();
final TIntArrayList sccs = builder.getSCCs();
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -208,7 +208,11 @@ public class ExtensionPointImplTest {
assertThat(ourTestLog.errors(), empty());
adapter.setFire(false);
assertThat(extensionPoint.getExtensions(), arrayContaining("second", "", "first"));
String[] extensions = extensionPoint.getExtensions();
assertEquals("second", extensions[0]);
assertThat(extensions[1], isOneOf("", "first"));
assertThat(extensions[2], isOneOf("", "first"));
assertNotEquals(extensions[2], extensions[1]);
assertThat(ourTestLog.errors(), empty());
}
@@ -303,8 +303,8 @@ public class ExtensionsImplTest {
assertEquals("1", extensions[0].getText());
assertEquals("2", extensions[1].getText());
assertEquals("3", extensions[2].getText());
assertEquals("4", extensions[3].getText());
assertEquals("5", extensions[4].getText());
assertTrue("4".equals(extensions[3].getText()) || "5".equals(extensions[3].getText()) );
assertTrue("4".equals(extensions[4].getText()) || "5".equals(extensions[4].getText()) );
assertEquals("6", extensions[5].getText());
assertEquals("7", extensions[6].getText());
assertEquals("8", extensions[7].getText());
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -115,7 +115,6 @@ public class ModulesDependenciesPanel extends JPanel implements ModuleRootListen
}
myModulesGraph = buildGraph();
DFSTBuilder<Module> builder = new DFSTBuilder<Module>(myModulesGraph);
builder.buildDFST();
if (builder.isAcyclic()){
mySplitter.setProportion(1.f);
} else {
@@ -16,6 +16,9 @@
package com.intellij.util.graph;
import com.intellij.openapi.util.Couple;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.IntStack;
import gnu.trove.TIntArrayList;
import gnu.trove.TObjectIntHashMap;
import org.jetbrains.annotations.NotNull;
@@ -27,52 +30,178 @@ import java.util.*;
*/
public class DFSTBuilder<Node> {
private final Graph<Node> myGraph;
private final TObjectIntHashMap<Node> myNodeToNNumber;
private TObjectIntHashMap<Node> myNodeToTNumber;
private final Node[] myInvN;
private final TObjectIntHashMap<Node> myNodeToNNumber; // node -> node number in topological order [0..size). Independent nodes are in reversed loading order (loading order is the graph.getNodes() order)
private final Node[] myInvN; // node number in topological order [0..size) -> node
private Couple<Node> myBackEdge;
private Comparator<Node> myComparator;
private boolean myNBuilt;
private boolean myTBuilt;
private TIntArrayList mySCCs;
private Node[] myInvT;
private final TIntArrayList mySCCs = new TIntArrayList(); // strongly connected component sizes
private final TObjectIntHashMap<Node> myNodeToTNumber = new TObjectIntHashMap<Node>(); // node -> number in scc topological order. Independent scc are in reversed loading order
public DFSTBuilder(Graph<Node> graph) {
private final Node[] myInvT; // number in (enumerate all nodes scc by scc) order -> node
private final Node[] myAllNodes;
public DFSTBuilder(@NotNull Graph<Node> graph) {
myAllNodes = (Node[])graph.getNodes().toArray();
myGraph = graph;
myNodeToNNumber = new TObjectIntHashMap<Node>(myGraph.getNodes().size() * 2, 0.5f);
myInvN = (Node[])new Object[myGraph.getNodes().size()];
int size = graph.getNodes().size();
myNodeToNNumber = new TObjectIntHashMap<Node>(size * 2, 0.5f);
myInvN = (Node[])new Object[size];
myInvT = (Node[])new Object[size];
new Tarjan().build();
}
@Deprecated
public void buildDFST() {
if (myNBuilt) return;
Collection<Node> nodes = myGraph.getNodes();
int indexN = nodes.size();
Set<Node> processed = new LinkedHashSet<Node>();
for (Node node : nodes) {
if (!myGraph.getIn(node).hasNext()) {
indexN = traverseSubGraph(node, indexN, processed);
}
private class Tarjan {
private final int[] lowLink = new int[myInvN.length];
private final int[] index = new int[myInvN.length];
private final IntStack nodesOnStack = new IntStack();
private final boolean[] isOnStack = new boolean[index.length];
private class Frame {
public Frame(int nodeI) {
this.nodeI = nodeI;
Iterator<Node> outNodes = myGraph.getOut(myAllNodes[nodeI]);
TIntArrayList list = new TIntArrayList();
while (outNodes.hasNext()) {
Node node = outNodes.next();
list.add(nodeIndex.get(node));
}
out = list.toNativeArray();
}
private final int nodeI;
private final int[] out;
private int nextUnexploredIndex;
@Override
public String toString() {
final StringBuilder o = new StringBuilder();
for (int id : out) {
o.append(myAllNodes[id] + ", ");
}
return myAllNodes[nodeI] + " -> [" + o + "]";
}
}
for (Node node : nodes) {
indexN = traverseSubGraph(node, indexN, processed);
private final Stack<Frame> frames = new Stack<Frame>(); // recursion stack
private final TObjectIntHashMap<Node> nodeIndex = new TObjectIntHashMap<Node>();
private int dfsIndex;
private int sccsSizeCombined;
private final TIntArrayList topo = new TIntArrayList(index.length); // nodes in reverse topological order
private void build() {
Arrays.fill(index, -1);
for (int i = 0; i < myAllNodes.length; i++) {
Node node = myAllNodes[i];
nodeIndex.put(node, i);
}
for (int i = 0; i < index.length; i++) {
if (index[i] == -1) {
frames.push(new Frame(i));
List<List<Node>> sccs = new ArrayList<List<Node>>();
strongConnect(sccs);
for (List<Node> scc : sccs) {
int sccSize = scc.size();
mySCCs.add(sccSize);
int sccBase = index.length - sccsSizeCombined - sccSize;
// root node should be first in scc for some reason
Node rootNode = myAllNodes[i];
int rIndex = scc.indexOf(rootNode);
if (rIndex != -1) {
ContainerUtil.swapElements(scc, rIndex, 0);
}
for (int j = 0; j < scc.size(); j++) {
Node sccNode = scc.get(j);
int tIndex = sccBase + j;
myInvT[tIndex] = sccNode;
myNodeToTNumber.put(sccNode, tIndex);
}
sccsSizeCombined += sccSize;
}
}
}
for (int i = 0; i < topo.size(); i++) {
int nodeI = topo.get(i);
Node node = myAllNodes[nodeI];
myNodeToNNumber.put(node, index.length - 1 - i);
myInvN[index.length - 1 - i] = node;
}
mySCCs.reverse(); // have to place sccs in topological order too
}
myNBuilt = true;
private void strongConnect(@NotNull List<List<Node>> sccs) {
int successor = -1;
nextNode:
while (!frames.isEmpty()) {
Frame pair = frames.peek();
int i = pair.nodeI;
// we have returned to the node
if (index[i] == -1) {
// actually we visit node first time, prepare
index[i] = dfsIndex;
lowLink[i] = dfsIndex;
dfsIndex++;
nodesOnStack.push(i);
isOnStack[i] = true;
}
if (ArrayUtil.indexOf(pair.out, successor) != -1) {
lowLink[i] = Math.min(lowLink[i], lowLink[successor]);
}
successor = i;
// if unexplored children left, dfs there
while (pair.nextUnexploredIndex<pair.out.length) {
int nextI = pair.out[pair.nextUnexploredIndex++];
if (index[nextI] == -1) {
frames.push(new Frame(nextI));
continue nextNode;
}
if (isOnStack[nextI]) {
lowLink[i] = Math.min(lowLink[i], index[nextI]);
if (myBackEdge == null) {
myBackEdge = Couple.of(myAllNodes[nextI], myAllNodes[i]);
}
}
}
frames.pop();
topo.add(i);
// we are really back, pop a scc
if (lowLink[i] == index[i]) {
// found yer
List<Node> scc = new ArrayList<Node>();
int pushedI;
do {
pushedI = nodesOnStack.pop();
Node pushed = myAllNodes[pushedI];
isOnStack[pushedI] = false;
scc.add(pushed);
}
while (pushedI != i);
sccs.add(scc);
}
}
}
}
@NotNull
public Comparator<Node> comparator() {
if (myComparator == null) {
buildDFST();
final TObjectIntHashMap<Node> map;
if (isAcyclic()) {
map = myNodeToNNumber;
}
else {
build_T();
map = myNodeToTNumber;
}
final TObjectIntHashMap<Node> map = isAcyclic() ? myNodeToNNumber : myNodeToTNumber;
myComparator = new Comparator<Node>() {
@Override
public int compare(@NotNull Node t, @NotNull Node t1) {
@@ -83,114 +212,30 @@ public class DFSTBuilder<Node> {
return myComparator;
}
private int traverseSubGraph (final Node node, int nNumber, Set<Node> processed) {
if (!processed.contains(node)) {
processed.add(node);
for (Iterator<Node> it = myGraph.getOut(node); it.hasNext(); ) {
nNumber = traverseSubGraph(it.next(), nNumber, processed);
}
nNumber--;
myNodeToNNumber.put(node, nNumber);
myInvN[nNumber] = node;
//Check for cycles
if (myBackEdge == null) {
for (Iterator<Node> it = myGraph.getIn(node); it.hasNext(); ) {
Node prev = it.next();
int prevNumber = myNodeToNNumber.get(prev);
if (myNodeToNNumber.containsKey(prev) && prevNumber > nNumber) {
myBackEdge = Couple.of(node, prev);
break;
}
}
}
}
return nNumber;
}
private Set<Node> region (Node v) {
LinkedList<Node> frontier = new LinkedList<Node>();
frontier.addFirst(v);
Set<Node> result = new LinkedHashSet<Node>();
int number = myNodeToNNumber.get(v);
while (!frontier.isEmpty()) {
Node curr = frontier.removeFirst();
result.add(curr);
Iterator<Node> it = myGraph.getIn(curr);
while (it.hasNext()) {
Node w = it.next();
if (myNodeToNNumber.get(w) > number && !result.contains(w)) frontier.add(w);
}
}
return result;
}
private void build_T() {
if (myTBuilt) return;
myInvT = (Node[])new Object[myGraph.getNodes().size()];
mySCCs = new TIntArrayList ();
int size = myGraph.getNodes().size();
myNodeToTNumber = new TObjectIntHashMap<Node>(size * 2, 0.5f);
int currT = 0;
for (int i = 0; i < size; i++) {
Node v = myInvN[i];
if (!myNodeToTNumber.containsKey(v)) {
final Set<Node> region = region(v);
mySCCs.add(region.size());
myNodeToTNumber.put(v, currT);
myInvT[currT++]=v;
for (Node w : region) {
if (w != v) {
myNodeToTNumber.put(w, currT);
myInvT[currT++] = w;
}
}
}
}
myTBuilt = true;
}
public Couple<Node> getCircularDependency() {
buildDFST();
return myBackEdge;
}
public boolean isAcyclic () {
public boolean isAcyclic() {
return getCircularDependency() == null;
}
public Node getNodeByNNumber (final int n){
@NotNull
public Node getNodeByNNumber(final int n) {
return myInvN[n];
}
public Node getNodeByTNumber (final int n){
return myInvT[n];
}
@NotNull
public Node getNodeByTNumber(final int n) {
return myInvT[n];
}
/**
*
* @return the list containing the number of nodes in strongly connected components.
* Respective nodes could be obtained via {@link #getNodeByTNumber(int)}.
* Respective nodes could be obtained via {@link #getNodeByTNumber(int)}.
*/
public TIntArrayList getSCCs (){
if (!myNBuilt){
buildDFST();
}
if (!myTBuilt){
build_T();
}
@NotNull
public TIntArrayList getSCCs() {
return mySCCs;
}
@@ -15,6 +15,7 @@
*/
package com.intellij.util.graph;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.HashMap;
import junit.framework.TestCase;
@@ -31,7 +32,7 @@ public class DFSTBuilderTest extends TestCase {
final TestNode nD = new TestNode("D");
final TestNode nE = new TestNode("E");
final TestNode nF = new TestNode("F");
final TestNode[] allNodes = new TestNode[]{nA, nB, nC, nD, nE, nF};
final TestNode[] allNodes = {nA, nB, nC, nD, nE, nF};
final Map<TestNode, TestNode[]> map = new HashMap<TestNode, TestNode[]>();
map.put(nA, new TestNode[0]);
map.put(nB, new TestNode[0]);
@@ -54,8 +55,7 @@ public class DFSTBuilderTest extends TestCase {
assertTrue(comparator.compare(nB, nF) < 0);
}
private GraphGenerator<TestNode> graphByNodes(final TestNode[] allNodes,
final Map<TestNode, TestNode[]> map) {
private static GraphGenerator<TestNode> graphByNodes(final TestNode[] allNodes, final Map<TestNode, TestNode[]> mapIn) {
final GraphGenerator<TestNode> graph = new GraphGenerator<TestNode>(new GraphGenerator.SemiGraph<TestNode>() {
@Override
public Collection<TestNode> getNodes() {
@@ -64,7 +64,7 @@ public class DFSTBuilderTest extends TestCase {
@Override
public Iterator<TestNode> getIn(TestNode n) {
return GraphTestUtil.iteratorOfArray(map.get(n));
return GraphTestUtil.iteratorOfArray(ObjectUtils.notNull(mapIn.get(n), new TestNode[0]));
}
});
return graph;
@@ -75,7 +75,7 @@ public class DFSTBuilderTest extends TestCase {
final TestNode nB = new TestNode("B");
final TestNode nC = new TestNode("C");
final TestNode nD = new TestNode("D");
final TestNode[] allNodes = new TestNode[]{nA, nB, nC, nD};
final TestNode[] allNodes = {nA, nB, nC, nD};
final Map<TestNode, TestNode[]> map = new HashMap<TestNode, TestNode[]>();
map.put(nA, new TestNode[0]);
map.put(nB, new TestNode[0]);
@@ -85,8 +85,8 @@ public class DFSTBuilderTest extends TestCase {
}
private void checkCircularDependecyDetected(final TestNode[] allNodes,
final Map<TestNode, TestNode[]> map) {
private static void checkCircularDependecyDetected(final TestNode[] allNodes,
final Map<TestNode, TestNode[]> map) {
GraphGenerator<TestNode> graph = graphByNodes(allNodes, map);
final DFSTBuilder<TestNode> dfstBuilder = new DFSTBuilder<TestNode>(graph);
assertTrue (dfstBuilder.getCircularDependency() != null);
@@ -95,7 +95,7 @@ public class DFSTBuilderTest extends TestCase {
public void testCircularDependency2() {
final TestNode nA = new TestNode("A");
final TestNode nB = new TestNode("B");
final TestNode[] allNodes = new TestNode[]{nA, nB};
final TestNode[] allNodes = {nA, nB};
final Map<TestNode, TestNode[]> map = new HashMap<TestNode, TestNode[]>();
map.put(nA, new TestNode[]{nB});
map.put(nB, new TestNode[]{nA});
@@ -106,7 +106,7 @@ public class DFSTBuilderTest extends TestCase {
final TestNode nA = new TestNode("A");
final TestNode nB = new TestNode("B");
final TestNode nC = new TestNode("C");
final TestNode[] allNodes = new TestNode[]{nA, nB, nC};
final TestNode[] allNodes = {nA, nB, nC};
final Map<TestNode, TestNode[]> map = new HashMap<TestNode, TestNode[]>();
map.put(nA, new TestNode[]{nB});
map.put(nB, new TestNode[]{nA});
@@ -114,12 +114,12 @@ public class DFSTBuilderTest extends TestCase {
checkCircularDependecyDetected(allNodes, map);
}
public void testTNumberingSimple () {
public void testTNumberingSimple() {
final TestNode nA = new TestNode("A");
final TestNode nB = new TestNode("B");
final TestNode nC = new TestNode("C");
final TestNode nD = new TestNode("D");
final TestNode[] allNodes = new TestNode[]{nA, nB, nC, nD};
final TestNode[] allNodes = {nA, nB, nC, nD};
final Map<TestNode, TestNode[]> map = new HashMap<TestNode, TestNode[]>();
map.put(nA, new TestNode[]{nC});
map.put(nB, new TestNode[]{nA});
@@ -127,10 +127,102 @@ public class DFSTBuilderTest extends TestCase {
map.put(nD, new TestNode[]{nB});
GraphGenerator<TestNode> graph = graphByNodes(allNodes, map);
final DFSTBuilder<TestNode> dfstBuilder = new DFSTBuilder<TestNode>(graph);
assertTrue (!dfstBuilder.isAcyclic());
assertFalse(dfstBuilder.isAcyclic());
Comparator<TestNode> comparator = dfstBuilder.comparator();
assertTrue(comparator.compare(nA, nD) < 0);
assertTrue(comparator.compare(nB, nD) < 0);
assertTrue(comparator.compare(nC, nD) < 0);
}
public void testStackOverflow() {
final TestNode[] allNodes = new TestNode[10000];
final Map<TestNode, TestNode[]> map = new HashMap<TestNode, TestNode[]>();
for (int i = 0; i < allNodes.length; i++) {
allNodes[i] = new TestNode(i + "");
if (i != 0) {
map.put(allNodes[i], new TestNode[]{allNodes[i - 1]});
}
}
map.put(allNodes[0], new TestNode[]{allNodes[allNodes.length - 1]});
GraphGenerator<TestNode> graph = graphByNodes(allNodes, map);
final DFSTBuilder<TestNode> dfstBuilder = new DFSTBuilder<TestNode>(graph);
assertFalse(dfstBuilder.isAcyclic());
}
public void testSccsReportedInLoadingOrder() {
final TestNode main = new TestNode("main");
final TestNode dep = new TestNode("dep");
final TestNode d = new TestNode("d");
final TestNode d2 = new TestNode("d2");
final TestNode resMain = new TestNode("resMain");
final TestNode resDep = new TestNode("resDep");
final TestNode[] allNodes = {main, dep, d, d2, resMain, resDep};
final Map<TestNode, TestNode[]> mapIn = new HashMap<TestNode, TestNode[]>();
mapIn.put(main, new TestNode[]{d, resMain});
mapIn.put(dep, new TestNode[]{main,resDep});
mapIn.put(d, new TestNode[]{d2});
mapIn.put(d2, new TestNode[]{dep, d});
GraphGenerator<TestNode> graph = graphByNodes(allNodes, mapIn);
final DFSTBuilder<TestNode> dfstBuilder = new DFSTBuilder<TestNode>(graph);
assertTrue (!dfstBuilder.isAcyclic());
Comparator<TestNode> comparator = dfstBuilder.comparator();
assertTrue(comparator.compare(resMain, main) < 0);
assertTrue(comparator.compare(resMain, d) < 0);
assertTrue(comparator.compare(resMain, d2) < 0);
assertTrue(comparator.compare(resDep, dep) < 0);
assertTrue(comparator.compare(resMain, resDep) > 0); //reversed loading order
}
public void testReportedInLoadingOrder() {
final TestNode o = new TestNode("o");
final TestNode a = new TestNode("a");
final TestNode b = new TestNode("b");
final TestNode c = new TestNode("c");
for (int oIndex = 0; oIndex<4; oIndex++) {
List<TestNode> list = new ArrayList<TestNode>(Arrays.asList(a,b,c));
list.add(oIndex, o);
TestNode[] allNodes = list.toArray(new TestNode[list.size()]);
Map<TestNode, TestNode[]> mapIn = new HashMap<TestNode, TestNode[]>();
mapIn.put(o, new TestNode[]{a,b,c});
final DFSTBuilder<TestNode> dfstBuilder = new DFSTBuilder<TestNode>(graphByNodes(allNodes, mapIn));
assertTrue (dfstBuilder.isAcyclic());
Comparator<TestNode> comparator = dfstBuilder.comparator();
TestNode[] sorted = allNodes.clone();
Arrays.sort(sorted, comparator);
assertEquals("All nodes: "+list, Arrays.asList(c,b,a,o), Arrays.asList(sorted));
}
}
public void testSccReportedInLoadingOrder() {
final TestNode o1 = new TestNode("o1");
final TestNode o2 = new TestNode("o2");
final TestNode a = new TestNode("a");
final TestNode b = new TestNode("b");
final TestNode c = new TestNode("c");
for (int oIndex = 0; oIndex<4; oIndex++) {
List<TestNode> list = new ArrayList<TestNode>(Arrays.asList(a,b,c));
list.add(oIndex, o1);
list.add(oIndex, o2);
TestNode[] allNodes = list.toArray(new TestNode[list.size()]);
Map<TestNode, TestNode[]> mapIn = new HashMap<TestNode, TestNode[]>();
mapIn.put(o1, new TestNode[]{a,b,c,o2});
mapIn.put(o2, new TestNode[]{o1});
final DFSTBuilder<TestNode> dfstBuilder = new DFSTBuilder<TestNode>(graphByNodes(allNodes, mapIn));
assertFalse(dfstBuilder.isAcyclic());
Comparator<TestNode> comparator = dfstBuilder.comparator();
assertTrue("All nodes: "+list,comparator.compare(b, a) < 0); //reversed loading order
assertTrue("All nodes: "+list,comparator.compare(c, a) < 0); //reversed loading order
assertTrue("All nodes: "+list,comparator.compare(c, b) < 0); //reversed loading order
assertTrue("All nodes: "+list,comparator.compare(a, o1) < 0);
assertTrue("All nodes: "+list,comparator.compare(a, o2) < 0);
assertTrue("All nodes: "+list,comparator.compare(b, o1) < 0);
assertTrue("All nodes: "+list,comparator.compare(b, o2) < 0);
assertTrue("All nodes: "+list,comparator.compare(c, o1) < 0);
assertTrue("All nodes: "+list,comparator.compare(c, o2) < 0);
}
}
}