android/guava/src/com/google/common/graph/Graphs.java
private static <N> boolean subgraphHasCycle(
      Graph<N> graph,
      Map<Object, NodeVisitState> visitedNodes,
      N node,
      @NullableDecl N previousNode) {
    NodeVisitState state = visitedNodes.get(node);
    if (state == NodeVisitState.COMPLETE) {
      return false;
    }
    if (state == NodeVisitState.PENDING) {
      return true;
    }

    visitedNodes.put(node, NodeVisitState.PENDING);
    for (N nextNode : graph.successors(node)) {
      if (canTraverseWithoutReusingEdge(graph, nextNode, previousNode)
          && subgraphHasCycle(graph, visitedNodes, nextNode, node)) {
        return true;
      }
    }
    visitedNodes.put(node, NodeVisitState.COMPLETE);
    return false;
  }
private static <N> boolean subgraphHasCycle(
      Graph<N> graph,
      Map<Object, NodeVisitState> visitedNodes,
      N node,
      @NullableDecl N previousNode) {
    NodeVisitState state = visitedNodes.get(node);
    if (state == NodeVisitState.COMPLETE) {
      return false;
    }
    if (state == NodeVisitState.PENDING) {
      return true;
    }

    visitedNodes.put(node, NodeVisitState.PENDING);
    for (N nextNode : graph.successors(node)) {
      if (canTraverseWithoutReusingEdge(graph, nextNode, previousNode)
          && subgraphHasCycle(graph, visitedNodes, nextNode, node)) {
        return true;
      }
    }
    visitedNodes.put(node, NodeVisitState.COMPLETE);
    return false;
  }
Performs a traversal of the nodes reachable from {@code node}. If we ever reach a node we've
already visited (following only outgoing edges and without reusing edges), we know there's a
cycle in the graph.