/* * Copyright 2000-2016 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. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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 com.intellij.util.containers.Stack; import gnu.trove.TIntArrayList; import gnu.trove.TObjectIntHashMap; import org.jetbrains.annotations.NotNull; import java.util.*; /** * @author dsl, ven */ public class DFSTBuilder<Node> { private final OutboundSemiGraph<Node> myGraph; 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 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 private final Node[] myInvT; // number in (enumerate all nodes scc by scc) order -> node private final Node[] myAllNodes; public DFSTBuilder(@NotNull Graph<Node> graph) { this((OutboundSemiGraph<Node>)graph); } @SuppressWarnings("unchecked") public DFSTBuilder(@NotNull OutboundSemiGraph<Node> graph) { myAllNodes = (Node[])graph.getNodes().toArray(); myGraph = graph; 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(); } /** * Tarjan's strongly connected components search algorithm * (<a href="https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm">Wikipedia article</a>).<br> * This implementation differs from the canonical one above by:<br> * <ul> * <li>being non-recursive</li> * <li>also computing a topological order during the same single pass</li> * </ul> */ 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() { StringBuilder o = new StringBuilder(); o.append(myAllNodes[nodeI]).append(" -> ["); for (int id : out) o.append(myAllNodes[id]).append(", "); return o.append(']').toString(); } } 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 } 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) { final TObjectIntHashMap<Node> map = isAcyclic() ? myNodeToNNumber : myNodeToTNumber; myComparator = new Comparator<Node>() { @Override public int compare(@NotNull Node t, @NotNull Node t1) { return map.get(t) - map.get(t1); } }; } return myComparator; } public Couple<Node> getCircularDependency() { return myBackEdge; } public boolean isAcyclic() { return getCircularDependency() == null; } @NotNull public Node getNodeByNNumber(final int n) { return myInvN[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)}. */ @NotNull public TIntArrayList getSCCs() { return mySCCs; } @NotNull public Collection<Collection<Node>> getComponents() { final TIntArrayList componentSizes = getSCCs(); if (componentSizes.isEmpty()) return Collections.emptyList(); return new MyCollection<Collection<Node>>(componentSizes.size()) { @NotNull @Override public Iterator<Collection<Node>> iterator() { return new MyIterator<Collection<Node>>(componentSizes.size()) { private int offset; @Override protected Collection<Node> get(int i) { final int cSize = componentSizes.get(i); final int cOffset = offset; if (cSize == 0) return Collections.emptyList(); offset += cSize; return new MyCollection<Node>(cSize) { @NotNull @Override public Iterator<Node> iterator() { return new MyIterator<Node>(cSize) { @Override public Node get(int i) { return getNodeByTNumber(cOffset + i); } }; } }; } }; } }; } private abstract static class MyCollection<T> extends AbstractCollection<T> { private final int size; protected MyCollection(int size) { this.size = size; } @Override public int size() { return size; } } private abstract static class MyIterator<T> implements Iterator<T> { private final int size; private int i; protected MyIterator(int size) { this.size = size; } @Override public boolean hasNext() { return i < size; } @Override public T next() { if (i == size) throw new NoSuchElementException(); return get(i++); } protected abstract T get(int i); @Override public void remove() { throw new UnsupportedOperationException(); } } @NotNull public List<Node> getSortedNodes() { List<Node> result = new ArrayList<Node>(myGraph.getNodes()); Collections.sort(result, comparator()); return result; } }