/* * Copyright (C) 2021 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.spi.model; import static com.google.common.collect.Sets.intersection; import static com.google.common.graph.Graphs.inducedSubgraph; import static com.google.common.graph.Graphs.reachableNodes; import static com.google.common.graph.Graphs.transpose; import static dagger.internal.codegen.extension.DaggerStreams.instancesOf; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSetMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.graph.EndpointPair; import com.google.common.graph.ImmutableNetwork; import com.google.common.graph.MutableNetwork; import com.google.common.graph.Network; import com.google.common.graph.NetworkBuilder; import dagger.Module; import java.util.Optional; import java.util.stream.Stream; /** * A graph of bindings, dependency requests, and components. * *

A {@link BindingGraph} represents one of the following: * *

* * In the case of a {@link BindingGraph} representing a module, the root {@link ComponentNode} will * actually represent the module type. The graph will also be a {@linkplain #isFullBindingGraph() * full binding graph}, which means it will contain all bindings in all modules, as well as nodes * for their dependencies. Otherwise it will contain only bindings that are reachable from at least * one {@linkplain #entryPointEdges() entry point}. * *

Nodes

* *

There is a {@link Binding} for each owned binding in the graph. If a binding is owned * by more than one component, there is one binding object for that binding for every owning * component. * *

There is a {@linkplain ComponentNode component node} (without a binding) for each * component in the graph. * *

Edges

* *

There is a {@linkplain DependencyEdge dependency edge} for each dependency request in * the graph. Its target node is the binding for the binding that satisfies the request. For entry * point dependency requests, the source node is the component node for the component for which it * is an entry point. For other dependency requests, the source node is the binding for the binding * that contains the request. * *

There is a subcomponent edge for each parent-child component relationship in the graph. * The target node is the component node for the child component. For subcomponents defined by a * {@linkplain SubcomponentCreatorBindingEdge subcomponent creator binding} (either a method on the * component or a set of {@code @Module.subcomponents} annotation values), the source node is the * binding for the {@code @Subcomponent.Builder} type. For subcomponents defined by {@linkplain * ChildFactoryMethodEdge subcomponent factory methods}, the source node is the component node for * the parent. * *

Note that this API is experimental and will change. */ public abstract class BindingGraph { /** Returns the graph in its {@link Network} representation. */ public abstract ImmutableNetwork network(); @Override public String toString() { return network().toString(); } /** * ReturnsĀ {@code true} if this graph was constructed from a module for full binding graph * validation. * * @deprecated use {@link #isFullBindingGraph()} to tell if this is a full binding graph, or * {@link ComponentNode#isRealComponent() rootComponentNode().isRealComponent()} to tell if * the root component node is really a component or derived from a module. Dagger can generate * full binding graphs for components and subcomponents as well as modules. */ @Deprecated public boolean isModuleBindingGraph() { return !rootComponentNode().isRealComponent(); } /** * Returns {@code true} if this is a full binding graph, which contains all bindings installed in * the component, or {@code false} if it is a reachable binding graph, which contains only * bindings that are reachable from at least one {@linkplain #entryPointEdges() entry point}. * * @see Full binding * graph validation */ public abstract boolean isFullBindingGraph(); public abstract DaggerProcessingEnv.Backend backend(); /** * Returns {@code true} if the {@link #rootComponentNode()} is a subcomponent. This occurs in * when {@code -Adagger.fullBindingGraphValidation} is used in a compilation with a subcomponent. * * @deprecated use {@link ComponentNode#isSubcomponent() rootComponentNode().isSubcomponent()} * instead */ @Deprecated public boolean isPartialBindingGraph() { return rootComponentNode().isSubcomponent(); } /** Returns the bindings. */ public ImmutableSet bindings() { return nodes(Binding.class); } /** Returns the bindings for a key. */ public ImmutableSet bindings(Key key) { return nodes(Binding.class).stream() .filter(binding -> binding.key().equals(key)) .collect(toImmutableSet()); } /** Returns the nodes that represent missing bindings. */ public ImmutableSet missingBindings() { return nodes(MissingBinding.class); } /** Returns the component nodes. */ public ImmutableSet componentNodes() { return nodes(ComponentNode.class); } /** Returns the component node for a component. */ public Optional componentNode(ComponentPath component) { return componentNodes().stream() .filter(node -> node.componentPath().equals(component)) .findFirst(); } /** Returns the component nodes for a component. */ public ImmutableSet componentNodes(DaggerTypeElement component) { return componentNodes().stream() .filter(node -> node.componentPath().currentComponent().equals(component)) .collect(toImmutableSet()); } /** Returns the component node for the root component. */ public ComponentNode rootComponentNode() { return componentNodes().stream() .filter(node -> node.componentPath().atRoot()) .findFirst() .get(); } /** Returns the dependency edges. */ public ImmutableSet dependencyEdges() { return dependencyEdgeStream().collect(toImmutableSet()); } /** * Returns the dependency edges for the dependencies of a binding. For valid graphs, each {@link * DependencyRequest} will map to a single {@link DependencyEdge}. When conflicting bindings exist * for a key, the multimap will have several edges for that {@link DependencyRequest}. Graphs that * have no binding for a key will have an edge whose {@linkplain EndpointPair#target() target * node} is a {@link MissingBinding}. */ public ImmutableSetMultimap dependencyEdges( Binding binding) { return dependencyEdgeStream(binding) .collect(toImmutableSetMultimap(DependencyEdge::dependencyRequest, edge -> edge)); } /** Returns the dependency edges for a dependency request. */ public ImmutableSet dependencyEdges(DependencyRequest dependencyRequest) { return dependencyEdgeStream() .filter(edge -> edge.dependencyRequest().equals(dependencyRequest)) .collect(toImmutableSet()); } /** * Returns the dependency edges for the entry points of a given {@code component}. Each edge's * source node is that component's component node. */ public ImmutableSet entryPointEdges(ComponentPath component) { return dependencyEdgeStream(componentNode(component).get()).collect(toImmutableSet()); } private Stream dependencyEdgeStream(Node node) { return network().outEdges(node).stream().flatMap(instancesOf(DependencyEdge.class)); } /** * Returns the dependency edges for all entry points for all components and subcomponents. Each * edge's source node is a component node. */ public ImmutableSet entryPointEdges() { return entryPointEdgeStream().collect(toImmutableSet()); } /** Returns the binding or missing binding nodes that directly satisfy entry points. */ public ImmutableSet entryPointBindings() { return entryPointEdgeStream() .map(edge -> (MaybeBinding) network().incidentNodes(edge).target()) .collect(toImmutableSet()); } /** * Returns the edges for entry points that transitively depend on a binding or missing binding for * a key. */ public ImmutableSet entryPointEdgesDependingOnBinding( MaybeBinding binding) { ImmutableNetwork dependencyGraph = dependencyGraph(); Network subgraphDependingOnBinding = inducedSubgraph( dependencyGraph, reachableNodes(transpose(dependencyGraph).asGraph(), binding)); return intersection(entryPointEdges(), subgraphDependingOnBinding.edges()).immutableCopy(); } /** Returns the bindings that directly request a given binding as a dependency. */ public ImmutableSet requestingBindings(MaybeBinding binding) { return network().predecessors(binding).stream() .flatMap(instancesOf(Binding.class)) .collect(toImmutableSet()); } /** * Returns the bindings that a given binding directly requests as a dependency. Does not include * any {@link MissingBinding}s. * * @see #requestedMaybeMissingBindings(Binding) */ public ImmutableSet requestedBindings(Binding binding) { return network().successors(binding).stream() .flatMap(instancesOf(Binding.class)) .collect(toImmutableSet()); } /** * Returns the bindings or missing bindings that a given binding directly requests as a * dependency. * * @see #requestedBindings(Binding) */ public ImmutableSet requestedMaybeMissingBindings(Binding binding) { return network().successors(binding).stream() .flatMap(instancesOf(MaybeBinding.class)) .collect(toImmutableSet()); } /** Returns a subnetwork that contains all nodes but only {@link DependencyEdge}s. */ // TODO(dpb): Make public. Cache. private ImmutableNetwork dependencyGraph() { MutableNetwork dependencyGraph = NetworkBuilder.from(network()) .expectedNodeCount(network().nodes().size()) .expectedEdgeCount((int) dependencyEdgeStream().count()) .build(); network().nodes().forEach(dependencyGraph::addNode); // include disconnected nodes dependencyEdgeStream() .forEach( edge -> { EndpointPair endpoints = network().incidentNodes(edge); dependencyGraph.addEdge(endpoints.source(), endpoints.target(), edge); }); return ImmutableNetwork.copyOf(dependencyGraph); } @SuppressWarnings({"rawtypes", "unchecked"}) private ImmutableSet nodes(Class clazz) { return (ImmutableSet) nodesByClass().get(clazz); } private static final ImmutableSet> NODE_TYPES = ImmutableSet.of(Binding.class, MissingBinding.class, ComponentNode.class); protected ImmutableSetMultimap, ? extends Node> nodesByClass() { return network().nodes().stream() .collect( toImmutableSetMultimap( node -> NODE_TYPES.stream().filter(clazz -> clazz.isInstance(node)).findFirst().get(), node -> node)); } private Stream dependencyEdgeStream() { return network().edges().stream().flatMap(instancesOf(DependencyEdge.class)); } private Stream entryPointEdgeStream() { return dependencyEdgeStream().filter(DependencyEdge::isEntryPoint); } /** * An edge in the binding graph. Either a {@link DependencyEdge}, a {@link * ChildFactoryMethodEdge}, or a {@link SubcomponentCreatorBindingEdge}. */ public interface Edge {} /** * An edge that represents a dependency on a binding. * *

Because one {@link DependencyRequest} may represent a dependency from two bindings (e.g., a * dependency of {@code Foo} and {@code Foo} may have the same key and request * element), this class does not override {@link #equals(Object)} to use value semantics. * *

For entry points, the source node is the {@link ComponentNode} that contains the entry * point. Otherwise the source node is a {@link Binding}. * *

For dependencies on missing bindings, the target node is a {@link MissingBinding}. Otherwise * the target node is a {@link Binding}. */ public interface DependencyEdge extends Edge { /** The dependency request. */ DependencyRequest dependencyRequest(); /** Returns {@code true} if this edge represents an entry point. */ boolean isEntryPoint(); } /** * An edge that represents a subcomponent factory method linking a parent component to a child * subcomponent. */ public interface ChildFactoryMethodEdge extends Edge { /** The subcomponent factory method element. */ DaggerExecutableElement factoryMethod(); } /** * An edge that represents the link between a parent component and a child subcomponent implied by * a subcomponent creator ({@linkplain dagger.Subcomponent.Builder builder} or {@linkplain * dagger.Subcomponent.Factory factory}) binding. * *

The {@linkplain com.google.common.graph.EndpointPair#source() source node} of this edge is a * {@link Binding} for the subcomponent creator {@link Key} and the {@linkplain * com.google.common.graph.EndpointPair#target() target node} is a {@link ComponentNode} for the * child subcomponent. */ public interface SubcomponentCreatorBindingEdge extends Edge { /** * The modules that {@linkplain Module#subcomponents() declare the subcomponent} that generated * this edge. Empty if the parent component has a subcomponent creator method and there are no * declaring modules. */ ImmutableSet declaringModules(); } /** A node in the binding graph. Either a {@link Binding} or a {@link ComponentNode}. */ // TODO(dpb): Make all the node/edge types top-level. public interface Node { /** The component this node belongs to. */ ComponentPath componentPath(); } /** A node in the binding graph that is either a {@link Binding} or a {@link MissingBinding}. */ public interface MaybeBinding extends Node { /** The component that owns the binding, or in which the binding is missing. */ @Override ComponentPath componentPath(); /** The key of the binding, or for which there is no binding. */ Key key(); /** The binding, or empty if missing. */ Optional binding(); } /** A node in the binding graph that represents a missing binding for a key in a component. */ public abstract static class MissingBinding implements MaybeBinding { /** The component in which the binding is missing. */ @Override public abstract ComponentPath componentPath(); /** The key for which there is no binding. */ @Override public abstract Key key(); /** @deprecated This always returns {@code Optional.empty()}. */ @Override @Deprecated public Optional binding() { return Optional.empty(); } @Override public String toString() { return String.format("missing binding for %s in %s", key(), componentPath()); } } /** * A component node in the graph. Every entry point {@linkplain DependencyEdge dependency * edge}'s source node is a component node for the component containing the entry point. */ public interface ComponentNode extends Node { /** The component represented by this node. */ @Override ComponentPath componentPath(); /** * Returns {@code true} if the component is a {@code @Subcomponent} or * {@code @ProductionSubcomponent}. */ boolean isSubcomponent(); /** * Returns {@code true} if the component is a real component, or {@code false} if it is a * fictional component based on a module. */ boolean isRealComponent(); /** The entry points on this component. */ ImmutableSet entryPoints(); /** The scopes declared on this component. */ ImmutableSet scopes(); } }