/* * Copyright (C) 2021 The Dagger Authors. * * 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 dagger.internal.codegen.base; import static com.google.common.base.Preconditions.checkState; import static java.lang.Math.min; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.graph.SuccessorsFunction; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; import java.util.Map; import java.util.Set; /** * An implementation of Tarjan's algorithm for finding the SCC of a graph. This is based on the * psuedo code algorithm here: * http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm */ public final class TarjanSCCs { /** Returns the set of strongly connected components in reverse topological order. */ public static ImmutableList> compute( ImmutableCollection nodes, SuccessorsFunction successorsFunction) { return new TarjanSCC<>(nodes, successorsFunction).compute(); } private static class TarjanSCC { private final ImmutableCollection nodes; private final SuccessorsFunction successorsFunction; private final Deque stack; private final Set onStack; private final Map indexes; private final Map lowLinks; private final List> stronglyConnectedComponents = new ArrayList<>(); TarjanSCC(ImmutableCollection nodes, SuccessorsFunction successorsFunction) { this.nodes = nodes; this.successorsFunction = successorsFunction; this.stack = new ArrayDeque<>(nodes.size()); this.onStack = Sets.newHashSetWithExpectedSize(nodes.size()); this.indexes = Maps.newHashMapWithExpectedSize(nodes.size()); this.lowLinks = Maps.newHashMapWithExpectedSize(nodes.size()); } private ImmutableList> compute() { checkState(indexes.isEmpty(), "TarjanSCC#compute() can only be called once per instance!"); for (NodeT node : nodes) { if (!indexes.containsKey(node)) { stronglyConnect(node); } } return ImmutableList.copyOf(stronglyConnectedComponents); } private void stronglyConnect(NodeT node) { // Set the index and lowLink for node to the smallest unused index and add it to the stack lowLinks.put(node, indexes.size()); indexes.put(node, indexes.size()); stack.push(node); onStack.add(node); for (NodeT successor : successorsFunction.successors(node)) { if (!indexes.containsKey(successor)) { // Successor has not been processed. stronglyConnect(successor); lowLinks.put(node, min(lowLinks.get(node), lowLinks.get(successor))); } else if (onStack.contains(successor)) { // Successor is on the stack and hence in the current SCC. lowLinks.put(node, min(lowLinks.get(node), indexes.get(successor))); } else { // Successor is not on the stack and hence in an already processed SCC, so ignore. } } // If node is the root of the SCC, pop the stack until reaching the root to get all SCC nodes. if (lowLinks.get(node).equals(indexes.get(node))) { ImmutableSet.Builder scc = ImmutableSet.builder(); NodeT currNode; do { currNode = stack.pop(); onStack.remove(currNode); scc.add(currNode); } while (!node.equals(currNode)); stronglyConnectedComponents.add(scc.build()); } } } private TarjanSCCs() {} }