/* * 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.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.ExposedChildBinding; import com.google.gwt.inject.rebind.resolution.DependencyExplorer.DependencyExplorerOutput; import com.google.gwt.inject.rebind.util.Preconditions; 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.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; /** * Given the dependency information about all unresolved (and required and optional) keys needed by * a given Ginjector (the origin), this determines the position for each key. This class computes * Level(k), the Ginjector in which a binding of the key k is available or will be created. It is * subject to the following constraints: * <ul> * <li>For each already available key k, Level(k) is the highest (closest to the root) * Ginjector that the the key is available from. * </li> * <li>For each key k that is not already bound, Level(k) is the highest Ginjector that satisfies * the following constraints: * <ol> * <li>For all dependencies d of the implicit binding for k, Level(k) is no higher than * Level(d). This is necessary to ensure that all of values that the binding depends on are * available to the ginjector at Level(k). * </li> * <li>There can be no descendant Ginjector below Level(k) that also has a binding for k. This * prevents us from creating a binding that causes double-binding errors. * * <p>Consider a simple injector hierarchy with two child-injectors in which we're trying to * materialize key K for one of the children. If the sibling already has a binding for K, even if * it's not exposed, we cannot materialize K at the parent; we must instead place it in the child * that needs it. * </li> * </ol> * </li> * </ul> * * <p>The positions of all the implicit bindings are solved simultaneously, by starting with an * initial guess for each key that starts with them placed as high as possible without causing any * double-binding problems, and then iterating over all the keys and moving them down according to * the following equation: * {@code * Level(k) = lowest(Level(k) U {Level(d) | d \in deps(k)}) * } * * <p>One exception to the rules above is bindings that are needed in the origin and exposed to the * parent. Instead of installing and using them from "as high as possible", we need to install * them in the origin, but still use them from "as high as possible." These bindings are treated * specially, using {@link installOverrides} to separate the use-location from the install-location. * * <p>See {@link BindingResolver} for how this fits into the overall algorithm for resolution. */ class BindingPositioner { private final TreeLogger logger; /** * The keys that still need to be positioned. We use a LinkedHashSet so that we visit keys in the * order they were added, but disallow a key be queued multiple times. */ private final LinkedHashSet<Key<?>> workqueue = new LinkedHashSet<Key<?>>(); /** * Map containing the current (and eventually correct) positions for each key. */ private Map<Key<?>, GinjectorBindings> positions = new LinkedHashMap<Key<?>, GinjectorBindings>(); /** * Stores positions for keys that need to be placed below where they are actually installed. This * is the case for keys that are exposed to parents. Specifically, if a child module binds and * exposes Foo, then we should install Foo in the child injector, while any uses of Foo (such as * by Bar) can still be created in the parent). */ private Map<Key<?>, GinjectorBindings> installOverrides = new LinkedHashMap<Key<?>, GinjectorBindings>(); /** * The output from {@link DependencyExplorer} which includes the dependency graph, and also the * positions for all already available keys. */ private DependencyExplorerOutput output; @Inject public BindingPositioner(@Assisted TreeLogger logger) { this.logger = logger; } public void position(DependencyExplorerOutput output) { Preconditions.checkState(this.output == null, "Should not call position more than once"); this.output = output; computeInitialPositions(); workqueue.addAll(output.getImplicitlyBoundKeys()); calculateExactPositions(); } /** * Returns the Ginjector where the binding for key should be placed, or null if the key was * removed from the dependency graph earlier. */ public GinjectorBindings getInstallPosition(Key<?> key) { Preconditions.checkNotNull(positions, "Must call position before calling getInstallPosition(Key<?>)"); GinjectorBindings position = installOverrides.get(key); if (position == null) { position = positions.get(key); } return position; } public GinjectorBindings getAccessPosition(Key<?> key) { Preconditions.checkNotNull(positions, "Must call position before calling getAccessPosition(Key<?>)"); return positions.get(key); } /** * Place an initial guess in the position map that places each implicit binding as high as * possible in the injector tree without causing double binding. */ private void computeInitialPositions() { positions.putAll(output.getPreExistingLocations()); for (Key<?> key : output.getImplicitlyBoundKeys()) { GinjectorBindings initialPosition = computeInitialPosition(key); PrettyPrinter.log(logger, TreeLogger.DEBUG, PrettyPrinter.format( "Initial highest visible position of %s is %s", key, initialPosition)); positions.put(key, initialPosition); } } /** * Returns the highest injector that we could possibly position the key at without causing a * double binding. */ private GinjectorBindings computeInitialPosition(Key<?> key) { GinjectorBindings initialPosition = output.getGraph().getOrigin(); boolean pinned = initialPosition.isPinned(key); // If the key is pinned (explicitly bound) at the origin, we may be in a situation where we need // to install a binding at the origin, even though we should *use* the binding form a higher // location. // If key is already bound in parent, there is a reason that {@link DependencyExplorer} // chose not to use that binding. Specifically, it implies that the key is exposed to the // parent from the origin. While we are fine using the higher binding, it is still necessary // to install the binding in the origin. if (pinned) { PrettyPrinter.log(logger, TreeLogger.DEBUG, PrettyPrinter.format("Forcing %s to be installed in %s due to a pin.", key, initialPosition)); installOverrides.put(key, initialPosition); } while (canExposeKeyFrom(key, initialPosition, pinned)) { PrettyPrinter.log(logger, TreeLogger.SPAM, "Moving the highest visible position of %s from %s to %s.", key, initialPosition, initialPosition.getParent()); initialPosition = initialPosition.getParent(); } return initialPosition; } /** * Tests whether a key from the given child injector can be made visible in * its parent. For pinned keys, this means that they're exposed to the * parent; for keys that aren't pinned, it means that there's no other * constraint preventing them from floating up. * * <p>Note that "pinned" states whether the key was pinned in the injector it * started in; it might not be pinned in child. */ private boolean canExposeKeyFrom(Key<?> key, GinjectorBindings child, boolean pinned) { GinjectorBindings parent = child.getParent(); if (parent == null) { // Can't move above the root. return false; } else if (parent.isBoundLocallyInChild(key)) { // If a sibling module already materialized a binding for this key, we // can't float over it. return false; } else if (pinned) { // If a key is pinned, it's visible in the parent iff it has an // ExposedChildBinding pointing at the child. Binding binding = parent.getBinding(key); if (binding == null) { return false; } else if (!(binding instanceof ExposedChildBinding)) { // This should never happen (it would have been caught as a // double-binding earlier). throw new RuntimeException("Unexpected binding shadowing a pinned binding: " + binding); } else { ExposedChildBinding exposedChildBinding = (ExposedChildBinding) binding; if (exposedChildBinding.getChildBindings() != child) { throw new RuntimeException( "Unexpected exposed child binding shadowing a pinned binding: " + binding); } else { return true; } } } else { return true; } } /** * Iterates on the position equation, updating each binding in the queue and re-queueing nodes * that depend on any node we move. This will always terminate, since we only re-queue when we * make a change, and there are a finite number of entries in the injector hierarchy. */ private void calculateExactPositions() { while (!workqueue.isEmpty()) { Key<?> key = workqueue.iterator().next(); workqueue.remove(key); Set<GinjectorBindings> injectors = getSourceGinjectors(key); injectors.add(positions.get(key)); GinjectorBindings newPosition = lowest(injectors); GinjectorBindings oldPosition = positions.put(key, newPosition); if (oldPosition != newPosition) { PrettyPrinter.log(logger, TreeLogger.DEBUG, "Moved the highest visible position of %s from %s to %s, the lowest injector of %s.", key, oldPosition, newPosition, injectors); // We don't care if GINJECTOR is present, as its Ginjector will resolve to "null", which // will never be reached on the path from the origin up to the root, therefore it won't // actually constrain anything. for (Dependency dependency : output.getGraph().getDependenciesTargeting(key)) { PrettyPrinter.log(logger, TreeLogger.DEBUG, "Re-enqueuing %s due to %s", dependency.getSource(), dependency); workqueue.add(dependency.getSource()); } } } } /** * Returns the injectors where the dependencies for node are currently placed. */ private Set<GinjectorBindings> getSourceGinjectors(Key<?> key) { Set<GinjectorBindings> sourceInjectors = new LinkedHashSet<GinjectorBindings>(); for (Dependency dep : output.getGraph().getDependenciesOf(key)) { sourceInjectors.add(positions.get(dep.getTarget())); } return sourceInjectors; } /** * Returns the member of {@code sources} closest to the origin. */ private GinjectorBindings lowest(Set<GinjectorBindings> sources) { GinjectorBindings lowest = output.getGraph().getOrigin(); while (!sources.contains(lowest)) { lowest = lowest.getParent(); } return Preconditions.checkNotNull(lowest, "Should never make it to null"); } interface Factory { BindingPositioner create(TreeLogger logger); } }