blob: 5e5292359ad2b12a694422d559f2b670120e4c4d [file] [log] [blame]
/*
* 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<ProductionBinding> {
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 ClassName nameGeneratedType(ProductionBinding binding) {
return generatedClassNameForBinding(binding);
}
@Override
public Element originatingElement(ProductionBinding binding) {
// we only create factories for bindings that have a binding element
return binding.bindingElement().get();
}
@Override
public Optional<TypeSpec.Builder> write(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 = nameGeneratedType(binding);
TypeSpec.Builder factoryBuilder =
classBuilder(generatedTypeName)
.addModifiers(PUBLIC, FINAL)
.addTypeVariables(bindingTypeElementTypeVariableNames(binding));
UniqueNameSet uniqueFieldNames = new UniqueNameSet();
ImmutableMap.Builder<DependencyRequest, FieldSpec> fieldsBuilder = ImmutableMap.builder();
MethodSpec.Builder constructorBuilder = constructorBuilder().addModifiers(PRIVATE);
Optional<FieldSpec> moduleField =
binding.requiresModuleInstance()
? Optional.of(
addFieldAndConstructorParameter(
factoryBuilder,
constructorBuilder,
uniqueFieldNames.getUniqueName("module"),
TypeName.get(binding.bindingTypeElement().get().asType())))
: Optional.empty();
List<CodeBlock> frameworkFieldAssignments = new ArrayList<>();
String executorParameterName = null;
String monitorParameterName = null;
ImmutableMap<DependencyRequest, FrameworkField> bindingFieldsForDependencies =
generateBindingFieldsForDependencies(binding);
for (Entry<DependencyRequest, FrameworkField> 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<DependencyRequest, FieldSpec> 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<DependencyRequest> 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 Optional.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<DependencyRequest> 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<DependencyRequest, FieldSpec> fields;
protected final ProductionBinding binding;
FutureTransform(ImmutableMap<DependencyRequest, FieldSpec> 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<CodeBlock> 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<DependencyRequest, FieldSpec> fields,
ProductionBinding binding,
ImmutableList<DependencyRequest> 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<DependencyRequest, FieldSpec> 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<CodeBlock> parameterCodeBlocks() {
return binding.explicitDependencies().stream()
.map(this::frameworkTypeUsageStatement)
.collect(toImmutableList());
}
}
static final class SingleArgFutureTransform extends FutureTransform {
private final DependencyRequest asyncDependency;
SingleArgFutureTransform(
ImmutableMap<DependencyRequest, FieldSpec> 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<CodeBlock> parameterCodeBlocks() {
ImmutableList.Builder<CodeBlock> 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<DependencyRequest> asyncDependencies;
MultiArgFutureTransform(
ImmutableMap<DependencyRequest, FieldSpec> fields,
ProductionBinding binding,
ImmutableList<DependencyRequest> 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<CodeBlock> parameterCodeBlocks() {
int argIndex = 0;
ImmutableList.Builder<CodeBlock> 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<CodeBlock> 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<? extends TypeName> getThrownTypeNames(
Iterable<? extends TypeMirror> thrownTypes) {
return FluentIterable.from(thrownTypes).transform(TypeName::get);
}
@Override
protected ImmutableSet<Suppression> warningSuppressions() {
// TODO(beder): examine if we can remove this or prevent subtypes of Future from being produced
return ImmutableSet.of(FUTURE_RETURN_VALUE_IGNORED);
}
}