
com.google.gwt.inject.rebind.resolution.UnresolvedBindingValidator Maven / Gradle / Ivy
Show all versions of gin Show documentation
/*
* 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.core.ext.TreeLogger;
import com.google.gwt.dev.util.Preconditions;
import com.google.gwt.inject.rebind.ErrorManager;
import com.google.gwt.inject.rebind.GinjectorBindings;
import com.google.gwt.inject.rebind.binding.Binding;
import com.google.gwt.inject.rebind.binding.Dependency;
import com.google.gwt.inject.rebind.binding.ParentBinding;
import com.google.gwt.inject.rebind.resolution.DependencyExplorer.DependencyExplorerOutput;
import com.google.gwt.inject.rebind.util.PrettyPrinter;
import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.assistedinject.Assisted;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Finds and reports errors in the dependency information. Removes all optional bindings that can't
* be constructed.
*
* A key is required if there exists a path from a key in the unresolved set of the origin
* Ginjector to the key in question that passes only through required dependencies. A key is
* optional if every path that leads to the key contains an optional dependency.
*
* This detects the following:
*
* - Circular dependencies that do not pass through a Provider or an AsyncProvider. This is a
* fatal error, even for optional keys.
*
* - Bindings that cannot be created without causing a double-binding. Specifically, bindings for
* which the key is already bound below, but not exposed to, the Ginjector that needs the key. This
* is only an error for required keys; optional keys are removed.
*
* - Bindings that cannot have implicit dependencies created for them. This is only an error if
* the key is required; optional keys are removed.
*
*
*
* Optional bindings with errors must be removed from the dependency graph before proceeding.
* This is to prevent them from incorrectly constraining the positions for keys that depend on them.
*
*
See {@link BindingResolver} for how this fits into the overall algorithm for resolution.
*/
public class UnresolvedBindingValidator {
private final ErrorManager errorManager;
private final EagerCycleFinder cycleFinder;
private final TreeLogger logger;
@Inject
public UnresolvedBindingValidator(EagerCycleFinder cycleFinder, ErrorManager errorManager,
@Assisted TreeLogger logger) {
this.cycleFinder = cycleFinder;
this.errorManager = errorManager;
this.logger = logger;
}
/**
* Returns an {@link InvalidKeys} object containing information about all the errors that we
* discovered in required keys, and the set of all optional bindings that should be removed from
* the graph in order to make it valid.
*/
public InvalidKeys getInvalidKeys(DependencyExplorerOutput output) {
Map, String> allInvalidKeys = getAllInvalidKeys(output);
Set> optionalInvalidKeys = removeOptionalKeys(output, allInvalidKeys);
optionalInvalidKeys = getKeysToRemove(output.getGraph(), optionalInvalidKeys);
return new InvalidKeys(allInvalidKeys, optionalInvalidKeys);
}
/**
* Returns true if the graph is valid (does not have any cycles or problems creating required
* keys). If there are any errors, they will be reported to the global {@link ErrorManager}.
*/
public boolean validate(DependencyExplorerOutput output, InvalidKeys invalidKeys) {
Collection, String>> invalidRequiredKeys =
invalidKeys.getInvalidRequiredKeys();
for (Map.Entry, String> error : invalidRequiredKeys) {
reportError(output, error.getKey(), error.getValue());
}
return !cycleFinder.findAndReportCycles(output.getGraph()) && invalidRequiredKeys.isEmpty();
}
/**
* Prune all of the invalid optional keys from the graph. After this method, all of the keys
* remaining in the graph are resolvable.
*/
public void pruneInvalidOptional(DependencyExplorerOutput output, InvalidKeys invalidKeys) {
DependencyGraph.GraphPruner prunedGraph = new DependencyGraph.GraphPruner(output.getGraph());
for (Key> key : invalidKeys.getInvalidOptionalKeys()) {
prunedGraph.remove(key);
output.removeBinding(key);
}
output.setGraph(prunedGraph.update());
}
/**
* Returns a map from keys that are invalid to errors explaining why each key is invalid.
*/
private Map, String> getAllInvalidKeys(DependencyExplorerOutput output) {
Map, String> invalidKeys = new LinkedHashMap, String>();
// Look for errors in the nodes, and either report the error (if its required) or remove the
// node (if its optional).
for (Entry, String> error : output.getBindingErrors()) {
invalidKeys.put(error.getKey(), "Unable to create or inherit binding: " + error.getValue());
}
GinjectorBindings origin = output.getGraph().getOrigin();
for (Key> key : output.getImplicitlyBoundKeys()) {
if (origin.isBoundLocallyInChild(key)) {
GinjectorBindings child = origin.getChildWhichBindsLocally(key);
Binding childBinding = child.getBinding(key);
PrettyPrinter.log(logger, TreeLogger.DEBUG,
"Marking the key %s as bound in the ginjector %s (implicitly), and in the child"
+ " %s (%s)", key, origin, child, childBinding.getContext());
// TODO(schmitt): Determine path to binding in child ginjector (requires
// different DependencyExplorerOutput).
invalidKeys.put(key,
PrettyPrinter.format("Already bound in child Ginjector %s. Consider exposing it?",
child));
}
}
return invalidKeys;
}
/**
* Remove optional keys from the {@code invalidKeys} map, and return the the set of optional keys
* that had problems.
*/
private Set> removeOptionalKeys(
DependencyExplorerOutput output, Map, String> invalidKeys) {
RequiredKeySet requiredKeys = new RequiredKeySet(output.getGraph());
Set> optionalKeys = new LinkedHashSet>();
// We need to use a for loop instead of a foreach loop because we remove
// entries as we go (see the call to iterator.remove() below).
for (Iterator, String>> iterator = invalidKeys.entrySet().iterator();
iterator.hasNext();) {
Map.Entry, String> entry = iterator.next();
Key> key = entry.getKey();
if (!requiredKeys.isRequired(key)) {
PrettyPrinter.log(logger, TreeLogger.DEBUG,
"Removing the optional key %s because it had errors: %s", key, entry.getValue());
optionalKeys.add(key);
iterator.remove();
}
}
return optionalKeys;
}
/**
* Given the set of optional keys that had problems, compute the set of all optional keys that
* should be removed. This may include additional keys, for instance if an optional key requires
* an invalid key, than the optional key should also be removed.
*
* This will only add optional keys to {@code toRemove}. Recall that a key is required iff
* there exists a path to it that passes through only required edges. If key Y is optional, and
* there is a required edge from X -> Y, then X must also be optional. If X were required, by
* our definition Y would also be required.
*/
private Set> getKeysToRemove(DependencyGraph graph, Collection> discovered) {
Set> toRemove = new LinkedHashSet>();
while (!discovered.isEmpty()) {
toRemove.addAll(discovered);
discovered = getRequiredSourcesTargeting(graph, discovered);
discovered.removeAll(toRemove);
}
return toRemove;
}
/**
* Returns all of the source keys that have a required dependency on any key in the target set.
*/
private Collection> getRequiredSourcesTargeting(
DependencyGraph graph, Iterable> targets) {
Collection> requiredSources = new LinkedHashSet>();
for (Key> target : targets) {
for (Dependency edge : graph.getDependenciesTargeting(target)) {
if (!edge.isOptional()) {
PrettyPrinter.log(logger, TreeLogger.DEBUG, "Removing the key %s because of %s",
edge.getSource(), edge);
requiredSources.add(edge.getSource());
}
}
}
return requiredSources;
}
private void reportError(DependencyExplorerOutput output, Key> key, String error) {
// TODO(dburrows, bchambers): consider better approaches to pretty-printing keys.
List path = new PathFinder()
.onGraph(output.getGraph())
.withOnlyRequiredEdges(true)
.addRoots(Dependency.GINJECTOR)
.addDestinations(key)
.findShortestPath();
errorManager.logError("Error injecting %s: %s%n Path to required node:%n%s",
key, error, path);
}
/**
* Container for information about invalid keys.
*/
public static class InvalidKeys {
private final Map, String> invalidRequiredKeys;
private final Collection> invalidOptionalKeys;
private InvalidKeys(Map, String> invalidRequiredKeys, Set> invalidOptionalKeys) {
this.invalidRequiredKeys = Collections.unmodifiableMap(invalidRequiredKeys);
this.invalidOptionalKeys = Collections.unmodifiableCollection(invalidOptionalKeys);
}
public Set, String>> getInvalidRequiredKeys() {
return invalidRequiredKeys.entrySet();
}
public Collection> getInvalidOptionalKeys() {
return invalidOptionalKeys;
}
}
public interface Factory {
UnresolvedBindingValidator create(TreeLogger logger);
}
}