/* * 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; 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.DaggerStreams.instancesOf; import static dagger.internal.codegen.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.Util.componentCanMakeNewInstances; 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.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 Map> inheritedModulesCache = new HashMap<>(); @Inject SubcomponentFactoryMethodValidator(DaggerTypes types) { this.types = types; } @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 missingModules = findMissingModules(edge, bindingGraph); if (!missingModules.isEmpty()) { reportMissingModuleParameters( edge, missingModules, bindingGraph, diagnosticReporter); } }); } private ImmutableSet findMissingModules( ChildFactoryMethodEdge edge, BindingGraph graph) { ImmutableSet factoryMethodParameters = subgraphFactoryMethodParameters(edge, graph); ComponentNode child = (ComponentNode) graph.network().incidentNodes(edge).target(); SetView 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)) .collect(toImmutableSet()); } private ImmutableSet 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 ownedModules(ComponentNode component, BindingGraph graph) { return Sets.difference( ((ComponentNodeImpl) component).componentDescriptor().moduleTypes(), inheritedModules(component, graph)); } private Set inheritedModules(ComponentNode component, BindingGraph graph) { return Util.reentrantComputeIfAbsent( inheritedModulesCache, component, uncachedInheritedModules(graph)); } private Function> 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 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)); } }