/* * 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.writing; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Verify.verifyNotNull; import static com.squareup.javapoet.ClassName.OBJECT; import static com.squareup.javapoet.MethodSpec.constructorBuilder; import static com.squareup.javapoet.MethodSpec.methodBuilder; import static com.squareup.javapoet.TypeSpec.classBuilder; import static dagger.internal.codegen.binding.SourceFiles.bindingTypeElementTypeVariableNames; import static dagger.internal.codegen.binding.SourceFiles.generateBindingFieldsForDependencies; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; import static dagger.internal.codegen.binding.SourceFiles.parameterizedGeneratedTypeNameForBinding; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.FUTURE_RETURN_VALUE_IGNORED; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock; import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; import static dagger.internal.codegen.javapoet.TypeNames.FUTURES; import static dagger.internal.codegen.javapoet.TypeNames.PRODUCERS; import static dagger.internal.codegen.javapoet.TypeNames.PRODUCER_TOKEN; import static dagger.internal.codegen.javapoet.TypeNames.VOID_CLASS; import static dagger.internal.codegen.javapoet.TypeNames.listOf; import static dagger.internal.codegen.javapoet.TypeNames.listenableFutureOf; import static dagger.internal.codegen.javapoet.TypeNames.producedOf; import static dagger.internal.codegen.writing.GwtCompatibility.gwtIncompatibleAnnotation; import static java.util.stream.Collectors.joining; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PROTECTED; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.base.UniqueNameSet; import dagger.internal.codegen.binding.Binding; import dagger.internal.codegen.binding.FrameworkField; import dagger.internal.codegen.binding.KeyFactory; import dagger.internal.codegen.binding.ProductionBinding; import dagger.internal.codegen.binding.SourceFiles; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.javapoet.AnnotationSpecs; import dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.model.DependencyRequest; import dagger.model.Key; import dagger.model.RequestKind; import dagger.producers.Producer; import dagger.producers.internal.AbstractProducesMethodProducer; import dagger.producers.internal.Producers; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import java.util.Optional; import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.type.TypeMirror; /** Generates {@link Producer} implementations from {@link ProductionBinding} instances. */ public final class ProducerFactoryGenerator extends SourceFileGenerator { private final CompilerOptions compilerOptions; private final KeyFactory keyFactory; @Inject ProducerFactoryGenerator( Filer filer, DaggerElements elements, SourceVersion sourceVersion, CompilerOptions compilerOptions, KeyFactory keyFactory) { super(filer, elements, sourceVersion); this.compilerOptions = compilerOptions; this.keyFactory = keyFactory; } @Override public Element originatingElement(ProductionBinding binding) { // we only create factories for bindings that have a binding element return binding.bindingElement().get(); } @Override public ImmutableList topLevelTypes(ProductionBinding binding) { // We don't want to write out resolved bindings -- we want to write out the generic version. checkArgument(!binding.unresolved().isPresent()); checkArgument(binding.bindingElement().isPresent()); TypeName providedTypeName = TypeName.get(binding.contributedType()); TypeName futureTypeName = listenableFutureOf(providedTypeName); ClassName generatedTypeName = generatedClassNameForBinding(binding); TypeSpec.Builder factoryBuilder = classBuilder(generatedTypeName) .addModifiers(PUBLIC, FINAL) .addTypeVariables(bindingTypeElementTypeVariableNames(binding)); UniqueNameSet uniqueFieldNames = new UniqueNameSet(); ImmutableMap.Builder fieldsBuilder = ImmutableMap.builder(); MethodSpec.Builder constructorBuilder = constructorBuilder().addModifiers(PRIVATE); Optional moduleField = binding.requiresModuleInstance() ? Optional.of( addFieldAndConstructorParameter( factoryBuilder, constructorBuilder, uniqueFieldNames.getUniqueName("module"), TypeName.get(binding.bindingTypeElement().get().asType()))) : Optional.empty(); List frameworkFieldAssignments = new ArrayList<>(); String executorParameterName = null; String monitorParameterName = null; ImmutableMap bindingFieldsForDependencies = generateBindingFieldsForDependencies(binding); for (Entry entry : bindingFieldsForDependencies.entrySet()) { DependencyRequest dependency = entry.getKey(); Key key = dependency.key(); FrameworkField bindingField = entry.getValue(); String fieldName = uniqueFieldNames.getUniqueName(bindingField.name()); if (key.equals(keyFactory.forProductionImplementationExecutor())) { executorParameterName = fieldName; constructorBuilder.addParameter(bindingField.type(), executorParameterName); } else if (key.equals(keyFactory.forProductionComponentMonitor())) { monitorParameterName = fieldName; constructorBuilder.addParameter(bindingField.type(), monitorParameterName); } else { FieldSpec field = addFieldAndConstructorParameter( factoryBuilder, constructorBuilder, fieldName, bindingField.type()); fieldsBuilder.put(dependency, field); frameworkFieldAssignments.add(fieldAssignment(field, bindingField.type())); } } ImmutableMap fields = fieldsBuilder.build(); constructorBuilder.addStatement( "super($N, $L, $N)", verifyNotNull(monitorParameterName), producerTokenConstruction(generatedTypeName, binding), verifyNotNull(executorParameterName)); if (binding.requiresModuleInstance()) { assignField(constructorBuilder, moduleField.get(), null); } constructorBuilder.addCode(CodeBlock.join(frameworkFieldAssignments, "\n")); MethodSpec.Builder collectDependenciesBuilder = methodBuilder("collectDependencies").addAnnotation(Override.class).addModifiers(PROTECTED); ImmutableList asyncDependencies = asyncDependencies(binding); for (DependencyRequest dependency : asyncDependencies) { TypeName futureType = listenableFutureOf(asyncDependencyType(dependency)); CodeBlock futureAccess = CodeBlock.of("$N.get()", fields.get(dependency)); collectDependenciesBuilder.addStatement( "$T $L = $L", futureType, dependencyFutureName(dependency), dependency.kind().equals(RequestKind.PRODUCED) ? CodeBlock.of("$T.createFutureProduced($L)", PRODUCERS, futureAccess) : futureAccess); } FutureTransform futureTransform = FutureTransform.create(fields, binding, asyncDependencies); collectDependenciesBuilder .returns(listenableFutureOf(futureTransform.applyArgType())) .addStatement("return $L", futureTransform.futureCodeBlock()); MethodSpec.Builder callProducesMethod = methodBuilder("callProducesMethod") .returns(futureTypeName) .addAnnotation(Override.class) .addModifiers(PUBLIC) .addParameter(futureTransform.applyArgType(), futureTransform.applyArgName()) .addExceptions(getThrownTypeNames(binding.thrownTypes())) .addCode( getInvocationCodeBlock( binding, providedTypeName, futureTransform.parameterCodeBlocks())); if (futureTransform.hasUncheckedCast()) { callProducesMethod.addAnnotation(AnnotationSpecs.suppressWarnings(UNCHECKED)); } MethodSpec constructor = constructorBuilder.build(); factoryBuilder .superclass( ParameterizedTypeName.get( ClassName.get(AbstractProducesMethodProducer.class), futureTransform.applyArgType(), providedTypeName)) .addMethod(constructor) .addMethod(staticFactoryMethod(binding, constructor)) .addMethod(collectDependenciesBuilder.build()) .addMethod(callProducesMethod.build()); gwtIncompatibleAnnotation(binding).ifPresent(factoryBuilder::addAnnotation); // TODO(gak): write a sensible toString return ImmutableList.of(factoryBuilder); } private MethodSpec staticFactoryMethod(ProductionBinding binding, MethodSpec constructor) { return MethodSpec.methodBuilder("create") .addModifiers(PUBLIC, STATIC) .returns(parameterizedGeneratedTypeNameForBinding(binding)) .addTypeVariables(bindingTypeElementTypeVariableNames(binding)) .addParameters(constructor.parameters) .addStatement( "return new $T($L)", parameterizedGeneratedTypeNameForBinding(binding), constructor.parameters.stream() .map(p -> CodeBlock.of("$N", p.name)) .collect(toParametersCodeBlock())) .build(); } // TODO(ronshapiro): consolidate versions of these private static FieldSpec addFieldAndConstructorParameter( TypeSpec.Builder typeBuilder, MethodSpec.Builder constructorBuilder, String variableName, TypeName variableType) { FieldSpec field = FieldSpec.builder(variableType, variableName, PRIVATE, FINAL).build(); typeBuilder.addField(field); constructorBuilder.addParameter(field.type, field.name); return field; } private static CodeBlock fieldAssignment(FieldSpec field, ParameterizedTypeName type) { CodeBlock.Builder statement = CodeBlock.builder(); if (type != null && type.rawType.equals(TypeNames.PRODUCER)) { statement.addStatement( "this.$1N = $2T.nonCancellationPropagatingViewOf($1N)", field, Producers.class); } else { statement.addStatement("this.$1N = $1N", field); } return statement.build(); } private static void assignField( MethodSpec.Builder constructorBuilder, FieldSpec field, ParameterizedTypeName type) { if (type != null && type.rawType.equals(TypeNames.PRODUCER)) { constructorBuilder.addStatement( "this.$1N = $2T.nonCancellationPropagatingViewOf($1N)", field, Producers.class); } else { constructorBuilder.addStatement("this.$1N = $1N", field); } } /** Returns a list of dependencies that are generated asynchronously. */ private static ImmutableList asyncDependencies(Binding binding) { return binding.dependencies().stream() .filter(ProducerFactoryGenerator::isAsyncDependency) .collect(toImmutableList()); } private CodeBlock producerTokenConstruction( ClassName generatedTypeName, ProductionBinding binding) { CodeBlock producerTokenArgs = compilerOptions.writeProducerNameInToken() ? CodeBlock.of( "$S", String.format( "%s#%s", ClassName.get(binding.bindingTypeElement().get()), binding.bindingElement().get().getSimpleName())) : CodeBlock.of("$T.class", generatedTypeName); return CodeBlock.of("$T.create($L)", PRODUCER_TOKEN, producerTokenArgs); } /** Returns a name of the variable representing this dependency's future. */ private static String dependencyFutureName(DependencyRequest dependency) { return dependency.requestElement().get().getSimpleName() + "Future"; } /** Represents the transformation of an input future by a producer method. */ abstract static class FutureTransform { protected final ImmutableMap fields; protected final ProductionBinding binding; FutureTransform(ImmutableMap fields, ProductionBinding binding) { this.fields = fields; this.binding = binding; } /** The code block representing the future that should be transformed. */ abstract CodeBlock futureCodeBlock(); /** The type of the argument to the apply method. */ abstract TypeName applyArgType(); /** The name of the argument to the apply method */ abstract String applyArgName(); /** The code blocks to be passed to the produces method itself. */ abstract ImmutableList parameterCodeBlocks(); /** Whether the transform method has an unchecked cast. */ boolean hasUncheckedCast() { return false; } CodeBlock frameworkTypeUsageStatement(DependencyRequest dependency) { return SourceFiles.frameworkTypeUsageStatement( CodeBlock.of("$N", fields.get(dependency)), dependency.kind()); } static FutureTransform create( ImmutableMap fields, ProductionBinding binding, ImmutableList asyncDependencies) { if (asyncDependencies.isEmpty()) { return new NoArgFutureTransform(fields, binding); } else if (asyncDependencies.size() == 1) { return new SingleArgFutureTransform( fields, binding, Iterables.getOnlyElement(asyncDependencies)); } else { return new MultiArgFutureTransform(fields, binding, asyncDependencies); } } } static final class NoArgFutureTransform extends FutureTransform { NoArgFutureTransform( ImmutableMap fields, ProductionBinding binding) { super(fields, binding); } @Override CodeBlock futureCodeBlock() { return CodeBlock.of("$T.<$T>immediateFuture(null)", FUTURES, VOID_CLASS); } @Override TypeName applyArgType() { return VOID_CLASS; } @Override String applyArgName() { return "ignoredVoidArg"; } @Override ImmutableList parameterCodeBlocks() { return binding.explicitDependencies().stream() .map(this::frameworkTypeUsageStatement) .collect(toImmutableList()); } } static final class SingleArgFutureTransform extends FutureTransform { private final DependencyRequest asyncDependency; SingleArgFutureTransform( ImmutableMap fields, ProductionBinding binding, DependencyRequest asyncDependency) { super(fields, binding); this.asyncDependency = asyncDependency; } @Override CodeBlock futureCodeBlock() { return CodeBlock.of("$L", dependencyFutureName(asyncDependency)); } @Override TypeName applyArgType() { return asyncDependencyType(asyncDependency); } @Override String applyArgName() { String argName = asyncDependency.requestElement().get().getSimpleName().toString(); if (argName.equals("module")) { return "moduleArg"; } return argName; } @Override ImmutableList parameterCodeBlocks() { ImmutableList.Builder parameterCodeBlocks = ImmutableList.builder(); for (DependencyRequest dependency : binding.explicitDependencies()) { // We really want to compare instances here, because asyncDependency is an element in the // set binding.dependencies(). if (dependency == asyncDependency) { parameterCodeBlocks.add(CodeBlock.of("$L", applyArgName())); } else { parameterCodeBlocks.add(frameworkTypeUsageStatement(dependency)); } } return parameterCodeBlocks.build(); } } static final class MultiArgFutureTransform extends FutureTransform { private final ImmutableList asyncDependencies; MultiArgFutureTransform( ImmutableMap fields, ProductionBinding binding, ImmutableList asyncDependencies) { super(fields, binding); this.asyncDependencies = asyncDependencies; } @Override CodeBlock futureCodeBlock() { return CodeBlock.of( "$T.<$T>allAsList($L)", FUTURES, OBJECT, asyncDependencies .stream() .map(ProducerFactoryGenerator::dependencyFutureName) .collect(joining(", "))); } @Override TypeName applyArgType() { return listOf(OBJECT); } @Override String applyArgName() { return "args"; } @Override ImmutableList parameterCodeBlocks() { int argIndex = 0; ImmutableList.Builder codeBlocks = ImmutableList.builder(); for (DependencyRequest dependency : binding.explicitDependencies()) { if (isAsyncDependency(dependency)) { codeBlocks.add( CodeBlock.of( "($T) $L.get($L)", asyncDependencyType(dependency), applyArgName(), argIndex)); argIndex++; } else { codeBlocks.add(frameworkTypeUsageStatement(dependency)); } } return codeBlocks.build(); } @Override boolean hasUncheckedCast() { return true; } } private static boolean isAsyncDependency(DependencyRequest dependency) { switch (dependency.kind()) { case INSTANCE: case PRODUCED: return true; default: return false; } } private static TypeName asyncDependencyType(DependencyRequest dependency) { TypeName keyName = TypeName.get(dependency.key().type()); switch (dependency.kind()) { case INSTANCE: return keyName; case PRODUCED: return producedOf(keyName); default: throw new AssertionError(); } } /** * Creates a code block for the invocation of the producer method from the module, which should be * used entirely within a method body. * * @param binding The binding to generate the invocation code block for. * @param providedTypeName The type name that should be provided by this producer. * @param parameterCodeBlocks The code blocks for all the parameters to the producer method. */ private CodeBlock getInvocationCodeBlock( ProductionBinding binding, TypeName providedTypeName, ImmutableList parameterCodeBlocks) { CodeBlock moduleCodeBlock = CodeBlock.of( "$L.$L($L)", binding.requiresModuleInstance() ? "module" : CodeBlock.of("$T", ClassName.get(binding.bindingTypeElement().get())), binding.bindingElement().get().getSimpleName(), makeParametersCodeBlock(parameterCodeBlocks)); final CodeBlock returnCodeBlock; switch (binding.productionKind().get()) { case IMMEDIATE: returnCodeBlock = CodeBlock.of("$T.<$T>immediateFuture($L)", FUTURES, providedTypeName, moduleCodeBlock); break; case FUTURE: returnCodeBlock = moduleCodeBlock; break; case SET_OF_FUTURE: returnCodeBlock = CodeBlock.of("$T.allAsSet($L)", PRODUCERS, moduleCodeBlock); break; default: throw new AssertionError(); } return CodeBlock.of("return $L;", returnCodeBlock); } /** * Converts the list of thrown types into type names. * * @param thrownTypes the list of thrown types. */ private FluentIterable getThrownTypeNames( Iterable thrownTypes) { return FluentIterable.from(thrownTypes).transform(TypeName::get); } @Override protected ImmutableSet warningSuppressions() { // TODO(beder): examine if we can remove this or prevent subtypes of Future from being produced return ImmutableSet.of(FUTURE_RETURN_VALUE_IGNORED); } }