• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.internal.codegen.binding;
18 
19 import static com.google.common.base.Verify.verify;
20 import static dagger.internal.codegen.binding.BindingRequest.bindingRequest;
21 import static dagger.internal.codegen.extension.DaggerGraphs.unreachableNodes;
22 import static dagger.internal.codegen.model.BindingKind.SUBCOMPONENT_CREATOR;
23 
24 import androidx.room.compiler.processing.XType;
25 import androidx.room.compiler.processing.XTypeElement;
26 import com.google.common.collect.ImmutableList;
27 import com.google.common.graph.ImmutableNetwork;
28 import com.google.common.graph.MutableNetwork;
29 import com.google.common.graph.NetworkBuilder;
30 import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor;
31 import dagger.internal.codegen.binding.LegacyBindingGraphFactory.LegacyBindingGraph;
32 import dagger.internal.codegen.binding.LegacyBindingGraphFactory.LegacyResolvedBindings;
33 import dagger.internal.codegen.model.BindingGraph.ComponentNode;
34 import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
35 import dagger.internal.codegen.model.BindingGraph.Edge;
36 import dagger.internal.codegen.model.BindingGraph.MissingBinding;
37 import dagger.internal.codegen.model.BindingGraph.Node;
38 import dagger.internal.codegen.model.ComponentPath;
39 import dagger.internal.codegen.model.DaggerTypeElement;
40 import dagger.internal.codegen.model.DependencyRequest;
41 import java.util.ArrayDeque;
42 import java.util.Deque;
43 import java.util.HashSet;
44 import java.util.Set;
45 import javax.inject.Inject;
46 
47 /** Converts {@link BindingGraph}s to {@link dagger.internal.codegen.model.BindingGraph}s. */
48 final class LegacyBindingGraphConverter {
49   @Inject
LegacyBindingGraphConverter()50   LegacyBindingGraphConverter() {}
51 
52   /**
53    * Creates the external {@link dagger.internal.codegen.model.BindingGraph} representing the given
54    * internal {@link BindingGraph}.
55    */
convert(LegacyBindingGraph legacyBindingGraph, boolean isFullBindingGraph)56   BindingGraph convert(LegacyBindingGraph legacyBindingGraph, boolean isFullBindingGraph) {
57     MutableNetwork<Node, Edge> network = Converter.convertToNetwork(legacyBindingGraph);
58     ComponentNode rootNode = legacyBindingGraph.componentNode();
59 
60     // When bindings are copied down into child graphs because they transitively depend on local
61     // multibindings or optional bindings, the parent-owned binding is still there. If that
62     // parent-owned binding is not reachable from its component, it doesn't need to be in the graph
63     // because it will never be used. So remove all nodes that are not reachable from the root
64     // component—unless we're converting a full binding graph.
65     if (!isFullBindingGraph) {
66       unreachableNodes(network.asGraph(), rootNode).forEach(network::removeNode);
67     }
68 
69     return BindingGraph.create(
70         ImmutableNetwork.copyOf(network),
71         isFullBindingGraph);
72   }
73 
74   private static final class Converter {
convertToNetwork(LegacyBindingGraph graph)75     static MutableNetwork<Node, Edge> convertToNetwork(LegacyBindingGraph graph) {
76       Converter converter = new Converter();
77       converter.visitRootComponent(graph);
78       return converter.network;
79     }
80 
81     /** The path from the root graph to the currently visited graph. */
82     private final Deque<LegacyBindingGraph> bindingGraphPath = new ArrayDeque<>();
83 
84     private final MutableNetwork<Node, Edge> network =
85         NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build();
86     private final Set<BindingNode> bindings = new HashSet<>();
87 
visitRootComponent(LegacyBindingGraph graph)88     private void visitRootComponent(LegacyBindingGraph graph) {
89       visitComponent(graph);
90     }
91 
92     /**
93      * Called once for each component in a component hierarchy.
94      *
95      * <p>This implementation does the following:
96      *
97      * <ol>
98      *   <li>If this component is installed in its parent by a subcomponent factory method, adds
99      *       an edge between the parent and child components.
100      *   <li>For each entry point, adds an edge between the component and the entry point.
101      *   <li>For each child component, calls {@link #visitComponent(LegacyBindingGraph)},
102      *       updating the traversal state.
103      * </ol>
104      *
105      * @param graph the currently visited graph
106      */
visitComponent(LegacyBindingGraph graph)107     private void visitComponent(LegacyBindingGraph graph) {
108       bindingGraphPath.addLast(graph);
109 
110       network.addNode(graph.componentNode());
111 
112       for (ComponentMethodDescriptor entryPointMethod :
113           graph.componentDescriptor().entryPointMethods()) {
114         addDependencyEdges(graph.componentNode(), entryPointMethod.dependencyRequest().get());
115       }
116 
117       for (LegacyResolvedBindings resolvedBindings : graph.resolvedBindings()) {
118         for (BindingNode binding : resolvedBindings.bindingNodes()) {
119           if (bindings.add(binding)) {
120             network.addNode(binding);
121             for (DependencyRequest dependencyRequest : binding.dependencies()) {
122               addDependencyEdges(binding, dependencyRequest);
123             }
124           }
125           if (binding.kind().equals(SUBCOMPONENT_CREATOR)
126               && binding.componentPath().equals(graph.componentPath())) {
127             network.addEdge(
128                 binding,
129                 subcomponentNode(binding.key().type().xprocessing(), graph),
130                 new SubcomponentCreatorBindingEdgeImpl(binding.subcomponentDeclarations()));
131           }
132         }
133       }
134 
135       for (LegacyBindingGraph childGraph : graph.subgraphs()) {
136         visitComponent(childGraph);
137         graph
138             .componentDescriptor()
139             .getFactoryMethodForChildComponent(childGraph.componentDescriptor())
140             .ifPresent(
141                 childFactoryMethod ->
142                     network.addEdge(
143                         graph.componentNode(),
144                         childGraph.componentNode(),
145                         new ChildFactoryMethodEdgeImpl(childFactoryMethod.methodElement())));
146       }
147 
148       verify(bindingGraphPath.removeLast().equals(graph));
149     }
150 
151     /**
152      * Returns an immutable snapshot of the path from the root component to the currently visited
153      * component.
154      */
componentPath()155     private ComponentPath componentPath() {
156       return bindingGraphPath.getLast().componentPath();
157     }
158 
159     /**
160      * Returns the LegacyBindingGraph for {@code ancestor}, where {@code ancestor} is in the
161      * component path of the current traversal.
162      */
graphForAncestor(XTypeElement ancestor)163     private LegacyBindingGraph graphForAncestor(XTypeElement ancestor) {
164       for (LegacyBindingGraph graph : bindingGraphPath) {
165         if (graph.componentDescriptor().typeElement().equals(ancestor)) {
166           return graph;
167         }
168       }
169       throw new IllegalArgumentException(
170           String.format(
171               "%s is not in the current path: %s", ancestor.getQualifiedName(), componentPath()));
172     }
173 
174     /**
175      * Adds a {@link dagger.internal.codegen.model.BindingGraph.DependencyEdge} from a node to the
176      * binding(s) that satisfy a dependency request.
177      */
addDependencyEdges(Node source, DependencyRequest dependencyRequest)178     private void addDependencyEdges(Node source, DependencyRequest dependencyRequest) {
179       LegacyResolvedBindings dependencies = resolvedDependencies(source, dependencyRequest);
180       if (dependencies.isEmpty()) {
181         addDependencyEdge(source, dependencyRequest, missingBindingNode(dependencies));
182       } else {
183         for (BindingNode dependency : dependencies.bindingNodes()) {
184           addDependencyEdge(source, dependencyRequest, dependency);
185         }
186       }
187     }
188 
addDependencyEdge( Node source, DependencyRequest dependencyRequest, Node dependency)189     private void addDependencyEdge(
190         Node source, DependencyRequest dependencyRequest, Node dependency) {
191       network.addNode(dependency);
192       if (!hasDependencyEdge(source, dependency, dependencyRequest)) {
193         network.addEdge(
194             source,
195             dependency,
196             new DependencyEdgeImpl(dependencyRequest, source instanceof ComponentNode));
197       }
198     }
199 
hasDependencyEdge( Node source, Node dependency, DependencyRequest dependencyRequest)200     private boolean hasDependencyEdge(
201         Node source, Node dependency, DependencyRequest dependencyRequest) {
202       // An iterative approach is used instead of a Stream because this method is called in a hot
203       // loop, and the Stream calculates the size of network.edgesConnecting(), which is slow. This
204       // seems to be because caculating the edges connecting two nodes in a Network that supports
205       // parallel edges is must check the equality of many nodes, and BindingNode's equality
206       // semantics drag in the equality of many other expensive objects
207       for (Edge edge : network.edgesConnecting(source, dependency)) {
208         if (edge instanceof DependencyEdge) {
209           if (((DependencyEdge) edge).dependencyRequest().equals(dependencyRequest)) {
210             return true;
211           }
212         }
213       }
214       return false;
215     }
216 
resolvedDependencies( Node source, DependencyRequest dependencyRequest)217     private LegacyResolvedBindings resolvedDependencies(
218         Node source, DependencyRequest dependencyRequest) {
219       return graphForAncestor(source.componentPath().currentComponent().xprocessing())
220           .resolvedBindings(bindingRequest(dependencyRequest));
221     }
222 
missingBindingNode(LegacyResolvedBindings dependencies)223     private MissingBinding missingBindingNode(LegacyResolvedBindings dependencies) {
224       // Put all missing binding nodes in the root component. This simplifies the binding graph
225       // and produces better error messages for users since all dependents point to the same node.
226       return MissingBindingImpl.create(
227           ComponentPath.create(ImmutableList.of(componentPath().rootComponent())),
228           dependencies.key());
229     }
230 
subcomponentNode( XType subcomponentBuilderType, LegacyBindingGraph graph)231     private ComponentNode subcomponentNode(
232         XType subcomponentBuilderType, LegacyBindingGraph graph) {
233       XTypeElement subcomponentBuilderElement = subcomponentBuilderType.getTypeElement();
234       ComponentDescriptor subcomponent =
235           graph.componentDescriptor().getChildComponentWithBuilderType(subcomponentBuilderElement);
236       return ComponentNodeImpl.create(
237           componentPath().childPath(DaggerTypeElement.from(subcomponent.typeElement())),
238           subcomponent);
239     }
240   }
241 }
242