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.auto.common.MoreTypes.asTypeElement; 20 import static com.google.common.base.Verify.verify; 21 import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; 22 import static dagger.internal.codegen.extension.DaggerGraphs.unreachableNodes; 23 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 24 import static dagger.model.BindingKind.SUBCOMPONENT_CREATOR; 25 26 import com.google.auto.value.AutoValue; 27 import com.google.auto.value.extension.memoized.Memoized; 28 import com.google.common.collect.ImmutableList; 29 import com.google.common.collect.ImmutableSet; 30 import com.google.common.collect.Iterables; 31 import com.google.common.collect.Iterators; 32 import com.google.common.graph.ImmutableNetwork; 33 import com.google.common.graph.MutableNetwork; 34 import com.google.common.graph.Network; 35 import com.google.common.graph.NetworkBuilder; 36 import dagger.internal.codegen.binding.BindingGraph.TopLevelBindingGraph; 37 import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; 38 import dagger.model.BindingGraph.ComponentNode; 39 import dagger.model.BindingGraph.DependencyEdge; 40 import dagger.model.BindingGraph.Edge; 41 import dagger.model.BindingGraph.MissingBinding; 42 import dagger.model.BindingGraph.Node; 43 import dagger.model.ComponentPath; 44 import dagger.model.DependencyRequest; 45 import dagger.model.Key; 46 import java.util.ArrayDeque; 47 import java.util.Deque; 48 import java.util.HashMap; 49 import java.util.HashSet; 50 import java.util.Map; 51 import java.util.Set; 52 import javax.inject.Inject; 53 import javax.lang.model.element.ExecutableElement; 54 import javax.lang.model.element.TypeElement; 55 import javax.lang.model.type.TypeMirror; 56 57 /** Converts {@link BindingGraph}s to {@link dagger.model.BindingGraph}s. */ 58 final class BindingGraphConverter { 59 private final BindingDeclarationFormatter bindingDeclarationFormatter; 60 61 @Inject BindingGraphConverter(BindingDeclarationFormatter bindingDeclarationFormatter)62 BindingGraphConverter(BindingDeclarationFormatter bindingDeclarationFormatter) { 63 this.bindingDeclarationFormatter = bindingDeclarationFormatter; 64 } 65 66 /** 67 * Creates the external {@link dagger.model.BindingGraph} representing the given internal {@link 68 * BindingGraph}. 69 */ convert(LegacyBindingGraph legacyBindingGraph, boolean isFullBindingGraph)70 BindingGraph convert(LegacyBindingGraph legacyBindingGraph, boolean isFullBindingGraph) { 71 MutableNetwork<Node, Edge> network = asNetwork(legacyBindingGraph); 72 ComponentNode rootNode = rootComponentNode(network); 73 74 // When bindings are copied down into child graphs because they transitively depend on local 75 // multibindings or optional bindings, the parent-owned binding is still there. If that 76 // parent-owned binding is not reachable from its component, it doesn't need to be in the graph 77 // because it will never be used. So remove all nodes that are not reachable from the root 78 // component—unless we're converting a full binding graph. 79 if (!isFullBindingGraph) { 80 unreachableNodes(network.asGraph(), rootNode).forEach(network::removeNode); 81 } 82 83 TopLevelBindingGraph topLevelBindingGraph = 84 TopLevelBindingGraph.create(ImmutableNetwork.copyOf(network), isFullBindingGraph); 85 return BindingGraph.create(rootNode, topLevelBindingGraph); 86 } 87 asNetwork(LegacyBindingGraph graph)88 private MutableNetwork<Node, Edge> asNetwork(LegacyBindingGraph graph) { 89 Converter converter = new Converter(bindingDeclarationFormatter); 90 converter.visitRootComponent(graph); 91 return converter.network; 92 } 93 94 // TODO(dpb): Example of BindingGraph logic applied to derived networks. rootComponentNode(Network<Node, Edge> network)95 private ComponentNode rootComponentNode(Network<Node, Edge> network) { 96 return (ComponentNode) 97 Iterables.find( 98 network.nodes(), 99 node -> node instanceof ComponentNode && node.componentPath().atRoot()); 100 } 101 102 /** 103 * Used as a cache key to make sure resolved bindings are cached per component path. 104 * This is required so that binding nodes are not reused across different branches of the 105 * graph since the ResolvedBindings class only contains the component and not the path. 106 */ 107 @AutoValue 108 abstract static class ResolvedBindingsWithPath { resolvedBindings()109 abstract ResolvedBindings resolvedBindings(); componentPath()110 abstract ComponentPath componentPath(); 111 create( ResolvedBindings resolvedBindings, ComponentPath componentPath)112 static ResolvedBindingsWithPath create( 113 ResolvedBindings resolvedBindings, ComponentPath componentPath) { 114 return new AutoValue_BindingGraphConverter_ResolvedBindingsWithPath( 115 resolvedBindings, componentPath); 116 } 117 } 118 119 private static final class Converter { 120 /** The path from the root graph to the currently visited graph. */ 121 private final Deque<LegacyBindingGraph> bindingGraphPath = new ArrayDeque<>(); 122 123 /** The {@link ComponentPath} for each component in {@link #bindingGraphPath}. */ 124 private final Deque<ComponentPath> componentPaths = new ArrayDeque<>(); 125 126 private final BindingDeclarationFormatter bindingDeclarationFormatter; 127 private final MutableNetwork<Node, Edge> network = 128 NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build(); 129 private final Set<BindingNode> bindings = new HashSet<>(); 130 131 private final Map<ResolvedBindingsWithPath, ImmutableSet<BindingNode>> resolvedBindingsMap = 132 new HashMap<>(); 133 134 /** Constructs a converter for a root (component, not subcomponent) binding graph. */ Converter(BindingDeclarationFormatter bindingDeclarationFormatter)135 private Converter(BindingDeclarationFormatter bindingDeclarationFormatter) { 136 this.bindingDeclarationFormatter = bindingDeclarationFormatter; 137 } 138 visitRootComponent(LegacyBindingGraph graph)139 private void visitRootComponent(LegacyBindingGraph graph) { 140 visitComponent(graph, null); 141 } 142 143 /** 144 * Called once for each component in a component hierarchy. 145 * 146 * <p>This implementation does the following: 147 * 148 * <ol> 149 * <li>If this component is installed in its parent by a subcomponent factory method, calls 150 * {@link #visitSubcomponentFactoryMethod(ComponentNode, ComponentNode, 151 * ExecutableElement)}. 152 * <li>For each entry point in the component, calls {@link #visitEntryPoint(ComponentNode, 153 * DependencyRequest)}. 154 * <li>For each child component, calls {@link #visitComponent(LegacyBindingGraph, 155 * ComponentNode)}, updating the traversal state. 156 * </ol> 157 * 158 * @param graph the currently visited graph 159 */ visitComponent(LegacyBindingGraph graph, ComponentNode parentComponent)160 private void visitComponent(LegacyBindingGraph graph, ComponentNode parentComponent) { 161 bindingGraphPath.addLast(graph); 162 ComponentPath graphPath = 163 ComponentPath.create( 164 bindingGraphPath.stream() 165 .map(LegacyBindingGraph::componentDescriptor) 166 .map(ComponentDescriptor::typeElement) 167 .collect(toImmutableList())); 168 componentPaths.addLast(graphPath); 169 ComponentNode currentComponent = 170 ComponentNodeImpl.create(componentPath(), graph.componentDescriptor()); 171 172 network.addNode(currentComponent); 173 174 for (ComponentMethodDescriptor entryPointMethod : 175 graph.componentDescriptor().entryPointMethods()) { 176 visitEntryPoint(currentComponent, entryPointMethod.dependencyRequest().get()); 177 } 178 179 for (ResolvedBindings resolvedBindings : graph.resolvedBindings()) { 180 for (BindingNode binding : bindingNodes(resolvedBindings)) { 181 if (bindings.add(binding)) { 182 network.addNode(binding); 183 for (DependencyRequest dependencyRequest : binding.dependencies()) { 184 addDependencyEdges(binding, dependencyRequest); 185 } 186 } 187 if (binding.kind().equals(SUBCOMPONENT_CREATOR) 188 && binding.componentPath().equals(currentComponent.componentPath())) { 189 network.addEdge( 190 binding, 191 subcomponentNode(binding.key().type(), graph), 192 new SubcomponentCreatorBindingEdgeImpl( 193 resolvedBindings.subcomponentDeclarations())); 194 } 195 } 196 } 197 198 if (bindingGraphPath.size() > 1) { 199 LegacyBindingGraph parent = Iterators.get(bindingGraphPath.descendingIterator(), 1); 200 parent 201 .componentDescriptor() 202 .getFactoryMethodForChildComponent(graph.componentDescriptor()) 203 .ifPresent( 204 childFactoryMethod -> 205 visitSubcomponentFactoryMethod( 206 parentComponent, currentComponent, childFactoryMethod.methodElement())); 207 } 208 209 for (LegacyBindingGraph child : graph.subgraphs()) { 210 visitComponent(child, currentComponent); 211 } 212 213 verify(bindingGraphPath.removeLast().equals(graph)); 214 verify(componentPaths.removeLast().equals(graphPath)); 215 } 216 217 /** 218 * Called once for each entry point in a component. 219 * 220 * @param componentNode the component that contains the entry point 221 * @param entryPoint the entry point to visit 222 */ visitEntryPoint(ComponentNode componentNode, DependencyRequest entryPoint)223 private void visitEntryPoint(ComponentNode componentNode, DependencyRequest entryPoint) { 224 addDependencyEdges(componentNode, entryPoint); 225 } 226 227 /** 228 * Called if this component was installed in its parent by a subcomponent factory method. 229 * 230 * @param parentComponent the parent graph 231 * @param currentComponent the currently visited graph 232 * @param factoryMethod the factory method in the parent component that declares that the 233 * current component is a child 234 */ visitSubcomponentFactoryMethod( ComponentNode parentComponent, ComponentNode currentComponent, ExecutableElement factoryMethod)235 private void visitSubcomponentFactoryMethod( 236 ComponentNode parentComponent, 237 ComponentNode currentComponent, 238 ExecutableElement factoryMethod) { 239 network.addEdge( 240 parentComponent, 241 currentComponent, 242 new ChildFactoryMethodEdgeImpl(factoryMethod)); 243 } 244 245 /** 246 * Returns an immutable snapshot of the path from the root component to the currently visited 247 * component. 248 */ componentPath()249 private ComponentPath componentPath() { 250 return componentPaths.getLast(); 251 } 252 253 /** 254 * Returns the subpath from the root component to the matching {@code ancestor} of the current 255 * component. 256 */ pathFromRootToAncestor(TypeElement ancestor)257 private ComponentPath pathFromRootToAncestor(TypeElement ancestor) { 258 for (ComponentPath componentPath : componentPaths) { 259 if (componentPath.currentComponent().equals(ancestor)) { 260 return componentPath; 261 } 262 } 263 throw new IllegalArgumentException( 264 String.format( 265 "%s is not in the current path: %s", ancestor.getQualifiedName(), componentPath())); 266 } 267 268 /** 269 * Returns the LegacyBindingGraph for {@code ancestor}, where {@code ancestor} is in the 270 * component path of the current traversal. 271 */ graphForAncestor(TypeElement ancestor)272 private LegacyBindingGraph graphForAncestor(TypeElement ancestor) { 273 for (LegacyBindingGraph graph : bindingGraphPath) { 274 if (graph.componentDescriptor().typeElement().equals(ancestor)) { 275 return graph; 276 } 277 } 278 throw new IllegalArgumentException( 279 String.format( 280 "%s is not in the current path: %s", ancestor.getQualifiedName(), componentPath())); 281 } 282 283 /** 284 * Adds a {@link dagger.model.BindingGraph.DependencyEdge} from a node to the binding(s) that 285 * satisfy a dependency request. 286 */ addDependencyEdges(Node source, DependencyRequest dependencyRequest)287 private void addDependencyEdges(Node source, DependencyRequest dependencyRequest) { 288 ResolvedBindings dependencies = resolvedDependencies(source, dependencyRequest); 289 if (dependencies.isEmpty()) { 290 addDependencyEdge(source, dependencyRequest, missingBindingNode(dependencies)); 291 } else { 292 for (BindingNode dependency : bindingNodes(dependencies)) { 293 addDependencyEdge(source, dependencyRequest, dependency); 294 } 295 } 296 } 297 addDependencyEdge( Node source, DependencyRequest dependencyRequest, Node dependency)298 private void addDependencyEdge( 299 Node source, DependencyRequest dependencyRequest, Node dependency) { 300 network.addNode(dependency); 301 if (!hasDependencyEdge(source, dependency, dependencyRequest)) { 302 network.addEdge( 303 source, 304 dependency, 305 new DependencyEdgeImpl(dependencyRequest, source instanceof ComponentNode)); 306 } 307 } 308 hasDependencyEdge( Node source, Node dependency, DependencyRequest dependencyRequest)309 private boolean hasDependencyEdge( 310 Node source, Node dependency, DependencyRequest dependencyRequest) { 311 // An iterative approach is used instead of a Stream because this method is called in a hot 312 // loop, and the Stream calculates the size of network.edgesConnecting(), which is slow. This 313 // seems to be because caculating the edges connecting two nodes in a Network that supports 314 // parallel edges is must check the equality of many nodes, and BindingNode's equality 315 // semantics drag in the equality of many other expensive objects 316 for (Edge edge : network.edgesConnecting(source, dependency)) { 317 if (edge instanceof DependencyEdge) { 318 if (((DependencyEdge) edge).dependencyRequest().equals(dependencyRequest)) { 319 return true; 320 } 321 } 322 } 323 return false; 324 } 325 resolvedDependencies( Node source, DependencyRequest dependencyRequest)326 private ResolvedBindings resolvedDependencies( 327 Node source, DependencyRequest dependencyRequest) { 328 return graphForAncestor(source.componentPath().currentComponent()) 329 .resolvedBindings(bindingRequest(dependencyRequest)); 330 } 331 bindingNodes(ResolvedBindings resolvedBindings)332 private ImmutableSet<BindingNode> bindingNodes(ResolvedBindings resolvedBindings) { 333 ResolvedBindingsWithPath resolvedBindingsWithPath = 334 ResolvedBindingsWithPath.create(resolvedBindings, componentPath()); 335 return resolvedBindingsMap.computeIfAbsent( 336 resolvedBindingsWithPath, this::uncachedBindingNodes); 337 } 338 uncachedBindingNodes( ResolvedBindingsWithPath resolvedBindingsWithPath)339 private ImmutableSet<BindingNode> uncachedBindingNodes( 340 ResolvedBindingsWithPath resolvedBindingsWithPath) { 341 ImmutableSet.Builder<BindingNode> bindingNodes = ImmutableSet.builder(); 342 resolvedBindingsWithPath.resolvedBindings() 343 .allBindings() 344 .asMap() 345 .forEach( 346 (component, bindings) -> { 347 for (Binding binding : bindings) { 348 bindingNodes.add( 349 bindingNode(resolvedBindingsWithPath.resolvedBindings(), binding, component)); 350 } 351 }); 352 return bindingNodes.build(); 353 } 354 bindingNode( ResolvedBindings resolvedBindings, Binding binding, TypeElement owningComponent)355 private BindingNode bindingNode( 356 ResolvedBindings resolvedBindings, Binding binding, TypeElement owningComponent) { 357 return BindingNode.create( 358 pathFromRootToAncestor(owningComponent), 359 binding, 360 resolvedBindings.multibindingDeclarations(), 361 resolvedBindings.optionalBindingDeclarations(), 362 resolvedBindings.subcomponentDeclarations(), 363 bindingDeclarationFormatter); 364 } 365 missingBindingNode(ResolvedBindings dependencies)366 private MissingBinding missingBindingNode(ResolvedBindings dependencies) { 367 // Put all missing binding nodes in the root component. This simplifies the binding graph 368 // and produces better error messages for users since all dependents point to the same node. 369 return MissingBindingImpl.create( 370 ComponentPath.create(ImmutableList.of(componentPath().rootComponent())), 371 dependencies.key()); 372 } 373 subcomponentNode( TypeMirror subcomponentBuilderType, LegacyBindingGraph graph)374 private ComponentNode subcomponentNode( 375 TypeMirror subcomponentBuilderType, LegacyBindingGraph graph) { 376 TypeElement subcomponentBuilderElement = asTypeElement(subcomponentBuilderType); 377 ComponentDescriptor subcomponent = 378 graph.componentDescriptor().getChildComponentWithBuilderType(subcomponentBuilderElement); 379 return ComponentNodeImpl.create( 380 componentPath().childPath(subcomponent.typeElement()), subcomponent); 381 } 382 } 383 384 @AutoValue 385 abstract static class MissingBindingImpl extends MissingBinding { create(ComponentPath component, Key key)386 static MissingBinding create(ComponentPath component, Key key) { 387 return new AutoValue_BindingGraphConverter_MissingBindingImpl(component, key); 388 } 389 390 @Memoized 391 @Override hashCode()392 public abstract int hashCode(); 393 394 @Override equals(Object o)395 public abstract boolean equals(Object o); 396 } 397 } 398