dagger.internal.codegen.bindinggraphvalidation.SubcomponentFactoryMethodValidator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dagger-compiler Show documentation
Show all versions of dagger-compiler Show documentation
A fast dependency injector for Android and Java.
/*
* 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.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 java.util.stream.Collectors.joining;
import static javax.tools.Diagnostic.Kind.ERROR;
import androidx.room.compiler.processing.XExecutableType;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XTypeElement;
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.model.BindingGraph;
import dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge;
import dagger.internal.codegen.model.BindingGraph.ComponentNode;
import dagger.internal.codegen.model.DiagnosticReporter;
import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import javax.inject.Inject;
/** Reports an error if a subcomponent factory method is missing required modules. */
final class SubcomponentFactoryMethodValidator extends ValidationBindingGraphPlugin {
private final Map> inheritedModulesCache = new HashMap<>();
@Inject
SubcomponentFactoryMethodValidator() {}
@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().xprocessing())
.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();
XType parentType = parent.componentPath().currentComponent().xprocessing().getType();
XExecutableType factoryMethodType = edge.factoryMethod().xprocessing().asMemberOf(parentType);
return factoryMethodType.getParameterTypes().stream()
.map(XType::getTypeElement)
.collect(toImmutableSet());
}
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()
.className()
.canonicalName(),
missingModules.stream()
.map(XTypeElement::getQualifiedName)
.collect(joining(", ")));
}
}