/* * 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; import static com.google.auto.common.MoreElements.asType; import static com.google.auto.common.MoreTypes.asDeclared; import static com.google.auto.common.MoreTypes.asTypeElement; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; import static dagger.internal.codegen.javapoet.TypeNames.INSTANCE_FACTORY; import static dagger.internal.codegen.javapoet.TypeNames.providerOf; import static java.util.stream.Collectors.joining; import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; import com.google.auto.common.MoreElements; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; import dagger.assisted.AssistedFactory; import dagger.internal.codegen.base.SourceFileGenerationException; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.binding.AssistedInjectionAnnotations; import dagger.internal.codegen.binding.AssistedInjectionAnnotations.AssistedFactoryMetadata; import dagger.internal.codegen.binding.AssistedInjectionAnnotations.AssistedParameter; import dagger.internal.codegen.binding.BindingFactory; import dagger.internal.codegen.binding.ProvisionBinding; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; import dagger.internal.codegen.validation.TypeCheckingProcessingStep; import dagger.internal.codegen.validation.ValidationReport; import java.lang.annotation.Annotation; import java.util.HashSet; import java.util.Optional; import java.util.Set; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.inject.Inject; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; 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.TypeKind; import javax.lang.model.type.TypeMirror; /** An annotation processor for {@link dagger.assisted.AssistedFactory}-annotated types. */ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep { private final Messager messager; private final Filer filer; private final SourceVersion sourceVersion; private final DaggerElements elements; private final DaggerTypes types; private final BindingFactory bindingFactory; @Inject AssistedFactoryProcessingStep( Messager messager, Filer filer, SourceVersion sourceVersion, DaggerElements elements, DaggerTypes types, BindingFactory bindingFactory) { super(MoreElements::asType); this.messager = messager; this.filer = filer; this.sourceVersion = sourceVersion; this.elements = elements; this.types = types; this.bindingFactory = bindingFactory; } @Override public ImmutableSet> annotations() { return ImmutableSet.of(AssistedFactory.class); } @Override protected void process( TypeElement factory, ImmutableSet> annotations) { ValidationReport report = new AssistedFactoryValidator().validate(factory); report.printMessagesTo(messager); if (report.isClean()) { try { ProvisionBinding binding = bindingFactory.assistedFactoryBinding(factory, Optional.empty()); new AssistedFactoryImplGenerator().generate(binding); } catch (SourceFileGenerationException e) { e.printMessageTo(messager); } } } private final class AssistedFactoryValidator { ValidationReport validate(TypeElement factory) { ValidationReport.Builder report = ValidationReport.about(factory); if (!factory.getModifiers().contains(ABSTRACT)) { return report .addError( "The @AssistedFactory-annotated type must be either an abstract class or " + "interface.", factory) .build(); } if (factory.getNestingKind().isNested() && !factory.getModifiers().contains(STATIC)) { report.addError("Nested @AssistedFactory-annotated types must be static. ", factory); } ImmutableSet abstractFactoryMethods = AssistedInjectionAnnotations.assistedFactoryMethods(factory, elements); if (abstractFactoryMethods.isEmpty()) { report.addError( "The @AssistedFactory-annotated type is missing an abstract, non-default method " + "whose return type matches the assisted injection type.", factory); } for (ExecutableElement method : abstractFactoryMethods) { ExecutableType methodType = types.resolveExecutableType(method, factory.asType()); if (!isAssistedInjectionType(methodType.getReturnType())) { report.addError( String.format( "Invalid return type: %s. An assisted factory's abstract method must return a " + "type with an @AssistedInject-annotated constructor.", methodType.getReturnType()), method); } if (!method.getTypeParameters().isEmpty()) { report.addError( "@AssistedFactory does not currently support type parameters in the creator " + "method. See https://github.com/google/dagger/issues/2279", method); } } if (abstractFactoryMethods.size() > 1) { report.addError( "The @AssistedFactory-annotated type should contain a single abstract, non-default" + " method but found multiple: " + abstractFactoryMethods, factory); } if (!report.build().isClean()) { return report.build(); } AssistedFactoryMetadata metadata = AssistedFactoryMetadata.create(factory.asType(), elements, types); // Note: We check uniqueness of the @AssistedInject constructor parameters in // AssistedInjectProcessingStep. We need to check uniqueness for here too because we may // have resolved some type parameters that were not resolved in the @AssistedInject type. Set uniqueAssistedParameters = new HashSet<>(); for (AssistedParameter assistedParameter : metadata.assistedFactoryAssistedParameters()) { if (!uniqueAssistedParameters.add(assistedParameter)) { report.addError( "@AssistedFactory method has duplicate @Assisted types: " + assistedParameter, assistedParameter.variableElement()); } } if (!ImmutableSet.copyOf(metadata.assistedInjectAssistedParameters()) .equals(ImmutableSet.copyOf(metadata.assistedFactoryAssistedParameters()))) { report.addError( String.format( "The parameters in the factory method must match the @Assisted parameters in %s." + "\n Actual: %s#%s" + "\n Expected: %s#%s(%s)", metadata.assistedInjectType(), metadata.factory().getQualifiedName(), metadata.factoryMethod(), metadata.factory().getQualifiedName(), metadata.factoryMethod().getSimpleName(), metadata.assistedInjectAssistedParameters().stream() .map(AssistedParameter::type) .map(Object::toString) .collect(joining(", "))), metadata.factoryMethod()); } return report.build(); } private boolean isAssistedInjectionType(TypeMirror type) { return type.getKind() == TypeKind.DECLARED && AssistedInjectionAnnotations.isAssistedInjectionType(asTypeElement(type)); } } /** Generates an implementation of the {@link dagger.assisted.AssistedFactory}-annotated class. */ private final class AssistedFactoryImplGenerator extends SourceFileGenerator { AssistedFactoryImplGenerator() { super(filer, elements, sourceVersion); } @Override public Element originatingElement(ProvisionBinding binding) { return binding.bindingElement().get(); } // For each @AssistedFactory-annotated type, we generates a class named "*_Impl" that implements // that type. // // Note that this class internally delegates to the @AssistedInject generated class, which // contains the actual implementation logic for creating the @AssistedInject type. The reason we // need both of these generated classes is because while the @AssistedInject generated class // knows how to create the @AssistedInject type, it doesn't know about all of the // @AssistedFactory interfaces that it needs to extend when it's generated. Thus, the role of // the @AssistedFactory generated class is purely to implement the @AssistedFactory type. // Furthermore, while we could have put all of the logic into the @AssistedFactory generated // class and not generate the @AssistedInject generated class, having the @AssistedInject // generated class ensures we have proper accessibility to the @AssistedInject type, and reduces // duplicate logic if there are multiple @AssistedFactory types for the same @AssistedInject // type. // // Example: // public class FooFactory_Impl implements FooFactory { // private final Foo_Factory delegateFactory; // // FooFactory_Impl(Foo_Factory delegateFactory) { // this.delegateFactory = delegateFactory; // } // // @Override // public Foo createFoo(AssistedDep assistedDep) { // return delegateFactory.get(assistedDep); // } // // public static Provider create(Foo_Factory delegateFactory) { // return InstanceFactory.create(new FooFactory_Impl(delegateFactory)); // } // } @Override public ImmutableList topLevelTypes(ProvisionBinding binding) { TypeElement factory = asType(binding.bindingElement().get()); ClassName name = generatedClassNameForBinding(binding); TypeSpec.Builder builder = TypeSpec.classBuilder(name) .addModifiers(PUBLIC, FINAL) .addTypeVariables( factory.getTypeParameters().stream() .map(TypeVariableName::get) .collect(toImmutableList())); if (factory.getKind() == ElementKind.INTERFACE) { builder.addSuperinterface(factory.asType()); } else { builder.superclass(factory.asType()); } AssistedFactoryMetadata metadata = AssistedFactoryMetadata.create(asDeclared(factory.asType()), elements, types); ParameterSpec delegateFactoryParam = ParameterSpec.builder( delegateFactoryTypeName(metadata.assistedInjectType()), "delegateFactory") .build(); builder .addField( FieldSpec.builder(delegateFactoryParam.type, delegateFactoryParam.name) .addModifiers(PRIVATE, FINAL) .build()) .addMethod( MethodSpec.constructorBuilder() .addParameter(delegateFactoryParam) .addStatement("this.$1N = $1N", delegateFactoryParam) .build()) .addMethod( MethodSpec.overriding(metadata.factoryMethod(), metadata.factoryType(), types) .addStatement( "return $N.get($L)", delegateFactoryParam, // Use the order of the parameters from the @AssistedInject constructor but // use the parameter names of the @AssistedFactory method. metadata.assistedInjectAssistedParameters().stream() .map(metadata.assistedFactoryAssistedParametersMap()::get) .map(param -> CodeBlock.of("$L", param.getSimpleName())) .collect(toParametersCodeBlock())) .build()) .addMethod( MethodSpec.methodBuilder("create") .addModifiers(PUBLIC, STATIC) .addParameter(delegateFactoryParam) .addTypeVariables( metadata.assistedInjectElement().getTypeParameters().stream() .map(TypeVariableName::get) .collect(toImmutableList())) .returns(providerOf(TypeName.get(factory.asType()))) .addStatement( "return $T.$Lcreate(new $T($N))", INSTANCE_FACTORY, // Java 7 type inference requires the method call provide the exact type here. sourceVersion.compareTo(SourceVersion.RELEASE_7) <= 0 ? CodeBlock.of("<$T>", types.accessibleType(metadata.factoryType(), name)) : CodeBlock.of(""), name, delegateFactoryParam) .build()); return ImmutableList.of(builder); } /** Returns the generated factory {@link TypeName type} for an @AssistedInject constructor. */ private TypeName delegateFactoryTypeName(DeclaredType assistedInjectType) { // The name of the generated factory for the assisted inject type, // e.g. an @AssistedInject Foo(...) {...} constructor will generate a Foo_Factory class. ClassName generatedFactoryClassName = generatedClassNameForBinding( bindingFactory.injectionBinding( getOnlyElement(assistedInjectedConstructors(asTypeElement(assistedInjectType))), Optional.empty())); // Return the factory type resolved with the same type parameters as the assisted inject type. return assistedInjectType.getTypeArguments().isEmpty() ? generatedFactoryClassName : ParameterizedTypeName.get( generatedFactoryClassName, assistedInjectType.getTypeArguments().stream() .map(TypeName::get) .collect(toImmutableList()) .toArray(new TypeName[0])); } } }