/* * 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 create() { if (!componentImplementation.componentDescriptor().hasCreator()) { return Optional.empty(); } Optional 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 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 componentConstructorRequirements() { return componentImplementation.graph().componentRequirements(); } /** Returns the requirements that have setter methods on the creator type. */ abstract ImmutableSet setterMethods(); /** * Returns the component requirements that have factory method parameters, mapped to the name * for that parameter. */ abstract ImmutableMap 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 userSettableRequirements(); /** * Component requirements that are both settable by the creator and needed to construct the * component. */ private Set 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 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 addFields() { // Fields in an abstract creator class need to be visible from subclasses. UniqueNameSet fieldNames = new UniqueNameSet(); ImmutableMap 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 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, 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 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, 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, 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 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 userSettableRequirements() { return Maps.toMap(creatorDescriptor.userSettableRequirements(), this::requirementStatus); } @Override protected Optional 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 setterMethods() { return ImmutableSet.copyOf(creatorDescriptor.setterMethods().keySet()); } @Override protected ImmutableMap 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 userSettableRequirements() { return Maps.toMap( setterMethods(), requirement -> componentConstructorRequirements().contains(requirement) ? RequirementStatus.NEEDED : RequirementStatus.UNNEEDED); } @Override protected Optional 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 setterMethods() { return componentDescriptor().dependenciesAndConcreteModules(); } @Override protected ImmutableMap 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, ; } }