/* * Copyright 2011 Google Inc. * * 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.gwt.inject.rebind.resolution; import com.google.gwt.inject.rebind.ErrorManager; import com.google.gwt.inject.rebind.binding.Dependency; import com.google.inject.Inject; import com.google.inject.Key; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Searches for "eager" cycles in the dependency graph. These are cycles that do not pass through * a Provider or AsyncProvider. * * <p>This only finds cycles that are necessary to resolve the dependencies for the current origin * Ginjector. * * <p>Reports errors including the detected cycle and the path that led here from the unresolved * bindings in the ginjector to the global {@link ErrorManager}. * * <p>See {@link BindingResolver} for how this fits into the overall algorithm for resolution. */ public class EagerCycleFinder { /** * For each key that has been visited, this maps to the eager edge that was followed to reach the * node, or null if it was used in the initial call to visit. */ private Map<Key<?>, Dependency> visitedEdge; /** * Nodes that are active in the current DFS. Revisiting any of these nodes indicates an eager * cycle, and should be reported as a problem. */ private Set<Key<?>> dfsStack = new LinkedHashSet<Key<?>>(); private final ErrorManager errorManager; private boolean cycleDetected = false; private DependencyGraph graph; @Inject public EagerCycleFinder(ErrorManager errorManager) { this.errorManager = errorManager; } /** * Detects cycles in the given graph. * * @return {@code true} if any cycles were detected */ public boolean findAndReportCycles(DependencyGraph graph) { this.graph = graph; cycleDetected = false; visitedEdge = new LinkedHashMap<Key<?>, Dependency>(graph.size()); for (Key<?> key : graph.getAllKeys()) { visit(key, null); } return cycleDetected; } private void visit(Key<?> key, Dependency edge) { // If we loop back to a key that is "active" in the current DFS, we have found an eager cycle. if (!dfsStack.add(key)) { reportCycle(edge); return; } // If this is a first time an edge to the target has been visited, we're "discovering" it. // We need to recursively walk over the dependencies. if (!visitedEdge.containsKey(key)) { visitedEdge.put(key, edge); for (Dependency nextEdge : graph.getDependenciesOf(key)) { if (!nextEdge.isLazy()) { visit(nextEdge.getTarget(), nextEdge); // Recursively visit eager edges in the current DFS } } } dfsStack.remove(key); } private List<Dependency> describeCycle(Dependency cycleEdge) { List<Dependency> cycle = new ArrayList<Dependency>(); cycle.add(cycleEdge); Key<?> curr = cycleEdge.getSource(); while (!curr.equals(cycleEdge.getTarget())) { Dependency edge = visitedEdge.get(curr); cycle.add(edge); curr = edge.getSource(); } Collections.reverse(cycle); return cycle; } private void reportCycle(Dependency cycleEdge) { cycleDetected = true; // Get the edges in the cycle List<Dependency> cycle = describeCycle(cycleEdge); // Using the edges, determine the keys in the cycle PathFinder pathFinder = new PathFinder().onGraph(graph).addRoots(Dependency.GINJECTOR); for (Dependency edge : cycle) { pathFinder.addDestinations(edge.getTarget()); } List<Dependency> path = pathFinder.findShortestPath(); if (path != null && !path.isEmpty()) { cycle = rootCycleAt(cycle, path.get(path.size() - 1).getTarget()); } reportError(path, cycle); } /** * Attempts to root the given dependency cycle at the given key. If the key * is present in the cycle, rotates the dependency cycle so that the key is * the first source. Otherwise, returns the cycle unchanged. */ static List<Dependency> rootCycleAt(List<Dependency> cycle, Key<?> key) { for (int i = 0; i < cycle.size(); ++i) { if (key.equals(cycle.get(i).getSource())) { List<Dependency> returnValue = new ArrayList<Dependency>(); returnValue.addAll(cycle.subList(i, cycle.size())); returnValue.addAll(cycle.subList(0, i)); return returnValue; } } return cycle; } void reportError(List<Dependency> pathToCycle, List<Dependency> cycle) { Object pathToCycleArg = pathToCycle == null ? "(none)" : pathToCycle; errorManager.logError("Cycle detected in the dependency graph. " + "Consider using a Provider?%n Path To Cycle:%n%s%n Cycle:%n%s%n", pathToCycleArg, cycle); } }