blob: c42cbcf9bba4197b1d19adfa202a293f24d025a3 [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 com.google.common.base.Preconditions.checkState;
import static dagger.internal.codegen.extension.DaggerStreams.presentValues;
import static dagger.internal.codegen.extension.DaggerStreams.stream;
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.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import com.google.common.graph.Traverser;
import dagger.Subcomponent;
import dagger.model.Key;
import dagger.model.RequestKind;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
// TODO(bcorso): Remove the LegacyBindingGraph after we've migrated to the new BindingGraph.
/** The canonical representation of a full-resolved graph. */
@AutoValue
abstract class LegacyBindingGraph {
abstract ComponentDescriptor componentDescriptor();
/**
* The resolved bindings for all {@link ContributionBinding}s in this graph, keyed by {@link Key}.
*/
// TODO(ronshapiro): when MembersInjectionBinding no longer extends Binding, rename this to
// bindings()
abstract ImmutableMap<Key, ResolvedBindings> contributionBindings();
/**
* The resolved bindings for all {@link MembersInjectionBinding}s in this graph, keyed by {@link
* Key}.
*/
abstract ImmutableMap<Key, ResolvedBindings> membersInjectionBindings();
/**
* Returns the {@link ResolvedBindings resolved bindings} instance for {@code
* bindingExpressionKey}. If the bindings will be used for members injection, a {@link
* ResolvedBindings} with {@linkplain #membersInjectionBindings() members injection bindings} will
* be returned, otherwise a {@link ResolvedBindings} with {@link #contributionBindings()} will be
* returned.
*/
final ResolvedBindings resolvedBindings(BindingRequest request) {
return request.isRequestKind(RequestKind.MEMBERS_INJECTION)
? membersInjectionBindings().get(request.key())
: contributionBindings().get(request.key());
}
final Iterable<ResolvedBindings> resolvedBindings() {
// Don't return an immutable collection - this is only ever used for looping over all bindings
// in the graph. Copying is wasteful, especially if is a hashing collection, since the values
// should all, by definition, be distinct.
// TODO(dpb): consider inlining this to callers and removing this.
return Iterables.concat(membersInjectionBindings().values(), contributionBindings().values());
}
abstract ImmutableList<LegacyBindingGraph> subgraphs();
/**
* The type that defines the component for this graph.
*
* @see ComponentDescriptor#typeElement()
*/
TypeElement componentTypeElement() {
return componentDescriptor().typeElement();
}
/**
* 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.
*/
abstract ImmutableSet<ModuleDescriptor> ownedModules();
@Memoized
ImmutableSet<TypeElement> ownedModuleTypes() {
return FluentIterable.from(ownedModules()).transform(ModuleDescriptor::moduleElement).toSet();
}
/**
* 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 LegacyBindingGraph#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.
abstract Optional<ExecutableElement> factoryMethod();
/**
* Returns a map between the {@linkplain ComponentRequirement component requirement} and the
* corresponding {@link VariableElement} for each module parameter in the {@linkplain
* LegacyBindingGraph#factoryMethod factory method}.
*/
// TODO(dpb): Consider disallowing modules if none of their bindings are used.
ImmutableMap<ComponentRequirement, VariableElement> factoryMethodParameters() {
checkState(factoryMethod().isPresent());
ImmutableMap.Builder<ComponentRequirement, VariableElement> builder = ImmutableMap.builder();
for (VariableElement parameter : factoryMethod().get().getParameters()) {
builder.put(ComponentRequirement.forModule(parameter.asType()), parameter);
}
return builder.build();
}
private static final Traverser<LegacyBindingGraph> SUBGRAPH_TRAVERSER =
Traverser.forTree(LegacyBindingGraph::subgraphs);
/**
* The types for which the component needs instances.
*
* <ul>
* <li>component dependencies
* <li>{@linkplain #ownedModules() owned modules} with concrete instance bindings that are used
* in the graph
* <li>bound instances
* </ul>
*/
@Memoized
ImmutableSet<ComponentRequirement> componentRequirements() {
ImmutableSet<TypeElement> requiredModules = requiredModuleElements();
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();
}
private ImmutableSet<TypeElement> requiredModuleElements() {
return stream(SUBGRAPH_TRAVERSER.depthFirstPostOrder(this))
.flatMap(graph -> graph.bindingModules().stream())
.filter(ownedModuleTypes()::contains)
.collect(toImmutableSet());
}
@Memoized
protected ImmutableSet<TypeElement> bindingModules() {
return contributionBindings().values().stream()
.flatMap(bindings -> bindings.contributionBindings().stream())
.map(ContributionBinding::contributingModule)
.flatMap(presentValues())
.collect(toImmutableSet());
}
/** Returns the {@link ComponentDescriptor}s for this component and its subcomponents. */
ImmutableSet<ComponentDescriptor> componentDescriptors() {
return FluentIterable.from(SUBGRAPH_TRAVERSER.depthFirstPreOrder(this))
.transform(LegacyBindingGraph::componentDescriptor)
.toSet();
}
/**
* {@code true} if this graph contains all bindings installed in the component; {@code false} if
* it contains only those bindings that are reachable from at least one entry point.
*/
abstract boolean isFullBindingGraph();
@Memoized
@Override
public abstract int hashCode();
@Override // Suppresses ErrorProne warning that hashCode was overridden w/o equals
public abstract boolean equals(Object other);
static LegacyBindingGraph create(
ComponentDescriptor componentDescriptor,
Map<Key, ResolvedBindings> resolvedContributionBindingsMap,
Map<Key, ResolvedBindings> resolvedMembersInjectionBindings,
List<LegacyBindingGraph> subgraphs,
Set<ModuleDescriptor> ownedModules,
Optional<ExecutableElement> factoryMethod,
boolean isFullBindingGraph) {
checkForDuplicates(subgraphs);
return new AutoValue_LegacyBindingGraph(
componentDescriptor,
ImmutableMap.copyOf(resolvedContributionBindingsMap),
ImmutableMap.copyOf(resolvedMembersInjectionBindings),
ImmutableList.copyOf(subgraphs),
ImmutableSet.copyOf(ownedModules),
factoryMethod,
isFullBindingGraph);
}
private static final void checkForDuplicates(Iterable<LegacyBindingGraph> graphs) {
Map<TypeElement, Collection<LegacyBindingGraph>> duplicateGraphs =
Maps.filterValues(
Multimaps.index(graphs, graph -> graph.componentDescriptor().typeElement()).asMap(),
overlapping -> overlapping.size() > 1);
if (!duplicateGraphs.isEmpty()) {
throw new IllegalArgumentException("Expected no duplicates: " + duplicateGraphs);
}
}
}