| /* |
| * 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.auto.common.MoreTypes.asTypeElement; |
| import static com.google.auto.common.MoreTypes.isType; |
| import static com.google.auto.common.MoreTypes.isTypeOf; |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static dagger.internal.codegen.base.RequestKinds.getRequestKind; |
| import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; |
| import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedFactoryType; |
| import static dagger.internal.codegen.binding.ComponentDescriptor.isComponentContributionMethod; |
| import static dagger.internal.codegen.binding.SourceFiles.generatedMonitoringModuleName; |
| import static dagger.model.BindingKind.ASSISTED_INJECTION; |
| import static dagger.model.BindingKind.DELEGATE; |
| import static dagger.model.BindingKind.INJECTION; |
| import static dagger.model.BindingKind.OPTIONAL; |
| import static dagger.model.BindingKind.SUBCOMPONENT_CREATOR; |
| import static dagger.model.RequestKind.MEMBERS_INJECTION; |
| import static java.util.function.Predicate.isEqual; |
| import static javax.lang.model.util.ElementFilter.methodsIn; |
| |
| import com.google.auto.common.MoreTypes; |
| import com.google.common.collect.HashMultimap; |
| 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.Multimaps; |
| import dagger.MembersInjector; |
| import dagger.Reusable; |
| import dagger.internal.codegen.base.ClearableCache; |
| import dagger.internal.codegen.base.ContributionType; |
| import dagger.internal.codegen.base.Keys; |
| import dagger.internal.codegen.base.MapType; |
| import dagger.internal.codegen.base.OptionalType; |
| import dagger.internal.codegen.compileroption.CompilerOptions; |
| import dagger.internal.codegen.langmodel.DaggerElements; |
| import dagger.model.DependencyRequest; |
| import dagger.model.Key; |
| import dagger.model.Scope; |
| import dagger.producers.Produced; |
| import dagger.producers.Producer; |
| import dagger.producers.internal.ProductionExecutorModule; |
| import java.util.ArrayDeque; |
| import java.util.Deque; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Queue; |
| import java.util.Set; |
| import javax.inject.Inject; |
| import javax.inject.Provider; |
| import javax.inject.Singleton; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.type.TypeKind; |
| |
| /** A factory for {@link BindingGraph} objects. */ |
| @Singleton |
| public final class BindingGraphFactory implements ClearableCache { |
| |
| private final DaggerElements elements; |
| private final InjectBindingRegistry injectBindingRegistry; |
| private final KeyFactory keyFactory; |
| private final BindingFactory bindingFactory; |
| private final ModuleDescriptor.Factory moduleDescriptorFactory; |
| private final BindingGraphConverter bindingGraphConverter; |
| private final Map<Key, ImmutableSet<Key>> keysMatchingRequestCache = new HashMap<>(); |
| private final CompilerOptions compilerOptions; |
| |
| @Inject |
| BindingGraphFactory( |
| DaggerElements elements, |
| InjectBindingRegistry injectBindingRegistry, |
| KeyFactory keyFactory, |
| BindingFactory bindingFactory, |
| ModuleDescriptor.Factory moduleDescriptorFactory, |
| BindingGraphConverter bindingGraphConverter, |
| CompilerOptions compilerOptions) { |
| this.elements = elements; |
| this.injectBindingRegistry = injectBindingRegistry; |
| this.keyFactory = keyFactory; |
| this.bindingFactory = bindingFactory; |
| this.moduleDescriptorFactory = moduleDescriptorFactory; |
| this.bindingGraphConverter = bindingGraphConverter; |
| this.compilerOptions = compilerOptions; |
| } |
| |
| /** |
| * Creates a binding graph for a component. |
| * |
| * @param createFullBindingGraph if {@code true}, the binding graph will include all bindings; |
| * otherwise it will include only bindings reachable from at least one entry point |
| */ |
| public BindingGraph create( |
| ComponentDescriptor componentDescriptor, boolean createFullBindingGraph) { |
| return bindingGraphConverter.convert( |
| createLegacyBindingGraph(Optional.empty(), componentDescriptor, createFullBindingGraph), |
| createFullBindingGraph); |
| } |
| |
| private LegacyBindingGraph createLegacyBindingGraph( |
| Optional<Resolver> parentResolver, |
| ComponentDescriptor componentDescriptor, |
| boolean createFullBindingGraph) { |
| ImmutableSet.Builder<ContributionBinding> explicitBindingsBuilder = ImmutableSet.builder(); |
| ImmutableSet.Builder<DelegateDeclaration> delegatesBuilder = ImmutableSet.builder(); |
| ImmutableSet.Builder<OptionalBindingDeclaration> optionalsBuilder = ImmutableSet.builder(); |
| |
| if (componentDescriptor.isRealComponent()) { |
| // binding for the component itself |
| explicitBindingsBuilder.add( |
| bindingFactory.componentBinding(componentDescriptor.typeElement())); |
| } |
| |
| // Collect Component dependencies. |
| for (ComponentRequirement dependency : componentDescriptor.dependencies()) { |
| explicitBindingsBuilder.add(bindingFactory.componentDependencyBinding(dependency)); |
| List<ExecutableElement> dependencyMethods = |
| methodsIn(elements.getAllMembers(dependency.typeElement())); |
| |
| // Within a component dependency, we want to allow the same method to appear multiple |
| // times assuming it is the exact same method. We do this by tracking a set of bindings |
| // we've already added with the binding element removed since that is the only thing |
| // allowed to differ. |
| HashMultimap<String, ContributionBinding> dedupeBindings = HashMultimap.create(); |
| for (ExecutableElement method : dependencyMethods) { |
| // MembersInjection methods aren't "provided" explicitly, so ignore them. |
| if (isComponentContributionMethod(elements, method)) { |
| ContributionBinding binding = bindingFactory.componentDependencyMethodBinding( |
| componentDescriptor, method); |
| if (dedupeBindings.put( |
| method.getSimpleName().toString(), |
| // Remove the binding element since we know that will be different, but everything |
| // else we want to be the same to consider it a duplicate. |
| binding.toBuilder().clearBindingElement().build())) { |
| explicitBindingsBuilder.add(binding); |
| } |
| } |
| } |
| } |
| |
| // Collect bindings on the creator. |
| componentDescriptor |
| .creatorDescriptor() |
| .ifPresent( |
| creatorDescriptor -> |
| creatorDescriptor.boundInstanceRequirements().stream() |
| .map( |
| requirement -> |
| bindingFactory.boundInstanceBinding( |
| requirement, creatorDescriptor.elementForRequirement(requirement))) |
| .forEach(explicitBindingsBuilder::add)); |
| |
| componentDescriptor |
| .childComponentsDeclaredByBuilderEntryPoints() |
| .forEach( |
| (builderEntryPoint, childComponent) -> { |
| if (!componentDescriptor |
| .childComponentsDeclaredByModules() |
| .contains(childComponent)) { |
| explicitBindingsBuilder.add( |
| bindingFactory.subcomponentCreatorBinding( |
| builderEntryPoint.methodElement(), componentDescriptor.typeElement())); |
| } |
| }); |
| |
| ImmutableSet.Builder<MultibindingDeclaration> multibindingDeclarations = ImmutableSet.builder(); |
| ImmutableSet.Builder<SubcomponentDeclaration> subcomponentDeclarations = ImmutableSet.builder(); |
| |
| // Collect transitive module bindings and multibinding declarations. |
| for (ModuleDescriptor moduleDescriptor : modules(componentDescriptor, parentResolver)) { |
| explicitBindingsBuilder.addAll(moduleDescriptor.bindings()); |
| multibindingDeclarations.addAll(moduleDescriptor.multibindingDeclarations()); |
| subcomponentDeclarations.addAll(moduleDescriptor.subcomponentDeclarations()); |
| delegatesBuilder.addAll(moduleDescriptor.delegateDeclarations()); |
| optionalsBuilder.addAll(moduleDescriptor.optionalDeclarations()); |
| } |
| |
| final Resolver requestResolver = |
| new Resolver( |
| parentResolver, |
| componentDescriptor, |
| indexBindingDeclarationsByKey(explicitBindingsBuilder.build()), |
| indexBindingDeclarationsByKey(multibindingDeclarations.build()), |
| indexBindingDeclarationsByKey(subcomponentDeclarations.build()), |
| indexBindingDeclarationsByKey(delegatesBuilder.build()), |
| indexBindingDeclarationsByKey(optionalsBuilder.build())); |
| |
| componentDescriptor.entryPointMethods().stream() |
| .map(method -> method.dependencyRequest().get()) |
| .forEach( |
| entryPoint -> { |
| if (entryPoint.kind().equals(MEMBERS_INJECTION)) { |
| requestResolver.resolveMembersInjection(entryPoint.key()); |
| } else { |
| requestResolver.resolve(entryPoint.key()); |
| } |
| }); |
| |
| if (createFullBindingGraph) { |
| // Resolve the keys for all bindings in all modules, stripping any multibinding contribution |
| // identifier so that the multibinding itself is resolved. |
| modules(componentDescriptor, parentResolver).stream() |
| .flatMap(module -> module.allBindingKeys().stream()) |
| .map(key -> key.toBuilder().multibindingContributionIdentifier(Optional.empty()).build()) |
| .forEach(requestResolver::resolve); |
| } |
| |
| // Resolve all bindings for subcomponents, creating subgraphs for all subcomponents that have |
| // been detected during binding resolution. If a binding for a subcomponent is never resolved, |
| // no BindingGraph will be created for it and no implementation will be generated. This is |
| // done in a queue since resolving one subcomponent might resolve a key for a subcomponent |
| // from a parent graph. This is done until no more new subcomponents are resolved. |
| Set<ComponentDescriptor> resolvedSubcomponents = new HashSet<>(); |
| ImmutableList.Builder<LegacyBindingGraph> subgraphs = ImmutableList.builder(); |
| for (ComponentDescriptor subcomponent : |
| Iterables.consumingIterable(requestResolver.subcomponentsToResolve)) { |
| if (resolvedSubcomponents.add(subcomponent)) { |
| subgraphs.add( |
| createLegacyBindingGraph( |
| Optional.of(requestResolver), subcomponent, createFullBindingGraph)); |
| } |
| } |
| |
| return new LegacyBindingGraph( |
| componentDescriptor, |
| ImmutableMap.copyOf(requestResolver.getResolvedContributionBindings()), |
| ImmutableMap.copyOf(requestResolver.getResolvedMembersInjectionBindings()), |
| ImmutableList.copyOf(subgraphs.build())); |
| } |
| |
| /** |
| * Returns all the modules that should be installed in the component. For production components |
| * and production subcomponents that have a parent that is not a production component or |
| * subcomponent, also includes the production monitoring module for the component and the |
| * production executor module. |
| */ |
| private ImmutableSet<ModuleDescriptor> modules( |
| ComponentDescriptor componentDescriptor, Optional<Resolver> parentResolver) { |
| return shouldIncludeImplicitProductionModules(componentDescriptor, parentResolver) |
| ? new ImmutableSet.Builder<ModuleDescriptor>() |
| .addAll(componentDescriptor.modules()) |
| .add(descriptorForMonitoringModule(componentDescriptor.typeElement())) |
| .add(descriptorForProductionExecutorModule()) |
| .build() |
| : componentDescriptor.modules(); |
| } |
| |
| private boolean shouldIncludeImplicitProductionModules( |
| ComponentDescriptor component, Optional<Resolver> parentResolver) { |
| return component.isProduction() |
| && ((!component.isSubcomponent() && component.isRealComponent()) |
| || (parentResolver.isPresent() |
| && !parentResolver.get().componentDescriptor.isProduction())); |
| } |
| |
| /** |
| * Returns a descriptor for a generated module that handles monitoring for production components. |
| * This module is generated in the {@link |
| * dagger.internal.codegen.validation.MonitoringModuleProcessingStep}. |
| * |
| * @throws TypeNotPresentException if the module has not been generated yet. This will cause the |
| * processor to retry in a later processing round. |
| */ |
| private ModuleDescriptor descriptorForMonitoringModule(TypeElement componentDefinitionType) { |
| return moduleDescriptorFactory.create( |
| elements.checkTypePresent( |
| generatedMonitoringModuleName(componentDefinitionType).toString())); |
| } |
| |
| /** Returns a descriptor {@link ProductionExecutorModule}. */ |
| private ModuleDescriptor descriptorForProductionExecutorModule() { |
| return moduleDescriptorFactory.create(elements.getTypeElement(ProductionExecutorModule.class)); |
| } |
| |
| /** Indexes {@code bindingDeclarations} by {@link BindingDeclaration#key()}. */ |
| private static <T extends BindingDeclaration> |
| ImmutableSetMultimap<Key, T> indexBindingDeclarationsByKey(Iterable<T> declarations) { |
| return ImmutableSetMultimap.copyOf(Multimaps.index(declarations, BindingDeclaration::key)); |
| } |
| |
| @Override |
| public void clearCache() { |
| keysMatchingRequestCache.clear(); |
| } |
| |
| private final class Resolver { |
| final Optional<Resolver> parentResolver; |
| final ComponentDescriptor componentDescriptor; |
| final ImmutableSetMultimap<Key, ContributionBinding> explicitBindings; |
| final ImmutableSet<ContributionBinding> explicitBindingsSet; |
| final ImmutableSetMultimap<Key, ContributionBinding> explicitMultibindings; |
| final ImmutableSetMultimap<Key, MultibindingDeclaration> multibindingDeclarations; |
| final ImmutableSetMultimap<Key, SubcomponentDeclaration> subcomponentDeclarations; |
| final ImmutableSetMultimap<Key, DelegateDeclaration> delegateDeclarations; |
| final ImmutableSetMultimap<Key, OptionalBindingDeclaration> optionalBindingDeclarations; |
| final ImmutableSetMultimap<Key, DelegateDeclaration> delegateMultibindingDeclarations; |
| final Map<Key, ResolvedBindings> resolvedContributionBindings = new LinkedHashMap<>(); |
| final Map<Key, ResolvedBindings> resolvedMembersInjectionBindings = new LinkedHashMap<>(); |
| final Deque<Key> cycleStack = new ArrayDeque<>(); |
| final Map<Key, Boolean> keyDependsOnLocalBindingsCache = new HashMap<>(); |
| final Map<Binding, Boolean> bindingDependsOnLocalBindingsCache = new HashMap<>(); |
| final Queue<ComponentDescriptor> subcomponentsToResolve = new ArrayDeque<>(); |
| |
| Resolver( |
| Optional<Resolver> parentResolver, |
| ComponentDescriptor componentDescriptor, |
| ImmutableSetMultimap<Key, ContributionBinding> explicitBindings, |
| ImmutableSetMultimap<Key, MultibindingDeclaration> multibindingDeclarations, |
| ImmutableSetMultimap<Key, SubcomponentDeclaration> subcomponentDeclarations, |
| ImmutableSetMultimap<Key, DelegateDeclaration> delegateDeclarations, |
| ImmutableSetMultimap<Key, OptionalBindingDeclaration> optionalBindingDeclarations) { |
| this.parentResolver = parentResolver; |
| this.componentDescriptor = checkNotNull(componentDescriptor); |
| this.explicitBindings = checkNotNull(explicitBindings); |
| this.explicitBindingsSet = ImmutableSet.copyOf(explicitBindings.values()); |
| this.multibindingDeclarations = checkNotNull(multibindingDeclarations); |
| this.subcomponentDeclarations = checkNotNull(subcomponentDeclarations); |
| this.delegateDeclarations = checkNotNull(delegateDeclarations); |
| this.optionalBindingDeclarations = checkNotNull(optionalBindingDeclarations); |
| this.explicitMultibindings = multibindingContributionsByMultibindingKey(explicitBindingsSet); |
| this.delegateMultibindingDeclarations = |
| multibindingContributionsByMultibindingKey(delegateDeclarations.values()); |
| subcomponentsToResolve.addAll( |
| componentDescriptor.childComponentsDeclaredByFactoryMethods().values()); |
| subcomponentsToResolve.addAll( |
| componentDescriptor.childComponentsDeclaredByBuilderEntryPoints().values()); |
| } |
| |
| /** |
| * Returns the resolved contribution bindings for the given {@link Key}: |
| * |
| * <ul> |
| * <li>All explicit bindings for: |
| * <ul> |
| * <li>the requested key |
| * <li>{@code Set<T>} if the requested key's type is {@code Set<Produced<T>>} |
| * <li>{@code Map<K, Provider<V>>} if the requested key's type is {@code Map<K, |
| * Producer<V>>}. |
| * </ul> |
| * <li>An implicit {@link Inject @Inject}-annotated constructor binding if there is one and |
| * there are no explicit bindings or synthetic bindings. |
| * </ul> |
| */ |
| ResolvedBindings lookUpBindings(Key requestKey) { |
| Set<ContributionBinding> bindings = new LinkedHashSet<>(); |
| Set<ContributionBinding> multibindingContributions = new LinkedHashSet<>(); |
| Set<MultibindingDeclaration> multibindingDeclarations = new LinkedHashSet<>(); |
| Set<OptionalBindingDeclaration> optionalBindingDeclarations = new LinkedHashSet<>(); |
| Set<SubcomponentDeclaration> subcomponentDeclarations = new LinkedHashSet<>(); |
| |
| // Gather all bindings, multibindings, optional, and subcomponent declarations/contributions. |
| ImmutableSet<Key> keysMatchingRequest = keysMatchingRequest(requestKey); |
| for (Resolver resolver : getResolverLineage()) { |
| bindings.addAll(resolver.getLocalExplicitBindings(requestKey)); |
| |
| for (Key key : keysMatchingRequest) { |
| multibindingContributions.addAll(resolver.getLocalExplicitMultibindings(key)); |
| multibindingDeclarations.addAll(resolver.multibindingDeclarations.get(key)); |
| subcomponentDeclarations.addAll(resolver.subcomponentDeclarations.get(key)); |
| // The optional binding declarations are keyed by the unwrapped type. |
| keyFactory.unwrapOptional(key) |
| .map(resolver.optionalBindingDeclarations::get) |
| .ifPresent(optionalBindingDeclarations::addAll); |
| } |
| } |
| |
| // Add synthetic multibinding |
| if (!multibindingContributions.isEmpty() || !multibindingDeclarations.isEmpty()) { |
| bindings.add(bindingFactory.syntheticMultibinding(requestKey, multibindingContributions)); |
| } |
| |
| // Add synthetic optional binding |
| if (!optionalBindingDeclarations.isEmpty()) { |
| bindings.add( |
| bindingFactory.syntheticOptionalBinding( |
| requestKey, |
| getRequestKind(OptionalType.from(requestKey).valueType()), |
| lookUpBindings(keyFactory.unwrapOptional(requestKey).get()).bindings())); |
| } |
| |
| // Add subcomponent creator binding |
| if (!subcomponentDeclarations.isEmpty()) { |
| ProvisionBinding binding = |
| bindingFactory.subcomponentCreatorBinding( |
| ImmutableSet.copyOf(subcomponentDeclarations)); |
| bindings.add(binding); |
| addSubcomponentToOwningResolver(binding); |
| } |
| |
| // Add members injector binding |
| if (isType(requestKey.type()) && isTypeOf(MembersInjector.class, requestKey.type())) { |
| injectBindingRegistry |
| .getOrFindMembersInjectorProvisionBinding(requestKey) |
| .ifPresent(bindings::add); |
| } |
| |
| // Add Assisted Factory binding |
| if (isType(requestKey.type()) |
| && requestKey.type().getKind() == TypeKind.DECLARED |
| && isAssistedFactoryType(asTypeElement(requestKey.type()))) { |
| bindings.add( |
| bindingFactory.assistedFactoryBinding( |
| asTypeElement(requestKey.type()), Optional.of(requestKey.type()))); |
| } |
| |
| // If there are no bindings, add the implicit @Inject-constructed binding if there is one. |
| if (bindings.isEmpty()) { |
| injectBindingRegistry |
| .getOrFindProvisionBinding(requestKey) |
| .filter(this::isCorrectlyScopedInSubcomponent) |
| .ifPresent(bindings::add); |
| } |
| |
| return ResolvedBindings.forContributionBindings( |
| requestKey, |
| Multimaps.index(bindings, binding -> getOwningComponent(requestKey, binding)), |
| multibindingDeclarations, |
| subcomponentDeclarations, |
| optionalBindingDeclarations); |
| } |
| |
| /** |
| * Returns true if this binding graph resolution is for a subcomponent and the {@code @Inject} |
| * binding's scope correctly matches one of the components in the current component ancestry. |
| * If not, it means the binding is not owned by any of the currently known components, and will |
| * be owned by a future ancestor (or, if never owned, will result in an incompatibly scoped |
| * binding error at the root component). |
| */ |
| private boolean isCorrectlyScopedInSubcomponent(ProvisionBinding binding) { |
| checkArgument(binding.kind() == INJECTION || binding.kind() == ASSISTED_INJECTION); |
| if (!rootComponent().isSubcomponent() |
| || !binding.scope().isPresent() |
| || binding.scope().get().isReusable()) { |
| return true; |
| } |
| |
| Resolver owningResolver = getOwningResolver(binding).orElse(this); |
| ComponentDescriptor owningComponent = owningResolver.componentDescriptor; |
| return owningComponent.scopes().contains(binding.scope().get()); |
| } |
| |
| private ComponentDescriptor rootComponent() { |
| return parentResolver.map(Resolver::rootComponent).orElse(componentDescriptor); |
| } |
| |
| /** Returns the resolved members injection bindings for the given {@link Key}. */ |
| ResolvedBindings lookUpMembersInjectionBinding(Key requestKey) { |
| // no explicit deps for members injection, so just look it up |
| Optional<MembersInjectionBinding> binding = |
| injectBindingRegistry.getOrFindMembersInjectionBinding(requestKey); |
| return binding.isPresent() |
| ? ResolvedBindings.forMembersInjectionBinding( |
| requestKey, componentDescriptor, binding.get()) |
| : ResolvedBindings.noBindings(requestKey); |
| } |
| |
| /** |
| * When a binding is resolved for a {@link SubcomponentDeclaration}, adds corresponding {@link |
| * ComponentDescriptor subcomponent} to a queue in the owning component's resolver. The queue |
| * will be used to detect which subcomponents need to be resolved. |
| */ |
| private void addSubcomponentToOwningResolver(ProvisionBinding subcomponentCreatorBinding) { |
| checkArgument(subcomponentCreatorBinding.kind().equals(SUBCOMPONENT_CREATOR)); |
| Resolver owningResolver = getOwningResolver(subcomponentCreatorBinding).get(); |
| |
| TypeElement builderType = MoreTypes.asTypeElement(subcomponentCreatorBinding.key().type()); |
| owningResolver.subcomponentsToResolve.add( |
| owningResolver.componentDescriptor.getChildComponentWithBuilderType(builderType)); |
| } |
| |
| /** |
| * Profiling has determined that computing the keys matching {@code requestKey} has measurable |
| * performance impact. It is called repeatedly (at least 3 times per key resolved per {@link |
| * BindingGraph}. {@code javac}'s name-checking performance seems suboptimal (converting byte |
| * strings to Strings repeatedly), and the matching keys creations relies on that. This also |
| * ensures that the resulting keys have their hash codes cached on successive calls to this |
| * method. |
| * |
| * <p>This caching may become obsolete if: |
| * |
| * <ul> |
| * <li>We decide to intern all {@link Key} instances |
| * <li>We fix javac's name-checking peformance (though we may want to keep this for older |
| * javac users) |
| * </ul> |
| */ |
| private ImmutableSet<Key> keysMatchingRequest(Key requestKey) { |
| return keysMatchingRequestCache.computeIfAbsent( |
| requestKey, this::keysMatchingRequestUncached); |
| } |
| |
| private ImmutableSet<Key> keysMatchingRequestUncached(Key requestKey) { |
| ImmutableSet.Builder<Key> keys = ImmutableSet.builder(); |
| keys.add(requestKey); |
| keyFactory.unwrapSetKey(requestKey, Produced.class).ifPresent(keys::add); |
| keyFactory.rewrapMapKey(requestKey, Producer.class, Provider.class).ifPresent(keys::add); |
| keyFactory.rewrapMapKey(requestKey, Provider.class, Producer.class).ifPresent(keys::add); |
| keys.addAll(keyFactory.implicitFrameworkMapKeys(requestKey)); |
| return keys.build(); |
| } |
| |
| private ImmutableSet<ContributionBinding> createDelegateBindings( |
| ImmutableSet<DelegateDeclaration> delegateDeclarations) { |
| ImmutableSet.Builder<ContributionBinding> builder = ImmutableSet.builder(); |
| for (DelegateDeclaration delegateDeclaration : delegateDeclarations) { |
| builder.add(createDelegateBinding(delegateDeclaration)); |
| } |
| return builder.build(); |
| } |
| |
| /** |
| * Creates one (and only one) delegate binding for a delegate declaration, based on the resolved |
| * bindings of the right-hand-side of a {@link dagger.Binds} method. If there are duplicate |
| * bindings for the dependency key, there should still be only one binding for the delegate key. |
| */ |
| private ContributionBinding createDelegateBinding(DelegateDeclaration delegateDeclaration) { |
| Key delegateKey = delegateDeclaration.delegateRequest().key(); |
| if (cycleStack.contains(delegateKey)) { |
| return bindingFactory.unresolvedDelegateBinding(delegateDeclaration); |
| } |
| |
| ResolvedBindings resolvedDelegate; |
| try { |
| cycleStack.push(delegateKey); |
| resolvedDelegate = lookUpBindings(delegateKey); |
| } finally { |
| cycleStack.pop(); |
| } |
| if (resolvedDelegate.contributionBindings().isEmpty()) { |
| // This is guaranteed to result in a missing binding error, so it doesn't matter if the |
| // binding is a Provision or Production, except if it is a @IntoMap method, in which |
| // case the key will be of type Map<K, Provider<V>>, which will be "upgraded" into a |
| // Map<K, Producer<V>> if it's requested in a ProductionComponent. This may result in a |
| // strange error, that the RHS needs to be provided with an @Inject or @Provides |
| // annotated method, but a user should be able to figure out if a @Produces annotation |
| // is needed. |
| // TODO(gak): revisit how we model missing delegates if/when we clean up how we model |
| // binding declarations |
| return bindingFactory.unresolvedDelegateBinding(delegateDeclaration); |
| } |
| // It doesn't matter which of these is selected, since they will later on produce a |
| // duplicate binding error. |
| ContributionBinding explicitDelegate = |
| resolvedDelegate.contributionBindings().iterator().next(); |
| return bindingFactory.delegateBinding(delegateDeclaration, explicitDelegate); |
| } |
| |
| /** |
| * Returns the component that should contain the framework field for {@code binding}. |
| * |
| * <p>If {@code binding} is either not bound in an ancestor component or depends transitively on |
| * bindings in this component, returns this component. |
| * |
| * <p>Otherwise, resolves {@code request} in this component's parent in order to resolve any |
| * multibinding contributions in the parent, and returns the parent-resolved {@link |
| * ResolvedBindings#owningComponent(ContributionBinding)}. |
| */ |
| private TypeElement getOwningComponent(Key requestKey, ContributionBinding binding) { |
| if (isResolvedInParent(requestKey, binding) |
| && !new LocalDependencyChecker().dependsOnLocalBindings(binding)) { |
| ResolvedBindings parentResolvedBindings = |
| parentResolver.get().resolvedContributionBindings.get(requestKey); |
| return parentResolvedBindings.owningComponent(binding); |
| } else { |
| return componentDescriptor.typeElement(); |
| } |
| } |
| |
| /** |
| * Returns {@code true} if {@code binding} is owned by an ancestor. If so, {@linkplain #resolve |
| * resolves} the {@link Key} in this component's parent. Don't resolve directly in the owning |
| * component in case it depends on multibindings in any of its descendants. |
| */ |
| private boolean isResolvedInParent(Key requestKey, ContributionBinding binding) { |
| Optional<Resolver> owningResolver = getOwningResolver(binding); |
| if (owningResolver.isPresent() && !owningResolver.get().equals(this)) { |
| parentResolver.get().resolve(requestKey); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| private Optional<Resolver> getOwningResolver(ContributionBinding binding) { |
| // TODO(ronshapiro): extract the different pieces of this method into their own methods |
| if ((binding.scope().isPresent() && binding.scope().get().isProductionScope()) |
| || binding.bindingType().equals(BindingType.PRODUCTION)) { |
| for (Resolver requestResolver : getResolverLineage()) { |
| // Resolve @Inject @ProductionScope bindings at the highest production component. |
| if (binding.kind().equals(INJECTION) |
| && requestResolver.componentDescriptor.isProduction()) { |
| return Optional.of(requestResolver); |
| } |
| |
| // Resolve explicit @Produces and @ProductionScope bindings at the highest component that |
| // installs the binding. |
| if (requestResolver.containsExplicitBinding(binding)) { |
| return Optional.of(requestResolver); |
| } |
| } |
| } |
| |
| if (binding.scope().isPresent() && binding.scope().get().isReusable()) { |
| for (Resolver requestResolver : getResolverLineage().reverse()) { |
| // If a @Reusable binding was resolved in an ancestor, use that component. |
| ResolvedBindings resolvedBindings = |
| requestResolver.resolvedContributionBindings.get(binding.key()); |
| if (resolvedBindings != null |
| && resolvedBindings.contributionBindings().contains(binding)) { |
| return Optional.of(requestResolver); |
| } |
| } |
| // If a @Reusable binding was not resolved in any ancestor, resolve it here. |
| return Optional.empty(); |
| } |
| |
| for (Resolver requestResolver : getResolverLineage().reverse()) { |
| if (requestResolver.containsExplicitBinding(binding)) { |
| return Optional.of(requestResolver); |
| } |
| } |
| |
| // look for scope separately. we do this for the case where @Singleton can appear twice |
| // in the †compatibility mode |
| Optional<Scope> bindingScope = binding.scope(); |
| if (bindingScope.isPresent()) { |
| for (Resolver requestResolver : getResolverLineage().reverse()) { |
| if (requestResolver.componentDescriptor.scopes().contains(bindingScope.get())) { |
| return Optional.of(requestResolver); |
| } |
| } |
| } |
| return Optional.empty(); |
| } |
| |
| private boolean containsExplicitBinding(ContributionBinding binding) { |
| return explicitBindingsSet.contains(binding) |
| || resolverContainsDelegateDeclarationForBinding(binding) |
| || subcomponentDeclarations.containsKey(binding.key()); |
| } |
| |
| /** Returns true if {@code binding} was installed in a module in this resolver's component. */ |
| private boolean resolverContainsDelegateDeclarationForBinding(ContributionBinding binding) { |
| if (!binding.kind().equals(DELEGATE)) { |
| return false; |
| } |
| |
| // Map multibinding key values are wrapped with a framework type. This needs to be undone |
| // to look it up in the delegate declarations map. |
| // TODO(erichang): See if we can standardize the way map keys are used in these data |
| // structures, either always wrapped or unwrapped to be consistent and less errorprone. |
| Key bindingKey = binding.key(); |
| if (compilerOptions.strictMultibindingValidation() |
| && binding.contributionType().equals(ContributionType.MAP)) { |
| bindingKey = keyFactory.unwrapMapValueType(bindingKey); |
| } |
| |
| return delegateDeclarations.get(bindingKey).stream() |
| .anyMatch( |
| declaration -> |
| declaration.contributingModule().equals(binding.contributingModule()) |
| && declaration.bindingElement().equals(binding.bindingElement())); |
| } |
| |
| /** Returns the resolver lineage from parent to child. */ |
| private ImmutableList<Resolver> getResolverLineage() { |
| ImmutableList.Builder<Resolver> resolverList = ImmutableList.builder(); |
| for (Optional<Resolver> currentResolver = Optional.of(this); |
| currentResolver.isPresent(); |
| currentResolver = currentResolver.get().parentResolver) { |
| resolverList.add(currentResolver.get()); |
| } |
| return resolverList.build().reverse(); |
| } |
| |
| /** |
| * Returns the explicit {@link ContributionBinding}s that match the {@code key} from this |
| * resolver. |
| */ |
| private ImmutableSet<ContributionBinding> getLocalExplicitBindings(Key key) { |
| return new ImmutableSet.Builder<ContributionBinding>() |
| .addAll(explicitBindings.get(key)) |
| // @Binds @IntoMap declarations have key Map<K, V>, unlike @Provides @IntoMap or @Produces |
| // @IntoMap, which have Map<K, Provider/Producer<V>> keys. So unwrap the key's type's |
| // value type if it's a Map<K, Provider/Producer<V>> before looking in |
| // delegateDeclarations. createDelegateBindings() will create bindings with the properly |
| // wrapped key type. |
| .addAll( |
| createDelegateBindings(delegateDeclarations.get(keyFactory.unwrapMapValueType(key)))) |
| .build(); |
| } |
| |
| /** |
| * Returns the explicit multibinding contributions that contribute to the map or set requested |
| * by {@code key} from this resolver. |
| */ |
| private ImmutableSet<ContributionBinding> getLocalExplicitMultibindings(Key key) { |
| ImmutableSet.Builder<ContributionBinding> multibindings = ImmutableSet.builder(); |
| multibindings.addAll(explicitMultibindings.get(key)); |
| if (!MapType.isMap(key) |
| || MapType.from(key).isRawType() |
| || MapType.from(key).valuesAreFrameworkType()) { |
| // @Binds @IntoMap declarations have key Map<K, V>, unlike @Provides @IntoMap or @Produces |
| // @IntoMap, which have Map<K, Provider/Producer<V>> keys. So unwrap the key's type's |
| // value type if it's a Map<K, Provider/Producer<V>> before looking in |
| // delegateMultibindingDeclarations. createDelegateBindings() will create bindings with the |
| // properly wrapped key type. |
| multibindings.addAll( |
| createDelegateBindings( |
| delegateMultibindingDeclarations.get(keyFactory.unwrapMapValueType(key)))); |
| } |
| return multibindings.build(); |
| } |
| |
| /** |
| * Returns the {@link OptionalBindingDeclaration}s that match the {@code key} from this and all |
| * ancestor resolvers. |
| */ |
| private ImmutableSet<OptionalBindingDeclaration> getOptionalBindingDeclarations(Key key) { |
| Optional<Key> unwrapped = keyFactory.unwrapOptional(key); |
| if (!unwrapped.isPresent()) { |
| return ImmutableSet.of(); |
| } |
| ImmutableSet.Builder<OptionalBindingDeclaration> declarations = ImmutableSet.builder(); |
| for (Resolver resolver : getResolverLineage()) { |
| declarations.addAll(resolver.optionalBindingDeclarations.get(unwrapped.get())); |
| } |
| return declarations.build(); |
| } |
| |
| /** |
| * Returns the {@link ResolvedBindings} for {@code key} that was resolved in this resolver or an |
| * ancestor resolver. Only checks for {@link ContributionBinding}s as {@link |
| * MembersInjectionBinding}s are not inherited. |
| */ |
| private Optional<ResolvedBindings> getPreviouslyResolvedBindings(Key key) { |
| Optional<ResolvedBindings> result = |
| Optional.ofNullable(resolvedContributionBindings.get(key)); |
| if (result.isPresent()) { |
| return result; |
| } else if (parentResolver.isPresent()) { |
| return parentResolver.get().getPreviouslyResolvedBindings(key); |
| } else { |
| return Optional.empty(); |
| } |
| } |
| |
| private void resolveMembersInjection(Key key) { |
| ResolvedBindings bindings = lookUpMembersInjectionBinding(key); |
| resolveDependencies(bindings); |
| resolvedMembersInjectionBindings.put(key, bindings); |
| } |
| |
| void resolve(Key key) { |
| // If we find a cycle, stop resolving. The original request will add it with all of the |
| // other resolved deps. |
| if (cycleStack.contains(key)) { |
| return; |
| } |
| |
| // If the binding was previously resolved in this (sub)component, don't resolve it again. |
| if (resolvedContributionBindings.containsKey(key)) { |
| return; |
| } |
| |
| /* |
| * If the binding was previously resolved in an ancestor component, then we may be able to |
| * avoid resolving it here and just depend on the ancestor component resolution. |
| * |
| * 1. If it depends transitively on multibinding contributions or optional bindings with |
| * bindings from this subcomponent, then we have to resolve it in this subcomponent so |
| * that it sees the local bindings. |
| * |
| * 2. If there are any explicit bindings in this component, they may conflict with those in |
| * the ancestor component, so resolve them here so that conflicts can be caught. |
| */ |
| if (getPreviouslyResolvedBindings(key).isPresent() && !Keys.isComponentOrCreator(key)) { |
| /* Resolve in the parent in case there are multibinding contributions or conflicts in some |
| * component between this one and the previously-resolved one. */ |
| parentResolver.get().resolve(key); |
| if (!new LocalDependencyChecker().dependsOnLocalBindings(key) |
| && getLocalExplicitBindings(key).isEmpty()) { |
| /* Cache the inherited parent component's bindings in case resolving at the parent found |
| * bindings in some component between this one and the previously-resolved one. */ |
| resolvedContributionBindings.put(key, getPreviouslyResolvedBindings(key).get()); |
| return; |
| } |
| } |
| |
| cycleStack.push(key); |
| try { |
| ResolvedBindings bindings = lookUpBindings(key); |
| resolvedContributionBindings.put(key, bindings); |
| resolveDependencies(bindings); |
| } finally { |
| cycleStack.pop(); |
| } |
| } |
| |
| /** |
| * {@link #resolve(Key) Resolves} each of the dependencies of the bindings owned by this |
| * component. |
| */ |
| private void resolveDependencies(ResolvedBindings resolvedBindings) { |
| for (Binding binding : resolvedBindings.bindingsOwnedBy(componentDescriptor)) { |
| for (DependencyRequest dependency : binding.dependencies()) { |
| resolve(dependency.key()); |
| } |
| } |
| } |
| |
| /** |
| * Returns all of the {@link ResolvedBindings} for {@link ContributionBinding}s from this and |
| * all ancestor resolvers, indexed by {@link ResolvedBindings#key()}. |
| */ |
| Map<Key, ResolvedBindings> getResolvedContributionBindings() { |
| Map<Key, ResolvedBindings> bindings = new LinkedHashMap<>(); |
| parentResolver.ifPresent(parent -> bindings.putAll(parent.getResolvedContributionBindings())); |
| bindings.putAll(resolvedContributionBindings); |
| return bindings; |
| } |
| |
| /** |
| * Returns all of the {@link ResolvedBindings} for {@link MembersInjectionBinding} from this |
| * resolvers, indexed by {@link ResolvedBindings#key()}. |
| */ |
| ImmutableMap<Key, ResolvedBindings> getResolvedMembersInjectionBindings() { |
| return ImmutableMap.copyOf(resolvedMembersInjectionBindings); |
| } |
| |
| private final class LocalDependencyChecker { |
| private final Set<Object> cycleChecker = new HashSet<>(); |
| |
| /** |
| * Returns {@code true} if any of the bindings resolved for {@code key} are multibindings with |
| * contributions declared within this component's modules or optional bindings with present |
| * values declared within this component's modules, or if any of its unscoped dependencies |
| * depend on such bindings. |
| * |
| * <p>We don't care about scoped dependencies because they will never depend on bindings from |
| * subcomponents. |
| * |
| * @throws IllegalArgumentException if {@link #getPreviouslyResolvedBindings(Key)} is empty |
| */ |
| private boolean dependsOnLocalBindings(Key key) { |
| // Don't recur infinitely if there are valid cycles in the dependency graph. |
| // http://b/23032377 |
| if (!cycleChecker.add(key)) { |
| return false; |
| } |
| return reentrantComputeIfAbsent( |
| keyDependsOnLocalBindingsCache, key, this::dependsOnLocalBindingsUncached); |
| } |
| |
| /** |
| * Returns {@code true} if {@code binding} is unscoped (or has {@link Reusable @Reusable} |
| * scope) and depends on multibindings with contributions declared within this component's |
| * modules, or if any of its unscoped or {@link Reusable @Reusable} scoped dependencies depend |
| * on such local multibindings. |
| * |
| * <p>We don't care about non-reusable scoped dependencies because they will never depend on |
| * multibindings with contributions from subcomponents. |
| */ |
| private boolean dependsOnLocalBindings(Binding binding) { |
| if (!cycleChecker.add(binding)) { |
| return false; |
| } |
| return reentrantComputeIfAbsent( |
| bindingDependsOnLocalBindingsCache, binding, this::dependsOnLocalBindingsUncached); |
| } |
| |
| private boolean dependsOnLocalBindingsUncached(Key key) { |
| checkArgument( |
| getPreviouslyResolvedBindings(key).isPresent(), |
| "no previously resolved bindings in %s for %s", |
| Resolver.this, |
| key); |
| ResolvedBindings previouslyResolvedBindings = getPreviouslyResolvedBindings(key).get(); |
| if (hasLocalMultibindingContributions(key) |
| || hasLocalOptionalBindingContribution(previouslyResolvedBindings)) { |
| return true; |
| } |
| |
| for (Binding binding : previouslyResolvedBindings.bindings()) { |
| if (dependsOnLocalBindings(binding)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean dependsOnLocalBindingsUncached(Binding binding) { |
| if ((!binding.scope().isPresent() || binding.scope().get().isReusable()) |
| // TODO(beder): Figure out what happens with production subcomponents. |
| && !binding.bindingType().equals(BindingType.PRODUCTION)) { |
| for (DependencyRequest dependency : binding.dependencies()) { |
| if (dependsOnLocalBindings(dependency.key())) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns {@code true} if there is at least one multibinding contribution declared within |
| * this component's modules that matches the key. |
| */ |
| private boolean hasLocalMultibindingContributions(Key requestKey) { |
| return keysMatchingRequest(requestKey) |
| .stream() |
| .anyMatch(key -> !getLocalExplicitMultibindings(key).isEmpty()); |
| } |
| |
| /** |
| * Returns {@code true} if there is a contribution in this component for an {@code |
| * Optional<Foo>} key that has not been contributed in a parent. |
| */ |
| private boolean hasLocalOptionalBindingContribution(ResolvedBindings resolvedBindings) { |
| if (resolvedBindings |
| .contributionBindings() |
| .stream() |
| .map(ContributionBinding::kind) |
| .anyMatch(isEqual(OPTIONAL))) { |
| return !getLocalExplicitBindings(keyFactory.unwrapOptional(resolvedBindings.key()).get()) |
| .isEmpty(); |
| } else { |
| // If a parent contributes a @Provides Optional<Foo> binding and a child has a |
| // @BindsOptionalOf Foo method, the two should conflict, even if there is no binding for |
| // Foo on its own |
| return !getOptionalBindingDeclarations(resolvedBindings.key()).isEmpty(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * A multimap of those {@code declarations} that are multibinding contribution declarations, |
| * indexed by the key of the set or map to which they contribute. |
| */ |
| static <T extends BindingDeclaration> |
| ImmutableSetMultimap<Key, T> multibindingContributionsByMultibindingKey( |
| Iterable<T> declarations) { |
| ImmutableSetMultimap.Builder<Key, T> builder = ImmutableSetMultimap.builder(); |
| for (T declaration : declarations) { |
| if (declaration.key().multibindingContributionIdentifier().isPresent()) { |
| builder.put( |
| declaration |
| .key() |
| .toBuilder() |
| .multibindingContributionIdentifier(Optional.empty()) |
| .build(), |
| declaration); |
| } |
| } |
| return builder.build(); |
| } |
| } |