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