blob: 28557220ce6d23e0fa49a87495c1e8fe485746a0 [file] [log] [blame]
/*
* 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();
}
}