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.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.graph.ImmutableNetwork; 31 import com.google.common.graph.MutableNetwork; 32 import com.google.common.graph.NetworkBuilder; 33 import dagger.internal.codegen.binding.BindingGraph.TopLevelBindingGraph; 34 import dagger.internal.codegen.binding.BindingGraphFactory.LegacyBindingGraph; 35 import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; 36 import dagger.internal.codegen.model.BindingGraph.ComponentNode; 37 import dagger.internal.codegen.model.BindingGraph.DependencyEdge; 38 import dagger.internal.codegen.model.BindingGraph.Edge; 39 import dagger.internal.codegen.model.BindingGraph.MissingBinding; 40 import dagger.internal.codegen.model.BindingGraph.Node; 41 import dagger.internal.codegen.model.ComponentPath; 42 import dagger.internal.codegen.model.DaggerTypeElement; 43 import dagger.internal.codegen.model.DependencyRequest; 44 import dagger.internal.codegen.model.Key; 45 import java.util.ArrayDeque; 46 import java.util.Deque; 47 import java.util.HashMap; 48 import java.util.HashSet; 49 import java.util.Map; 50 import java.util.Set; 51 import javax.inject.Inject; 52 53 /** Converts {@link BindingGraph}s to {@link dagger.internal.codegen.model.BindingGraph}s. */ 54 final class BindingGraphConverter { 55 private final BindingDeclarationFormatter bindingDeclarationFormatter; 56 57 @Inject BindingGraphConverter(BindingDeclarationFormatter bindingDeclarationFormatter)58 BindingGraphConverter(BindingDeclarationFormatter bindingDeclarationFormatter) { 59 this.bindingDeclarationFormatter = bindingDeclarationFormatter; 60 } 61 62 /** 63 * Creates the external {@link dagger.internal.codegen.model.BindingGraph} representing the given 64 * internal {@link BindingGraph}. 65 */ convert(LegacyBindingGraph legacyBindingGraph, boolean isFullBindingGraph)66 BindingGraph convert(LegacyBindingGraph legacyBindingGraph, boolean isFullBindingGraph) { 67 MutableNetwork<Node, Edge> network = asNetwork(legacyBindingGraph); 68 ComponentNode rootNode = legacyBindingGraph.componentNode(); 69 70 // When bindings are copied down into child graphs because they transitively depend on local 71 // multibindings or optional bindings, the parent-owned binding is still there. If that 72 // parent-owned binding is not reachable from its component, it doesn't need to be in the graph 73 // because it will never be used. So remove all nodes that are not reachable from the root 74 // component—unless we're converting a full binding graph. 75 if (!isFullBindingGraph) { 76 unreachableNodes(network.asGraph(), rootNode).forEach(network::removeNode); 77 } 78 79 TopLevelBindingGraph topLevelBindingGraph = 80 TopLevelBindingGraph.create(ImmutableNetwork.copyOf(network), isFullBindingGraph); 81 return BindingGraph.create(rootNode, topLevelBindingGraph); 82 } 83 asNetwork(LegacyBindingGraph graph)84 private MutableNetwork<Node, Edge> asNetwork(LegacyBindingGraph graph) { 85 Converter converter = new Converter(); 86 converter.visitRootComponent(graph); 87 return converter.network; 88 } 89 90 private final class Converter { 91 /** The path from the root graph to the currently visited graph. */ 92 private final Deque<LegacyBindingGraph> bindingGraphPath = new ArrayDeque<>(); 93 94 private final MutableNetwork<Node, Edge> network = 95 NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build(); 96 private final Set<BindingNode> bindings = new HashSet<>(); 97 98 private final Map<ResolvedBindings, ImmutableSet<BindingNode>> resolvedBindingsMap = 99 new HashMap<>(); 100 visitRootComponent(LegacyBindingGraph graph)101 private void visitRootComponent(LegacyBindingGraph graph) { 102 visitComponent(graph); 103 } 104 105 /** 106 * Called once for each component in a component hierarchy. 107 * 108 * <p>This implementation does the following: 109 * 110 * <ol> 111 * <li>If this component is installed in its parent by a subcomponent factory method, adds 112 * an edge between the parent and child components. 113 * <li>For each entry point, adds an edge between the component and the entry point. 114 * <li>For each child component, calls {@link #visitComponent(LegacyBindingGraph)}, 115 * updating the traversal state. 116 * </ol> 117 * 118 * @param graph the currently visited graph 119 */ visitComponent(LegacyBindingGraph graph)120 private void visitComponent(LegacyBindingGraph graph) { 121 bindingGraphPath.addLast(graph); 122 123 network.addNode(graph.componentNode()); 124 125 for (ComponentMethodDescriptor entryPointMethod : 126 graph.componentDescriptor().entryPointMethods()) { 127 addDependencyEdges(graph.componentNode(), entryPointMethod.dependencyRequest().get()); 128 } 129 130 for (ResolvedBindings resolvedBindings : graph.resolvedBindings()) { 131 for (BindingNode binding : bindingNodes(resolvedBindings)) { 132 if (bindings.add(binding)) { 133 network.addNode(binding); 134 for (DependencyRequest dependencyRequest : binding.dependencies()) { 135 addDependencyEdges(binding, dependencyRequest); 136 } 137 } 138 if (binding.kind().equals(SUBCOMPONENT_CREATOR) 139 && binding.componentPath().equals(graph.componentPath())) { 140 network.addEdge( 141 binding, 142 subcomponentNode(binding.key().type().xprocessing(), graph), 143 new SubcomponentCreatorBindingEdgeImpl( 144 resolvedBindings.subcomponentDeclarations())); 145 } 146 } 147 } 148 149 for (LegacyBindingGraph childGraph : graph.subgraphs()) { 150 visitComponent(childGraph); 151 graph 152 .componentDescriptor() 153 .getFactoryMethodForChildComponent(childGraph.componentDescriptor()) 154 .ifPresent( 155 childFactoryMethod -> 156 network.addEdge( 157 graph.componentNode(), 158 childGraph.componentNode(), 159 new ChildFactoryMethodEdgeImpl(childFactoryMethod.methodElement()))); 160 } 161 162 verify(bindingGraphPath.removeLast().equals(graph)); 163 } 164 165 /** 166 * Returns an immutable snapshot of the path from the root component to the currently visited 167 * component. 168 */ componentPath()169 private ComponentPath componentPath() { 170 return bindingGraphPath.getLast().componentPath(); 171 } 172 173 /** 174 * Returns the subpath from the root component to the matching {@code ancestor} of the current 175 * component. 176 */ pathFromRootToAncestor(XTypeElement ancestor)177 private ComponentPath pathFromRootToAncestor(XTypeElement ancestor) { 178 for (LegacyBindingGraph graph : bindingGraphPath) { 179 if (graph.componentDescriptor().typeElement().equals(ancestor)) { 180 return graph.componentPath(); 181 } 182 } 183 throw new IllegalArgumentException( 184 String.format( 185 "%s is not in the current path: %s", ancestor.getQualifiedName(), componentPath())); 186 } 187 188 /** 189 * Returns the LegacyBindingGraph for {@code ancestor}, where {@code ancestor} is in the 190 * component path of the current traversal. 191 */ graphForAncestor(XTypeElement ancestor)192 private LegacyBindingGraph graphForAncestor(XTypeElement ancestor) { 193 for (LegacyBindingGraph graph : bindingGraphPath) { 194 if (graph.componentDescriptor().typeElement().equals(ancestor)) { 195 return graph; 196 } 197 } 198 throw new IllegalArgumentException( 199 String.format( 200 "%s is not in the current path: %s", ancestor.getQualifiedName(), componentPath())); 201 } 202 203 /** 204 * Adds a {@link dagger.internal.codegen.model.BindingGraph.DependencyEdge} from a node to the 205 * binding(s) that satisfy a dependency request. 206 */ addDependencyEdges(Node source, DependencyRequest dependencyRequest)207 private void addDependencyEdges(Node source, DependencyRequest dependencyRequest) { 208 ResolvedBindings dependencies = resolvedDependencies(source, dependencyRequest); 209 if (dependencies.isEmpty()) { 210 addDependencyEdge(source, dependencyRequest, missingBindingNode(dependencies)); 211 } else { 212 for (BindingNode dependency : bindingNodes(dependencies)) { 213 addDependencyEdge(source, dependencyRequest, dependency); 214 } 215 } 216 } 217 addDependencyEdge( Node source, DependencyRequest dependencyRequest, Node dependency)218 private void addDependencyEdge( 219 Node source, DependencyRequest dependencyRequest, Node dependency) { 220 network.addNode(dependency); 221 if (!hasDependencyEdge(source, dependency, dependencyRequest)) { 222 network.addEdge( 223 source, 224 dependency, 225 new DependencyEdgeImpl(dependencyRequest, source instanceof ComponentNode)); 226 } 227 } 228 hasDependencyEdge( Node source, Node dependency, DependencyRequest dependencyRequest)229 private boolean hasDependencyEdge( 230 Node source, Node dependency, DependencyRequest dependencyRequest) { 231 // An iterative approach is used instead of a Stream because this method is called in a hot 232 // loop, and the Stream calculates the size of network.edgesConnecting(), which is slow. This 233 // seems to be because caculating the edges connecting two nodes in a Network that supports 234 // parallel edges is must check the equality of many nodes, and BindingNode's equality 235 // semantics drag in the equality of many other expensive objects 236 for (Edge edge : network.edgesConnecting(source, dependency)) { 237 if (edge instanceof DependencyEdge) { 238 if (((DependencyEdge) edge).dependencyRequest().equals(dependencyRequest)) { 239 return true; 240 } 241 } 242 } 243 return false; 244 } 245 resolvedDependencies( Node source, DependencyRequest dependencyRequest)246 private ResolvedBindings resolvedDependencies( 247 Node source, DependencyRequest dependencyRequest) { 248 return graphForAncestor(source.componentPath().currentComponent().xprocessing()) 249 .resolvedBindings(bindingRequest(dependencyRequest)); 250 } 251 bindingNodes(ResolvedBindings resolvedBindings)252 private ImmutableSet<BindingNode> bindingNodes(ResolvedBindings resolvedBindings) { 253 return resolvedBindingsMap.computeIfAbsent(resolvedBindings, this::uncachedBindingNodes); 254 } 255 uncachedBindingNodes(ResolvedBindings resolvedBindings)256 private ImmutableSet<BindingNode> uncachedBindingNodes(ResolvedBindings resolvedBindings) { 257 ImmutableSet.Builder<BindingNode> bindingNodes = ImmutableSet.builder(); 258 resolvedBindings 259 .allBindings() 260 .asMap() 261 .forEach( 262 (component, bindings) -> { 263 for (Binding binding : bindings) { 264 bindingNodes.add(bindingNode(resolvedBindings, binding, component)); 265 } 266 }); 267 return bindingNodes.build(); 268 } 269 bindingNode( ResolvedBindings resolvedBindings, Binding binding, XTypeElement owningComponent)270 private BindingNode bindingNode( 271 ResolvedBindings resolvedBindings, Binding binding, XTypeElement owningComponent) { 272 return BindingNode.create( 273 pathFromRootToAncestor(owningComponent), 274 binding, 275 resolvedBindings.multibindingDeclarations(), 276 resolvedBindings.optionalBindingDeclarations(), 277 resolvedBindings.subcomponentDeclarations(), 278 bindingDeclarationFormatter); 279 } 280 missingBindingNode(ResolvedBindings dependencies)281 private MissingBinding missingBindingNode(ResolvedBindings dependencies) { 282 // Put all missing binding nodes in the root component. This simplifies the binding graph 283 // and produces better error messages for users since all dependents point to the same node. 284 return MissingBindingImpl.create( 285 ComponentPath.create(ImmutableList.of(componentPath().rootComponent())), 286 dependencies.key()); 287 } 288 subcomponentNode( XType subcomponentBuilderType, LegacyBindingGraph graph)289 private ComponentNode subcomponentNode( 290 XType subcomponentBuilderType, LegacyBindingGraph graph) { 291 XTypeElement subcomponentBuilderElement = subcomponentBuilderType.getTypeElement(); 292 ComponentDescriptor subcomponent = 293 graph.componentDescriptor().getChildComponentWithBuilderType(subcomponentBuilderElement); 294 return ComponentNodeImpl.create( 295 componentPath().childPath(DaggerTypeElement.from(subcomponent.typeElement())), 296 subcomponent); 297 } 298 } 299 300 @AutoValue 301 abstract static class MissingBindingImpl extends MissingBinding { create(ComponentPath component, Key key)302 static MissingBinding create(ComponentPath component, Key key) { 303 return new AutoValue_BindingGraphConverter_MissingBindingImpl(component, key); 304 } 305 306 @Memoized 307 @Override hashCode()308 public abstract int hashCode(); 309 310 @Override equals(Object o)311 public abstract boolean equals(Object o); 312 } 313 } 314