/* * 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.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.extension.Optionals.firstPresent; 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 static java.util.Arrays.asList; 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.google.common.collect.ImmutableSet; 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.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.Map; import java.util.Optional; import java.util.stream.Stream; import javax.inject.Inject; /** A factory for {@link Key}s. */ public final class KeyFactory { private final XProcessingEnv processingEnv; private final InjectionAnnotations injectionAnnotations; @Inject KeyFactory(XProcessingEnv processingEnv, InjectionAnnotations injectionAnnotations) { this.processingEnv = processingEnv; 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()); } /** Returns {@code Map>}. */ private XType mapOfFrameworkType(XType keyType, ClassName frameworkClassName, XType valueType) { 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) { return forBindingMethod(method, contributingModule, Optional.of(TypeNames.PROVIDER)); } public Key forProducesMethod(XMethodElement method, XTypeElement contributingModule) { 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 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. * *

The key's type is either {@code Set} or {@code Map>}. 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 frameworkClassName) { switch (contributionType) { case UNIQUE: return returnType; case SET: return setOf(returnType); case MAP: Optional 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() ? 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}. * *

If {@code delegateDeclaration} is {@code @IntoMap}, transforms the {@code Map} key * from {@link DelegateDeclaration#key()} to {@code Map>}. If {@code * delegateDeclaration} is not a map contribution, its key is returned. */ Key forDelegateBinding(DelegateDeclaration delegateDeclaration, ClassName frameworkType) { return delegateDeclaration.contributionType().equals(ContributionType.MAP) ? 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 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 requestKey} is for a {@code Map} or {@code Map>}, returns keys * for {@code Map>} and {@code Map>} (if Dagger-Producers is on the * classpath). */ ImmutableSet implicitFrameworkMapKeys(Key requestKey) { return Stream.of(implicitMapProviderKeyFrom(requestKey), implicitMapProducerKeyFrom(requestKey)) .filter(Optional::isPresent) .map(Optional::get) .collect(toImmutableSet()); } /** * Optionally extract a {@link Key} for the underlying provision binding(s) if such a valid key * can be inferred from the given key. Specifically, if the key represents a {@link Map}{@code } or {@code Map>}, a key of {@code Map>} will be returned. */ Optional implicitMapProviderKeyFrom(Key possibleMapKey) { return firstPresent( rewrapMapKey(possibleMapKey, TypeNames.PRODUCED, TypeNames.PROVIDER), wrapMapKey(possibleMapKey, TypeNames.PROVIDER)); } /** * Optionally extract a {@link Key} for the underlying production binding(s) if such a valid key * can be inferred from the given key. Specifically, if the key represents a {@link Map}{@code } or {@code Map>}, a key of {@code Map>} will be returned. */ Optional implicitMapProducerKeyFrom(Key possibleMapKey) { return firstPresent( rewrapMapKey(possibleMapKey, TypeNames.PRODUCED, TypeNames.PRODUCER), wrapMapKey(possibleMapKey, TypeNames.PRODUCER)); } /** * If {@code key}'s type is {@code Map>}, {@code Map>}, or {@code * Map>}, returns a key with the same qualifier and {@link * Key#multibindingContributionIdentifier()} whose type is simply {@code Map}. * *

Otherwise, returns {@code key}. */ public Key unwrapMapValueType(Key key) { if (MapType.isMap(key)) { MapType mapType = MapType.from(key); if (!mapType.isRawType()) { for (ClassName frameworkClass : asList(TypeNames.PROVIDER, TypeNames.PRODUCER, TypeNames.PRODUCED)) { if (mapType.valuesAreTypeOf(frameworkClass)) { return key.withType( DaggerType.from( mapOf(mapType.keyType(), mapType.unwrappedValueType(frameworkClass)))); } } } } return key; } /** Converts a {@link Key} of type {@code Map} to {@code Map>}. */ private Key wrapMapValue(Key key, ClassName newWrappingClassName) { checkArgument(FrameworkTypes.isFrameworkType(processingEnv.requireType(newWrappingClassName))); return wrapMapKey(key, newWrappingClassName).get(); } /** * If {@code key}'s type is {@code Map>}, returns a key with type * {@code Map>} with the same qualifier. Otherwise returns {@link * Optional#empty()}. * *

Returns {@link Optional#empty()} if {@code newWrappingClass} is not in the classpath. * * @throws IllegalArgumentException if {@code newWrappingClass} is the same as {@code * currentWrappingClass} */ public Optional rewrapMapKey( Key possibleMapKey, ClassName currentWrappingClassName, ClassName newWrappingClassName) { checkArgument(!currentWrappingClassName.equals(newWrappingClassName)); if (MapType.isMap(possibleMapKey)) { MapType mapType = MapType.from(possibleMapKey); if (!mapType.isRawType() && mapType.valuesAreTypeOf(currentWrappingClassName)) { XTypeElement wrappingElement = processingEnv.findTypeElement(newWrappingClassName); if (wrappingElement == null) { // This target might not be compiled with Producers, so wrappingClass might not have an // associated element. return Optional.empty(); } XType wrappedValueType = processingEnv.getDeclaredType( wrappingElement, mapType.unwrappedValueType(currentWrappingClassName)); return Optional.of( possibleMapKey.withType(DaggerType.from(mapOf(mapType.keyType(), wrappedValueType)))); } } return Optional.empty(); } /** * If {@code key}'s type is {@code Map} and {@code Foo} is not {@code WrappingClass * }, returns a key with type {@code Map>} with the same qualifier. * Otherwise returns {@link Optional#empty()}. * *

Returns {@link Optional#empty()} if {@code WrappingClass} is not in the classpath. */ private Optional wrapMapKey(Key possibleMapKey, ClassName wrappingClassName) { if (MapType.isMap(possibleMapKey)) { MapType mapType = MapType.from(possibleMapKey); if (!mapType.isRawType() && !mapType.valuesAreTypeOf(wrappingClassName)) { XTypeElement wrappingElement = processingEnv.findTypeElement(wrappingClassName); if (wrappingElement == null) { // This target might not be compiled with Producers, so wrappingClass might not have an // associated element. return Optional.empty(); } XType wrappedValueType = processingEnv.getDeclaredType(wrappingElement, mapType.valueType()); return Optional.of( possibleMapKey.withType(DaggerType.from(mapOf(mapType.keyType(), wrappedValueType)))); } } return Optional.empty(); } /** * If {@code key}'s type is {@code Set>}, returns a key with type {@code Set * } with the same qualifier. Otherwise returns {@link Optional#empty()}. */ Optional 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} for some {@code T}, returns a key with the same * qualifier whose type is {@linkplain RequestKinds#extractKeyType(RequestKind, XType)} * extracted} from {@code T}. */ Optional 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)))); } }