/* * 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.google.devtools.cyclefinder; import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.SetMultimap; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * A graph representing possible references between Java types. * * @author Keith Stanger */ public class ReferenceGraph { private SetMultimap<TypeNode, Edge> edges = HashMultimap.create(); public Set<TypeNode> getNodes() { return Collections.unmodifiableSet(edges.keySet()); } public Set<Edge> getEdges(TypeNode node) { return Collections.unmodifiableSet(edges.get(node)); } public void addEdge(Edge e) { edges.put(e.getOrigin(), e); } public List<ReferenceGraph> getStronglyConnectedComponents(Set<TypeNode> seedNodes) { List<List<TypeNode>> componentNodesList = Tarjans.getStronglyConnectedComponents(edges, seedNodes); List<ReferenceGraph> components = new ArrayList<>(); for (List<TypeNode> componentNodes : componentNodesList) { components.add(getSubgraph(componentNodes)); } return components; } /** * Runs a version of Dijkstra's algorithm to find a tight cycle in the given * strongly connected component. */ public List<Edge> findShortestCycle(TypeNode root) { Map<TypeNode, Edge> backlinks = new HashMap<>(); Set<TypeNode> visited = new HashSet<>(); List<TypeNode> toVisit = Lists.newArrayList(root); outer: while (true) { List<TypeNode> visitNext = new ArrayList<>(); for (TypeNode source : toVisit) { visited.add(source); for (Edge e : edges.get(source)) { TypeNode target = e.getTarget(); if (!visited.contains(target)) { visitNext.add(target); backlinks.put(target, e); } else if (target.equals(root)) { backlinks.put(root, e); break outer; } } } toVisit = visitNext; } List<Edge> cycle = new ArrayList<>(); TypeNode curNode = root; while (!curNode.equals(root) || cycle.size() == 0) { Edge nextEdge = backlinks.get(curNode); cycle.add(nextEdge); curNode = nextEdge.getOrigin(); } return Lists.newArrayList(Lists.reverse(cycle)); } private ReferenceGraph getSubgraph(Collection<TypeNode> vertices) { ReferenceGraph subgraph = new ReferenceGraph(); for (TypeNode type : vertices) { for (Edge e : edges.get(type)) { if (vertices.contains(e.getTarget())) { subgraph.addEdge(e); } } } return subgraph; } public void print(PrintStream printStream) { ArrayList<TypeNode> typeNodes = new ArrayList<>(edges.keySet()); Collections.sort(typeNodes, (a, b) -> a.getName().compareTo(b.getName())); for (TypeNode typeNode : typeNodes) { ArrayList<Edge> outgoingEdges = new ArrayList<>(edges.get(typeNode)); Collections.sort( outgoingEdges, (a, b) -> a.getTarget().getName().compareTo(b.getTarget().getName())); printStream.println("class: " + typeNode); for (Edge e : outgoingEdges) { printStream.println(" " + e); } } } }