• 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;
18 
19 import static com.google.auto.common.MoreTypes.asDeclared;
20 import static com.google.auto.common.MoreTypes.asExecutable;
21 import static com.google.auto.common.MoreTypes.asTypeElements;
22 import static com.google.common.collect.Sets.union;
23 import static dagger.internal.codegen.DaggerStreams.instancesOf;
24 import static dagger.internal.codegen.DaggerStreams.toImmutableSet;
25 import static dagger.internal.codegen.Util.componentCanMakeNewInstances;
26 import static javax.tools.Diagnostic.Kind.ERROR;
27 
28 import com.google.common.base.Joiner;
29 import com.google.common.collect.ImmutableSet;
30 import com.google.common.collect.Sets;
31 import com.google.common.collect.Sets.SetView;
32 import dagger.internal.codegen.langmodel.DaggerTypes;
33 import dagger.model.BindingGraph;
34 import dagger.model.BindingGraph.ChildFactoryMethodEdge;
35 import dagger.model.BindingGraph.ComponentNode;
36 import dagger.spi.BindingGraphPlugin;
37 import dagger.spi.DiagnosticReporter;
38 import java.util.HashMap;
39 import java.util.Map;
40 import java.util.Set;
41 import java.util.function.Function;
42 import javax.inject.Inject;
43 import javax.lang.model.element.TypeElement;
44 import javax.lang.model.type.DeclaredType;
45 import javax.lang.model.type.ExecutableType;
46 
47 /** Reports an error if a subcomponent factory method is missing required modules. */
48 final class SubcomponentFactoryMethodValidator implements BindingGraphPlugin {
49 
50   private final DaggerTypes types;
51   private final Map<ComponentNode, Set<TypeElement>> inheritedModulesCache = new HashMap<>();
52 
53   @Inject
SubcomponentFactoryMethodValidator(DaggerTypes types)54   SubcomponentFactoryMethodValidator(DaggerTypes types) {
55     this.types = types;
56   }
57 
58   @Override
pluginName()59   public String pluginName() {
60     return "Dagger/SubcomponentFactoryMethodMissingModule";
61   }
62 
63   @Override
visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter)64   public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {
65     if (!bindingGraph.rootComponentNode().isRealComponent()
66         || bindingGraph.rootComponentNode().isSubcomponent()) {
67       // We don't know all the modules that might be owned by the child until we know the real root
68       // component, which we don't if the root component node is really a module or a subcomponent.
69       return;
70     }
71     bindingGraph.network().edges().stream()
72         .flatMap(instancesOf(ChildFactoryMethodEdge.class))
73         .forEach(
74             edge -> {
75               ImmutableSet<TypeElement> missingModules = findMissingModules(edge, bindingGraph);
76               if (!missingModules.isEmpty()) {
77                 reportMissingModuleParameters(
78                     edge, missingModules, bindingGraph, diagnosticReporter);
79               }
80             });
81   }
82 
findMissingModules( ChildFactoryMethodEdge edge, BindingGraph graph)83   private ImmutableSet<TypeElement> findMissingModules(
84       ChildFactoryMethodEdge edge, BindingGraph graph) {
85     ImmutableSet<TypeElement> factoryMethodParameters =
86         subgraphFactoryMethodParameters(edge, graph);
87     ComponentNode child = (ComponentNode) graph.network().incidentNodes(edge).target();
88     SetView<TypeElement> modulesOwnedByChild = ownedModules(child, graph);
89     return graph.bindings().stream()
90         // bindings owned by child
91         .filter(binding -> binding.componentPath().equals(child.componentPath()))
92         // that require a module instance
93         .filter(binding -> binding.requiresModuleInstance())
94         .map(binding -> binding.contributingModule().get())
95         .distinct()
96         // module owned by child
97         .filter(module -> modulesOwnedByChild.contains(module))
98         // module not in the method parameters
99         .filter(module -> !factoryMethodParameters.contains(module))
100         // module doesn't have an accessible no-arg constructor
101         .filter(moduleType -> !componentCanMakeNewInstances(moduleType))
102         .collect(toImmutableSet());
103   }
104 
subgraphFactoryMethodParameters( ChildFactoryMethodEdge edge, BindingGraph bindingGraph)105   private ImmutableSet<TypeElement> subgraphFactoryMethodParameters(
106       ChildFactoryMethodEdge edge, BindingGraph bindingGraph) {
107     ComponentNode parent = (ComponentNode) bindingGraph.network().incidentNodes(edge).source();
108     DeclaredType parentType = asDeclared(parent.componentPath().currentComponent().asType());
109     ExecutableType factoryMethodType =
110         asExecutable(types.asMemberOf(parentType, edge.factoryMethod()));
111     return asTypeElements(factoryMethodType.getParameterTypes());
112   }
113 
ownedModules(ComponentNode component, BindingGraph graph)114   private SetView<TypeElement> ownedModules(ComponentNode component, BindingGraph graph) {
115     return Sets.difference(
116         ((ComponentNodeImpl) component).componentDescriptor().moduleTypes(),
117         inheritedModules(component, graph));
118   }
119 
inheritedModules(ComponentNode component, BindingGraph graph)120   private Set<TypeElement> inheritedModules(ComponentNode component, BindingGraph graph) {
121     return Util.reentrantComputeIfAbsent(
122         inheritedModulesCache, component, uncachedInheritedModules(graph));
123   }
124 
uncachedInheritedModules(BindingGraph graph)125   private Function<ComponentNode, Set<TypeElement>> uncachedInheritedModules(BindingGraph graph) {
126     return componentNode ->
127         componentNode.componentPath().atRoot()
128             ? ImmutableSet.of()
129             : graph
130                 .componentNode(componentNode.componentPath().parent())
131                 .map(parent -> union(ownedModules(parent, graph), inheritedModules(parent, graph)))
132                 .get();
133   }
134 
reportMissingModuleParameters( ChildFactoryMethodEdge edge, ImmutableSet<TypeElement> missingModules, BindingGraph graph, DiagnosticReporter diagnosticReporter)135   private void reportMissingModuleParameters(
136       ChildFactoryMethodEdge edge,
137       ImmutableSet<TypeElement> missingModules,
138       BindingGraph graph,
139       DiagnosticReporter diagnosticReporter) {
140     diagnosticReporter.reportSubcomponentFactoryMethod(
141         ERROR,
142         edge,
143         "%s requires modules which have no visible default constructors. "
144             + "Add the following modules as parameters to this method: %s",
145         graph
146             .network()
147             .incidentNodes(edge)
148             .target()
149             .componentPath()
150             .currentComponent()
151             .getQualifiedName(),
152         Joiner.on(", ").join(missingModules));
153   }
154 }
155