blob: f78f2caf8f2e768666436c5e7fbaaa9c89d9a4c3 [file] [log] [blame]
/*
* Copyright (C) 2016 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.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Verify.verify;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
import dagger.model.ComponentPath;
import dagger.model.DependencyRequest;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.function.Predicate;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
/**
* An object that traverses the entire component hierarchy, starting from the root component.
*
* <p>Subclasses can override {@link #visitComponent(BindingGraph)} to perform custom logic at each
* component in the tree, and {@link #visitSubcomponentFactoryMethod(BindingGraph, BindingGraph,
* ExecutableElement)} to perform custom logic at each subcomponent factory method.
*/
public class ComponentTreeTraverser {
/** The path from the root graph to the currently visited graph. */
private final Deque<BindingGraph> bindingGraphPath = new ArrayDeque<>();
/** Constructs a traverser for a root (component, not subcomponent) binding graph. */
public ComponentTreeTraverser(BindingGraph rootGraph, CompilerOptions compilerOptions) {
checkArgument(
rootGraph.componentDescriptor().kind().isRoot()
|| compilerOptions.aheadOfTimeSubcomponents(),
"only root graphs can be traversed, not %s",
rootGraph.componentTypeElement().getQualifiedName());
bindingGraphPath.add(rootGraph);
}
/**
* Calls {@link #visitComponent(BindingGraph)} for the root component.
*
* @throws IllegalStateException if a traversal is in progress
*/
public final void traverseComponents() {
checkState(bindingGraphPath.size() == 1);
visitComponent(bindingGraphPath.getFirst());
}
/**
* Called once for each component in a component hierarchy.
*
* <p>Subclasses can override this method to perform whatever logic is required per component.
* They should call the {@code super} implementation if they want to continue the traversal in the
* standard order.
*
* <p>This implementation does the following:
*
* <ol>
* <li>If this component is installed in its parent by a subcomponent factory method, calls
* {@link #visitSubcomponentFactoryMethod(BindingGraph, BindingGraph, ExecutableElement)}.
* <li>For each entry point in the component, calls {@link #visitEntryPoint(DependencyRequest,
* BindingGraph)}.
* <li>For each child component, calls {@link #visitComponent(BindingGraph)}, updating the
* traversal state.
* </ol>
*
* @param graph the currently visited graph
*/
protected void visitComponent(BindingGraph graph) {
if (bindingGraphPath.size() > 1) {
BindingGraph parent = Iterators.get(bindingGraphPath.descendingIterator(), 1);
parent
.componentDescriptor()
.getFactoryMethodForChildComponent(graph.componentDescriptor())
.ifPresent(
childFactoryMethod ->
visitSubcomponentFactoryMethod(
graph, parent, childFactoryMethod.methodElement()));
}
for (ComponentMethodDescriptor entryPointMethod :
graph.componentDescriptor().entryPointMethods()) {
visitEntryPoint(entryPointMethod.dependencyRequest().get(), graph);
}
for (BindingGraph child : graph.subgraphs()) {
bindingGraphPath.addLast(child);
try {
visitComponent(child);
} finally {
verify(bindingGraphPath.removeLast().equals(child));
}
}
}
/**
* Called if this component was installed in its parent by a subcomponent factory method.
*
* <p>This implementation does nothing.
*
* @param graph the currently visited graph
* @param parent the parent graph
* @param factoryMethod the factory method in the parent component that declares that the current
* component is a child
*/
protected void visitSubcomponentFactoryMethod(
BindingGraph graph, BindingGraph parent, ExecutableElement factoryMethod) {}
/**
* Called once for each entry point in a component.
*
* <p>Subclasses can override this method to perform whatever logic is required per entry point.
* They should call the {@code super} implementation if they want to continue the traversal in the
* standard order.
*
* <p>This implementation does nothing.
*
* @param graph the graph for the component that contains the entry point
*/
protected void visitEntryPoint(DependencyRequest entryPoint, BindingGraph graph) {}
/**
* Returns an immutable snapshot of the path from the root component to the currently visited
* component.
*/
protected final ComponentTreePath componentTreePath() {
return ComponentTreePath.create(bindingGraphPath);
}
/**
* A path from the root component to a component within the component tree during a {@linkplain
* ComponentTreeTraverser traversal}.
*/
@AutoValue
public abstract static class ComponentTreePath {
private static ComponentTreePath create(Iterable<BindingGraph> path) {
return new AutoValue_ComponentTreeTraverser_ComponentTreePath(ImmutableList.copyOf(path));
}
/**
* Returns the binding graphs in the path, starting from the {@linkplain #rootGraph() root
* graph} and ending with the {@linkplain #currentGraph() current graph}.
*/
public abstract ImmutableList<BindingGraph> graphsInPath();
/** Returns the binding graph for the component at the end of the path. */
public BindingGraph currentGraph() {
return Iterables.getLast(graphsInPath());
}
/** Returns the type of the component at the end of the path. */
public TypeElement currentComponent() {
return currentGraph().componentTypeElement();
}
/**
* Returns the binding graph for the parent of the {@linkplain #currentGraph() current
* component}.
*
* @throws IllegalStateException if the current graph is the {@linkplain #atRoot() root graph}
*/
public BindingGraph parentGraph() {
checkState(!atRoot());
return graphsInPath().reverse().get(1);
}
/** Returns the binding graph for the root component. */
public BindingGraph rootGraph() {
return graphsInPath().get(0);
}
/**
* Returns {@code true} if the {@linkplain #currentGraph() current graph} is the {@linkplain
* #rootGraph() root graph}.
*/
public boolean atRoot() {
return graphsInPath().size() == 1;
}
/** Returns the rootmost binding graph in the component path among the given components. */
public BindingGraph rootmostGraph(Iterable<ComponentDescriptor> components) {
ImmutableSet<ComponentDescriptor> set = ImmutableSet.copyOf(components);
return rootmostGraph(graph -> set.contains(graph.componentDescriptor()));
}
/** Returns the binding graph within this path that represents the given component. */
public BindingGraph graphForComponent(ComponentDescriptor component) {
checkNotNull(component);
return rootmostGraph(graph -> graph.componentDescriptor().equals(component));
}
/**
* Returns the subpath from the root component to the matching {@code ancestor} of the current
* component.
*/
ComponentTreePath pathFromRootToAncestor(TypeElement ancestor) {
ImmutableList.Builder<BindingGraph> path = ImmutableList.builder();
for (BindingGraph graph : graphsInPath()) {
path.add(graph);
if (graph.componentTypeElement().equals(ancestor)) {
return create(path.build());
}
}
throw new IllegalArgumentException(
String.format("%s is not in the current path: %s", ancestor.getQualifiedName(), this));
}
/**
* Returns the path from the root component to the child of the current component for a {@code
* subcomponent}.
*
* @throws IllegalArgumentException if {@code subcomponent} is not a child of the current
* component
*/
ComponentTreePath childPath(TypeElement subcomponent) {
for (BindingGraph child : currentGraph().subgraphs()) {
if (child.componentTypeElement().equals(subcomponent)) {
return create(
ImmutableList.<BindingGraph>builder().addAll(graphsInPath()).add(child).build());
}
}
throw new IllegalArgumentException(
String.format(
"%s is not a child of %s",
subcomponent.getQualifiedName(),
currentGraph().componentTypeElement().getQualifiedName()));
}
private BindingGraph rootmostGraph(Predicate<? super BindingGraph> predicate) {
return graphsInPath().stream().filter(predicate).findFirst().get();
}
/** Converts this {@link ComponentTreePath} into a {@link ComponentPath}. */
ComponentPath toComponentPath() {
return ComponentPath.create(
graphsInPath().stream().map(BindingGraph::componentTypeElement).collect(toList()));
}
@Override
public final String toString() {
return graphsInPath().stream()
.map(BindingGraph::componentTypeElement)
.map(TypeElement::getQualifiedName)
.collect(joining(" → "));
}
}
}