| /* |
| * Copyright (C) 2018 The Dagger Authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package dagger.internal.codegen.bindinggraphvalidation; |
| |
| import static com.google.auto.common.MoreTypes.asDeclared; |
| import static com.google.auto.common.MoreTypes.asExecutable; |
| import static com.google.auto.common.MoreTypes.asTypeElements; |
| import static com.google.common.collect.Sets.union; |
| import static dagger.internal.codegen.binding.ComponentRequirement.componentCanMakeNewInstances; |
| import static dagger.internal.codegen.extension.DaggerStreams.instancesOf; |
| import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; |
| import static javax.tools.Diagnostic.Kind.ERROR; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Sets; |
| import com.google.common.collect.Sets.SetView; |
| import dagger.internal.codegen.base.Util; |
| import dagger.internal.codegen.binding.ComponentNodeImpl; |
| import dagger.internal.codegen.kotlin.KotlinMetadataUtil; |
| import dagger.internal.codegen.langmodel.DaggerTypes; |
| import dagger.model.BindingGraph; |
| import dagger.model.BindingGraph.ChildFactoryMethodEdge; |
| import dagger.model.BindingGraph.ComponentNode; |
| import dagger.spi.BindingGraphPlugin; |
| import dagger.spi.DiagnosticReporter; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Function; |
| import javax.inject.Inject; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.ExecutableType; |
| |
| /** Reports an error if a subcomponent factory method is missing required modules. */ |
| final class SubcomponentFactoryMethodValidator implements BindingGraphPlugin { |
| |
| private final DaggerTypes types; |
| private final KotlinMetadataUtil metadataUtil; |
| private final Map<ComponentNode, Set<TypeElement>> inheritedModulesCache = new HashMap<>(); |
| |
| @Inject |
| SubcomponentFactoryMethodValidator(DaggerTypes types, KotlinMetadataUtil metadataUtil) { |
| this.types = types; |
| this.metadataUtil = metadataUtil; |
| } |
| |
| @Override |
| public String pluginName() { |
| return "Dagger/SubcomponentFactoryMethodMissingModule"; |
| } |
| |
| @Override |
| public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) { |
| if (!bindingGraph.rootComponentNode().isRealComponent() |
| || bindingGraph.rootComponentNode().isSubcomponent()) { |
| // We don't know all the modules that might be owned by the child until we know the real root |
| // component, which we don't if the root component node is really a module or a subcomponent. |
| return; |
| } |
| bindingGraph.network().edges().stream() |
| .flatMap(instancesOf(ChildFactoryMethodEdge.class)) |
| .forEach( |
| edge -> { |
| ImmutableSet<TypeElement> missingModules = findMissingModules(edge, bindingGraph); |
| if (!missingModules.isEmpty()) { |
| reportMissingModuleParameters( |
| edge, missingModules, bindingGraph, diagnosticReporter); |
| } |
| }); |
| } |
| |
| private ImmutableSet<TypeElement> findMissingModules( |
| ChildFactoryMethodEdge edge, BindingGraph graph) { |
| ImmutableSet<TypeElement> factoryMethodParameters = |
| subgraphFactoryMethodParameters(edge, graph); |
| ComponentNode child = (ComponentNode) graph.network().incidentNodes(edge).target(); |
| SetView<TypeElement> modulesOwnedByChild = ownedModules(child, graph); |
| return graph.bindings().stream() |
| // bindings owned by child |
| .filter(binding -> binding.componentPath().equals(child.componentPath())) |
| // that require a module instance |
| .filter(binding -> binding.requiresModuleInstance()) |
| .map(binding -> binding.contributingModule().get()) |
| .distinct() |
| // module owned by child |
| .filter(module -> modulesOwnedByChild.contains(module)) |
| // module not in the method parameters |
| .filter(module -> !factoryMethodParameters.contains(module)) |
| // module doesn't have an accessible no-arg constructor |
| .filter(moduleType -> !componentCanMakeNewInstances(moduleType, metadataUtil)) |
| .collect(toImmutableSet()); |
| } |
| |
| private ImmutableSet<TypeElement> subgraphFactoryMethodParameters( |
| ChildFactoryMethodEdge edge, BindingGraph bindingGraph) { |
| ComponentNode parent = (ComponentNode) bindingGraph.network().incidentNodes(edge).source(); |
| DeclaredType parentType = asDeclared(parent.componentPath().currentComponent().asType()); |
| ExecutableType factoryMethodType = |
| asExecutable(types.asMemberOf(parentType, edge.factoryMethod())); |
| return asTypeElements(factoryMethodType.getParameterTypes()); |
| } |
| |
| private SetView<TypeElement> ownedModules(ComponentNode component, BindingGraph graph) { |
| return Sets.difference( |
| ((ComponentNodeImpl) component).componentDescriptor().moduleTypes(), |
| inheritedModules(component, graph)); |
| } |
| |
| private Set<TypeElement> inheritedModules(ComponentNode component, BindingGraph graph) { |
| return Util.reentrantComputeIfAbsent( |
| inheritedModulesCache, component, uncachedInheritedModules(graph)); |
| } |
| |
| private Function<ComponentNode, Set<TypeElement>> uncachedInheritedModules(BindingGraph graph) { |
| return componentNode -> |
| componentNode.componentPath().atRoot() |
| ? ImmutableSet.of() |
| : graph |
| .componentNode(componentNode.componentPath().parent()) |
| .map(parent -> union(ownedModules(parent, graph), inheritedModules(parent, graph))) |
| .get(); |
| } |
| |
| private void reportMissingModuleParameters( |
| ChildFactoryMethodEdge edge, |
| ImmutableSet<TypeElement> missingModules, |
| BindingGraph graph, |
| DiagnosticReporter diagnosticReporter) { |
| diagnosticReporter.reportSubcomponentFactoryMethod( |
| ERROR, |
| edge, |
| "%s requires modules which have no visible default constructors. " |
| + "Add the following modules as parameters to this method: %s", |
| graph |
| .network() |
| .incidentNodes(edge) |
| .target() |
| .componentPath() |
| .currentComponent() |
| .getQualifiedName(), |
| Joiner.on(", ").join(missingModules)); |
| } |
| } |