blob: e1b35daf9c0cc2d631ec9ededb4d3fe4995d6610 [file] [log] [blame]
/*
* Copyright (C) 2017 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.componentgenerator;
import static com.google.auto.common.MoreTypes.asDeclared;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.getOnlyElement;
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.simpleVariableName;
import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock;
import static dagger.internal.codegen.javapoet.TypeSpecs.addSupertype;
import static dagger.internal.codegen.langmodel.Accessibility.isElementAccessibleFrom;
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.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
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.Preconditions;
import dagger.internal.codegen.base.UniqueNameSet;
import dagger.internal.codegen.binding.ComponentCreatorDescriptor;
import dagger.internal.codegen.binding.ComponentDescriptor;
import dagger.internal.codegen.binding.ComponentRequirement;
import dagger.internal.codegen.binding.ComponentRequirement.NullPolicy;
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.ComponentCreatorImplementation;
import dagger.internal.codegen.writing.ComponentImplementation;
import dagger.internal.codegen.writing.ModuleProxies;
import java.util.Optional;
import java.util.Set;
import javax.inject.Inject;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
/** Factory for creating {@link ComponentCreatorImplementation} instances. */
final class ComponentCreatorImplementationFactory {
private final ComponentImplementation componentImplementation;
private final DaggerElements elements;
private final DaggerTypes types;
private final KotlinMetadataUtil metadataUtil;
private final ModuleProxies moduleProxies;
@Inject
ComponentCreatorImplementationFactory(
ComponentImplementation componentImplementation,
DaggerElements elements,
DaggerTypes types,
KotlinMetadataUtil metadataUtil,
ModuleProxies moduleProxies) {
this.componentImplementation = componentImplementation;
this.elements = elements;
this.types = types;
this.metadataUtil = metadataUtil;
this.moduleProxies = moduleProxies;
}
/** Returns a new creator implementation for the given component, if necessary. */
Optional<ComponentCreatorImplementation> create() {
if (!componentImplementation.componentDescriptor().hasCreator()) {
return Optional.empty();
}
Optional<ComponentCreatorDescriptor> creatorDescriptor =
componentImplementation.componentDescriptor().creatorDescriptor();
Builder builder =
creatorDescriptor.isPresent()
? new BuilderForCreatorDescriptor(componentImplementation, creatorDescriptor.get())
: new BuilderForGeneratedRootComponentBuilder(componentImplementation);
return Optional.of(builder.build());
}
/** Base class for building a creator implementation. */
private abstract class Builder {
final ComponentImplementation componentImplementation;
final ClassName className;
final TypeSpec.Builder classBuilder;
private ImmutableMap<ComponentRequirement, FieldSpec> fields;
Builder(ComponentImplementation componentImplementation) {
this.componentImplementation = componentImplementation;
this.className = componentImplementation.getCreatorName();
this.classBuilder = classBuilder(className);
}
/** Builds the {@link ComponentCreatorImplementation}. */
ComponentCreatorImplementation build() {
setModifiers();
setSupertype();
this.fields = addFields();
addConstructor();
addSetterMethods();
addFactoryMethod();
return ComponentCreatorImplementation.create(classBuilder.build(), className, fields);
}
/** Returns the descriptor for the component. */
final ComponentDescriptor componentDescriptor() {
return componentImplementation.componentDescriptor();
}
/**
* The set of requirements that must be passed to the component's constructor in the order
* they must be passed.
*/
final ImmutableSet<ComponentRequirement> componentConstructorRequirements() {
return componentImplementation.graph().componentRequirements();
}
/** Returns the requirements that have setter methods on the creator type. */
abstract ImmutableSet<ComponentRequirement> setterMethods();
/**
* Returns the component requirements that have factory method parameters, mapped to the name
* for that parameter.
*/
abstract ImmutableMap<ComponentRequirement, String> factoryMethodParameters();
/**
* The {@link ComponentRequirement}s that this creator allows users to set. Values are a status
* for each requirement indicating what's needed for that requirement in the implementation
* class currently being generated.
*/
abstract ImmutableMap<ComponentRequirement, RequirementStatus> userSettableRequirements();
/**
* Component requirements that are both settable by the creator and needed to construct the
* component.
*/
private Set<ComponentRequirement> neededUserSettableRequirements() {
return Sets.intersection(
userSettableRequirements().keySet(), componentConstructorRequirements());
}
private void setModifiers() {
visibility().ifPresent(classBuilder::addModifiers);
if (!componentImplementation.isNested()) {
classBuilder.addModifiers(STATIC);
}
classBuilder.addModifiers(FINAL);
}
/** Returns the visibility modifier the generated class should have, if any. */
protected abstract Optional<Modifier> visibility();
/** Sets the superclass being extended or interface being implemented for this creator. */
protected abstract void setSupertype();
/** Adds a constructor for the creator type, if needed. */
protected abstract void addConstructor();
private ImmutableMap<ComponentRequirement, FieldSpec> addFields() {
// Fields in an abstract creator class need to be visible from subclasses.
UniqueNameSet fieldNames = new UniqueNameSet();
ImmutableMap<ComponentRequirement, FieldSpec> result =
Maps.toMap(
Sets.intersection(neededUserSettableRequirements(), setterMethods()),
requirement ->
FieldSpec.builder(
TypeName.get(requirement.type()),
fieldNames.getUniqueName(requirement.variableName()),
PRIVATE)
.build());
classBuilder.addFields(result.values());
return result;
}
private void addSetterMethods() {
Maps.filterKeys(userSettableRequirements(), setterMethods()::contains)
.forEach(
(requirement, status) ->
createSetterMethod(requirement, status).ifPresent(classBuilder::addMethod));
}
/** Creates a new setter method builder, with no method body, for the given requirement. */
protected abstract MethodSpec.Builder setterMethodBuilder(ComponentRequirement requirement);
private Optional<MethodSpec> createSetterMethod(
ComponentRequirement requirement, RequirementStatus status) {
switch (status) {
case NEEDED:
return Optional.of(normalSetterMethod(requirement));
case UNNEEDED:
// TODO(bcorso): Don't generate noop setters for any unneeded requirements.
// However, since this is a breaking change we can at least avoid trying
// to generate noop setters for impossible cases like when the requirement type
// is in another package. This avoids unnecessary breakages in Dagger's generated
// due to the noop setters.
if (isElementAccessibleFrom(requirement.typeElement(), className.packageName())) {
return Optional.of(noopSetterMethod(requirement));
} else {
return Optional.empty();
}
case UNSETTABLE_REPEATED_MODULE:
return Optional.of(repeatedModuleSetterMethod(requirement));
}
throw new AssertionError();
}
private MethodSpec normalSetterMethod(ComponentRequirement requirement) {
MethodSpec.Builder method = setterMethodBuilder(requirement);
ParameterSpec parameter = parameter(method.build());
method.addStatement(
"this.$N = $L",
fields.get(requirement),
requirement.nullPolicy(elements, types, metadataUtil).equals(NullPolicy.ALLOW)
? CodeBlock.of("$N", parameter)
: CodeBlock.of("$T.checkNotNull($N)", Preconditions.class, parameter));
return maybeReturnThis(method);
}
private MethodSpec noopSetterMethod(ComponentRequirement requirement) {
MethodSpec.Builder method = setterMethodBuilder(requirement);
ParameterSpec parameter = parameter(method.build());
method
.addAnnotation(Deprecated.class)
.addJavadoc(
"@deprecated This module is declared, but an instance is not used in the component. "
+ "This method is a no-op. For more, see https://dagger.dev/unused-modules.\n")
.addStatement("$T.checkNotNull($N)", Preconditions.class, parameter);
return maybeReturnThis(method);
}
private MethodSpec repeatedModuleSetterMethod(ComponentRequirement requirement) {
return setterMethodBuilder(requirement)
.addStatement(
"throw new $T($T.format($S, $T.class.getCanonicalName()))",
UnsupportedOperationException.class,
String.class,
"%s cannot be set because it is inherited from the enclosing component",
TypeNames.rawTypeName(TypeName.get(requirement.type())))
.build();
}
private ParameterSpec parameter(MethodSpec method) {
return getOnlyElement(method.parameters);
}
private MethodSpec maybeReturnThis(MethodSpec.Builder method) {
MethodSpec built = method.build();
return built.returnType.equals(TypeName.VOID)
? built
: method.addStatement("return this").build();
}
private void addFactoryMethod() {
classBuilder.addMethod(factoryMethod());
}
MethodSpec factoryMethod() {
MethodSpec.Builder factoryMethod = factoryMethodBuilder();
factoryMethod
.returns(ClassName.get(componentDescriptor().typeElement()))
.addModifiers(PUBLIC);
ImmutableMap<ComponentRequirement, String> factoryMethodParameters =
factoryMethodParameters();
userSettableRequirements()
.keySet()
.forEach(
requirement -> {
if (fields.containsKey(requirement)) {
FieldSpec field = fields.get(requirement);
addNullHandlingForField(requirement, field, factoryMethod);
} else if (factoryMethodParameters.containsKey(requirement)) {
String parameterName = factoryMethodParameters.get(requirement);
addNullHandlingForParameter(requirement, parameterName, factoryMethod);
}
});
factoryMethod.addStatement(
"return new $T($L)",
componentImplementation.name(),
componentConstructorArgs(factoryMethodParameters));
return factoryMethod.build();
}
private void addNullHandlingForField(
ComponentRequirement requirement, FieldSpec field, MethodSpec.Builder factoryMethod) {
switch (requirement.nullPolicy(elements, types, metadataUtil)) {
case NEW:
checkState(requirement.kind().isModule());
factoryMethod
.beginControlFlow("if ($N == null)", field)
.addStatement("this.$N = $L", field, newModuleInstance(requirement))
.endControlFlow();
break;
case THROW:
// TODO(cgdecker,ronshapiro): ideally this should use the key instead of a class for
// @BindsInstance requirements, but that's not easily proguardable.
factoryMethod.addStatement(
"$T.checkBuilderRequirement($N, $T.class)",
Preconditions.class,
field,
TypeNames.rawTypeName(field.type));
break;
case ALLOW:
break;
}
}
private void addNullHandlingForParameter(
ComponentRequirement requirement, String parameter, MethodSpec.Builder factoryMethod) {
if (!requirement.nullPolicy(elements, types, metadataUtil).equals(NullPolicy.ALLOW)) {
// Factory method parameters are always required unless they are a nullable
// binds-instance (i.e. ALLOW)
factoryMethod.addStatement("$T.checkNotNull($L)", Preconditions.class, parameter);
}
}
/** Returns a builder for the creator's factory method. */
protected abstract MethodSpec.Builder factoryMethodBuilder();
private CodeBlock componentConstructorArgs(
ImmutableMap<ComponentRequirement, String> factoryMethodParameters) {
return componentConstructorRequirements().stream()
.map(
requirement -> {
if (fields.containsKey(requirement)) {
return CodeBlock.of("$N", fields.get(requirement));
} else if (factoryMethodParameters.containsKey(requirement)) {
return CodeBlock.of("$L", factoryMethodParameters.get(requirement));
} else {
return newModuleInstance(requirement);
}
})
.collect(toParametersCodeBlock());
}
private CodeBlock newModuleInstance(ComponentRequirement requirement) {
checkArgument(requirement.kind().isModule()); // this should be guaranteed to be true here
return moduleProxies.newModuleInstance(requirement.typeElement(), className);
}
}
/** Builder for a creator type defined by a {@code ComponentCreatorDescriptor}. */
private final class BuilderForCreatorDescriptor extends Builder {
final ComponentCreatorDescriptor creatorDescriptor;
BuilderForCreatorDescriptor(
ComponentImplementation componentImplementation,
ComponentCreatorDescriptor creatorDescriptor) {
super(componentImplementation);
this.creatorDescriptor = creatorDescriptor;
}
@Override
protected ImmutableMap<ComponentRequirement, RequirementStatus> userSettableRequirements() {
return Maps.toMap(creatorDescriptor.userSettableRequirements(), this::requirementStatus);
}
@Override
protected Optional<Modifier> visibility() {
return Optional.of(PRIVATE);
}
@Override
protected void setSupertype() {
addSupertype(classBuilder, creatorDescriptor.typeElement());
}
@Override
protected void addConstructor() {
// Just use the implicit no-arg public constructor.
}
@Override
protected ImmutableSet<ComponentRequirement> setterMethods() {
return ImmutableSet.copyOf(creatorDescriptor.setterMethods().keySet());
}
@Override
protected ImmutableMap<ComponentRequirement, String> factoryMethodParameters() {
return ImmutableMap.copyOf(
Maps.transformValues(
creatorDescriptor.factoryParameters(),
element -> element.getSimpleName().toString()));
}
private DeclaredType creatorType() {
return asDeclared(creatorDescriptor.typeElement().asType());
}
@Override
protected MethodSpec.Builder factoryMethodBuilder() {
return MethodSpec.overriding(creatorDescriptor.factoryMethod(), creatorType(), types);
}
private RequirementStatus requirementStatus(ComponentRequirement requirement) {
if (isRepeatedModule(requirement)) {
return RequirementStatus.UNSETTABLE_REPEATED_MODULE;
}
return componentConstructorRequirements().contains(requirement)
? RequirementStatus.NEEDED
: RequirementStatus.UNNEEDED;
}
/**
* Returns whether the given requirement is for a repeat of a module inherited from an ancestor
* component. This creator is not allowed to set such a module.
*/
final boolean isRepeatedModule(ComponentRequirement requirement) {
return !componentConstructorRequirements().contains(requirement)
&& !isOwnedModule(requirement);
}
/**
* Returns whether the given {@code requirement} is for a module type owned by the component.
*/
private boolean isOwnedModule(ComponentRequirement requirement) {
return componentImplementation.graph().ownedModuleTypes().contains(requirement.typeElement());
}
@Override
protected MethodSpec.Builder setterMethodBuilder(ComponentRequirement requirement) {
ExecutableElement supertypeMethod = creatorDescriptor.setterMethods().get(requirement);
MethodSpec.Builder method = MethodSpec.overriding(supertypeMethod, creatorType(), types);
if (!supertypeMethod.getReturnType().getKind().equals(TypeKind.VOID)) {
// Take advantage of covariant returns so that we don't have to worry about type variables
method.returns(className);
}
return method;
}
}
/**
* Builder for a component builder class that is automatically generated for a root component that
* does not have its own user-defined creator type (i.e. a {@code ComponentCreatorDescriptor}).
*/
private final class BuilderForGeneratedRootComponentBuilder extends Builder {
BuilderForGeneratedRootComponentBuilder(ComponentImplementation componentImplementation) {
super(componentImplementation);
}
@Override
protected ImmutableMap<ComponentRequirement, RequirementStatus> userSettableRequirements() {
return Maps.toMap(
setterMethods(),
requirement ->
componentConstructorRequirements().contains(requirement)
? RequirementStatus.NEEDED
: RequirementStatus.UNNEEDED);
}
@Override
protected Optional<Modifier> visibility() {
return componentImplementation
.componentDescriptor()
.typeElement()
.getModifiers()
.contains(PUBLIC) ? Optional.of(PUBLIC) : Optional.empty();
}
@Override
protected void setSupertype() {
// There's never a supertype for a root component auto-generated builder type.
}
@Override
protected void addConstructor() {
classBuilder.addMethod(constructorBuilder().addModifiers(PRIVATE).build());
}
@Override
protected ImmutableSet<ComponentRequirement> setterMethods() {
return componentDescriptor().dependenciesAndConcreteModules();
}
@Override
protected ImmutableMap<ComponentRequirement, String> factoryMethodParameters() {
return ImmutableMap.of();
}
@Override
protected MethodSpec.Builder factoryMethodBuilder() {
return methodBuilder("build");
}
@Override
protected MethodSpec.Builder setterMethodBuilder(ComponentRequirement requirement) {
String name = simpleVariableName(requirement.typeElement());
return methodBuilder(name)
.addModifiers(PUBLIC)
.addParameter(TypeName.get(requirement.type()), name)
.returns(className);
}
}
/** Enumeration of statuses a component requirement may have in a creator. */
enum RequirementStatus {
/** An instance is needed to create the component. */
NEEDED,
/**
* An instance is not needed to create the component, but the requirement is for a module owned
* by the component. Setting the requirement is a no-op and any setter method should be marked
* deprecated on the generated type as a warning to the user.
*/
UNNEEDED,
/**
* The requirement may not be set in this creator because the module it is for is already
* inherited from an ancestor component. Any setter method for it should throw an exception.
*/
UNSETTABLE_REPEATED_MODULE,
;
}
}