Package com.google.gwt.inject.rebind.resolution

Source Code of com.google.gwt.inject.rebind.resolution.BindingPositioner$Factory

/*
* 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);
  }
}
TOP

Related Classes of com.google.gwt.inject.rebind.resolution.BindingPositioner$Factory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.