diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/impl/ArtifactSorter.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/impl/ArtifactSorter.java index 80aad694db23..9c4678791b77 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/impl/ArtifactSorter.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/impl/ArtifactSorter.java @@ -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 doGetSortedArtifacts() { GraphGenerator graph = createArtifactsGraph(); DFSTBuilder builder = new DFSTBuilder(graph); - builder.buildDFST(); List names = new ArrayList(); names.addAll(graph.getNodes()); Collections.sort(names, builder.comparator()); @@ -83,7 +82,6 @@ public class ArtifactSorter { } final DFSTBuilder builder = new DFSTBuilder(graph); - builder.buildDFST(); if (builder.isAcyclic() && result.isEmpty()) return Collections.emptyMap(); final TIntArrayList sccs = builder.getSCCs(); diff --git a/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionPointImplTest.java b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionPointImplTest.java index e7a7e29dfd0b..c9b4042148b2 100644 --- a/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionPointImplTest.java +++ b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionPointImplTest.java @@ -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()); } diff --git a/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionsImplTest.java b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionsImplTest.java index 6171379bd21d..4e4c26820b68 100644 --- a/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionsImplTest.java +++ b/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionsImplTest.java @@ -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()); diff --git a/platform/lang-impl/src/com/intellij/moduleDependencies/ModulesDependenciesPanel.java b/platform/lang-impl/src/com/intellij/moduleDependencies/ModulesDependenciesPanel.java index ef975a73332e..2119c91cf230 100644 --- a/platform/lang-impl/src/com/intellij/moduleDependencies/ModulesDependenciesPanel.java +++ b/platform/lang-impl/src/com/intellij/moduleDependencies/ModulesDependenciesPanel.java @@ -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 builder = new DFSTBuilder(myModulesGraph); - builder.buildDFST(); if (builder.isAcyclic()){ mySplitter.setProportion(1.f); } else { diff --git a/platform/util/src/com/intellij/util/graph/DFSTBuilder.java b/platform/util/src/com/intellij/util/graph/DFSTBuilder.java index 0250edc38bfd..90549b08e09e 100644 --- a/platform/util/src/com/intellij/util/graph/DFSTBuilder.java +++ b/platform/util/src/com/intellij/util/graph/DFSTBuilder.java @@ -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 { private final Graph myGraph; - private final TObjectIntHashMap myNodeToNNumber; - private TObjectIntHashMap myNodeToTNumber; - private final Node[] myInvN; + private final TObjectIntHashMap 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 myBackEdge; private Comparator 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 myNodeToTNumber = new TObjectIntHashMap(); // node -> number in scc topological order. Independent scc are in reversed loading order - public DFSTBuilder(Graph graph) { + private final Node[] myInvT; // number in (enumerate all nodes scc by scc) order -> node + private final Node[] myAllNodes; + + public DFSTBuilder(@NotNull Graph graph) { + myAllNodes = (Node[])graph.getNodes().toArray(); myGraph = graph; - myNodeToNNumber = new TObjectIntHashMap(myGraph.getNodes().size() * 2, 0.5f); - myInvN = (Node[])new Object[myGraph.getNodes().size()]; + int size = graph.getNodes().size(); + myNodeToNNumber = new TObjectIntHashMap(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 nodes = myGraph.getNodes(); - int indexN = nodes.size(); - Set processed = new LinkedHashSet(); - 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 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 frames = new Stack(); // recursion stack + private final TObjectIntHashMap nodeIndex = new TObjectIntHashMap(); + 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> sccs = new ArrayList>(); + + strongConnect(sccs); + + for (List 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> 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 scc = new ArrayList(); + 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 comparator() { if (myComparator == null) { - buildDFST(); - final TObjectIntHashMap map; - if (isAcyclic()) { - map = myNodeToNNumber; - } - else { - build_T(); - map = myNodeToTNumber; - } + final TObjectIntHashMap map = isAcyclic() ? myNodeToNNumber : myNodeToTNumber; myComparator = new Comparator() { @Override public int compare(@NotNull Node t, @NotNull Node t1) { @@ -83,114 +212,30 @@ public class DFSTBuilder { return myComparator; } - private int traverseSubGraph (final Node node, int nNumber, Set processed) { - if (!processed.contains(node)) { - processed.add(node); - for (Iterator 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 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 region (Node v) { - LinkedList frontier = new LinkedList(); - frontier.addFirst(v); - Set result = new LinkedHashSet(); - int number = myNodeToNNumber.get(v); - while (!frontier.isEmpty()) { - Node curr = frontier.removeFirst(); - result.add(curr); - Iterator 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(size * 2, 0.5f); - - int currT = 0; - for (int i = 0; i < size; i++) { - Node v = myInvN[i]; - if (!myNodeToTNumber.containsKey(v)) { - final Set 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 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; } diff --git a/platform/util/testSrc/com/intellij/util/graph/DFSTBuilderTest.java b/platform/util/testSrc/com/intellij/util/graph/DFSTBuilderTest.java index 9e9211c56a09..cace627e5a6e 100644 --- a/platform/util/testSrc/com/intellij/util/graph/DFSTBuilderTest.java +++ b/platform/util/testSrc/com/intellij/util/graph/DFSTBuilderTest.java @@ -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 map = new HashMap(); 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 graphByNodes(final TestNode[] allNodes, - final Map map) { + private static GraphGenerator graphByNodes(final TestNode[] allNodes, final Map mapIn) { final GraphGenerator graph = new GraphGenerator(new GraphGenerator.SemiGraph() { @Override public Collection getNodes() { @@ -64,7 +64,7 @@ public class DFSTBuilderTest extends TestCase { @Override public Iterator 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 map = new HashMap(); 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 map) { + private static void checkCircularDependecyDetected(final TestNode[] allNodes, + final Map map) { GraphGenerator graph = graphByNodes(allNodes, map); final DFSTBuilder dfstBuilder = new DFSTBuilder(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 map = new HashMap(); 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 map = new HashMap(); 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 map = new HashMap(); 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 graph = graphByNodes(allNodes, map); final DFSTBuilder dfstBuilder = new DFSTBuilder(graph); - assertTrue (!dfstBuilder.isAcyclic()); + assertFalse(dfstBuilder.isAcyclic()); Comparator 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 map = new HashMap(); + 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 graph = graphByNodes(allNodes, map); + final DFSTBuilder dfstBuilder = new DFSTBuilder(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 mapIn = new HashMap(); + 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 graph = graphByNodes(allNodes, mapIn); + + final DFSTBuilder dfstBuilder = new DFSTBuilder(graph); + assertTrue (!dfstBuilder.isAcyclic()); + Comparator 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 list = new ArrayList(Arrays.asList(a,b,c)); + list.add(oIndex, o); + TestNode[] allNodes = list.toArray(new TestNode[list.size()]); + + Map mapIn = new HashMap(); + mapIn.put(o, new TestNode[]{a,b,c}); + + final DFSTBuilder dfstBuilder = new DFSTBuilder(graphByNodes(allNodes, mapIn)); + assertTrue (dfstBuilder.isAcyclic()); + Comparator 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 list = new ArrayList(Arrays.asList(a,b,c)); + list.add(oIndex, o1); + list.add(oIndex, o2); + TestNode[] allNodes = list.toArray(new TestNode[list.size()]); + + Map mapIn = new HashMap(); + mapIn.put(o1, new TestNode[]{a,b,c,o2}); + mapIn.put(o2, new TestNode[]{o1}); + + final DFSTBuilder dfstBuilder = new DFSTBuilder(graphByNodes(allNodes, mapIn)); + assertFalse(dfstBuilder.isAcyclic()); + Comparator 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); + } + } }