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

dagger.internal.codegen.binding.BindingGraph Maven / Gradle / Ivy

There is a newer version: 2.52
Show newest version
/*
 * Copyright (C) 2014 The Dagger Authors.
 *
 * 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 dagger.internal.codegen.binding;

import static com.google.common.collect.Iterables.transform;
import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
import static dagger.internal.codegen.extension.DaggerStreams.instancesOf;
import static dagger.internal.codegen.extension.DaggerStreams.presentValues;
import static dagger.internal.codegen.extension.DaggerStreams.stream;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;

import androidx.room.compiler.processing.XExecutableElement;
import androidx.room.compiler.processing.XExecutableParameterElement;
import androidx.room.compiler.processing.XMethodElement;
import androidx.room.compiler.processing.XTypeElement;
import com.google.auto.value.AutoValue;
import com.google.auto.value.extension.memoized.Memoized;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import com.google.common.graph.ImmutableNetwork;
import com.google.common.graph.Traverser;
import dagger.internal.codegen.base.TarjanSCCs;
import dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge;
import dagger.internal.codegen.model.BindingGraph.ComponentNode;
import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
import dagger.internal.codegen.model.BindingGraph.Edge;
import dagger.internal.codegen.model.BindingGraph.Node;
import dagger.internal.codegen.model.ComponentPath;
import dagger.internal.codegen.model.DaggerTypeElement;
import dagger.internal.codegen.model.DependencyRequest;
import dagger.internal.codegen.model.Key;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

/**
 * A graph that represents a single component or subcomponent within a fully validated top-level
 * binding graph.
 */
@AutoValue
public abstract class BindingGraph {

  /**
   * A graph that represents the entire network of nodes from all components, subcomponents and
   * their bindings.
   */
  @AutoValue
  public abstract static class TopLevelBindingGraph
      extends dagger.internal.codegen.model.BindingGraph {
    static TopLevelBindingGraph create(
        ImmutableNetwork network, boolean isFullBindingGraph) {
      TopLevelBindingGraph topLevelBindingGraph =
          new AutoValue_BindingGraph_TopLevelBindingGraph(network, isFullBindingGraph);

      ImmutableMap componentNodes =
          topLevelBindingGraph.componentNodes().stream()
              .collect(
                  toImmutableMap(ComponentNode::componentPath, componentNode -> componentNode));

      ImmutableSetMultimap.Builder subcomponentNodesBuilder =
          ImmutableSetMultimap.builder();
      topLevelBindingGraph.componentNodes().stream()
          .filter(componentNode -> !componentNode.componentPath().atRoot())
          .forEach(
              componentNode ->
                  subcomponentNodesBuilder.put(
                      componentNodes.get(componentNode.componentPath().parent()), componentNode));

      // Set these fields directly on the instance rather than passing these in as input to the
      // AutoValue to prevent exposing this data outside of the class.
      topLevelBindingGraph.componentNodes = componentNodes;
      topLevelBindingGraph.subcomponentNodes = subcomponentNodesBuilder.build();
      topLevelBindingGraph.frameworkTypeBindings =
          frameworkRequestBindingSet(network, topLevelBindingGraph.bindings());
      return topLevelBindingGraph;
    }

    private ImmutableMap componentNodes;
    private ImmutableSetMultimap subcomponentNodes;
    private ImmutableSet frameworkTypeBindings;

    TopLevelBindingGraph() {}

    // This overrides dagger.internal.codegen.model.BindingGraph with a more efficient
    // implementation.
    @Override
    public Optional componentNode(ComponentPath componentPath) {
      return componentNodes.containsKey(componentPath)
          ? Optional.of(componentNodes.get(componentPath))
          : Optional.empty();
    }

    /** Returns the set of subcomponent nodes of the given component node. */
    ImmutableSet subcomponentNodes(ComponentNode componentNode) {
      return subcomponentNodes.get(componentNode);
    }

    @Override
    @Memoized
    public ImmutableSetMultimap, ? extends Node> nodesByClass() {
      return super.nodesByClass();
    }

    /**
     * Returns an index of each {@link BindingNode} by its {@link ComponentPath}. Accessing this for
     * a component and its parent components is faster than doing a graph traversal.
     */
    @Memoized
    ImmutableListMultimap bindingsByComponent() {
      return Multimaps.index(transform(bindings(), BindingNode.class::cast), Node::componentPath);
    }

    /** Returns a {@link Comparator} in the same order as {@link Network#nodes()}. */
    @Memoized
    Comparator nodeOrder() {
      Map nodeOrderMap = Maps.newHashMapWithExpectedSize(network().nodes().size());
      int i = 0;
      for (Node node : network().nodes()) {
        nodeOrderMap.put(node, i++);
      }
      return (n1, n2) -> nodeOrderMap.get(n1).compareTo(nodeOrderMap.get(n2));
    }

    /** Returns the set of strongly connected nodes in this graph in reverse topological order. */
    @Memoized
    public ImmutableSet> stronglyConnectedNodes() {
      return TarjanSCCs.compute(
          ImmutableSet.copyOf(network().nodes()),
          // NetworkBuilder does not have a stable successor order, so we have to roll our own
          // based on the node order, which is stable.
          // TODO(bcorso): Fix once https://github.com/google/guava/issues/2650 is fixed.
          node ->
              network().successors(node).stream().sorted(nodeOrder()).collect(toImmutableList()));
    }

    public boolean hasFrameworkRequest(Binding binding) {
      return frameworkTypeBindings.contains(binding);
    }

    private static ImmutableSet frameworkRequestBindingSet(
        ImmutableNetwork network,
        ImmutableSet bindings) {
      Set frameworkRequestBindings = new HashSet<>();
      for (dagger.internal.codegen.model.Binding binding : bindings) {
        ImmutableList edges =
            network.inEdges(binding).stream()
                .flatMap(instancesOf(DependencyEdge.class))
                .collect(toImmutableList());
        for (DependencyEdge edge : edges) {
          DependencyRequest request = edge.dependencyRequest();
          switch (request.kind()) {
            case INSTANCE:
            case FUTURE:
              continue;
            case PRODUCED:
            case PRODUCER:
            case MEMBERS_INJECTION:
            case PROVIDER_OF_LAZY:
            case LAZY:
            case PROVIDER:
              frameworkRequestBindings.add(((BindingNode) binding).delegate());
              break;
          }
        }
      }
      return ImmutableSet.copyOf(frameworkRequestBindings);
    }
  }

  static BindingGraph create(
      ComponentNode componentNode, TopLevelBindingGraph topLevelBindingGraph) {
    return create(Optional.empty(), componentNode, topLevelBindingGraph);
  }

  private static BindingGraph create(
      Optional parent,
      ComponentNode componentNode,
      TopLevelBindingGraph topLevelBindingGraph) {
    // TODO(bcorso): Mapping binding nodes by key is flawed since bindings that depend on local
    // multibindings can have multiple nodes (one in each component). In this case, we choose the
    // node in the child-most component since this is likely the node that users of this
    // BindingGraph will want (and to remain consistent with LegacyBindingGraph). However, ideally
    // we would avoid this ambiguity by getting dependencies directly from the top-level network.
    // In particular, rather than using a Binding's list of DependencyRequests (which only
    // contains the key) we would use the top-level network to find the DependencyEdges for a
    // particular BindingNode.
    Map contributionBindings = new LinkedHashMap<>();
    Map membersInjectionBindings = new LinkedHashMap<>();

    // Construct the maps of the ContributionBindings and MembersInjectionBindings by iterating
    // bindings from this component and then from each successive parent. If a binding exists in
    // multple components, this order ensures that the child-most binding is always chosen first.
    Stream.iterate(componentNode.componentPath(), ComponentPath::parent)
        // Stream.iterate is inifinte stream so we need limit it to the known size of the path.
        .limit(componentNode.componentPath().components().size())
        .flatMap(path -> topLevelBindingGraph.bindingsByComponent().get(path).stream())
        .forEach(
            bindingNode -> {
              if (bindingNode.delegate() instanceof ContributionBinding) {
                contributionBindings.putIfAbsent(bindingNode.key(), bindingNode);
              } else if (bindingNode.delegate() instanceof MembersInjectionBinding) {
                membersInjectionBindings.putIfAbsent(bindingNode.key(), bindingNode);
              } else {
                throw new AssertionError("Unexpected binding node type: " + bindingNode.delegate());
              }
            });

    BindingGraph bindingGraph = new AutoValue_BindingGraph(componentNode, topLevelBindingGraph);

    ImmutableSet modules =
        ((ComponentNodeImpl) componentNode).componentDescriptor().modules();

    ImmutableSet inheritedModules =
        parent.isPresent()
            ? Sets.union(parent.get().ownedModules, parent.get().inheritedModules).immutableCopy()
            : ImmutableSet.of();

    // Set these fields directly on the instance rather than passing these in as input to the
    // AutoValue to prevent exposing this data outside of the class.
    bindingGraph.inheritedModules = inheritedModules;
    bindingGraph.ownedModules = Sets.difference(modules, inheritedModules).immutableCopy();
    bindingGraph.contributionBindings = ImmutableMap.copyOf(contributionBindings);
    bindingGraph.membersInjectionBindings = ImmutableMap.copyOf(membersInjectionBindings);
    bindingGraph.bindingModules =
        contributionBindings.values().stream()
            .map(BindingNode::contributingModule)
            .flatMap(presentValues())
            .map(DaggerTypeElement::xprocessing)
            .collect(toImmutableSet());

    return bindingGraph;
  }

  private ImmutableMap contributionBindings;
  private ImmutableMap membersInjectionBindings;
  private ImmutableSet inheritedModules;
  private ImmutableSet ownedModules;
  private ImmutableSet bindingModules;

  BindingGraph() {}

  /** Returns the {@link ComponentNode} for this graph. */
  public abstract ComponentNode componentNode();

  /** Returns the {@link ComponentPath} for this graph. */
  public final ComponentPath componentPath() {
    return componentNode().componentPath();
  }

  /** Returns the {@link TopLevelBindingGraph} from which this graph is contained. */
  public abstract TopLevelBindingGraph topLevelBindingGraph();

  /** Returns the {@link ComponentDescriptor} for this graph */
  public final ComponentDescriptor componentDescriptor() {
    return ((ComponentNodeImpl) componentNode()).componentDescriptor();
  }

  /**
   * Returns the {@link ContributionBinding} for the given {@link Key} in this component or {@link
   * Optional#empty()} if one doesn't exist.
   */
  public final Optional localContributionBinding(Key key) {
    return contributionBindings.containsKey(key)
        ? Optional.of(contributionBindings.get(key))
            .filter(bindingNode -> bindingNode.componentPath().equals(componentPath()))
            .map(BindingNode::delegate)
        : Optional.empty();
  }

  /**
   * Returns the {@link MembersInjectionBinding} for the given {@link Key} in this component or
   * {@link Optional#empty()} if one doesn't exist.
   */
  public final Optional localMembersInjectionBinding(Key key) {
    return membersInjectionBindings.containsKey(key)
        ? Optional.of(membersInjectionBindings.get(key))
            .filter(bindingNode -> bindingNode.componentPath().equals(componentPath()))
            .map(BindingNode::delegate)
        : Optional.empty();
  }

  /** Returns the {@link ContributionBinding} for the given {@link Key}. */
  public final ContributionBinding contributionBinding(Key key) {
    return (ContributionBinding) contributionBindings.get(key).delegate();
  }

  /**
   * Returns the {@link MembersInjectionBinding} for the given {@link Key} or {@link
   * Optional#empty()} if one does not exist.
   */
  public final Optional membersInjectionBinding(Key key) {
    return membersInjectionBindings.containsKey(key)
        ? Optional.of((MembersInjectionBinding) membersInjectionBindings.get(key).delegate())
        : Optional.empty();
  }

  /** Returns the {@link XTypeElement} for the component this graph represents. */
  public final XTypeElement componentTypeElement() {
    return componentPath().currentComponent().xprocessing();
  }

  /**
   * Returns the set of modules that are owned by this graph regardless of whether or not any of
   * their bindings are used in this graph. For graphs representing top-level {@link
   * dagger.Component components}, this set will be the same as {@linkplain
   * ComponentDescriptor#modules() the component's transitive modules}. For {@linkplain Subcomponent
   * subcomponents}, this set will be the transitive modules that are not owned by any of their
   * ancestors.
   */
  public final ImmutableSet ownedModuleTypes() {
    return ownedModules.stream()
        .map(ModuleDescriptor::moduleElement)
        .collect(toImmutableSet());
  }

  /**
   * Returns the factory method for this subcomponent, if it exists.
   *
   * 

This factory method is the one defined in the parent component's interface. * *

In the example below, the {@link BindingGraph#factoryMethod} for {@code ChildComponent} * would return the {@link XExecutableElement}: {@code childComponent(ChildModule1)} . * *


   *   {@literal @Component}
   *   interface ParentComponent {
   *     ChildComponent childComponent(ChildModule1 childModule);
   *   }
   * 
*/ // TODO(b/73294201): Consider returning the resolved ExecutableType for the factory method. public final Optional factoryMethod() { return topLevelBindingGraph().network().inEdges(componentNode()).stream() .filter(edge -> edge instanceof ChildFactoryMethodEdge) .map(edge -> ((ChildFactoryMethodEdge) edge).factoryMethod().xprocessing()) // Factory methods are represented by XMethodElement (rather than XConstructorElement) // TODO(bcorso): consider adding DaggerMethodElement so this cast isn't needed. .map(XMethodElement.class::cast) .collect(toOptional()); } /** * Returns a map between the {@linkplain ComponentRequirement component requirement} and the * corresponding {@link XExecutableParameterElement} for each module parameter in the {@linkplain * BindingGraph#factoryMethod factory method}. */ // TODO(dpb): Consider disallowing modules if none of their bindings are used. public final ImmutableMap factoryMethodParameters() { return factoryMethod().get().getParameters().stream() .collect( toImmutableMap( parameter -> ComponentRequirement.forModule(parameter.getType()), parameter -> parameter)); } /** * The types for which the component needs instances. * *
    *
  • component dependencies *
  • owned modules with concrete instance bindings that are used in the graph *
  • bound instances *
*/ @Memoized public ImmutableSet componentRequirements() { ImmutableSet requiredModules = stream(Traverser.forTree(BindingGraph::subgraphs).depthFirstPostOrder(this)) .flatMap(graph -> graph.bindingModules.stream()) .filter(ownedModuleTypes()::contains) .collect(toImmutableSet()); ImmutableSet.Builder requirements = ImmutableSet.builder(); componentDescriptor().requirements().stream() .filter( requirement -> !requirement.kind().isModule() || requiredModules.contains(requirement.typeElement())) .forEach(requirements::add); if (factoryMethod().isPresent()) { requirements.addAll(factoryMethodParameters().keySet()); } return requirements.build(); } /** * Returns all {@link ComponentDescriptor}s in the {@link TopLevelBindingGraph} mapped by the * component path. */ @Memoized public ImmutableMap componentDescriptorsByPath() { return topLevelBindingGraph().componentNodes().stream() .map(ComponentNodeImpl.class::cast) .collect( toImmutableMap(ComponentNode::componentPath, ComponentNodeImpl::componentDescriptor)); } @Memoized public ImmutableList subgraphs() { return topLevelBindingGraph().subcomponentNodes(componentNode()).stream() .map(subcomponent -> create(Optional.of(this), subcomponent, topLevelBindingGraph())) .collect(toImmutableList()); } /** Returns the list of all {@link BindingNode}s local to this component. */ public ImmutableList localBindingNodes() { return topLevelBindingGraph().bindingsByComponent().get(componentPath()); } @Memoized public ImmutableSet bindingNodes() { return ImmutableSet.builder() .addAll(contributionBindings.values()) .addAll(membersInjectionBindings.values()) .build(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy