blob: ea83778b0c10ca04a318a073194a9e6d2fc7407f [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.squareup.javapoet.MethodSpec.constructorBuilder;
import static com.squareup.javapoet.MethodSpec.methodBuilder;
import static com.squareup.javapoet.TypeSpec.classBuilder;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedParameters;
import static dagger.internal.codegen.binding.SourceFiles.bindingTypeElementTypeVariableNames;
import static dagger.internal.codegen.binding.SourceFiles.frameworkFieldUsages;
import static dagger.internal.codegen.binding.SourceFiles.frameworkTypeUsageStatement;
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.presentValues;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap;
import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES;
import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED;
import static dagger.internal.codegen.javapoet.AnnotationSpecs.suppressWarnings;
import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock;
import static dagger.internal.codegen.javapoet.TypeNames.factoryOf;
import static dagger.internal.codegen.writing.GwtCompatibility.gwtIncompatibleAnnotation;
import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
import static dagger.spi.model.BindingKind.INJECTION;
import static dagger.spi.model.BindingKind.PROVISION;
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 androidx.room.compiler.processing.XElement;
import androidx.room.compiler.processing.XFiler;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XTypeElement;
import androidx.room.compiler.processing.compat.XConverters;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.squareup.javapoet.AnnotationSpec;
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.TypeName;
import com.squareup.javapoet.TypeSpec;
import dagger.internal.Factory;
import dagger.internal.codegen.base.SourceFileGenerator;
import dagger.internal.codegen.base.UniqueNameSet;
import dagger.internal.codegen.binding.Binding;
import dagger.internal.codegen.binding.ProvisionBinding;
import dagger.internal.codegen.compileroption.CompilerOptions;
import dagger.internal.codegen.javapoet.TypeNames;
import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
import dagger.internal.codegen.langmodel.DaggerElements;
import dagger.internal.codegen.langmodel.DaggerTypes;
import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod;
import dagger.internal.codegen.writing.InjectionMethods.ProvisionMethod;
import dagger.spi.model.BindingKind;
import dagger.spi.model.DaggerAnnotation;
import dagger.spi.model.DependencyRequest;
import dagger.spi.model.Key;
import dagger.spi.model.Scope;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.VariableElement;
/**
* Generates {@link Factory} implementations from {@link ProvisionBinding} instances for {@link
* Inject} constructors.
*/
public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding> {
private final DaggerTypes types;
private final CompilerOptions compilerOptions;
private final KotlinMetadataUtil metadataUtil;
@Inject
FactoryGenerator(
XFiler filer,
SourceVersion sourceVersion,
DaggerTypes types,
DaggerElements elements,
CompilerOptions compilerOptions,
KotlinMetadataUtil metadataUtil) {
super(filer, elements, sourceVersion);
this.types = types;
this.compilerOptions = compilerOptions;
this.metadataUtil = metadataUtil;
}
@Override
public XElement originatingElement(ProvisionBinding binding) {
// we only create factories for bindings that have a binding element
return binding.bindingElement().get();
}
@Override
public ImmutableList<TypeSpec.Builder> topLevelTypes(ProvisionBinding 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());
if (binding.kind() == BindingKind.DELEGATE) {
return ImmutableList.of();
}
return ImmutableList.of(factoryBuilder(binding));
}
private TypeSpec.Builder factoryBuilder(ProvisionBinding binding) {
TypeSpec.Builder factoryBuilder =
classBuilder(generatedClassNameForBinding(binding))
.addModifiers(PUBLIC, FINAL)
.addTypeVariables(bindingTypeElementTypeVariableNames(binding));
if (binding.kind() == BindingKind.INJECTION
|| binding.kind() == BindingKind.ASSISTED_INJECTION
|| binding.kind() == BindingKind.PROVISION) {
factoryBuilder.addAnnotation(scopeMetadataAnnotation(binding));
factoryBuilder.addAnnotation(qualifierMetadataAnnotation(binding));
}
factoryTypeName(binding).ifPresent(factoryBuilder::addSuperinterface);
addConstructorAndFields(binding, factoryBuilder);
factoryBuilder.addMethod(getMethod(binding));
addCreateMethod(binding, factoryBuilder);
factoryBuilder.addMethod(ProvisionMethod.create(binding, compilerOptions, metadataUtil));
gwtIncompatibleAnnotation(binding).ifPresent(factoryBuilder::addAnnotation);
return factoryBuilder;
}
private void addConstructorAndFields(ProvisionBinding binding, TypeSpec.Builder factoryBuilder) {
if (FactoryCreationStrategy.of(binding) == FactoryCreationStrategy.SINGLETON_INSTANCE) {
return;
}
// TODO(bcorso): Make the constructor private?
MethodSpec.Builder constructor = constructorBuilder().addModifiers(PUBLIC);
constructorParams(binding).forEach(
param -> {
constructor.addParameter(param).addStatement("this.$1N = $1N", param);
factoryBuilder.addField(
FieldSpec.builder(param.type, param.name, PRIVATE, FINAL).build());
});
factoryBuilder.addMethod(constructor.build());
}
private ImmutableList<ParameterSpec> constructorParams(ProvisionBinding binding) {
ImmutableList.Builder<ParameterSpec> params = ImmutableList.builder();
moduleParameter(binding).ifPresent(params::add);
frameworkFields(binding).values().forEach(field -> params.add(toParameter(field)));
return params.build();
}
private Optional<ParameterSpec> moduleParameter(ProvisionBinding binding) {
if (binding.requiresModuleInstance()) {
// TODO(bcorso, dpb): Should this use contributingModule()?
TypeName type = binding.bindingTypeElement().get().getType().getTypeName();
return Optional.of(ParameterSpec.builder(type, "module").build());
}
return Optional.empty();
}
private ImmutableMap<DependencyRequest, FieldSpec> frameworkFields(ProvisionBinding binding) {
UniqueNameSet uniqueFieldNames = new UniqueNameSet();
// TODO(bcorso, dpb): Add a test for the case when a Factory parameter is named "module".
moduleParameter(binding).ifPresent(module -> uniqueFieldNames.claim(module.name));
// We avoid Maps.transformValues here because it would implicitly depend on the order in which
// the transform function is evaluated on each entry in the map.
ImmutableMap.Builder<DependencyRequest, FieldSpec> builder = ImmutableMap.builder();
generateBindingFieldsForDependencies(binding).forEach(
(dependency, field) ->
builder.put(dependency,
FieldSpec.builder(
field.type(), uniqueFieldNames.getUniqueName(field.name()), PRIVATE, FINAL)
.build()));
return builder.build();
}
private void addCreateMethod(ProvisionBinding binding, TypeSpec.Builder factoryBuilder) {
// If constructing a factory for @Inject or @Provides bindings, we use a static create method
// so that generated components can avoid having to refer to the generic types
// of the factory. (Otherwise they may have visibility problems referring to the types.)
MethodSpec.Builder createMethodBuilder =
methodBuilder("create")
.addModifiers(PUBLIC, STATIC)
.returns(parameterizedGeneratedTypeNameForBinding(binding))
.addTypeVariables(bindingTypeElementTypeVariableNames(binding));
switch (FactoryCreationStrategy.of(binding)) {
case SINGLETON_INSTANCE:
FieldSpec.Builder instanceFieldBuilder =
FieldSpec.builder(
generatedClassNameForBinding(binding), "INSTANCE", PRIVATE, STATIC, FINAL)
.initializer("new $T()", generatedClassNameForBinding(binding));
if (!bindingTypeElementTypeVariableNames(binding).isEmpty()) {
// If the factory has type parameters, ignore them in the field declaration & initializer
instanceFieldBuilder.addAnnotation(suppressWarnings(RAWTYPES));
createMethodBuilder.addAnnotation(suppressWarnings(UNCHECKED));
}
ClassName instanceHolderName =
generatedClassNameForBinding(binding).nestedClass("InstanceHolder");
createMethodBuilder.addStatement("return $T.INSTANCE", instanceHolderName);
factoryBuilder.addType(
TypeSpec.classBuilder(instanceHolderName)
.addModifiers(PRIVATE, STATIC, FINAL)
.addField(instanceFieldBuilder.build())
.build());
break;
case CLASS_CONSTRUCTOR:
List<ParameterSpec> params = constructorParams(binding);
createMethodBuilder.addParameters(params);
createMethodBuilder.addStatement(
"return new $T($L)",
parameterizedGeneratedTypeNameForBinding(binding),
makeParametersCodeBlock(Lists.transform(params, input -> CodeBlock.of("$N", input))));
break;
default:
throw new AssertionError();
}
factoryBuilder.addMethod(createMethodBuilder.build());
}
private MethodSpec getMethod(ProvisionBinding binding) {
UniqueNameSet uniqueFieldNames = new UniqueNameSet();
ImmutableMap<DependencyRequest, FieldSpec> frameworkFields = frameworkFields(binding);
frameworkFields.values().forEach(field -> uniqueFieldNames.claim(field.name));
Map<VariableElement, ParameterSpec> assistedParameters =
assistedParameters(binding).stream()
.collect(
toImmutableMap(
XConverters::toJavac,
element ->
ParameterSpec.builder(
element.getType().getTypeName(),
uniqueFieldNames.getUniqueName(getSimpleName(element)))
.build()));
TypeName providedTypeName = providedTypeName(binding);
MethodSpec.Builder getMethod =
methodBuilder("get")
.addModifiers(PUBLIC)
.returns(providedTypeName)
.addParameters(assistedParameters.values());
if (factoryTypeName(binding).isPresent()) {
getMethod.addAnnotation(Override.class);
}
CodeBlock invokeNewInstance =
ProvisionMethod.invoke(
binding,
request ->
frameworkTypeUsageStatement(
CodeBlock.of("$N", frameworkFields.get(request)), request.kind()),
param -> assistedParameters.get(param).name,
generatedClassNameForBinding(binding),
moduleParameter(binding).map(module -> CodeBlock.of("$N", module)),
compilerOptions,
metadataUtil);
if (binding.kind().equals(PROVISION)) {
binding
.nullableType()
.map(XType::getTypeElement)
.map(XTypeElement::getClassName)
.ifPresent(getMethod::addAnnotation);
getMethod.addStatement("return $L", invokeNewInstance);
} else if (!binding.injectionSites().isEmpty()) {
CodeBlock instance = CodeBlock.of("instance");
getMethod
.addStatement("$T $L = $L", providedTypeName, instance, invokeNewInstance)
.addCode(
InjectionSiteMethod.invokeAll(
binding.injectionSites(),
generatedClassNameForBinding(binding),
instance,
binding.key().type().java(),
frameworkFieldUsages(binding.dependencies(), frameworkFields)::get,
types,
metadataUtil))
.addStatement("return $L", instance);
} else {
getMethod.addStatement("return $L", invokeNewInstance);
}
return getMethod.build();
}
private AnnotationSpec scopeMetadataAnnotation(ProvisionBinding binding) {
AnnotationSpec.Builder builder = AnnotationSpec.builder(TypeNames.SCOPE_METADATA);
binding.scope()
.map(Scope::scopeAnnotation)
.map(DaggerAnnotation::className)
.map(ClassName::canonicalName)
.ifPresent(scopeCanonicalName -> builder.addMember("value", "$S", scopeCanonicalName));
return builder.build();
}
private AnnotationSpec qualifierMetadataAnnotation(ProvisionBinding binding) {
AnnotationSpec.Builder builder = AnnotationSpec.builder(TypeNames.QUALIFIER_METADATA);
// Collect all qualifiers on the binding itself or its dependencies
Stream.concat(
Stream.of(binding.key()),
binding.provisionDependencies().stream().map(DependencyRequest::key))
.map(Key::qualifier)
.flatMap(presentValues())
.map(DaggerAnnotation::className)
.map(ClassName::canonicalName)
.distinct()
.forEach(qualifier -> builder.addMember("value", "$S", qualifier));
return builder.build();
}
private static TypeName providedTypeName(ProvisionBinding binding) {
return binding.contributedType().getTypeName();
}
private static Optional<TypeName> factoryTypeName(ProvisionBinding binding) {
return binding.kind() == BindingKind.ASSISTED_INJECTION
? Optional.empty()
: Optional.of(factoryOf(providedTypeName(binding)));
}
private static ParameterSpec toParameter(FieldSpec field) {
return ParameterSpec.builder(field.type, field.name).build();
}
/** The strategy for getting an instance of a factory for a {@link Binding}. */
private enum FactoryCreationStrategy {
/** The factory class is a single instance. */
SINGLETON_INSTANCE,
/** The factory must be created by calling the constructor. */
CLASS_CONSTRUCTOR;
static FactoryCreationStrategy of(Binding binding) {
switch (binding.kind()) {
case DELEGATE:
throw new AssertionError("Delegate bindings don't have a factory.");
case PROVISION:
return binding.dependencies().isEmpty() && !binding.requiresModuleInstance()
? SINGLETON_INSTANCE
: CLASS_CONSTRUCTOR;
case INJECTION:
case MULTIBOUND_SET:
case MULTIBOUND_MAP:
return binding.dependencies().isEmpty()
? SINGLETON_INSTANCE
: CLASS_CONSTRUCTOR;
default:
return CLASS_CONSTRUCTOR;
}
}
}
}