/* * 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.MoreElements.isAnnotationPresent; import static com.google.auto.common.MoreTypes.asExecutable; import static com.google.auto.common.MoreTypes.isType; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.base.RequestKinds.extractKeyType; import static dagger.internal.codegen.binding.MapKeys.getMapKey; import static dagger.internal.codegen.binding.MapKeys.mapKeyType; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.extension.Optionals.firstPresent; import static dagger.internal.codegen.langmodel.DaggerTypes.isFutureType; import static dagger.internal.codegen.langmodel.DaggerTypes.unwrapType; import static java.util.Arrays.asList; import static javax.lang.model.element.ElementKind.METHOD; import com.google.auto.common.MoreTypes; import com.google.common.collect.ImmutableSet; 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.base.SimpleAnnotationMirror; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; import dagger.model.Key; import dagger.model.Key.MultibindingContributionIdentifier; import dagger.model.RequestKind; import dagger.multibindings.Multibinds; import dagger.producers.Produced; import dagger.producers.Producer; import dagger.producers.Production; import dagger.producers.internal.ProductionImplementation; import dagger.producers.monitoring.ProductionComponentMonitor; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Provider; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ExecutableType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeMirror; /** A factory for {@link Key}s. */ public final class KeyFactory { private final DaggerTypes types; private final DaggerElements elements; private final InjectionAnnotations injectionAnnotations; @Inject KeyFactory( DaggerTypes types, DaggerElements elements, InjectionAnnotations injectionAnnotations) { this.types = checkNotNull(types); this.elements = checkNotNull(elements); this.injectionAnnotations = injectionAnnotations; } private TypeMirror boxPrimitives(TypeMirror type) { return type.getKind().isPrimitive() ? types.boxedClass((PrimitiveType) type).asType() : type; } private DeclaredType setOf(TypeMirror elementType) { return types.getDeclaredType(elements.getTypeElement(Set.class), boxPrimitives(elementType)); } private DeclaredType mapOf(TypeMirror keyType, TypeMirror valueType) { return types.getDeclaredType( elements.getTypeElement(Map.class), boxPrimitives(keyType), boxPrimitives(valueType)); } /** Returns {@code Map>}. */ private TypeMirror mapOfFrameworkType( TypeMirror keyType, TypeElement frameworkType, TypeMirror valueType) { return mapOf(keyType, types.getDeclaredType(frameworkType, boxPrimitives(valueType))); } Key forComponentMethod(ExecutableElement componentMethod) { checkArgument(componentMethod.getKind().equals(METHOD)); return forMethod(componentMethod, componentMethod.getReturnType()); } Key forProductionComponentMethod(ExecutableElement componentMethod) { checkArgument(componentMethod.getKind().equals(METHOD)); TypeMirror returnType = componentMethod.getReturnType(); TypeMirror keyType = isFutureType(returnType) ? getOnlyElement(MoreTypes.asDeclared(returnType).getTypeArguments()) : returnType; return forMethod(componentMethod, keyType); } Key forSubcomponentCreatorMethod( ExecutableElement subcomponentCreatorMethod, DeclaredType declaredContainer) { checkArgument(subcomponentCreatorMethod.getKind().equals(METHOD)); ExecutableType resolvedMethod = asExecutable(types.asMemberOf(declaredContainer, subcomponentCreatorMethod)); return Key.builder(resolvedMethod.getReturnType()).build(); } public Key forSubcomponentCreator(TypeMirror creatorType) { return Key.builder(creatorType).build(); } public Key forProvidesMethod(ExecutableElement method, TypeElement contributingModule) { return forBindingMethod( method, contributingModule, Optional.of(elements.getTypeElement(Provider.class))); } public Key forProducesMethod(ExecutableElement method, TypeElement contributingModule) { return forBindingMethod( method, contributingModule, Optional.of(elements.getTypeElement(Producer.class))); } /** Returns the key bound by a {@link Binds} method. */ Key forBindsMethod(ExecutableElement method, TypeElement contributingModule) { checkArgument(isAnnotationPresent(method, Binds.class)); return forBindingMethod(method, contributingModule, Optional.empty()); } /** Returns the base key bound by a {@link BindsOptionalOf} method. */ Key forBindsOptionalOfMethod(ExecutableElement method, TypeElement contributingModule) { checkArgument(isAnnotationPresent(method, BindsOptionalOf.class)); return forBindingMethod(method, contributingModule, Optional.empty()); } private Key forBindingMethod( ExecutableElement method, TypeElement contributingModule, Optional frameworkType) { checkArgument(method.getKind().equals(METHOD)); ExecutableType methodType = MoreTypes.asExecutable( types.asMemberOf(MoreTypes.asDeclared(contributingModule.asType()), method)); ContributionType contributionType = ContributionType.fromBindingElement(method); TypeMirror returnType = methodType.getReturnType(); if (frameworkType.isPresent() && frameworkType.get().equals(elements.getTypeElement(Producer.class)) && isType(returnType)) { if (isFutureType(methodType.getReturnType())) { returnType = getOnlyElement(MoreTypes.asDeclared(returnType).getTypeArguments()); } else if (contributionType.equals(ContributionType.SET_VALUES) && SetType.isSet(returnType)) { SetType setType = SetType.from(returnType); if (isFutureType(setType.elementType())) { returnType = types.getDeclaredType( elements.getTypeElement(Set.class), unwrapType(setType.elementType())); } } } TypeMirror keyType = bindingMethodKeyType(returnType, method, contributionType, frameworkType); Key key = forMethod(method, keyType); return contributionType.equals(ContributionType.UNIQUE) ? key : key.toBuilder() .multibindingContributionIdentifier( new MultibindingContributionIdentifier(method, contributingModule)) .build(); } /** * 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(ExecutableType executableType, ExecutableElement method) { checkArgument(method.getKind().equals(METHOD), "%s must be a method", method); TypeMirror returnType = executableType.getReturnType(); TypeMirror keyType = MapType.isMap(returnType) ? mapOfFrameworkType( MapType.from(returnType).keyType(), elements.getTypeElement(Provider.class), MapType.from(returnType).valueType()) : returnType; return forMethod(method, keyType); } private TypeMirror bindingMethodKeyType( TypeMirror returnType, ExecutableElement method, ContributionType contributionType, Optional frameworkType) { switch (contributionType) { case UNIQUE: return returnType; case SET: return setOf(returnType); case MAP: TypeMirror mapKeyType = mapKeyType(getMapKey(method).get(), types); return frameworkType.isPresent() ? mapOfFrameworkType(mapKeyType, frameworkType.get(), returnType) : mapOf(mapKeyType, 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, Class frameworkType) { return delegateDeclaration.contributionType().equals(ContributionType.MAP) ? wrapMapValue(delegateDeclaration.key(), frameworkType) : delegateDeclaration.key(); } private Key forMethod(ExecutableElement method, TypeMirror keyType) { return forQualifiedType(injectionAnnotations.getQualifier(method), keyType); } public Key forInjectConstructorWithResolvedType(TypeMirror type) { return Key.builder(type).build(); } // TODO(ronshapiro): Remove these conveniences which are simple wrappers around Key.Builder Key forType(TypeMirror type) { return Key.builder(type).build(); } public Key forMembersInjectedType(TypeMirror type) { return Key.builder(type).build(); } Key forQualifiedType(Optional qualifier, TypeMirror type) { return Key.builder(boxPrimitives(type)).qualifier(qualifier).build(); } public Key forProductionExecutor() { return Key.builder(elements.getTypeElement(Executor.class).asType()) .qualifier(SimpleAnnotationMirror.of(elements.getTypeElement(Production.class))) .build(); } public Key forProductionImplementationExecutor() { return Key.builder(elements.getTypeElement(Executor.class).asType()) .qualifier(SimpleAnnotationMirror.of(elements.getTypeElement(ProductionImplementation.class))) .build(); } public Key forProductionComponentMonitor() { return Key.builder(elements.getTypeElement(ProductionComponentMonitor.class).asType()).build(); } /** * 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, Produced.class, Provider.class), wrapMapKey(possibleMapKey, Provider.class)); } /** * 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, Produced.class, Producer.class), wrapMapKey(possibleMapKey, Producer.class)); } /** * 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 (Class frameworkClass : asList(Provider.class, Producer.class, Produced.class)) { if (mapType.valuesAreTypeOf(frameworkClass)) { return key.toBuilder() .type(mapOf(mapType.keyType(), mapType.unwrappedValueType(frameworkClass))) .build(); } } } } return key; } /** * Converts a {@link Key} of type {@code Map} to {@code Map>}. */ private Key wrapMapValue(Key key, Class newWrappingClass) { checkArgument( FrameworkTypes.isFrameworkType(elements.getTypeElement(newWrappingClass).asType())); return wrapMapKey(key, newWrappingClass).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, Class currentWrappingClass, Class newWrappingClass) { checkArgument(!currentWrappingClass.equals(newWrappingClass)); if (MapType.isMap(possibleMapKey)) { MapType mapType = MapType.from(possibleMapKey); if (!mapType.isRawType() && mapType.valuesAreTypeOf(currentWrappingClass)) { TypeElement wrappingElement = elements.getTypeElement(newWrappingClass); if (wrappingElement == null) { // This target might not be compiled with Producers, so wrappingClass might not have an // associated element. return Optional.empty(); } DeclaredType wrappedValueType = types.getDeclaredType( wrappingElement, mapType.unwrappedValueType(currentWrappingClass)); return Optional.of( possibleMapKey.toBuilder().type(mapOf(mapType.keyType(), wrappedValueType)).build()); } } 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, Class wrappingClass) { if (MapType.isMap(possibleMapKey)) { MapType mapType = MapType.from(possibleMapKey); if (!mapType.isRawType() && !mapType.valuesAreTypeOf(wrappingClass)) { TypeElement wrappingElement = elements.getTypeElement(wrappingClass); if (wrappingElement == null) { // This target might not be compiled with Producers, so wrappingClass might not have an // associated element. return Optional.empty(); } DeclaredType wrappedValueType = types.getDeclaredType(wrappingElement, mapType.valueType()); return Optional.of( possibleMapKey.toBuilder().type(mapOf(mapType.keyType(), wrappedValueType)).build()); } } 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, Class wrappingClass) { if (SetType.isSet(key)) { SetType setType = SetType.from(key); if (!setType.isRawType() && setType.elementsAreTypeOf(wrappingClass)) { return Optional.of( key.toBuilder().type(setOf(setType.unwrappedElementType(wrappingClass))).build()); } } 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, TypeMirror)} * extracted} from {@code T}. */ Optional unwrapOptional(Key key) { if (!OptionalType.isOptional(key)) { return Optional.empty(); } TypeMirror optionalValueType = OptionalType.from(key).valueType(); return Optional.of(key.toBuilder().type(extractKeyType(optionalValueType)).build()); } }