blob: 4936a05267e6ace9305aadefa1aad2c2cac98a3f [file] [log] [blame]
/*
* Copyright (C) 2014 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.binding;
import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
import static dagger.internal.codegen.extension.DaggerStreams.presentValues;
import static dagger.internal.codegen.extension.DaggerStreams.stream;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import com.google.auto.value.AutoValue;
import com.google.auto.value.extension.memoized.Memoized;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Sets;
import com.google.common.graph.Graphs;
import com.google.common.graph.ImmutableNetwork;
import com.google.common.graph.Traverser;
import dagger.model.BindingGraph.ChildFactoryMethodEdge;
import dagger.model.BindingGraph.ComponentNode;
import dagger.model.BindingGraph.Edge;
import dagger.model.BindingGraph.Node;
import dagger.model.ComponentPath;
import dagger.model.Key;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
/**
* A graph that represents a single component or subcomponent within a fully validated top-level
* binding graph.
*/
@AutoValue
public abstract class BindingGraph {
@AutoValue
abstract static class TopLevelBindingGraph extends dagger.model.BindingGraph {
static TopLevelBindingGraph create(
ImmutableNetwork<Node, Edge> network, boolean isFullBindingGraph) {
TopLevelBindingGraph topLevelBindingGraph =
new AutoValue_BindingGraph_TopLevelBindingGraph(network, isFullBindingGraph);
ImmutableMap<ComponentPath, ComponentNode> componentNodes =
topLevelBindingGraph.componentNodes().stream()
.collect(
toImmutableMap(ComponentNode::componentPath, componentNode -> componentNode));
ImmutableSetMultimap.Builder<ComponentNode, ComponentNode> subcomponentNodesBuilder =
ImmutableSetMultimap.builder();
topLevelBindingGraph.componentNodes().stream()
.filter(componentNode -> !componentNode.componentPath().atRoot())
.forEach(
componentNode ->
subcomponentNodesBuilder.put(
componentNodes.get(componentNode.componentPath().parent()), componentNode));
// Set these fields directly on the instance rather than passing these in as input to the
// AutoValue to prevent exposing this data outside of the class.
topLevelBindingGraph.componentNodes = componentNodes;
topLevelBindingGraph.subcomponentNodes = subcomponentNodesBuilder.build();
return topLevelBindingGraph;
}
private ImmutableMap<ComponentPath, ComponentNode> componentNodes;
private ImmutableSetMultimap<ComponentNode, ComponentNode> subcomponentNodes;
TopLevelBindingGraph() {}
// This overrides dagger.model.BindingGraph with a more efficient implementation.
@Override
public Optional<ComponentNode> componentNode(ComponentPath componentPath) {
return componentNodes.containsKey(componentPath)
? Optional.of(componentNodes.get(componentPath))
: Optional.empty();
}
/** Returns the set of subcomponent nodes of the given component node. */
ImmutableSet<ComponentNode> subcomponentNodes(ComponentNode componentNode) {
return subcomponentNodes.get(componentNode);
}
@Override
@Memoized
public ImmutableSetMultimap<Class<? extends Node>, ? extends Node> nodesByClass() {
return super.nodesByClass();
}
}
static BindingGraph create(
ComponentNode componentNode, TopLevelBindingGraph topLevelBindingGraph) {
return create(Optional.empty(), componentNode, topLevelBindingGraph);
}
private static BindingGraph create(
Optional<BindingGraph> parent,
ComponentNode componentNode,
TopLevelBindingGraph topLevelBindingGraph) {
ImmutableSet<BindingNode> reachableBindingNodes =
Graphs.reachableNodes(topLevelBindingGraph.network().asGraph(), componentNode).stream()
.filter(node -> isSubpath(componentNode.componentPath(), node.componentPath()))
.filter(node -> node instanceof BindingNode)
.map(node -> (BindingNode) node)
.collect(toImmutableSet());
// Construct the maps of the ContributionBindings and MembersInjectionBindings.
Map<Key, BindingNode> contributionBindings = new HashMap<>();
Map<Key, BindingNode> membersInjectionBindings = new HashMap<>();
for (BindingNode bindingNode : reachableBindingNodes) {
Map<Key, BindingNode> bindingsMap;
if (bindingNode.delegate() instanceof ContributionBinding) {
bindingsMap = contributionBindings;
} else if (bindingNode.delegate() instanceof MembersInjectionBinding) {
bindingsMap = membersInjectionBindings;
} else {
throw new AssertionError("Unexpected binding node type: " + bindingNode.delegate());
}
// TODO(bcorso): Mapping binding nodes by key is flawed since bindings that depend on local
// multibindings can have multiple nodes (one in each component). In this case, we choose the
// node in the child-most component since this is likely the node that users of this
// BindingGraph will want (and to remain consisted with LegacyBindingGraph). However, ideally
// we would avoid this ambiguity by getting dependencies directly from the top-level network.
// In particular, rather than using a Binding's list of DependencyRequests (which only
// contains the key) we would use the top-level network to find the DependencyEdges for a
// particular BindingNode.
Key key = bindingNode.key();
if (!bindingsMap.containsKey(key)
// Always choose the child-most binding node.
|| bindingNode.componentPath().components().size()
> bindingsMap.get(key).componentPath().components().size()) {
bindingsMap.put(key, bindingNode);
}
}
BindingGraph bindingGraph = new AutoValue_BindingGraph(componentNode, topLevelBindingGraph);
ImmutableSet<ModuleDescriptor> modules =
((ComponentNodeImpl) componentNode).componentDescriptor().modules();
ImmutableSet<ModuleDescriptor> inheritedModules =
parent.isPresent()
? Sets.union(parent.get().ownedModules, parent.get().inheritedModules).immutableCopy()
: ImmutableSet.of();
// Set these fields directly on the instance rather than passing these in as input to the
// AutoValue to prevent exposing this data outside of the class.
bindingGraph.inheritedModules = inheritedModules;
bindingGraph.ownedModules = Sets.difference(modules, inheritedModules).immutableCopy();
bindingGraph.contributionBindings = ImmutableMap.copyOf(contributionBindings);
bindingGraph.membersInjectionBindings = ImmutableMap.copyOf(membersInjectionBindings);
bindingGraph.bindingModules =
contributionBindings.values().stream()
.map(BindingNode::contributingModule)
.flatMap(presentValues())
.collect(toImmutableSet());
return bindingGraph;
}
private ImmutableMap<Key, BindingNode> contributionBindings;
private ImmutableMap<Key, BindingNode> membersInjectionBindings;
private ImmutableSet<ModuleDescriptor> inheritedModules;
private ImmutableSet<ModuleDescriptor> ownedModules;
private ImmutableSet<TypeElement> bindingModules;
BindingGraph() {}
/** Returns the {@link ComponentNode} for this graph. */
public abstract ComponentNode componentNode();
/** Returns the {@link ComponentPath} for this graph. */
public final ComponentPath componentPath() {
return componentNode().componentPath();
}
/** Returns the {@link TopLevelBindingGraph} from which this graph is contained. */
public abstract TopLevelBindingGraph topLevelBindingGraph();
/** Returns the {@link ComponentDescriptor} for this graph */
public final ComponentDescriptor componentDescriptor() {
return ((ComponentNodeImpl) componentNode()).componentDescriptor();
}
/** Returns the {@link ContributionBinding} for the given {@link Key}. */
public final ContributionBinding contributionBinding(Key key) {
return (ContributionBinding) contributionBindings.get(key).delegate();
}
/**
* Returns the {@link MembersInjectionBinding} for the given {@link Key} or {@link
* Optional#empty()} if one does not exist.
*/
public final Optional<MembersInjectionBinding> membersInjectionBinding(Key key) {
return membersInjectionBindings.containsKey(key)
? Optional.of((MembersInjectionBinding) membersInjectionBindings.get(key).delegate())
: Optional.empty();
}
/** Returns the {@link TypeElement} for the component this graph represents. */
public final TypeElement componentTypeElement() {
return componentPath().currentComponent();
}
/**
* Returns the set of modules that are owned by this graph regardless of whether or not any of
* their bindings are used in this graph. For graphs representing top-level {@link
* dagger.Component components}, this set will be the same as {@linkplain
* ComponentDescriptor#modules() the component's transitive modules}. For {@linkplain Subcomponent
* subcomponents}, this set will be the transitive modules that are not owned by any of their
* ancestors.
*/
public final ImmutableSet<TypeElement> ownedModuleTypes() {
return ownedModules.stream().map(ModuleDescriptor::moduleElement).collect(toImmutableSet());
}
/**
* Returns the factory method for this subcomponent, if it exists.
*
* <p>This factory method is the one defined in the parent component's interface.
*
* <p>In the example below, the {@link BindingGraph#factoryMethod} for {@code ChildComponent}
* would return the {@link ExecutableElement}: {@code childComponent(ChildModule1)} .
*
* <pre><code>
* {@literal @Component}
* interface ParentComponent {
* ChildComponent childComponent(ChildModule1 childModule);
* }
* </code></pre>
*/
// TODO(b/73294201): Consider returning the resolved ExecutableType for the factory method.
public final Optional<ExecutableElement> factoryMethod() {
return topLevelBindingGraph().network().inEdges(componentNode()).stream()
.filter(edge -> edge instanceof ChildFactoryMethodEdge)
.map(edge -> ((ChildFactoryMethodEdge) edge).factoryMethod())
.collect(toOptional());
}
/**
* Returns a map between the {@linkplain ComponentRequirement component requirement} and the
* corresponding {@link VariableElement} for each module parameter in the {@linkplain
* BindingGraph#factoryMethod factory method}.
*/
// TODO(dpb): Consider disallowing modules if none of their bindings are used.
public final ImmutableMap<ComponentRequirement, VariableElement> factoryMethodParameters() {
return factoryMethod().get().getParameters().stream()
.collect(
toImmutableMap(
parameter -> ComponentRequirement.forModule(parameter.asType()),
parameter -> parameter));
}
/**
* The types for which the component needs instances.
*
* <ul>
* <li>component dependencies
* <li>owned modules with concrete instance bindings that are used in the graph
* <li>bound instances
* </ul>
*/
@Memoized
public ImmutableSet<ComponentRequirement> componentRequirements() {
ImmutableSet<TypeElement> requiredModules =
stream(Traverser.forTree(BindingGraph::subgraphs).depthFirstPostOrder(this))
.flatMap(graph -> graph.bindingModules.stream())
.filter(ownedModuleTypes()::contains)
.collect(toImmutableSet());
ImmutableSet.Builder<ComponentRequirement> requirements = ImmutableSet.builder();
componentDescriptor().requirements().stream()
.filter(
requirement ->
!requirement.kind().isModule()
|| requiredModules.contains(requirement.typeElement()))
.forEach(requirements::add);
if (factoryMethod().isPresent()) {
requirements.addAll(factoryMethodParameters().keySet());
}
return requirements.build();
}
/** Returns all {@link ComponentDescriptor}s in the {@link TopLevelBindingGraph}. */
public final ImmutableSet<ComponentDescriptor> componentDescriptors() {
return topLevelBindingGraph().componentNodes().stream()
.map(componentNode -> ((ComponentNodeImpl) componentNode).componentDescriptor())
.collect(toImmutableSet());
}
@Memoized
public ImmutableList<BindingGraph> subgraphs() {
return topLevelBindingGraph().subcomponentNodes(componentNode()).stream()
.map(subcomponent -> create(Optional.of(this), subcomponent, topLevelBindingGraph()))
.collect(toImmutableList());
}
public final ImmutableSet<BindingNode> bindingNodes(Key key) {
ImmutableSet.Builder<BindingNode> builder = ImmutableSet.builder();
if (contributionBindings.containsKey(key)) {
builder.add(contributionBindings.get(key));
}
if (membersInjectionBindings.containsKey(key)) {
builder.add(membersInjectionBindings.get(key));
}
return builder.build();
}
@Memoized
public ImmutableSet<BindingNode> bindingNodes() {
return ImmutableSet.<BindingNode>builder()
.addAll(contributionBindings.values())
.addAll(membersInjectionBindings.values())
.build();
}
// TODO(bcorso): Move this to ComponentPath
private static boolean isSubpath(ComponentPath path, ComponentPath subpath) {
if (path.components().size() < subpath.components().size()) {
return false;
}
for (int i = 0; i < subpath.components().size(); i++) {
if (!path.components().get(i).equals(subpath.components().get(i))) {
return false;
}
}
return true;
}
}