• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.internal.codegen.processingstep;
18 
19 import static com.google.common.collect.Iterables.getOnlyElement;
20 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedFactoryMethods;
21 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors;
22 import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding;
23 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
24 import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock;
25 import static dagger.internal.codegen.javapoet.TypeNames.INSTANCE_FACTORY;
26 import static dagger.internal.codegen.javapoet.TypeNames.daggerProviderOf;
27 import static dagger.internal.codegen.javapoet.TypeNames.providerOf;
28 import static dagger.internal.codegen.langmodel.Accessibility.accessibleTypeName;
29 import static dagger.internal.codegen.xprocessing.MethodSpecs.overriding;
30 import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
31 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
32 import static dagger.internal.codegen.xprocessing.XMethodElements.hasTypeParameters;
33 import static dagger.internal.codegen.xprocessing.XProcessingEnvs.isPreJava8SourceVersion;
34 import static dagger.internal.codegen.xprocessing.XTypeElements.typeVariableNames;
35 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
36 import static java.util.stream.Collectors.joining;
37 import static javax.lang.model.element.Modifier.FINAL;
38 import static javax.lang.model.element.Modifier.PRIVATE;
39 import static javax.lang.model.element.Modifier.PUBLIC;
40 import static javax.lang.model.element.Modifier.STATIC;
41 
42 import androidx.room.compiler.processing.XElement;
43 import androidx.room.compiler.processing.XExecutableParameterElement;
44 import androidx.room.compiler.processing.XFiler;
45 import androidx.room.compiler.processing.XMessager;
46 import androidx.room.compiler.processing.XMethodElement;
47 import androidx.room.compiler.processing.XProcessingEnv;
48 import androidx.room.compiler.processing.XType;
49 import androidx.room.compiler.processing.XTypeElement;
50 import com.google.common.collect.ImmutableList;
51 import com.google.common.collect.ImmutableSet;
52 import com.squareup.javapoet.ClassName;
53 import com.squareup.javapoet.CodeBlock;
54 import com.squareup.javapoet.FieldSpec;
55 import com.squareup.javapoet.MethodSpec;
56 import com.squareup.javapoet.ParameterSpec;
57 import com.squareup.javapoet.ParameterizedTypeName;
58 import com.squareup.javapoet.TypeName;
59 import com.squareup.javapoet.TypeSpec;
60 import dagger.internal.codegen.base.SourceFileGenerationException;
61 import dagger.internal.codegen.base.SourceFileGenerator;
62 import dagger.internal.codegen.binding.AssistedInjectionAnnotations;
63 import dagger.internal.codegen.binding.AssistedInjectionAnnotations.AssistedFactoryMetadata;
64 import dagger.internal.codegen.binding.AssistedInjectionAnnotations.AssistedParameter;
65 import dagger.internal.codegen.binding.BindingFactory;
66 import dagger.internal.codegen.binding.MethodSignatureFormatter;
67 import dagger.internal.codegen.binding.ProvisionBinding;
68 import dagger.internal.codegen.javapoet.TypeNames;
69 import dagger.internal.codegen.validation.ValidationReport;
70 import dagger.internal.codegen.xprocessing.XTypes;
71 import java.util.HashSet;
72 import java.util.Optional;
73 import java.util.Set;
74 import javax.inject.Inject;
75 
76 /** An annotation processor for {@link dagger.assisted.AssistedFactory}-annotated types. */
77 final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<XTypeElement> {
78   private final XProcessingEnv processingEnv;
79   @SuppressWarnings("HidingField")
80   private final XMessager messager;
81   private final XFiler filer;
82   private final BindingFactory bindingFactory;
83   private final MethodSignatureFormatter methodSignatureFormatter;
84   @SuppressWarnings("HidingField")
85   private final SuperficialValidator superficialValidator;
86 
87   @Inject
AssistedFactoryProcessingStep( XProcessingEnv processingEnv, XMessager messager, XFiler filer, BindingFactory bindingFactory, MethodSignatureFormatter methodSignatureFormatter, SuperficialValidator superficialValidator)88   AssistedFactoryProcessingStep(
89       XProcessingEnv processingEnv,
90       XMessager messager,
91       XFiler filer,
92       BindingFactory bindingFactory,
93       MethodSignatureFormatter methodSignatureFormatter,
94       SuperficialValidator superficialValidator) {
95     this.processingEnv = processingEnv;
96     this.messager = messager;
97     this.filer = filer;
98     this.bindingFactory = bindingFactory;
99     this.methodSignatureFormatter = methodSignatureFormatter;
100     this.superficialValidator = superficialValidator;
101   }
102 
103   @Override
annotationClassNames()104   public ImmutableSet<ClassName> annotationClassNames() {
105     return ImmutableSet.of(TypeNames.ASSISTED_FACTORY);
106   }
107 
108   @Override
process(XTypeElement factory, ImmutableSet<ClassName> annotations)109   protected void process(XTypeElement factory, ImmutableSet<ClassName> annotations) {
110     ValidationReport report = new AssistedFactoryValidator().validate(factory);
111     report.printMessagesTo(messager);
112     if (report.isClean()) {
113       try {
114         ProvisionBinding binding = bindingFactory.assistedFactoryBinding(factory, Optional.empty());
115         new AssistedFactoryImplGenerator().generate(binding);
116       } catch (SourceFileGenerationException e) {
117         e.printMessageTo(messager);
118       }
119     }
120   }
121 
122   private final class AssistedFactoryValidator {
validate(XTypeElement factory)123     ValidationReport validate(XTypeElement factory) {
124       ValidationReport.Builder report = ValidationReport.about(factory);
125 
126       if (!factory.isAbstract()) {
127         return report
128             .addError(
129                 "The @AssistedFactory-annotated type must be either an abstract class or "
130                     + "interface.",
131                 factory)
132             .build();
133       }
134 
135       if (factory.isNested() && !factory.isStatic()) {
136         report.addError("Nested @AssistedFactory-annotated types must be static. ", factory);
137       }
138 
139       ImmutableSet<XMethodElement> abstractFactoryMethods = assistedFactoryMethods(factory);
140 
141       if (abstractFactoryMethods.isEmpty()) {
142         report.addError(
143             "The @AssistedFactory-annotated type is missing an abstract, non-default method "
144                 + "whose return type matches the assisted injection type.",
145             factory);
146       }
147 
148       for (XMethodElement method : abstractFactoryMethods) {
149         XType returnType = method.asMemberOf(factory.getType()).getReturnType();
150         // The default superficial validation only applies to the @AssistedFactory-annotated
151         // element, so we have to manually check the superficial validation  of the @AssistedInject
152         // element before using it to ensure it's ready for processing.
153         if (isDeclared(returnType)) {
154           superficialValidator.throwIfNearestEnclosingTypeNotValid(returnType.getTypeElement());
155         }
156         if (!isAssistedInjectionType(returnType)) {
157           report.addError(
158               String.format(
159                   "Invalid return type: %s. An assisted factory's abstract method must return a "
160                       + "type with an @AssistedInject-annotated constructor.",
161                   XTypes.toStableString(returnType)),
162               method);
163         }
164         if (hasTypeParameters(method)) {
165           report.addError(
166               "@AssistedFactory does not currently support type parameters in the creator "
167                   + "method. See https://github.com/google/dagger/issues/2279",
168               method);
169         }
170       }
171 
172       if (abstractFactoryMethods.size() > 1) {
173         report.addError(
174             "The @AssistedFactory-annotated type should contain a single abstract, non-default"
175                 + " method but found multiple: "
176                 + abstractFactoryMethods.stream()
177                     .map(methodSignatureFormatter::formatWithoutReturnType)
178                     .collect(toImmutableList()),
179             factory);
180       }
181 
182       if (!report.build().isClean()) {
183         return report.build();
184       }
185 
186       AssistedFactoryMetadata metadata = AssistedFactoryMetadata.create(factory.getType());
187 
188       // Note: We check uniqueness of the @AssistedInject constructor parameters in
189       // AssistedInjectProcessingStep. We need to check uniqueness for here too because we may
190       // have resolved some type parameters that were not resolved in the @AssistedInject type.
191       Set<AssistedParameter> uniqueAssistedParameters = new HashSet<>();
192       for (AssistedParameter assistedParameter : metadata.assistedFactoryAssistedParameters()) {
193         if (!uniqueAssistedParameters.add(assistedParameter)) {
194           report.addError(
195               "@AssistedFactory method has duplicate @Assisted types: " + assistedParameter,
196               assistedParameter.element());
197         }
198       }
199 
200       if (!ImmutableSet.copyOf(metadata.assistedInjectAssistedParameters())
201           .equals(ImmutableSet.copyOf(metadata.assistedFactoryAssistedParameters()))) {
202         report.addError(
203             String.format(
204                 "The parameters in the factory method must match the @Assisted parameters in %s."
205                     + "\n    Actual: %s#%s(%s)"
206                     + "\n  Expected: %s#%s(%s)",
207                 XTypes.toStableString(metadata.assistedInjectType()),
208                 metadata.factory().getQualifiedName(),
209                 getSimpleName(metadata.factoryMethod()),
210                 metadata.factoryMethod().getParameters().stream()
211                     .map(XExecutableParameterElement::getType)
212                     .map(XTypes::toStableString)
213                     .collect(joining(", ")),
214                 metadata.factory().getQualifiedName(),
215                 getSimpleName(metadata.factoryMethod()),
216                 metadata.assistedInjectAssistedParameters().stream()
217                     .map(AssistedParameter::type)
218                     .map(XTypes::toStableString)
219                     .collect(joining(", "))),
220             metadata.factoryMethod());
221       }
222 
223       return report.build();
224     }
225 
isAssistedInjectionType(XType type)226     private boolean isAssistedInjectionType(XType type) {
227       return isDeclared(type)
228           && AssistedInjectionAnnotations.isAssistedInjectionType(type.getTypeElement());
229     }
230   }
231 
232   /** Generates an implementation of the {@link dagger.assisted.AssistedFactory}-annotated class. */
233   private final class AssistedFactoryImplGenerator extends SourceFileGenerator<ProvisionBinding> {
AssistedFactoryImplGenerator()234     AssistedFactoryImplGenerator() {
235       super(filer, processingEnv);
236     }
237 
238     @Override
originatingElement(ProvisionBinding binding)239     public XElement originatingElement(ProvisionBinding binding) {
240       return binding.bindingElement().get();
241     }
242 
243     // For each @AssistedFactory-annotated type, we generates a class named "*_Impl" that implements
244     // that type.
245     //
246     // Note that this class internally delegates to the @AssistedInject generated class, which
247     // contains the actual implementation logic for creating the @AssistedInject type. The reason we
248     // need both of these generated classes is because while the @AssistedInject generated class
249     // knows how to create the @AssistedInject type, it doesn't know about all of the
250     // @AssistedFactory interfaces that it needs to extend when it's generated. Thus, the role of
251     // the @AssistedFactory generated class is purely to implement the @AssistedFactory type.
252     // Furthermore, while we could have put all of the logic into the @AssistedFactory generated
253     // class and not generate the @AssistedInject generated class, having the @AssistedInject
254     // generated class ensures we have proper accessibility to the @AssistedInject type, and reduces
255     // duplicate logic if there are multiple @AssistedFactory types for the same @AssistedInject
256     // type.
257     //
258     // Example:
259     // public class FooFactory_Impl implements FooFactory {
260     //   private final Foo_Factory delegateFactory;
261     //
262     //   FooFactory_Impl(Foo_Factory delegateFactory) {
263     //     this.delegateFactory = delegateFactory;
264     //   }
265     //
266     //   @Override
267     //   public Foo createFoo(AssistedDep assistedDep) {
268     //     return delegateFactory.get(assistedDep);
269     //   }
270     //
271     //   public static Provider<FooFactory> create(Foo_Factory delegateFactory) {
272     //     return InstanceFactory.create(new FooFactory_Impl(delegateFactory));
273     //   }
274     // }
275     @Override
topLevelTypes(ProvisionBinding binding)276     public ImmutableList<TypeSpec.Builder> topLevelTypes(ProvisionBinding binding) {
277       XTypeElement factory = asTypeElement(binding.bindingElement().get());
278 
279       ClassName name = generatedClassNameForBinding(binding);
280       TypeSpec.Builder builder =
281           TypeSpec.classBuilder(name)
282               .addModifiers(PUBLIC, FINAL)
283               .addTypeVariables(typeVariableNames(factory));
284 
285       if (factory.isInterface()) {
286         builder.addSuperinterface(factory.getType().getTypeName());
287       } else {
288         builder.superclass(factory.getType().getTypeName());
289       }
290 
291       AssistedFactoryMetadata metadata = AssistedFactoryMetadata.create(factory.getType());
292       ParameterSpec delegateFactoryParam =
293           ParameterSpec.builder(
294                   delegateFactoryTypeName(metadata.assistedInjectType()), "delegateFactory")
295               .build();
296       builder
297           .addField(
298               FieldSpec.builder(delegateFactoryParam.type, delegateFactoryParam.name)
299                   .addModifiers(PRIVATE, FINAL)
300                   .build())
301           .addMethod(
302               MethodSpec.constructorBuilder()
303                   .addParameter(delegateFactoryParam)
304                   .addStatement("this.$1N = $1N", delegateFactoryParam)
305                   .build())
306           .addMethod(
307               overriding(metadata.factoryMethod(), metadata.factoryType())
308                   .addStatement(
309                       "return $N.get($L)",
310                       delegateFactoryParam,
311                       // Use the order of the parameters from the @AssistedInject constructor but
312                       // use the parameter names of the @AssistedFactory method.
313                       metadata.assistedInjectAssistedParameters().stream()
314                           .map(metadata.assistedFactoryAssistedParametersMap()::get)
315                           .map(param -> CodeBlock.of("$L", param.getJvmName()))
316                           .collect(toParametersCodeBlock()))
317                   .build())
318           // In a future release, we should delete this javax method. This will still be a breaking
319           // change, but keeping compatibility for a while should reduce the likelihood of breakages
320           // as it would require components built at much older versions using factories built at
321           // newer versions to break.
322           .addMethod(
323               MethodSpec.methodBuilder("create")
324                   .addModifiers(PUBLIC, STATIC)
325                   .addParameter(delegateFactoryParam)
326                   .addTypeVariables(typeVariableNames(metadata.assistedInjectElement()))
327                   .returns(providerOf(factory.getType().getTypeName()))
328                   .addStatement(
329                       "return $T.$Lcreate(new $T($N))",
330                       INSTANCE_FACTORY,
331                       // Java 7 type inference requires the method call provide the exact type here.
332                       isPreJava8SourceVersion(processingEnv)
333                           ? CodeBlock.of(
334                               "<$T>",
335                               accessibleTypeName(metadata.factoryType(), name, processingEnv))
336                           : CodeBlock.of(""),
337                       name,
338                       delegateFactoryParam)
339                   .build())
340           // Normally we would have called this just "create", but because of backwards
341           // compatibility we can't have two methods with the same name/arguments returning
342           // different Provider types.
343           .addMethod(
344               MethodSpec.methodBuilder("createFactoryProvider")
345                   .addModifiers(PUBLIC, STATIC)
346                   .addParameter(delegateFactoryParam)
347                   .addTypeVariables(typeVariableNames(metadata.assistedInjectElement()))
348                   .returns(daggerProviderOf(factory.getType().getTypeName()))
349                   .addStatement(
350                       "return $T.$Lcreate(new $T($N))",
351                       INSTANCE_FACTORY,
352                       // Java 7 type inference requires the method call provide the exact type here.
353                       isPreJava8SourceVersion(processingEnv)
354                           ? CodeBlock.of(
355                               "<$T>",
356                               accessibleTypeName(metadata.factoryType(), name, processingEnv))
357                           : CodeBlock.of(""),
358                       name,
359                       delegateFactoryParam)
360                   .build());
361       return ImmutableList.of(builder);
362     }
363 
364     /** Returns the generated factory {@link TypeName type} for an @AssistedInject constructor. */
delegateFactoryTypeName(XType assistedInjectType)365     private TypeName delegateFactoryTypeName(XType assistedInjectType) {
366       // The name of the generated factory for the assisted inject type,
367       // e.g. an @AssistedInject Foo(...) {...} constructor will generate a Foo_Factory class.
368       ClassName generatedFactoryClassName =
369           generatedClassNameForBinding(
370               bindingFactory.injectionBinding(
371                   getOnlyElement(assistedInjectedConstructors(assistedInjectType.getTypeElement())),
372                   Optional.empty()));
373 
374       // Return the factory type resolved with the same type parameters as the assisted inject type.
375       return assistedInjectType.getTypeArguments().isEmpty()
376           ? generatedFactoryClassName
377           : ParameterizedTypeName.get(
378               generatedFactoryClassName,
379               assistedInjectType.getTypeArguments().stream()
380                   .map(XType::getTypeName)
381                   .collect(toImmutableList())
382                   .toArray(new TypeName[0]));
383     }
384   }
385 }
386