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.base; 18 19 import static androidx.room.compiler.processing.JavaPoetExtKt.addOriginatingElement; 20 import static com.google.common.base.Preconditions.checkNotNull; 21 import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.CAST; 22 import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.KOTLIN_INTERNAL; 23 import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES; 24 import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; 25 import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement; 26 27 import androidx.room.compiler.processing.XElement; 28 import androidx.room.compiler.processing.XFiler; 29 import androidx.room.compiler.processing.XMessager; 30 import androidx.room.compiler.processing.XProcessingEnv; 31 import com.google.common.base.Throwables; 32 import com.google.common.collect.ImmutableList; 33 import com.google.common.collect.ImmutableSet; 34 import com.squareup.javapoet.AnnotationSpec; 35 import com.squareup.javapoet.JavaFile; 36 import com.squareup.javapoet.TypeSpec; 37 import dagger.internal.DaggerGenerated; 38 import dagger.internal.codegen.javapoet.AnnotationSpecs; 39 import dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression; 40 import java.util.Optional; 41 42 /** 43 * A template class that provides a framework for properly handling IO while generating source files 44 * from an annotation processor. Particularly, it makes a best effort to ensure that files that fail 45 * to write successfully are deleted. 46 * 47 * @param <T> The input type from which source is to be generated. 48 */ 49 public abstract class SourceFileGenerator<T> { 50 private static final String GENERATED_COMMENTS = "https://dagger.dev"; 51 52 private final XFiler filer; 53 private final XProcessingEnv processingEnv; 54 SourceFileGenerator(XFiler filer, XProcessingEnv processingEnv)55 public SourceFileGenerator(XFiler filer, XProcessingEnv processingEnv) { 56 this.filer = checkNotNull(filer); 57 this.processingEnv = checkNotNull(processingEnv); 58 } 59 SourceFileGenerator(SourceFileGenerator<T> delegate)60 public SourceFileGenerator(SourceFileGenerator<T> delegate) { 61 this(delegate.filer, delegate.processingEnv); 62 } 63 64 /** 65 * Generates a source file to be compiled for {@code T}. Writes any generation exception to {@code 66 * messager} and does not throw. 67 */ generate(T input, XMessager messager)68 public void generate(T input, XMessager messager) { 69 try { 70 generate(input); 71 } catch (SourceFileGenerationException e) { 72 e.printMessageTo(messager); 73 } 74 } 75 76 /** Generates a source file to be compiled for {@code T}. */ generate(T input)77 public void generate(T input) throws SourceFileGenerationException { 78 for (TypeSpec.Builder type : topLevelTypes(input)) { 79 try { 80 filer.write(buildJavaFile(input, type), XFiler.Mode.Isolating); 81 } catch (RuntimeException e) { 82 // if the code above threw a SFGE, use that 83 Throwables.propagateIfPossible(e, SourceFileGenerationException.class); 84 // otherwise, throw a new one 85 throw new SourceFileGenerationException(Optional.empty(), e, originatingElement(input)); 86 } 87 } 88 } 89 buildJavaFile(T input, TypeSpec.Builder typeSpecBuilder)90 private JavaFile buildJavaFile(T input, TypeSpec.Builder typeSpecBuilder) { 91 XElement originatingElement = originatingElement(input); 92 addOriginatingElement(typeSpecBuilder, originatingElement); 93 typeSpecBuilder.addAnnotation(DaggerGenerated.class); 94 Optional<AnnotationSpec> generatedAnnotation = 95 Optional.ofNullable(processingEnv.findGeneratedAnnotation()) 96 .map( 97 annotation -> 98 AnnotationSpec.builder(annotation.getClassName()) 99 .addMember("value", "$S", "dagger.internal.codegen.ComponentProcessor") 100 .addMember("comments", "$S", GENERATED_COMMENTS) 101 .build()); 102 generatedAnnotation.ifPresent(typeSpecBuilder::addAnnotation); 103 104 // TODO(b/263891456): Remove KOTLIN_INTERNAL and use Object/raw types where necessary. 105 typeSpecBuilder.addAnnotation( 106 AnnotationSpecs.suppressWarnings( 107 ImmutableSet.<Suppression>builder() 108 .addAll(warningSuppressions()) 109 .add(UNCHECKED, RAWTYPES, KOTLIN_INTERNAL, CAST) 110 .build())); 111 112 String packageName = closestEnclosingTypeElement(originatingElement).getPackageName(); 113 JavaFile.Builder javaFileBuilder = 114 JavaFile.builder(packageName, typeSpecBuilder.build()).skipJavaLangImports(true); 115 if (!generatedAnnotation.isPresent()) { 116 javaFileBuilder.addFileComment("Generated by Dagger ($L).", GENERATED_COMMENTS); 117 } 118 return javaFileBuilder.build(); 119 } 120 121 /** Returns the originating element of the generating type. */ originatingElement(T input)122 public abstract XElement originatingElement(T input); 123 124 /** 125 * Returns {@link TypeSpec.Builder types} be generated for {@code T}, or an empty list if no types 126 * should be generated. 127 * 128 * <p>Every type will be generated in its own file. 129 */ topLevelTypes(T input)130 public abstract ImmutableList<TypeSpec.Builder> topLevelTypes(T input); 131 132 /** Returns {@link Suppression}s that are applied to files generated by this generator. */ 133 // TODO(b/134590785): When suppressions are removed locally, remove this and inline the usages warningSuppressions()134 protected ImmutableSet<Suppression> warningSuppressions() { 135 return ImmutableSet.of(); 136 } 137 } 138