• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.writing;
18 
19 import static androidx.room.compiler.processing.XTypeKt.isArray;
20 import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
21 import static com.squareup.javapoet.MethodSpec.constructorBuilder;
22 import static com.squareup.javapoet.MethodSpec.methodBuilder;
23 import static com.squareup.javapoet.TypeSpec.classBuilder;
24 import static dagger.internal.codegen.binding.AnnotationExpression.createMethodName;
25 import static dagger.internal.codegen.binding.AnnotationExpression.getAnnotationCreatorClassName;
26 import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock;
27 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
28 import static dagger.internal.codegen.xprocessing.XTypes.asArray;
29 import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf;
30 import static dagger.internal.codegen.xprocessing.XTypes.rewrapType;
31 import static javax.lang.model.element.Modifier.FINAL;
32 import static javax.lang.model.element.Modifier.PRIVATE;
33 import static javax.lang.model.element.Modifier.PUBLIC;
34 import static javax.lang.model.element.Modifier.STATIC;
35 
36 import androidx.room.compiler.processing.XElement;
37 import androidx.room.compiler.processing.XFiler;
38 import androidx.room.compiler.processing.XMethodElement;
39 import androidx.room.compiler.processing.XProcessingEnv;
40 import androidx.room.compiler.processing.XType;
41 import androidx.room.compiler.processing.XTypeElement;
42 import com.google.common.collect.ImmutableList;
43 import com.google.errorprone.annotations.CanIgnoreReturnValue;
44 import com.squareup.javapoet.ClassName;
45 import com.squareup.javapoet.CodeBlock;
46 import com.squareup.javapoet.MethodSpec;
47 import com.squareup.javapoet.TypeName;
48 import com.squareup.javapoet.TypeSpec;
49 import dagger.internal.codegen.base.SourceFileGenerator;
50 import dagger.internal.codegen.javapoet.TypeNames;
51 import java.util.LinkedHashSet;
52 import java.util.Set;
53 import javax.inject.Inject;
54 
55 /**
56  * Generates classes that create annotation instances for an annotation type. The generated class
57  * will have a private empty constructor, a static method that creates the annotation type itself,
58  * and a static method that creates each annotation type that is nested in the top-level annotation
59  * type.
60  *
61  * <p>So for an example annotation:
62  *
63  * <pre>
64  *   {@literal @interface} Foo {
65  *     String s();
66  *     int i();
67  *     Bar bar(); // an annotation defined elsewhere
68  *   }
69  * </pre>
70  *
71  * the generated class will look like:
72  *
73  * <pre>
74  *   public final class FooCreator {
75  *     private FooCreator() {}
76  *
77  *     public static Foo createFoo(String s, int i, Bar bar) { … }
78  *     public static Bar createBar(…) { … }
79  *   }
80  * </pre>
81  */
82 public class AnnotationCreatorGenerator extends SourceFileGenerator<XTypeElement> {
83   private static final ClassName AUTO_ANNOTATION =
84       ClassName.get("com.google.auto.value", "AutoAnnotation");
85 
86   @Inject
AnnotationCreatorGenerator(XFiler filer, XProcessingEnv processingEnv)87   AnnotationCreatorGenerator(XFiler filer, XProcessingEnv processingEnv) {
88     super(filer, processingEnv);
89   }
90 
91   @Override
originatingElement(XTypeElement annotationType)92   public XElement originatingElement(XTypeElement annotationType) {
93     return annotationType;
94   }
95 
96   @Override
topLevelTypes(XTypeElement annotationType)97   public ImmutableList<TypeSpec.Builder> topLevelTypes(XTypeElement annotationType) {
98     ClassName generatedTypeName = getAnnotationCreatorClassName(annotationType);
99     TypeSpec.Builder annotationCreatorBuilder =
100         classBuilder(generatedTypeName)
101             .addModifiers(PUBLIC, FINAL)
102             .addMethod(constructorBuilder().addModifiers(PRIVATE).build());
103 
104     for (XTypeElement annotationElement : annotationsToCreate(annotationType)) {
105       annotationCreatorBuilder.addMethod(buildCreateMethod(generatedTypeName, annotationElement));
106     }
107 
108     return ImmutableList.of(annotationCreatorBuilder);
109   }
110 
buildCreateMethod( ClassName generatedTypeName, XTypeElement annotationElement)111   private MethodSpec buildCreateMethod(
112       ClassName generatedTypeName, XTypeElement annotationElement) {
113     String createMethodName = createMethodName(annotationElement);
114     MethodSpec.Builder createMethod =
115         methodBuilder(createMethodName)
116             .addAnnotation(AUTO_ANNOTATION)
117             .addModifiers(PUBLIC, STATIC)
118             .returns(annotationElement.getType().getTypeName());
119 
120     ImmutableList.Builder<CodeBlock> parameters = ImmutableList.builder();
121     for (XMethodElement annotationMember : annotationElement.getDeclaredMethods()) {
122       String parameterName = getSimpleName(annotationMember);
123       TypeName parameterType = maybeRewrapKClass(annotationMember.getReturnType()).getTypeName();
124       createMethod.addParameter(parameterType, parameterName);
125       parameters.add(CodeBlock.of("$L", parameterName));
126     }
127 
128     ClassName autoAnnotationClass =
129         generatedTypeName.peerClass(
130             "AutoAnnotation_" + generatedTypeName.simpleName() + "_" + createMethodName);
131     createMethod.addStatement(
132         "return new $T($L)", autoAnnotationClass, makeParametersCodeBlock(parameters.build()));
133     return createMethod.build();
134   }
135 
136   /**
137    * Returns the annotation types for which {@code @AutoAnnotation static Foo createFoo(…)} methods
138    * should be written.
139    */
annotationsToCreate(XTypeElement annotationElement)140   protected Set<XTypeElement> annotationsToCreate(XTypeElement annotationElement) {
141     return nestedAnnotationElements(annotationElement, new LinkedHashSet<>());
142   }
143 
144   @CanIgnoreReturnValue
nestedAnnotationElements( XTypeElement annotationElement, Set<XTypeElement> annotationElements)145   private static Set<XTypeElement> nestedAnnotationElements(
146       XTypeElement annotationElement, Set<XTypeElement> annotationElements) {
147     if (annotationElements.add(annotationElement)) {
148       for (XMethodElement method : annotationElement.getDeclaredMethods()) {
149         XTypeElement returnType = method.getReturnType().getTypeElement();
150         // Return type may be null if it doesn't return a type or type is not known
151         if (returnType != null && returnType.isAnnotationClass()) {
152           // Ignore the return value since this method is just an accumulator method.
153           nestedAnnotationElements(returnType, annotationElements);
154         }
155       }
156     }
157     return annotationElements;
158   }
159 
160   // TODO(b/264464791): This KClass -> Class replacement can be removed once this bug is fixed.
maybeRewrapKClass(XType type)161   private XType maybeRewrapKClass(XType type) {
162     return isArray(type)
163         ? getProcessingEnv(type).getArrayType(maybeRewrapKClass(asArray(type).getComponentType()))
164         : isTypeOf(type, TypeNames.KCLASS) ? rewrapType(type, TypeNames.CLASS) : type;
165   }
166 }
167