/* * Copyright (C) 2014 Google, Inc. * * 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 com.google.auto.common.MoreTypes; import com.google.auto.value.AutoValue; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; 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.ImmutableSetMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.collect.TreeTraverser; import dagger.Component; import dagger.Subcomponent; import dagger.internal.codegen.Binding.Type; import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor; import dagger.producers.Producer; import dagger.producers.ProductionComponent; import java.util.ArrayDeque; import java.util.Collection; import java.util.Deque; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import javax.inject.Inject; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import static com.google.auto.common.MoreElements.getAnnotationMirror; import static com.google.common.base.Predicates.in; import static com.google.common.base.Verify.verify; import static com.google.common.collect.Iterables.any; import static com.google.common.collect.Sets.union; import static dagger.internal.codegen.BindingKey.Kind.CONTRIBUTION; import static dagger.internal.codegen.ComponentDescriptor.isComponentContributionMethod; import static dagger.internal.codegen.ComponentDescriptor.isComponentProductionMethod; import static dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor.isOfKind; import static dagger.internal.codegen.ComponentDescriptor.ComponentMethodKind.SUBCOMPONENT_BUILDER; import static dagger.internal.codegen.ComponentDescriptor.Kind.PRODUCTION_COMPONENT; import static dagger.internal.codegen.ConfigurationAnnotations.getComponentDependencies; import static dagger.internal.codegen.MembersInjectionBinding.Strategy.INJECT_MEMBERS; import static dagger.internal.codegen.MembersInjectionBinding.Strategy.NO_OP; import static javax.lang.model.element.Modifier.STATIC; /** * The canonical representation of a full-resolved graph. * * @author Gregory Kick */ @AutoValue abstract class BindingGraph { abstract ComponentDescriptor componentDescriptor(); abstract ImmutableMap resolvedBindings(); abstract ImmutableMap subgraphs(); /** * 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 Component * components}, this set will be the same as * {@linkplain ComponentDescriptor#transitiveModules 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 ownedModules(); ImmutableSet ownedModuleTypes() { return FluentIterable.from(ownedModules()) .transform(ModuleDescriptor.getModuleElement()) .toSet(); } private static final TreeTraverser SUBGRAPH_TRAVERSER = new TreeTraverser() { @Override public Iterable children(BindingGraph node) { return node.subgraphs().values(); } }; /** * Returns the set of types necessary to implement the component, but are not part of the injected * graph. This includes modules, component dependencies and an {@link Executor} in the case of * {@link ProductionComponent}. */ ImmutableSet componentRequirements() { return SUBGRAPH_TRAVERSER .preOrderTraversal(this) .transformAndConcat( new Function>() { @Override public Iterable apply(BindingGraph input) { return input.resolvedBindings().values(); } }) .transformAndConcat( new Function>() { @Override public Set apply(ResolvedBindings input) { return (input.bindingKey().kind().equals(CONTRIBUTION)) ? input.contributionBindings() : ImmutableSet.of(); } }) .transformAndConcat( new Function>() { @Override public Set apply(ContributionBinding input) { return input.bindingElement().getModifiers().contains(STATIC) ? ImmutableSet.of() : input.contributedBy().asSet(); } }) .filter(in(ownedModuleTypes())) .append(componentDescriptor().dependencies()) .append(componentDescriptor().executorDependency().asSet()) .toSet(); } ImmutableSet availableDependencies() { return new ImmutableSet.Builder() .addAll(componentDescriptor().transitiveModuleTypes()) .addAll(componentDescriptor().dependencies()) .addAll(componentDescriptor().executorDependency().asSet()) .build(); } static final class Factory { private final Elements elements; private final InjectBindingRegistry injectBindingRegistry; private final Key.Factory keyFactory; private final ProvisionBinding.Factory provisionBindingFactory; private final ProductionBinding.Factory productionBindingFactory; Factory(Elements elements, InjectBindingRegistry injectBindingRegistry, Key.Factory keyFactory, ProvisionBinding.Factory provisionBindingFactory, ProductionBinding.Factory productionBindingFactory) { this.elements = elements; this.injectBindingRegistry = injectBindingRegistry; this.keyFactory = keyFactory; this.provisionBindingFactory = provisionBindingFactory; this.productionBindingFactory = productionBindingFactory; } BindingGraph create(ComponentDescriptor componentDescriptor) { return create(Optional.absent(), componentDescriptor); } private BindingGraph create( Optional parentResolver, ComponentDescriptor componentDescriptor) { ImmutableSet.Builder explicitBindingsBuilder = ImmutableSet.builder(); // binding for the component itself TypeElement componentDefinitionType = componentDescriptor.componentDefinitionType(); explicitBindingsBuilder.add(provisionBindingFactory.forComponent(componentDefinitionType)); // Collect Component dependencies. Optional componentMirror = getAnnotationMirror(componentDefinitionType, Component.class) .or(getAnnotationMirror(componentDefinitionType, ProductionComponent.class)); ImmutableSet componentDependencyTypes = componentMirror.isPresent() ? MoreTypes.asTypeElements(getComponentDependencies(componentMirror.get())) : ImmutableSet.of(); for (TypeElement componentDependency : componentDependencyTypes) { explicitBindingsBuilder.add(provisionBindingFactory.forComponent(componentDependency)); List dependencyMethods = ElementFilter.methodsIn(elements.getAllMembers(componentDependency)); for (ExecutableElement method : dependencyMethods) { // MembersInjection methods aren't "provided" explicitly, so ignore them. if (isComponentContributionMethod(elements, method)) { explicitBindingsBuilder.add( componentDescriptor.kind().equals(PRODUCTION_COMPONENT) && isComponentProductionMethod(elements, method) ? productionBindingFactory.forComponentMethod(method) : provisionBindingFactory.forComponentMethod(method)); } } } // Bindings for subcomponent builders. for (ComponentMethodDescriptor subcomponentMethodDescriptor : Iterables.filter( componentDescriptor.subcomponents().keySet(), isOfKind(SUBCOMPONENT_BUILDER))) { explicitBindingsBuilder.add( provisionBindingFactory.forSubcomponentBuilderMethod( subcomponentMethodDescriptor.methodElement(), componentDescriptor.componentDefinitionType())); } // Collect transitive module bindings. for (ModuleDescriptor moduleDescriptor : componentDescriptor.transitiveModules()) { for (ContributionBinding binding : moduleDescriptor.bindings()) { explicitBindingsBuilder.add(binding); } } Resolver requestResolver = new Resolver( parentResolver, componentDescriptor, explicitBindingsByKey(explicitBindingsBuilder.build())); for (ComponentMethodDescriptor componentMethod : componentDescriptor.componentMethods()) { Optional componentMethodRequest = componentMethod.dependencyRequest(); if (componentMethodRequest.isPresent()) { requestResolver.resolve(componentMethodRequest.get()); } } ImmutableMap.Builder subgraphsBuilder = ImmutableMap.builder(); for (Entry subcomponentEntry : componentDescriptor.subcomponents().entrySet()) { subgraphsBuilder.put( subcomponentEntry.getKey().methodElement(), create(Optional.of(requestResolver), subcomponentEntry.getValue())); } for (ResolvedBindings resolvedBindings : requestResolver.getResolvedBindings().values()) { verify( resolvedBindings.owningComponent().equals(componentDescriptor), "%s is not owned by %s", resolvedBindings, componentDescriptor); } return new AutoValue_BindingGraph( componentDescriptor, requestResolver.getResolvedBindings(), subgraphsBuilder.build(), requestResolver.getOwnedModules()); } private ImmutableSetMultimap explicitBindingsByKey( Iterable bindings) { // Multimaps.index() doesn't do ImmutableSetMultimaps. ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); for (B binding : bindings) { builder.put(binding.key(), binding); } return builder.build(); } private final class Resolver { final Optional parentResolver; final ComponentDescriptor componentDescriptor; final ImmutableSetMultimap explicitBindings; final ImmutableSet explicitBindingsSet; final Map resolvedBindings; final Deque cycleStack = new ArrayDeque<>(); final Cache dependsOnLocalMultibindingsCache = CacheBuilder.newBuilder().build(); Resolver( Optional parentResolver, ComponentDescriptor componentDescriptor, ImmutableSetMultimap explicitBindings) { assert parentResolver != null; this.parentResolver = parentResolver; assert componentDescriptor != null; this.componentDescriptor = componentDescriptor; assert explicitBindings != null; this.explicitBindings = explicitBindings; this.explicitBindingsSet = ImmutableSet.copyOf(explicitBindings.values()); this.resolvedBindings = Maps.newLinkedHashMap(); } /** * Looks up the bindings associated with a given dependency request and returns them. * *

Requests for {@code Map} for which there are only bindings for * {@code Map>} will resolve to a single implicit binding for the latter map * (and similarly for {@link Producer}s). * *

If there are no explicit bindings for a contribution, looks for implicit * {@link Inject @Inject}-annotated constructor types. */ ResolvedBindings lookUpBindings(DependencyRequest request) { BindingKey bindingKey = request.bindingKey(); switch (bindingKey.kind()) { case CONTRIBUTION: // First, check for explicit keys (those from modules and components) ImmutableSet explicitBindingsForKey = getExplicitBindings(bindingKey.key()); // If the key is Map, get its implicit binding keys, which are either // Map> or Map>, and grab their explicit bindings. Optional mapProviderKey = keyFactory.implicitMapProviderKeyFrom(bindingKey.key()); ImmutableSet.Builder explicitMapBindingsBuilder = ImmutableSet.builder(); if (mapProviderKey.isPresent()) { explicitMapBindingsBuilder.addAll(getExplicitBindings(mapProviderKey.get())); } Optional mapProducerKey = keyFactory.implicitMapProducerKeyFrom(bindingKey.key()); if (mapProducerKey.isPresent()) { explicitMapBindingsBuilder.addAll(getExplicitBindings(mapProducerKey.get())); } ImmutableSet explicitMapBindings = explicitMapBindingsBuilder.build(); // If the key is Set>, then we look up bindings by the alternate key Set. Optional setKeyFromProduced = keyFactory.implicitSetKeyFromProduced(bindingKey.key()); ImmutableSet explicitSetBindings = setKeyFromProduced.isPresent() ? getExplicitBindings(setKeyFromProduced.get()) : ImmutableSet.of(); if (!explicitBindingsForKey.isEmpty() || !explicitSetBindings.isEmpty()) { /* If there are any explicit bindings for this key, then combine those with any * conflicting Map> bindings and let the validator fail. */ ImmutableSetMultimap.Builder bindings = ImmutableSetMultimap.builder(); for (ContributionBinding binding : union(explicitBindingsForKey, union(explicitSetBindings, explicitMapBindings))) { bindings.put(getOwningComponent(request, binding), binding); } return ResolvedBindings.forContributionBindings( bindingKey, componentDescriptor, bindings.build()); } else if (any(explicitMapBindings, Binding.Type.PRODUCTION)) { /* If this binding is for Map and there are no explicit Map bindings but * some explicit Map> bindings, then this binding must have only the * implicit dependency on Map>. */ return ResolvedBindings.forContributionBindings( bindingKey, componentDescriptor, productionBindingFactory.implicitMapOfProducerBinding(request)); } else if (any(explicitMapBindings, Binding.Type.PROVISION)) { /* If this binding is for Map and there are no explicit Map bindings but * some explicit Map> bindings, then this binding must have only the * implicit dependency on Map>. */ return ResolvedBindings.forContributionBindings( bindingKey, componentDescriptor, provisionBindingFactory.implicitMapOfProviderBinding(request)); } else { /* If there are no explicit bindings at all, look for an implicit @Inject-constructed * binding. */ Optional provisionBinding = injectBindingRegistry.getOrFindProvisionBinding(bindingKey.key()); ComponentDescriptor owningComponent = provisionBinding.isPresent() && isResolvedInParent(request, provisionBinding.get()) && !shouldOwnParentBinding(request, provisionBinding.get()) ? getOwningResolver(provisionBinding.get()).get().componentDescriptor : componentDescriptor; return ResolvedBindings.forContributionBindings( bindingKey, componentDescriptor, ImmutableSetMultimap.builder() .putAll(owningComponent, provisionBinding.asSet()) .build()); } case MEMBERS_INJECTION: // no explicit deps for members injection, so just look it up return ResolvedBindings.forMembersInjectionBinding( bindingKey, componentDescriptor, rollUpMembersInjectionBindings(bindingKey.key())); default: throw new AssertionError(); } } /** * If {@code binding} should be owned by a parent component, resolves the binding in that * component's resolver and returns that component. Otherwise returns the component for this * resolver. */ private ComponentDescriptor getOwningComponent( DependencyRequest request, ContributionBinding binding) { return isResolvedInParent(request, binding) && !shouldOwnParentBinding(request, binding) ? getOwningResolver(binding).get().componentDescriptor : componentDescriptor; } /** * Returns {@code true} if {@code binding} is owned by a parent resolver. If so, calls * {@link #resolve(DependencyRequest) resolve(request)} on that resolver. */ private boolean isResolvedInParent(DependencyRequest request, ContributionBinding binding) { Optional owningResolver = getOwningResolver(binding); if (owningResolver.isPresent() && !owningResolver.get().equals(this)) { owningResolver.get().resolve(request); return true; } else { return false; } } /** * Returns {@code true} if {@code binding}, which was previously resolved by a parent * resolver, should be moved into this resolver's bindings for {@code request} because it is * unscoped and {@linkplain #dependsOnLocalMultibindings(ResolvedBindings) depends on local * multibindings}, or {@code false} if it can satisfy {@code request} as an inherited binding. */ private boolean shouldOwnParentBinding( DependencyRequest request, ContributionBinding binding) { return !binding.scope().isPresent() && dependsOnLocalMultibindings( getPreviouslyResolvedBindings(request.bindingKey()).get()); } private MembersInjectionBinding rollUpMembersInjectionBindings(Key key) { MembersInjectionBinding membersInjectionBinding = injectBindingRegistry.getOrFindMembersInjectionBinding(key); if (membersInjectionBinding.parentInjectorRequest().isPresent() && membersInjectionBinding.injectionStrategy().equals(INJECT_MEMBERS)) { MembersInjectionBinding parentBinding = rollUpMembersInjectionBindings( membersInjectionBinding.parentInjectorRequest().get().key()); if (parentBinding.injectionStrategy().equals(NO_OP)) { return membersInjectionBinding.withoutParentInjectorRequest(); } } return membersInjectionBinding; } private Optional getOwningResolver(ContributionBinding provisionBinding) { for (Resolver requestResolver : getResolverLineage().reverse()) { if (requestResolver.explicitBindingsSet.contains(provisionBinding)) { return Optional.of(requestResolver); } } // look for scope separately. we do this for the case where @Singleton can appear twice // in the † compatibility mode Scope bindingScope = provisionBinding.scope(); if (bindingScope.isPresent()) { for (Resolver requestResolver : getResolverLineage().reverse()) { if (bindingScope.equals(requestResolver.componentDescriptor.scope())) { return Optional.of(requestResolver); } } } return Optional.absent(); } /** Returns the resolver lineage from parent to child. */ private ImmutableList getResolverLineage() { List resolverList = Lists.newArrayList(); for (Optional currentResolver = Optional.of(this); currentResolver.isPresent(); currentResolver = currentResolver.get().parentResolver) { resolverList.add(currentResolver.get()); } return ImmutableList.copyOf(Lists.reverse(resolverList)); } private ImmutableSet getExplicitBindings(Key requestKey) { ImmutableSet.Builder explicitBindingsForKey = ImmutableSet.builder(); for (Resolver resolver : getResolverLineage()) { explicitBindingsForKey.addAll(resolver.explicitBindings.get(requestKey)); } return explicitBindingsForKey.build(); } private Optional getPreviouslyResolvedBindings( final BindingKey bindingKey) { Optional result = Optional.fromNullable(resolvedBindings.get(bindingKey)); if (result.isPresent()) { return result; } else if (parentResolver.isPresent()) { return parentResolver.get().getPreviouslyResolvedBindings(bindingKey); } else { return Optional.absent(); } } void resolve(DependencyRequest request) { BindingKey bindingKey = request.bindingKey(); // If we find a cycle, stop resolving. The original request will add it with all of the // other resolved deps. if (cycleStack.contains(bindingKey)) { return; } // If the binding was previously resolved in this (sub)component, don't resolve it again. if (resolvedBindings.containsKey(bindingKey)) { return; } // If the binding was previously resolved in a supercomponent, then test to see if it // depends on multibindings with contributions from this subcomponent. If it does, then we // have to resolve it in this subcomponent so that it sees the local contributions. If it // does not, then we can stop resolving it in this subcomponent and rely on the // supercomponent resolution. Optional bindingsPreviouslyResolvedInParent = getPreviouslyResolvedBindings(bindingKey); if (bindingsPreviouslyResolvedInParent.isPresent() && !dependsOnLocalMultibindings(bindingsPreviouslyResolvedInParent.get())) { return; } cycleStack.push(bindingKey); try { ResolvedBindings bindings = lookUpBindings(request); for (Binding binding : bindings.ownedBindings()) { for (DependencyRequest dependency : binding.implicitDependencies()) { resolve(dependency); } } resolvedBindings.put(bindingKey, bindings); } finally { cycleStack.pop(); } } /** * Returns {@code true} if {@code previouslyResolvedBindings} is multibindings with * contributions declared within this (sub)component's modules, or if any of its unscoped * provision-dependencies depend on such local multibindings. * *

We don't care about scoped dependencies or production bindings because they will never * depend on multibindings with contributions from subcomponents. */ private boolean dependsOnLocalMultibindings(ResolvedBindings previouslyResolvedBindings) { return dependsOnLocalMultibindings(previouslyResolvedBindings, new HashSet()); } private boolean dependsOnLocalMultibindings( final ResolvedBindings previouslyResolvedBindings, final Set cycleChecker) { // Don't recur infinitely if there are valid cycles in the dependency graph. if (!cycleChecker.add(previouslyResolvedBindings.bindingKey())) { return false; } try { return dependsOnLocalMultibindingsCache.get( previouslyResolvedBindings.bindingKey(), new Callable() { @Override public Boolean call() { if (previouslyResolvedBindings.isMultibindings() && hasLocalContributions(previouslyResolvedBindings)) { return true; } for (Binding binding : previouslyResolvedBindings.bindings()) { if (!binding.scope().isPresent() && !binding.bindingType().equals(Type.PRODUCTION)) { for (DependencyRequest dependency : binding.implicitDependencies()) { if (dependsOnLocalMultibindings( getPreviouslyResolvedBindings(dependency.bindingKey()).get(), cycleChecker)) { return true; } } } } return false; } }); } catch (ExecutionException e) { throw new AssertionError(e); } } private boolean hasLocalContributions(ResolvedBindings resolvedBindings) { return !explicitBindings.get(resolvedBindings.bindingKey().key()).isEmpty(); } ImmutableMap getResolvedBindings() { ImmutableMap.Builder resolvedBindingsBuilder = ImmutableMap.builder(); resolvedBindingsBuilder.putAll(resolvedBindings); if (parentResolver.isPresent()) { Collection bindingsResolvedInParent = Maps.difference(parentResolver.get().getResolvedBindings(), resolvedBindings) .entriesOnlyOnLeft() .values(); for (ResolvedBindings resolvedInParent : bindingsResolvedInParent) { resolvedBindingsBuilder.put( resolvedInParent.bindingKey(), resolvedInParent.asInheritedIn(componentDescriptor)); } } return resolvedBindingsBuilder.build(); } ImmutableSet getInheritedModules() { return parentResolver.isPresent() ? Sets.union( parentResolver.get().getInheritedModules(), parentResolver.get().componentDescriptor.transitiveModules()) .immutableCopy() : ImmutableSet.of(); } ImmutableSet getOwnedModules() { return Sets.difference(componentDescriptor.transitiveModules(), getInheritedModules()) .immutableCopy(); } } } }