Files
Android11/external/dagger2/java/dagger/internal/codegen/FactoryGenerator.java
2023-10-13 14:01:41 +00:00

291 lines
12 KiB
Java

/*
* 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;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Maps.transformValues;
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.ContributionBinding.FactoryCreationStrategy.DELEGATE;
import static dagger.internal.codegen.ContributionBinding.FactoryCreationStrategy.SINGLETON_INSTANCE;
import static dagger.internal.codegen.GwtCompatibility.gwtIncompatibleAnnotation;
import static dagger.internal.codegen.SourceFiles.bindingTypeElementTypeVariableNames;
import static dagger.internal.codegen.SourceFiles.frameworkFieldUsages;
import static dagger.internal.codegen.SourceFiles.frameworkTypeUsageStatement;
import static dagger.internal.codegen.SourceFiles.generateBindingFieldsForDependencies;
import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding;
import static dagger.internal.codegen.SourceFiles.parameterizedGeneratedTypeNameForBinding;
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.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 com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
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.Preconditions;
import dagger.internal.codegen.InjectionMethods.InjectionSiteMethod;
import dagger.internal.codegen.InjectionMethods.ProvisionMethod;
import dagger.internal.codegen.javapoet.CodeBlocks;
import dagger.internal.codegen.langmodel.DaggerElements;
import dagger.internal.codegen.langmodel.DaggerTypes;
import dagger.model.Key;
import java.util.List;
import java.util.Optional;
import javax.annotation.processing.Filer;
import javax.inject.Inject;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
/**
* Generates {@link Factory} implementations from {@link ProvisionBinding} instances for
* {@link Inject} constructors.
*/
final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding> {
private final DaggerTypes types;
private final DaggerElements elements;
private final CompilerOptions compilerOptions;
@Inject
FactoryGenerator(
Filer filer,
SourceVersion sourceVersion,
DaggerTypes types,
DaggerElements elements,
CompilerOptions compilerOptions) {
super(filer, elements, sourceVersion);
this.types = types;
this.elements = elements;
this.compilerOptions = compilerOptions;
}
@Override
ClassName nameGeneratedType(ProvisionBinding binding) {
return generatedClassNameForBinding(binding);
}
@Override
Element originatingElement(ProvisionBinding binding) {
// we only create factories for bindings that have a binding element
return binding.bindingElement().get();
}
@Override
Optional<TypeSpec.Builder> write(ClassName generatedTypeName, 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());
return binding.factoryCreationStrategy().equals(DELEGATE)
? Optional.empty()
: Optional.of(factoryBuilder(binding));
}
private TypeSpec.Builder factoryBuilder(ProvisionBinding binding) {
TypeSpec.Builder factoryBuilder =
classBuilder(nameGeneratedType(binding))
.addModifiers(PUBLIC, FINAL)
.addSuperinterface(factoryTypeName(binding))
.addTypeVariables(bindingTypeElementTypeVariableNames(binding));
addConstructorAndFields(binding, factoryBuilder);
factoryBuilder.addMethod(getMethod(binding));
addCreateMethod(binding, factoryBuilder);
factoryBuilder.addMethod(
ProvisionMethod.create(binding, compilerOptions, elements).toMethodSpec());
gwtIncompatibleAnnotation(binding).ifPresent(factoryBuilder::addAnnotation);
return factoryBuilder;
}
private void addConstructorAndFields(ProvisionBinding binding, TypeSpec.Builder factoryBuilder) {
if (binding.factoryCreationStrategy().equals(SINGLETON_INSTANCE)) {
return;
}
// TODO(user): 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(user, dpb): Should this use contributingModule()?
TypeName type = TypeName.get(binding.bindingTypeElement().get().asType());
return Optional.of(ParameterSpec.builder(type, "module").build());
}
return Optional.empty();
}
private ImmutableMap<Key, FieldSpec> frameworkFields(ProvisionBinding binding) {
UniqueNameSet uniqueFieldNames = new UniqueNameSet();
// TODO(user, dpb): Add a test for the case when a Factory parameter is named "module".
if (binding.requiresModuleInstance()) {
uniqueFieldNames.claim("module");
}
return ImmutableMap.copyOf(
transformValues(
generateBindingFieldsForDependencies(binding),
field ->
FieldSpec.builder(
field.type(), uniqueFieldNames.getUniqueName(field.name()), PRIVATE, FINAL)
.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 (binding.factoryCreationStrategy()) {
case SINGLETON_INSTANCE:
FieldSpec.Builder instanceFieldBuilder =
FieldSpec.builder(nameGeneratedType(binding), "INSTANCE", PRIVATE, STATIC, FINAL)
.initializer("new $T()", nameGeneratedType(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));
}
createMethodBuilder.addStatement("return INSTANCE");
factoryBuilder.addField(instanceFieldBuilder.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) {
TypeName providedTypeName = providedTypeName(binding);
MethodSpec.Builder getMethod =
methodBuilder("get")
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.returns(providedTypeName);
ImmutableMap<Key, FieldSpec> frameworkFields = frameworkFields(binding);
CodeBlock parametersCodeBlock =
makeParametersCodeBlock(
frameworkFieldUsages(binding.provisionDependencies(), frameworkFields).values());
if (binding.kind().equals(PROVISION)) {
binding
.nullableType()
.ifPresent(nullableType -> CodeBlocks.addAnnotation(getMethod, nullableType));
getMethod.addStatement(
"return $L",
ProvisionMethod.invoke(
binding,
request ->
frameworkTypeUsageStatement(
CodeBlock.of("$N", frameworkFields.get(request.key())), request.kind()),
nameGeneratedType(binding),
binding.requiresModuleInstance()
? Optional.of(CodeBlock.of("module"))
: Optional.empty(),
compilerOptions,
elements));
} else if (!binding.injectionSites().isEmpty()) {
CodeBlock instance = CodeBlock.of("instance");
getMethod
.addStatement("$1T $2L = new $1T($3L)", providedTypeName, instance, parametersCodeBlock)
.addCode(
InjectionSiteMethod.invokeAll(
binding.injectionSites(),
nameGeneratedType(binding),
instance,
binding.key().type(),
types,
frameworkFieldUsages(binding.dependencies(), frameworkFields)::get,
elements))
.addStatement("return $L", instance);
} else {
getMethod.addStatement(
"return new $T($L)", providedTypeName, parametersCodeBlock);
}
return getMethod.build();
}
private static TypeName providedTypeName(ProvisionBinding binding) {
return TypeName.get(binding.contributedType());
}
private static TypeName factoryTypeName(ProvisionBinding binding) {
return factoryOf(providedTypeName(binding));
}
private static ParameterSpec toParameter(FieldSpec field) {
return ParameterSpec.builder(field.type, field.name).build();
}
/**
* Returns {@code Preconditions.checkNotNull(providesMethodInvocation)} with a message suitable
* for {@code @Provides} methods.
*/
static CodeBlock checkNotNullProvidesMethod(CodeBlock providesMethodInvocation) {
return CodeBlock.of(
"$T.checkNotNull($L, $S)",
Preconditions.class,
providesMethodInvocation,
"Cannot return null from a non-@Nullable @Provides method");
}
}