blob: 693382e490811590465fb62d36baae60a419bd7b [file] [log] [blame]
/*
* Copyright (C) 2014 The Dagger Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dagger.internal.codegen.binding;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.getOnlyElement;
import static dagger.internal.codegen.base.ProducerAnnotations.productionImplementationQualifier;
import static dagger.internal.codegen.base.ProducerAnnotations.productionQualifier;
import static dagger.internal.codegen.base.RequestKinds.extractKeyType;
import static dagger.internal.codegen.binding.MapKeys.getMapKey;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static dagger.internal.codegen.javapoet.TypeNames.isFutureType;
import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
import static dagger.internal.codegen.xprocessing.XTypes.unwrapType;
import androidx.room.compiler.processing.XAnnotation;
import androidx.room.compiler.processing.XMethodElement;
import androidx.room.compiler.processing.XMethodType;
import androidx.room.compiler.processing.XProcessingEnv;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XTypeElement;
import com.squareup.javapoet.ClassName;
import dagger.Binds;
import dagger.BindsOptionalOf;
import dagger.internal.codegen.base.ContributionType;
import dagger.internal.codegen.base.FrameworkTypes;
import dagger.internal.codegen.base.MapType;
import dagger.internal.codegen.base.OptionalType;
import dagger.internal.codegen.base.RequestKinds;
import dagger.internal.codegen.base.SetType;
import dagger.internal.codegen.compileroption.CompilerOptions;
import dagger.internal.codegen.javapoet.TypeNames;
import dagger.internal.codegen.model.DaggerAnnotation;
import dagger.internal.codegen.model.DaggerExecutableElement;
import dagger.internal.codegen.model.DaggerType;
import dagger.internal.codegen.model.DaggerTypeElement;
import dagger.internal.codegen.model.Key;
import dagger.internal.codegen.model.RequestKind;
import dagger.internal.codegen.xprocessing.XAnnotations;
import dagger.multibindings.Multibinds;
import java.util.Optional;
import javax.inject.Inject;
/** A factory for {@link Key}s. */
public final class KeyFactory {
private final XProcessingEnv processingEnv;
private final CompilerOptions compilerOptions;
private final InjectionAnnotations injectionAnnotations;
@Inject
KeyFactory(
XProcessingEnv processingEnv,
CompilerOptions compilerOptions,
InjectionAnnotations injectionAnnotations) {
this.processingEnv = processingEnv;
this.compilerOptions = compilerOptions;
this.injectionAnnotations = injectionAnnotations;
}
private XType setOf(XType elementType) {
return processingEnv.getDeclaredType(
processingEnv.requireTypeElement(TypeNames.SET), elementType.boxed());
}
private XType mapOf(XType keyType, XType valueType) {
return processingEnv.getDeclaredType(
processingEnv.requireTypeElement(TypeNames.MAP), keyType.boxed(), valueType.boxed());
}
/**
* If {@code key}'s type is {@code Optional<T>} for some {@code T}, returns a key with the same
* qualifier whose type is {@linkplain RequestKinds#extractKeyType(RequestKind, XType)}
* extracted} from {@code T}.
*/
Key optionalOf(Key key) {
return key.withType(DaggerType.from(optionalOf(key.type().xprocessing())));
}
private XType optionalOf(XType type) {
return processingEnv.getDeclaredType(
processingEnv.requireTypeElement(TypeNames.JDK_OPTIONAL), type.boxed());
}
/** Returns {@code Map<KeyType, FrameworkType<ValueType>>}. */
private XType mapOfFrameworkType(XType keyType, ClassName frameworkClassName, XType valueType) {
checkArgument(FrameworkTypes.MAP_VALUE_FRAMEWORK_TYPES.contains(frameworkClassName));
return mapOf(
keyType,
processingEnv.getDeclaredType(
processingEnv.requireTypeElement(frameworkClassName), valueType.boxed()));
}
Key forComponentMethod(XMethodElement componentMethod) {
return forMethod(componentMethod, componentMethod.getReturnType());
}
Key forProductionComponentMethod(XMethodElement componentMethod) {
XType returnType = componentMethod.getReturnType();
XType keyType =
isFutureType(returnType) ? getOnlyElement(returnType.getTypeArguments()) : returnType;
return forMethod(componentMethod, keyType);
}
Key forSubcomponentCreatorMethod(
XMethodElement subcomponentCreatorMethod, XType declaredContainer) {
checkArgument(isDeclared(declaredContainer));
XMethodType resolvedMethod = subcomponentCreatorMethod.asMemberOf(declaredContainer);
return forType(resolvedMethod.getReturnType());
}
public Key forSubcomponentCreator(XType creatorType) {
return forType(creatorType);
}
public Key forProvidesMethod(XMethodElement method, XTypeElement contributingModule) {
checkArgument(method.hasAnnotation(TypeNames.PROVIDES));
return forBindingMethod(method, contributingModule, Optional.of(TypeNames.PROVIDER));
}
public Key forProducesMethod(XMethodElement method, XTypeElement contributingModule) {
checkArgument(method.hasAnnotation(TypeNames.PRODUCES));
return forBindingMethod(method, contributingModule, Optional.of(TypeNames.PRODUCER));
}
/** Returns the key bound by a {@link Binds} method. */
Key forBindsMethod(XMethodElement method, XTypeElement contributingModule) {
checkArgument(method.hasAnnotation(TypeNames.BINDS));
return forBindingMethod(method, contributingModule, Optional.empty());
}
/** Returns the base key bound by a {@link BindsOptionalOf} method. */
Key forBindsOptionalOfMethod(XMethodElement method, XTypeElement contributingModule) {
checkArgument(method.hasAnnotation(TypeNames.BINDS_OPTIONAL_OF));
return forBindingMethod(method, contributingModule, Optional.empty());
}
private Key forBindingMethod(
XMethodElement method,
XTypeElement contributingModule,
Optional<ClassName> frameworkClassName) {
XMethodType methodType = method.asMemberOf(contributingModule.getType());
ContributionType contributionType = ContributionType.fromBindingElement(method);
XType returnType = methodType.getReturnType();
if (frameworkClassName.isPresent() && frameworkClassName.get().equals(TypeNames.PRODUCER)) {
if (isFutureType(returnType)) {
returnType = getOnlyElement(returnType.getTypeArguments());
} else if (contributionType.equals(ContributionType.SET_VALUES)
&& SetType.isSet(returnType)) {
SetType setType = SetType.from(returnType);
if (isFutureType(setType.elementType())) {
returnType = setOf(unwrapType(setType.elementType()));
}
}
}
XType keyType = bindingMethodKeyType(returnType, method, contributionType, frameworkClassName);
Key key = forMethod(method, keyType);
return contributionType.equals(ContributionType.UNIQUE)
? key
: key.withMultibindingContributionIdentifier(
DaggerTypeElement.from(contributingModule), DaggerExecutableElement.from(method));
}
/**
* Returns the key for a {@link Multibinds @Multibinds} method.
*
* <p>The key's type is either {@code Set<T>} or {@code Map<K, Provider<V>>}. The latter works
* even for maps used by {@code Producer}s.
*/
Key forMultibindsMethod(XMethodElement method, XMethodType methodType) {
XType returnType = method.getReturnType();
XType keyType =
MapType.isMap(returnType)
? mapOfFrameworkType(
MapType.from(returnType).keyType(),
TypeNames.PROVIDER,
MapType.from(returnType).valueType())
: returnType;
return forMethod(method, keyType);
}
private XType bindingMethodKeyType(
XType returnType,
XMethodElement method,
ContributionType contributionType,
Optional<ClassName> frameworkClassName) {
switch (contributionType) {
case UNIQUE:
return returnType;
case SET:
return setOf(returnType);
case MAP:
Optional<XType> mapKeyType = getMapKey(method).map(MapKeys::mapKeyType);
// TODO(bcorso): We've added a special checkState here since a number of people have run
// into this particular case, but technically it shouldn't be necessary if we are properly
// doing superficial validation and deferring on unresolvable types. We should revisit
// whether this is necessary once we're able to properly defer this case.
checkState(
mapKeyType.isPresent(),
"Missing map key annotation for method: %s#%s. That method was annotated with: %s. If a"
+ " map key annotation is included in that list, it means Dagger wasn't able to"
+ " detect that it was a map key because the dependency is missing from the"
+ " classpath of the current build. To fix, add a dependency for the map key to the"
+ " current build. For more details, see"
+ " https://github.com/google/dagger/issues/3133#issuecomment-1002790894.",
method.getEnclosingElement(),
method,
method.getAllAnnotations().stream()
.map(XAnnotations::toString)
.collect(toImmutableList()));
return (frameworkClassName.isPresent()
&& compilerOptions.useFrameworkTypeInMapMultibindingContributionKey())
? mapOfFrameworkType(mapKeyType.get(), frameworkClassName.get(), returnType)
: mapOf(mapKeyType.get(), returnType);
case SET_VALUES:
// TODO(gak): do we want to allow people to use "covariant return" here?
checkArgument(SetType.isSet(returnType));
return returnType;
}
throw new AssertionError();
}
/**
* Returns the key for a binding associated with a {@link DelegateDeclaration}.
*
* <p>If {@code delegateDeclaration} is a multibinding map contribution and
* {@link CompilerOptions#useFrameworkTypeInMapMultibindingContributionKey()} is enabled, then
* transforms the {@code Map<K, V>} key into {@code Map<K, FrameworkType<V>>}, otherwise returns
* the unaltered key.
*/
Key forDelegateBinding(DelegateDeclaration delegateDeclaration, ClassName frameworkType) {
return delegateDeclaration.contributionType().equals(ContributionType.MAP)
&& compilerOptions.useFrameworkTypeInMapMultibindingContributionKey()
? wrapMapValue(delegateDeclaration.key(), frameworkType)
: delegateDeclaration.key();
}
private Key forMethod(XMethodElement method, XType keyType) {
return forQualifiedType(injectionAnnotations.getQualifier(method), keyType);
}
public Key forInjectConstructorWithResolvedType(XType type) {
return forType(type);
}
Key forType(XType type) {
return Key.builder(DaggerType.from(type)).build();
}
public Key forMembersInjectedType(XType type) {
return forType(type);
}
Key forQualifiedType(Optional<XAnnotation> qualifier, XType type) {
return Key.builder(DaggerType.from(type.boxed()))
.qualifier(qualifier.map(DaggerAnnotation::from))
.build();
}
public Key forProductionExecutor() {
return Key.builder(DaggerType.from(processingEnv.requireType(TypeNames.EXECUTOR)))
.qualifier(DaggerAnnotation.from(productionQualifier(processingEnv)))
.build();
}
public Key forProductionImplementationExecutor() {
return Key.builder(DaggerType.from(processingEnv.requireType(TypeNames.EXECUTOR)))
.qualifier(DaggerAnnotation.from(productionImplementationQualifier(processingEnv)))
.build();
}
public Key forProductionComponentMonitor() {
return forType(processingEnv.requireType(TypeNames.PRODUCTION_COMPONENT_MONITOR));
}
/**
* If {@code key}'s type is {@code Map<K, Provider<V>>}, {@code Map<K, Producer<V>>}, or {@code
* Map<K, Produced<V>>}, returns a key with the same qualifier and {@link
* Key#multibindingContributionIdentifier()} whose type is simply {@code Map<K, V>}.
*
* <p>Otherwise, returns {@code key}.
*/
public Key unwrapMapValueType(Key key) {
if (MapType.isMap(key)) {
MapType mapType = MapType.from(key);
if (!mapType.isRawType() && mapType.valuesAreFrameworkType()) {
return key.withType(
DaggerType.from(mapOf(mapType.keyType(), mapType.unwrappedFrameworkValueType())));
}
}
return key;
}
/**
* Returns a key with the type {@code Map<K, FrameworkType<V>>} if the given key has a type of
* {@code Map<K, V>}. Otherwise, returns the unaltered key.
*
* @throws IllegalArgumentException if the {@code frameworkClassName} is not a valid framework
* type for multibinding maps.
* @throws IllegalStateException if the {@code key} is already wrapped in a (different) framework
* type.
*/
private Key wrapMapValue(Key key, ClassName frameworkClassName) {
checkArgument(FrameworkTypes.MAP_VALUE_FRAMEWORK_TYPES.contains(frameworkClassName));
if (MapType.isMap(key)) {
MapType mapType = MapType.from(key);
if (!mapType.isRawType() && !mapType.valuesAreTypeOf(frameworkClassName)) {
checkState(!mapType.valuesAreFrameworkType());
XTypeElement frameworkTypeElement = processingEnv.findTypeElement(frameworkClassName);
if (frameworkTypeElement == null) {
// This target might not be compiled with Producers, so wrappingClass might not have an
// associated element.
return key;
}
XType wrappedValueType =
processingEnv.getDeclaredType(frameworkTypeElement, mapType.valueType());
return key.withType(DaggerType.from(mapOf(mapType.keyType(), wrappedValueType)));
}
}
return key;
}
/**
* If {@code key}'s type is {@code Set<WrappingClass<Bar>>}, returns a key with type {@code Set
* <Bar>} with the same qualifier. Otherwise returns {@link Optional#empty()}.
*/
Optional<Key> unwrapSetKey(Key key, ClassName wrappingClassName) {
if (SetType.isSet(key)) {
SetType setType = SetType.from(key);
if (!setType.isRawType() && setType.elementsAreTypeOf(wrappingClassName)) {
return Optional.of(
key.withType(DaggerType.from(setOf(setType.unwrappedElementType(wrappingClassName)))));
}
}
return Optional.empty();
}
/**
* If {@code key}'s type is {@code Optional<T>} for some {@code T}, returns a key with the same
* qualifier whose type is {@linkplain RequestKinds#extractKeyType(RequestKind, XType)}
* extracted} from {@code T}.
*/
Optional<Key> unwrapOptional(Key key) {
if (!OptionalType.isOptional(key)) {
return Optional.empty();
}
XType optionalValueType = OptionalType.from(key).valueType();
return Optional.of(key.withType(DaggerType.from(extractKeyType(optionalValueType))));
}
}