aboutsummaryrefslogtreecommitdiff
path: root/java/dagger/internal/codegen/base/SourceFileGenerator.java
blob: 02348a4f477fa5b1e90234b354f26a0169c6982f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
 * 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();
  }
}