| /* |
| * 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); |
| } |
| } |
| } |