| /* |
| * Copyright (C) 2024 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 androidx.room.compiler.codegen.XTypeNameKt.toJavaPoet; |
| import static com.google.common.collect.Iterables.getOnlyElement; |
| import static dagger.internal.codegen.binding.SourceFiles.generatedMonitoringModuleName; |
| import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; |
| |
| import androidx.room.compiler.processing.XProcessingEnv; |
| import com.google.auto.value.AutoValue; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.ImmutableSetMultimap; |
| import com.google.common.collect.Multimaps; |
| import com.squareup.javapoet.ClassName; |
| import com.squareup.javapoet.ParameterizedTypeName; |
| import com.squareup.javapoet.TypeName; |
| import com.squareup.javapoet.WildcardTypeName; |
| import dagger.internal.codegen.base.DaggerSuperficialValidation; |
| import dagger.internal.codegen.base.FrameworkTypes; |
| import dagger.internal.codegen.javapoet.TypeNames; |
| import dagger.internal.codegen.model.DaggerAnnotation; |
| import dagger.internal.codegen.model.Key; |
| import dagger.internal.codegen.model.Key.MultibindingContributionIdentifier; |
| import java.util.Optional; |
| import javax.inject.Inject; |
| |
| /** Stores the bindings and declarations of a component by key. */ |
| final class ComponentDeclarations { |
| private final KeyFactory keyFactory; |
| private final ImmutableSetMultimap<Key, ContributionBinding> bindings; |
| private final ImmutableSetMultimap<Key, DelegateDeclaration> delegates; |
| private final ImmutableSetMultimap<Key, OptionalBindingDeclaration> optionalBindings; |
| private final ImmutableSetMultimap<Key, SubcomponentDeclaration> subcomponents; |
| private final ImmutableSetMultimap<TypeNameKey, MultibindingDeclaration> multibindings; |
| private final ImmutableSetMultimap<TypeNameKey, ContributionBinding> multibindingContributions; |
| private final ImmutableSetMultimap<TypeNameKey, DelegateDeclaration> |
| delegateMultibindingContributions; |
| |
| private ComponentDeclarations( |
| KeyFactory keyFactory, |
| ImmutableSetMultimap<Key, ContributionBinding> bindings, |
| ImmutableSetMultimap<Key, DelegateDeclaration> delegates, |
| ImmutableSetMultimap<Key, OptionalBindingDeclaration> optionalBindings, |
| ImmutableSetMultimap<Key, SubcomponentDeclaration> subcomponents, |
| ImmutableSetMultimap<TypeNameKey, MultibindingDeclaration> multibindings, |
| ImmutableSetMultimap<TypeNameKey, ContributionBinding> multibindingContributions, |
| ImmutableSetMultimap<TypeNameKey, DelegateDeclaration> delegateMultibindingContributions) { |
| this.keyFactory = keyFactory; |
| this.bindings = bindings; |
| this.delegates = delegates; |
| this.optionalBindings = optionalBindings; |
| this.subcomponents = subcomponents; |
| this.multibindings = multibindings; |
| this.multibindingContributions = multibindingContributions; |
| this.delegateMultibindingContributions = delegateMultibindingContributions; |
| } |
| |
| ImmutableSet<ContributionBinding> bindings(Key key) { |
| return bindings.get(key); |
| } |
| |
| ImmutableSet<DelegateDeclaration> delegates(Key key) { |
| // @Binds @IntoMap declarations have key Map<K, V> but may be requested as |
| // Map<K, Provider/Producer<V>> keys, so unwrap the multibinding map contribution key first. |
| // TODO(b/366277730): This can be simplified to "delegates.get(key)" once the flag for |
| // "useFrameworkTypeInMapMultibindingContributionKey" is removed. |
| return delegates.get( |
| key.multibindingContributionIdentifier().isPresent() |
| // TODO(bcorso): Consider using TypeNameKey here instead of Key, to avoid losing |
| // variance information when unwrapping KSP types (see TypeNameKey's javadoc). |
| ? keyFactory.unwrapMapValueType(key) |
| : key); |
| } |
| |
| /** |
| * Returns the delegate multibinding contributions (e.g. {@code @Binds @IntoMap}) for the given |
| * {@code key}, or an empty set if none exist. |
| * |
| * <p>For map multibindings, the following request keys represent the same underlying binding and |
| * will return the same results: |
| * <ul> |
| * <li> {@code Map<K, V>} |
| * <li> {@code Map<K, Provider<V>>} |
| * <li> {@code Map<K, Producer<V>>} |
| * <li> {@code Map<K, Produced<V>>} |
| * </ul> |
| * |
| * <p>For set multibindings, the following request keys represent the same underlying binding and |
| * will return the same results: |
| * <ul> |
| * <li> {@code Set<V>} |
| * <li> {@code Set<Produced<V>>} |
| * </ul> |
| */ |
| ImmutableSet<DelegateDeclaration> delegateMultibindingContributions(Key key) { |
| return delegateMultibindingContributions.get(unwrapMultibindingKey(key)); |
| } |
| |
| /** |
| * Returns the multibinding declarations (i.e. {@code @Multibinds}) for the given {@code key}, or |
| * an empty set if none exists. |
| * |
| * <p>For map multibindings, the following request keys represent the same underlying binding and |
| * will return the same results: |
| * <ul> |
| * <li> {@code Map<K, V>} |
| * <li> {@code Map<K, Provider<V>>} |
| * <li> {@code Map<K, Producer<V>>} |
| * <li> {@code Map<K, Produced<V>>} |
| * </ul> |
| * |
| * <p>For set multibindings, the following request keys represent the same underlying binding and |
| * will return the same results: |
| * <ul> |
| * <li> {@code Set<V>} |
| * <li> {@code Set<Produced<V>>} |
| * </ul> |
| */ |
| ImmutableSet<MultibindingDeclaration> multibindings(Key key) { |
| return multibindings.get(unwrapMultibindingKey(key)); |
| } |
| |
| /** |
| * Returns the multibinding contributions (e.g. {@code @Provides @IntoMap}) for the given |
| * {@code key}, or an empty set if none exists. |
| * |
| * <p>For map multibindings, the following request keys represent the same underlying binding and |
| * will return the same results: |
| * <ul> |
| * <li> {@code Map<K, V>} |
| * <li> {@code Map<K, Provider<V>>} |
| * <li> {@code Map<K, Producer<V>>} |
| * <li> {@code Map<K, Produced<V>>} |
| * </ul> |
| * |
| * <p>For set multibindings, the following request keys represent the same underlying binding and |
| * will return the same results: |
| * <ul> |
| * <li> {@code Set<V>} |
| * <li> {@code Set<Produced<V>>} |
| * </ul> |
| */ |
| ImmutableSet<ContributionBinding> multibindingContributions(Key key) { |
| return multibindingContributions.get(unwrapMultibindingKey(key)); |
| } |
| |
| ImmutableSet<OptionalBindingDeclaration> optionalBindings(Key key) { |
| return optionalBindings.get(key); |
| } |
| |
| ImmutableSet<SubcomponentDeclaration> subcomponents(Key key) { |
| return subcomponents.get(key); |
| } |
| |
| ImmutableSet<Declaration> allDeclarations() { |
| return ImmutableSet.<Declaration>builder() |
| .addAll(bindings.values()) |
| .addAll(delegates.values()) |
| .addAll(multibindings.values()) |
| .addAll(optionalBindings.values()) |
| .addAll(subcomponents.values()) |
| .build(); |
| } |
| |
| static final class Factory { |
| private final XProcessingEnv processingEnv; |
| private final KeyFactory keyFactory; |
| private final ModuleDescriptor.Factory moduleDescriptorFactory; |
| |
| @Inject |
| Factory( |
| XProcessingEnv processingEnv, |
| KeyFactory keyFactory, |
| ModuleDescriptor.Factory moduleDescriptorFactory) { |
| this.processingEnv = processingEnv; |
| this.keyFactory = keyFactory; |
| this.moduleDescriptorFactory = moduleDescriptorFactory; |
| } |
| |
| ComponentDeclarations create( |
| Optional<ComponentDescriptor> parentDescriptor, ComponentDescriptor descriptor) { |
| ImmutableSet.Builder<ContributionBinding> bindings = ImmutableSet.builder(); |
| ImmutableSet.Builder<DelegateDeclaration> delegates = ImmutableSet.builder(); |
| ImmutableSet.Builder<MultibindingDeclaration> multibindings = ImmutableSet.builder(); |
| ImmutableSet.Builder<OptionalBindingDeclaration> optionalBindings =ImmutableSet.builder(); |
| ImmutableSet.Builder<SubcomponentDeclaration> subcomponents = ImmutableSet.builder(); |
| |
| bindings.addAll(descriptor.bindings()); |
| delegates.addAll(descriptor.delegateDeclarations()); |
| multibindings.addAll(descriptor.multibindingDeclarations()); |
| optionalBindings.addAll(descriptor.optionalBindingDeclarations()); |
| subcomponents.addAll(descriptor.subcomponentDeclarations()); |
| |
| // Note: The implicit production modules are not included directly in the component descriptor |
| // because we don't know whether to install them or not without knowing the parent component. |
| for (ModuleDescriptor module : implicitProductionModules(descriptor, parentDescriptor)) { |
| bindings.addAll(module.bindings()); |
| delegates.addAll(module.delegateDeclarations()); |
| multibindings.addAll(module.multibindingDeclarations()); |
| optionalBindings.addAll(module.optionalDeclarations()); |
| subcomponents.addAll(module.subcomponentDeclarations()); |
| } |
| |
| return new ComponentDeclarations( |
| keyFactory, |
| indexDeclarationsByKey(bindings.build()), |
| indexDeclarationsByKey(delegates.build()), |
| indexDeclarationsByKey(optionalBindings.build()), |
| indexDeclarationsByKey(subcomponents.build()), |
| // The @Multibinds declarations and @IntoSet/@IntoMap multibinding contributions are all |
| // indexed by their "unwrapped" multibinding key (i.e. Map<K, V> or Set<V>) so that we |
| // don't have to check multiple different keys to gather all of the contributions. |
| indexDeclarationsByUnwrappedMultibindingKey(multibindings.build()), |
| indexDeclarationsByUnwrappedMultibindingKey(multibindingContributions(bindings.build())), |
| indexDeclarationsByUnwrappedMultibindingKey( |
| multibindingContributions(delegates.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> implicitProductionModules( |
| ComponentDescriptor descriptor, Optional<ComponentDescriptor> parentDescriptor) { |
| return shouldIncludeImplicitProductionModules(descriptor, parentDescriptor) |
| ? ImmutableSet.of( |
| moduleDescriptorFactory.create( |
| DaggerSuperficialValidation.requireTypeElement( |
| processingEnv, |
| toJavaPoet(generatedMonitoringModuleName(descriptor.typeElement())))), |
| moduleDescriptorFactory.create( |
| processingEnv.requireTypeElement(TypeNames.PRODUCTION_EXECTUTOR_MODULE))) |
| : ImmutableSet.of(); |
| } |
| |
| private static boolean shouldIncludeImplicitProductionModules( |
| ComponentDescriptor descriptor, Optional<ComponentDescriptor> parentDescriptor) { |
| return descriptor.isProduction() |
| && descriptor.isRealComponent() |
| && (parentDescriptor.isEmpty() || !parentDescriptor.get().isProduction()); |
| } |
| |
| /** Indexes {@code bindingDeclarations} by {@link Declaration#key()}. */ |
| private static <T extends Declaration> |
| ImmutableSetMultimap<Key, T> indexDeclarationsByKey(Iterable<T> declarations) { |
| return ImmutableSetMultimap.copyOf(Multimaps.index(declarations, Declaration::key)); |
| } |
| |
| /** Indexes {@code bindingDeclarations} by the unwrapped multibinding key. */ |
| private <T extends Declaration> ImmutableSetMultimap<TypeNameKey, T> |
| indexDeclarationsByUnwrappedMultibindingKey(Iterable<T> declarations) { |
| return ImmutableSetMultimap.copyOf( |
| Multimaps.index( |
| declarations, |
| declaration -> |
| unwrapMultibindingKey( |
| declaration.key().withoutMultibindingContributionIdentifier()))); |
| } |
| |
| private static <T extends Declaration> ImmutableSet<T> multibindingContributions( |
| ImmutableSet<T> declarations) { |
| return declarations.stream() |
| .filter(declaration -> declaration.key().multibindingContributionIdentifier().isPresent()) |
| .collect(toImmutableSet()); |
| } |
| } |
| |
| /** |
| * Returns a {@link TypeNameKey} with the same qualifiers and multibinding identifier as the |
| * original key, but with an unwrapped typed. |
| * |
| * <p>In this case, an unwrapped type is a map or set where the value type has been stripped of a |
| * leading framework type. If the given type is neither a map nor set type, then the original type |
| * is returned. |
| * |
| * <p>The following map types have an unwrapped type equal to {@code Map<K, V>}: |
| * <ul> |
| * <li> {@code Map<K, V>} |
| * <li> {@code Map<K, Provider<V>>} |
| * <li> {@code Map<K, Producer<V>>} |
| * <li> {@code Map<K, Produced<V>>} |
| * </ul> |
| * |
| * <p>The following set types have an unwrapped type equal to {@code Set<V>}: |
| * <ul> |
| * <li> {@code Set<V>} |
| * <li> {@code Set<Produced<V>>} |
| * </ul> |
| */ |
| private static TypeNameKey unwrapMultibindingKey(Key multibindingKey) { |
| return TypeNameKey.from( |
| multibindingKey.multibindingContributionIdentifier(), |
| multibindingKey.qualifier(), |
| unwrapMultibindingTypeName(multibindingKey.type().xprocessing().getTypeName())); |
| } |
| |
| private static TypeName unwrapMultibindingTypeName(TypeName typeName) { |
| if (isValidMapMultibindingTypeName(typeName)) { |
| ParameterizedTypeName mapTypeName = (ParameterizedTypeName) typeName; |
| TypeName mapKeyTypeName = mapTypeName.typeArguments.get(0); |
| TypeName mapValueTypeName = mapTypeName.typeArguments.get(1); |
| return ParameterizedTypeName.get( |
| mapTypeName.rawType, |
| mapKeyTypeName, |
| unwrapFrameworkTypeName(mapValueTypeName, FrameworkTypes.MAP_VALUE_FRAMEWORK_TYPES)); |
| } |
| if (isValidSetMultibindingTypeName(typeName)) { |
| ParameterizedTypeName setTypeName = (ParameterizedTypeName) typeName; |
| TypeName setValueTypeName = getOnlyElement(setTypeName.typeArguments); |
| return ParameterizedTypeName.get( |
| setTypeName.rawType, |
| unwrapFrameworkTypeName(setValueTypeName, FrameworkTypes.SET_VALUE_FRAMEWORK_TYPES)); |
| } |
| return typeName; |
| } |
| |
| private static boolean isValidMapMultibindingTypeName(TypeName typeName) { |
| if (!(typeName instanceof ParameterizedTypeName)) { |
| return false; |
| } |
| ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName; |
| return parameterizedTypeName.rawType.equals(TypeNames.MAP) |
| && parameterizedTypeName.typeArguments.size() == 2 |
| && !(parameterizedTypeName.typeArguments.get(0) instanceof WildcardTypeName) |
| && !(parameterizedTypeName.typeArguments.get(1) instanceof WildcardTypeName); |
| } |
| |
| private static boolean isValidSetMultibindingTypeName(TypeName typeName) { |
| if (!(typeName instanceof ParameterizedTypeName)) { |
| return false; |
| } |
| ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName; |
| return parameterizedTypeName.rawType.equals(TypeNames.SET) |
| && parameterizedTypeName.typeArguments.size() == 1 |
| && !(getOnlyElement(parameterizedTypeName.typeArguments) instanceof WildcardTypeName); |
| } |
| |
| private static TypeName unwrapFrameworkTypeName( |
| TypeName typeName, ImmutableSet<ClassName> frameworkTypeNames) { |
| if (typeName instanceof ParameterizedTypeName) { |
| ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName; |
| if (frameworkTypeNames.contains(parameterizedTypeName.rawType)) { |
| typeName = getOnlyElement(parameterizedTypeName.typeArguments); |
| } |
| } |
| return typeName; |
| } |
| |
| /** |
| * Represents a class similar to {@link Key} but uses {@link TypeName} rather than {@code XType}. |
| * |
| * <p>We use {@code TypeName} rather than {@code XType} here because we can lose variance |
| * information when unwrapping an {@code XType} in KSP (b/352142595), and using {@code TypeName} |
| * avoids this issue. |
| */ |
| @AutoValue |
| abstract static class TypeNameKey { |
| static TypeNameKey from( |
| Optional<MultibindingContributionIdentifier> multibindingContributionIdentifier, |
| Optional<DaggerAnnotation> qualifier, |
| TypeName typeName) { |
| return new AutoValue_ComponentDeclarations_TypeNameKey( |
| multibindingContributionIdentifier, qualifier, typeName); |
| } |
| |
| abstract Optional<MultibindingContributionIdentifier> multibindingContributionIdentifier(); |
| |
| abstract Optional<DaggerAnnotation> qualifier(); |
| |
| abstract TypeName type(); |
| } |
| } |