com.google.gwt.inject.rebind.resolution.PathFinder Maven / Gradle / Ivy
/*
* 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.binding.Dependency;
import com.google.gwt.inject.rebind.util.Preconditions;
import com.google.inject.Key;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
/**
* Finds the shortest path from the edges in the root set to any of one or more destination keys.
*
* This is used in {@link EagerCycleFinder} and {@link UnresolvedBindingValidator} for explaining
* why a given error/cycle was reachable from the Ginjector.
*/
public class PathFinder {
private DependencyGraph graph;
private Collection> destinations = new LinkedHashSet>();
private Collection> roots = new LinkedHashSet>();
private boolean onlyRequiredEdges;
/**
* For every key that is discovered during the Breadth-first search, this points to the edge that
* discovered it. We don't need to revisit(or requeue) any keys that are already defined here,
* because any new edge leading to back to the given key will at best be longer than the current
* path.
*/
private Map, Dependency> visited;
/**
* The nodes remaining to visit.
*/
private Queue> workQueue;
public PathFinder() {}
public PathFinder onGraph(DependencyGraph graph) {
this.graph = graph;
return this;
}
public PathFinder addRoots(Key>... roots) {
Collections.addAll(this.roots, roots);
return this;
}
/**
* Add destinations to be used for the next search. The shortest path from the unresolved set
* to any member of the destination set will be returned from {@link #findShortestPath()}.
*/
public PathFinder addDestinations(Key>... destinations) {
Collections.addAll(this.destinations, destinations);
return this;
}
/**
* @param onlyRequiredEdges if true, only required edges will be considered when searching for the
* path
*/
public PathFinder withOnlyRequiredEdges(boolean onlyRequiredEdges) {
this.onlyRequiredEdges = onlyRequiredEdges;
return this;
}
/**
* Find the shortest path from an unresolved edge in the roots to a key in the destinations.
*
* Implemented as a Breadth-first search from the destination set back to the origin.
*
* @return the shortest path from the roots to any of the destinations specified that passes
* through edges meeting the criteria; can be empty if destination is already in the root
* set, or null if no path exists
*/
public List findShortestPath() {
Preconditions.checkNotNull(graph, "Must call onGraph(DependencyGraph) before findShortestPath");
Preconditions.checkState(!roots.isEmpty(),
"Must call addRoots(Key>...) before findShortestPath");
Preconditions.checkState(!destinations.isEmpty(),
"Must call addDestinations(Key>...) before findShortestPath");
visited = new LinkedHashMap, Dependency>();
workQueue = new LinkedList>();
// Populate the workqueue with our initial destination keys. If any of them are in the root
// set, we can return early.
for (Key> key : destinations) {
visited.put(key, null);
if (roots.contains(key)) {
return getPathFor(key);
}
workQueue.add(key);
}
// Perform a BFS looking for a path back to a root edge
while (!workQueue.isEmpty()) {
Key> key = workQueue.remove();
for (Dependency edge : graph.getDependenciesTargeting(key)) {
if (isEdgeUsable(edge)) {
Key> sourceKey = edge.getSource();
if (!visited.containsKey(sourceKey)) {
workQueue.add(sourceKey);
visited.put(sourceKey, edge);
// Check for early termination
if (roots.contains(sourceKey)) {
return getPathFor(sourceKey);
}
}
}
}
}
// Shouldn't be possible, unless the only paths reaching the destinations take optional edges
// and requiredOnly is true.
return null;
}
private List getPathFor(Key> rootKey) {
List result = new ArrayList();
// Now, add the edges from the BFS path
Dependency edge = visited.get(rootKey);
while (edge != null) {
result.add(edge);
edge = visited.get(edge.getTarget());
}
return result;
}
/**
* Returns true if the given edge meets our criteria for use, false otherwise.
*/
private boolean isEdgeUsable(Dependency edge) {
return !edge.isOptional() || !onlyRequiredEdges;
}
}