/* * 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; 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.SourceFiles.simpleVariableName; import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; import static dagger.internal.codegen.javapoet.TypeSpecs.addSupertype; import static javax.lang.model.element.Modifier.ABSTRACT; 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.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.ComponentRequirement.NullPolicy; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; 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 DaggerElements elements; private final DaggerTypes types; @Inject ComponentCreatorImplementationFactory(DaggerElements elements, DaggerTypes types) { this.elements = elements; this.types = types; } /** Returns a new creator implementation for the given component, if necessary. */ Optional create( ComponentImplementation componentImplementation, Optional graph) { if (!componentImplementation.componentDescriptor().hasCreator()) { return Optional.empty(); } Optional creatorDescriptor = componentImplementation.componentDescriptor().creatorDescriptor(); if (componentImplementation.isAbstract() && (hasNoSetterMethods(creatorDescriptor) || componentImplementation.superclassImplementation().isPresent())) { // 1. Factory-like creators (those with no setter methods) are only generated in concrete // components, because they only have a factory method and the factory method must call // a concrete component's constructor. // 2. The component builder in ahead-of-time mode is generated with the base subcomponent // implementation, with the exception of the build method since that requires invoking the // constructor of a concrete component implementation. Intermediate component // implementations, because they still can't invoke the eventual constructor and have no // additional extensions to the builder, can ignore generating a builder implementation. return Optional.empty(); } Builder builder = creatorDescriptor.isPresent() ? new BuilderForCreatorDescriptor( componentImplementation, creatorDescriptor.get(), graph) : new BuilderForGeneratedRootComponentBuilder(componentImplementation); return Optional.of(builder.build()); } private static boolean hasNoSetterMethods( Optional creatorDescriptor) { return creatorDescriptor.filter(descriptor -> descriptor.setterMethods().isEmpty()).isPresent(); } /** 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 = getOrAddFields(); 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.requirements(); } /** 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(componentImplementation.isAbstract() ? ABSTRACT : 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 getOrAddFields() { // If a base implementation is present, any fields are already defined there and don't need to // be created in this implementation. return componentImplementation .baseCreatorImplementation() .map(ComponentCreatorImplementation::fields) .orElseGet(this::addFields); } private ImmutableMap addFields() { // Fields in an abstract creator class need to be visible from subclasses. Modifier modifier = componentImplementation.isAbstract() ? PROTECTED : PRIVATE; UniqueNameSet fieldNames = new UniqueNameSet(); ImmutableMap result = Maps.toMap( Sets.intersection(neededUserSettableRequirements(), setterMethods()), requirement -> FieldSpec.builder( TypeName.get(requirement.type()), fieldNames.getUniqueName(requirement.variableName()), modifier) .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: return Optional.of(noopSetterMethod(requirement)); case UNSETTABLE_REPEATED_MODULE: return Optional.of(repeatedModuleSetterMethod(requirement)); case IMPLEMENTED_IN_SUPERTYPE: return Optional.empty(); } 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).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() { if (!componentImplementation.isAbstract()) { 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) && componentConstructorRequirements().contains(requirement)) { // In AOT mode, there can be a field for a requirement even if the component's // constructor doesn't need it, because the base class for the creator was created // before the final graph for the component was known. 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)) { 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).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, elements); } } /** Builder for a creator type defined by a {@code ComponentCreatorDescriptor}. */ private final class BuilderForCreatorDescriptor extends Builder { final ComponentCreatorDescriptor creatorDescriptor; private final Optional graph; BuilderForCreatorDescriptor( ComponentImplementation componentImplementation, ComponentCreatorDescriptor creatorDescriptor, Optional graph) { super(componentImplementation); this.creatorDescriptor = creatorDescriptor; this.graph = graph; } @Override protected ImmutableMap userSettableRequirements() { return Maps.toMap(creatorDescriptor.userSettableRequirements(), this::requirementStatus); } @Override protected Optional visibility() { if (componentImplementation.isAbstract()) { // The component creator class of a top-level component implementation in ahead-of-time // subcomponents mode must be public, not protected, because the creator's subclass will // be a sibling of the component subclass implementation, not nested. return Optional.of(componentImplementation.isNested() ? PROTECTED : PUBLIC); } return Optional.of(PRIVATE); } @Override protected void setSupertype() { if (componentImplementation.baseCreatorImplementation().isPresent()) { // If an abstract base implementation for this creator exists, extend that class. classBuilder.superclass(componentImplementation.baseCreatorImplementation().get().name()); } else { 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) { // In ahead-of-time subcomponents mode, all builder methods are defined at the base // implementation. The only case where a method needs to be overridden is for a repeated // module, which is unknown at the point when a base implementation is generated. We do this // at the root for simplicity (and as an aside, repeated modules are never used in google // as of 11/28/18, and thus the additional cost of including these methods at the root is // negligible). if (isRepeatedModule(requirement)) { return RequirementStatus.UNSETTABLE_REPEATED_MODULE; } if (hasBaseCreatorImplementation()) { return RequirementStatus.IMPLEMENTED_IN_SUPERTYPE; } 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 graph.map(g -> g.ownedModuleTypes().contains(requirement.typeElement())).orElse(true); } private boolean hasBaseCreatorImplementation() { return !componentImplementation.isAbstract() && componentImplementation.baseImplementation().isPresent(); } @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, /** * The requirement is settable by the creator, but the setter method implementation already * exists in a supertype. */ IMPLEMENTED_IN_SUPERTYPE, ; } }