| /* |
| * Copyright (C) 2014 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.base; |
| |
| import static com.google.auto.common.GeneratedAnnotations.generatedAnnotation; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES; |
| import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; |
| |
| import com.google.common.base.Throwables; |
| import com.google.common.collect.ImmutableSet; |
| import com.squareup.javapoet.AnnotationSpec; |
| import com.squareup.javapoet.ClassName; |
| import com.squareup.javapoet.JavaFile; |
| import com.squareup.javapoet.TypeSpec; |
| import dagger.internal.codegen.javapoet.AnnotationSpecs; |
| import dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression; |
| import dagger.internal.codegen.langmodel.DaggerElements; |
| import java.util.Optional; |
| import javax.annotation.processing.Filer; |
| import javax.annotation.processing.Messager; |
| import javax.lang.model.SourceVersion; |
| import javax.lang.model.element.Element; |
| |
| /** |
| * A template class that provides a framework for properly handling IO while generating source files |
| * from an annotation processor. Particularly, it makes a best effort to ensure that files that fail |
| * to write successfully are deleted. |
| * |
| * @param <T> The input type from which source is to be generated. |
| */ |
| public abstract class SourceFileGenerator<T> { |
| private static final String GENERATED_COMMENTS = "https://dagger.dev"; |
| |
| private final Filer filer; |
| private final DaggerElements elements; |
| private final SourceVersion sourceVersion; |
| |
| public SourceFileGenerator(Filer filer, DaggerElements elements, SourceVersion sourceVersion) { |
| this.filer = checkNotNull(filer); |
| this.elements = checkNotNull(elements); |
| this.sourceVersion = checkNotNull(sourceVersion); |
| } |
| |
| public SourceFileGenerator(SourceFileGenerator<T> delegate) { |
| this(delegate.filer, delegate.elements, delegate.sourceVersion); |
| } |
| |
| /** |
| * Generates a source file to be compiled for {@code T}. Writes any generation exception to {@code |
| * messager} and does not throw. |
| */ |
| public void generate(T input, Messager messager) { |
| try { |
| generate(input); |
| } catch (SourceFileGenerationException e) { |
| e.printMessageTo(messager); |
| } |
| } |
| |
| /** Generates a source file to be compiled for {@code T}. */ |
| public void generate(T input) throws SourceFileGenerationException { |
| Optional<TypeSpec.Builder> type = write(input); |
| if (!type.isPresent()) { |
| return; |
| } |
| try { |
| buildJavaFile(input, type.get()).writeTo(filer); |
| } catch (Exception e) { |
| // if the code above threw a SFGE, use that |
| Throwables.propagateIfPossible(e, SourceFileGenerationException.class); |
| // otherwise, throw a new one |
| throw new SourceFileGenerationException(Optional.empty(), e, originatingElement(input)); |
| } |
| } |
| |
| private JavaFile buildJavaFile(T input, TypeSpec.Builder typeSpecBuilder) { |
| typeSpecBuilder.addOriginatingElement(originatingElement(input)); |
| Optional<AnnotationSpec> generatedAnnotation = |
| generatedAnnotation(elements, sourceVersion) |
| .map( |
| annotation -> |
| AnnotationSpec.builder(ClassName.get(annotation)) |
| .addMember("value", "$S", "dagger.internal.codegen.ComponentProcessor") |
| .addMember("comments", "$S", GENERATED_COMMENTS) |
| .build()); |
| generatedAnnotation.ifPresent(typeSpecBuilder::addAnnotation); |
| |
| // TODO(b/134590785): remove this and only suppress annotations locally, if necessary |
| typeSpecBuilder.addAnnotation( |
| AnnotationSpecs.suppressWarnings( |
| ImmutableSet.<Suppression>builder() |
| .addAll(warningSuppressions()) |
| .add(UNCHECKED, RAWTYPES) |
| .build())); |
| |
| JavaFile.Builder javaFileBuilder = |
| JavaFile.builder(nameGeneratedType(input).packageName(), typeSpecBuilder.build()) |
| .skipJavaLangImports(true); |
| if (!generatedAnnotation.isPresent()) { |
| javaFileBuilder.addFileComment("Generated by Dagger ($L).", GENERATED_COMMENTS); |
| } |
| return javaFileBuilder.build(); |
| } |
| |
| /** Implementations should return the {@link ClassName} for the top-level type to be generated. */ |
| public abstract ClassName nameGeneratedType(T input); |
| |
| /** Returns the originating element of the generating type. */ |
| public abstract Element originatingElement(T input); |
| |
| /** |
| * Returns a {@link TypeSpec.Builder type} to be generated for {@code T}, or {@link |
| * Optional#empty()} if no file should be generated. |
| */ |
| // TODO(ronshapiro): write() makes more sense in JavaWriter where all writers are mutable. |
| // consider renaming to something like typeBuilder() which conveys the mutability of the result |
| public abstract Optional<TypeSpec.Builder> write(T input); |
| |
| /** Returns {@link Suppression}s that are applied to files generated by this generator. */ |
| // TODO(b/134590785): When suppressions are removed locally, remove this and inline the usages |
| protected ImmutableSet<Suppression> warningSuppressions() { |
| return ImmutableSet.of(); |
| } |
| } |