All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.inject.rebind.resolution.UnresolvedBindingValidator 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.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); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy