/* * Copyright (C) 2020 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.asExecutable; import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.auto.common.MoreTypes.asDeclared; import static com.google.auto.common.MoreTypes.asExecutable; import static com.google.auto.common.MoreTypes.asTypeElement; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.base.MoreAnnotationValues.getStringValue; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror; import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.util.ElementFilter.constructorsIn; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.base.Equivalence; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeName; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; import dagger.model.BindingKind; import java.util.List; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ExecutableType; import javax.lang.model.type.TypeMirror; /** Assisted injection utility methods. */ public final class AssistedInjectionAnnotations { /** Returns the factory method for the given factory {@link TypeElement}. */ public static ExecutableElement assistedFactoryMethod( TypeElement factory, DaggerElements elements) { return getOnlyElement(assistedFactoryMethods(factory, elements)); } /** Returns the list of abstract factory methods for the given factory {@link TypeElement}. */ public static ImmutableSet assistedFactoryMethods( TypeElement factory, DaggerElements elements) { return elements.getLocalAndInheritedMethods(factory).stream() .filter(method -> method.getModifiers().contains(ABSTRACT)) .filter(method -> !method.isDefault()) .collect(toImmutableSet()); } /** Returns {@code true} if the element uses assisted injection. */ public static boolean isAssistedInjectionType(TypeElement typeElement) { ImmutableSet injectConstructors = assistedInjectedConstructors(typeElement); return !injectConstructors.isEmpty() && isAnnotationPresent(getOnlyElement(injectConstructors), AssistedInject.class); } /** Returns {@code true} if this binding is an assisted factory. */ public static boolean isAssistedFactoryType(Element element) { return isAnnotationPresent(element, AssistedFactory.class); } /** * Returns the list of assisted parameters as {@link ParameterSpec}s. * *

The type of each parameter will be the resolved type given by the binding key, and the name * of each parameter will be the name given in the {@link * dagger.assisted.AssistedInject}-annotated constructor. */ public static ImmutableList assistedParameterSpecs( Binding binding, DaggerTypes types) { checkArgument(binding.kind() == BindingKind.ASSISTED_INJECTION); ExecutableElement constructor = asExecutable(binding.bindingElement().get()); ExecutableType constructorType = asExecutable(types.asMemberOf(asDeclared(binding.key().type()), constructor)); return assistedParameterSpecs(constructor.getParameters(), constructorType.getParameterTypes()); } private static ImmutableList assistedParameterSpecs( List paramElements, List paramTypes) { ImmutableList.Builder assistedParameterSpecs = ImmutableList.builder(); for (int i = 0; i < paramElements.size(); i++) { VariableElement paramElement = paramElements.get(i); TypeMirror paramType = paramTypes.get(i); if (isAssistedParameter(paramElement)) { assistedParameterSpecs.add( ParameterSpec.builder(TypeName.get(paramType), paramElement.getSimpleName().toString()) .build()); } } return assistedParameterSpecs.build(); } /** * Returns the list of assisted factory parameters as {@link ParameterSpec}s. * *

The type of each parameter will be the resolved type given by the binding key, and the name * of each parameter will be the name given in the {@link * dagger.assisted.AssistedInject}-annotated constructor. */ public static ImmutableList assistedFactoryParameterSpecs( Binding binding, DaggerElements elements, DaggerTypes types) { checkArgument(binding.kind() == BindingKind.ASSISTED_FACTORY); AssistedFactoryMetadata metadata = AssistedFactoryMetadata.create(binding.bindingElement().get().asType(), elements, types); ExecutableType factoryMethodType = asExecutable(types.asMemberOf(asDeclared(binding.key().type()), metadata.factoryMethod())); return assistedParameterSpecs( // Use the order of the parameters from the @AssistedFactory method but use the parameter // names of the @AssistedInject constructor. metadata.assistedFactoryAssistedParameters().stream() .map(metadata.assistedInjectAssistedParametersMap()::get) .collect(toImmutableList()), factoryMethodType.getParameterTypes()); } /** Returns the constructors in {@code type} that are annotated with {@link AssistedInject}. */ public static ImmutableSet assistedInjectedConstructors(TypeElement type) { return constructorsIn(type.getEnclosedElements()).stream() .filter(constructor -> isAnnotationPresent(constructor, AssistedInject.class)) .collect(toImmutableSet()); } public static ImmutableList assistedParameters(Binding binding) { return binding.kind() == BindingKind.ASSISTED_INJECTION ? assistedParameters(asExecutable(binding.bindingElement().get())) : ImmutableList.of(); } private static ImmutableList assistedParameters(ExecutableElement constructor) { return constructor.getParameters().stream() .filter(AssistedInjectionAnnotations::isAssistedParameter) .collect(toImmutableList()); } /** Returns {@code true} if this binding is uses assisted injection. */ public static boolean isAssistedParameter(VariableElement param) { return isAnnotationPresent(MoreElements.asVariable(param), Assisted.class); } /** Metadata about an {@link dagger.assisted.AssistedFactory} annotated type. */ @AutoValue public abstract static class AssistedFactoryMetadata { public static AssistedFactoryMetadata create( TypeMirror factory, DaggerElements elements, DaggerTypes types) { DeclaredType factoryType = asDeclared(factory); TypeElement factoryElement = asTypeElement(factoryType); ExecutableElement factoryMethod = assistedFactoryMethod(factoryElement, elements); ExecutableType factoryMethodType = asExecutable(types.asMemberOf(factoryType, factoryMethod)); DeclaredType assistedInjectType = asDeclared(factoryMethodType.getReturnType()); return new AutoValue_AssistedInjectionAnnotations_AssistedFactoryMetadata( factoryElement, factoryType, factoryMethod, factoryMethodType, asTypeElement(assistedInjectType), assistedInjectType, AssistedInjectionAnnotations.assistedInjectAssistedParameters(assistedInjectType, types), AssistedInjectionAnnotations.assistedFactoryAssistedParameters( factoryMethod, factoryMethodType)); } public abstract TypeElement factory(); public abstract DeclaredType factoryType(); public abstract ExecutableElement factoryMethod(); public abstract ExecutableType factoryMethodType(); public abstract TypeElement assistedInjectElement(); public abstract DeclaredType assistedInjectType(); public abstract ImmutableList assistedInjectAssistedParameters(); public abstract ImmutableList assistedFactoryAssistedParameters(); @Memoized public ImmutableMap assistedInjectAssistedParametersMap() { ImmutableMap.Builder builder = ImmutableMap.builder(); for (AssistedParameter assistedParameter : assistedInjectAssistedParameters()) { builder.put(assistedParameter, assistedParameter.variableElement); } return builder.build(); } @Memoized public ImmutableMap assistedFactoryAssistedParametersMap() { ImmutableMap.Builder builder = ImmutableMap.builder(); for (AssistedParameter assistedParameter : assistedFactoryAssistedParameters()) { builder.put(assistedParameter, assistedParameter.variableElement); } return builder.build(); } } /** * Metadata about an {@link Assisted} annotated parameter. * *

This parameter can represent an {@link Assisted} annotated parameter from an {@link * AssistedInject} constructor or an {@link AssistedFactory} method. */ @AutoValue public abstract static class AssistedParameter { public static AssistedParameter create(VariableElement parameter, TypeMirror parameterType) { AssistedParameter assistedParameter = new AutoValue_AssistedInjectionAnnotations_AssistedParameter( getAnnotationMirror(parameter, Assisted.class) .map(assisted -> getStringValue(assisted, "value")) .orElse(""), MoreTypes.equivalence().wrap(parameterType)); assistedParameter.variableElement = parameter; return assistedParameter; } private VariableElement variableElement; /** Returns the string qualifier from the {@link Assisted#value()}. */ public abstract String qualifier(); /** Returns the wrapper for the type annotated with {@link Assisted}. */ public abstract Equivalence.Wrapper wrappedType(); /** Returns the type annotated with {@link Assisted}. */ public final TypeMirror type() { return wrappedType().get(); } public final VariableElement variableElement() { return variableElement; } @Override public final String toString() { return qualifier().isEmpty() ? String.format("@Assisted %s", type()) : String.format("@Assisted(\"%s\") %s", qualifier(), type()); } } public static ImmutableList assistedInjectAssistedParameters( DeclaredType assistedInjectType, DaggerTypes types) { // We keep track of the constructor both as an ExecutableElement to access @Assisted // parameters and as an ExecutableType to access the resolved parameter types. ExecutableElement assistedInjectConstructor = getOnlyElement(assistedInjectedConstructors(asTypeElement(assistedInjectType))); ExecutableType assistedInjectConstructorType = asExecutable(types.asMemberOf(assistedInjectType, assistedInjectConstructor)); ImmutableList.Builder builder = ImmutableList.builder(); for (int i = 0; i < assistedInjectConstructor.getParameters().size(); i++) { VariableElement parameter = assistedInjectConstructor.getParameters().get(i); TypeMirror parameterType = assistedInjectConstructorType.getParameterTypes().get(i); if (isAnnotationPresent(parameter, Assisted.class)) { builder.add(AssistedParameter.create(parameter, parameterType)); } } return builder.build(); } public static ImmutableList assistedFactoryAssistedParameters( ExecutableElement factoryMethod, ExecutableType factoryMethodType) { ImmutableList.Builder builder = ImmutableList.builder(); for (int i = 0; i < factoryMethod.getParameters().size(); i++) { VariableElement parameter = factoryMethod.getParameters().get(i); TypeMirror parameterType = factoryMethodType.getParameterTypes().get(i); builder.add(AssistedParameter.create(parameter, parameterType)); } return builder.build(); } private AssistedInjectionAnnotations() {} }