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