• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.hilt.processor.internal;
18 
19 import static com.google.common.base.Preconditions.checkState;
20 
21 import com.google.auto.common.MoreElements;
22 import com.google.auto.value.AutoValue;
23 import com.google.common.collect.LinkedHashMultimap;
24 import com.google.common.collect.SetMultimap;
25 import com.squareup.javapoet.ClassName;
26 import java.util.Collection;
27 import java.util.LinkedHashSet;
28 import java.util.Map;
29 import java.util.Set;
30 import javax.annotation.processing.AbstractProcessor;
31 import javax.annotation.processing.Messager;
32 import javax.annotation.processing.ProcessingEnvironment;
33 import javax.annotation.processing.RoundEnvironment;
34 import javax.lang.model.SourceVersion;
35 import javax.lang.model.element.Element;
36 import javax.lang.model.element.TypeElement;
37 import javax.lang.model.util.Elements;
38 import javax.lang.model.util.Types;
39 
40 /**
41  * Implements default configurations for Processors, and provides structure for exception handling.
42  *
43  * <p>By default #process() will do the following:
44  *
45  * <ol>
46  *   <li> #preRoundProcess()
47  *   <li> foreach element:
48  *     <ul><li> #processEach()</ul>
49  *   </li>
50  *   <li> #postRoundProcess()
51  *   <li> #claimAnnotation()
52  * </ol>
53  *
54  * <p>#processEach() allows each element to be processed, even if exceptions are thrown. Due to the
55  * non-deterministic ordering of the processed elements, this is needed to ensure a consistent set
56  * of exceptions are thrown with each build.
57  */
58 public abstract class BaseProcessor extends AbstractProcessor {
59   /** Stores the state of processing for a given annotation and element. */
60   @AutoValue
61   abstract static class ProcessingState {
of(TypeElement annotation, Element element)62     private static ProcessingState of(TypeElement annotation, Element element) {
63       // We currently only support TypeElements directly annotated with the annotation.
64       // TODO(bcorso): Switch to using BasicAnnotationProcessor if we need more than this.
65       // Note: Switching to BasicAnnotationProcessor is currently not possible because of cyclic
66       // references to generated types in our API. For example, an @AndroidEntryPoint annotated
67       // element will indefinitely defer its own processing because it extends a generated type
68       // that it's responsible for generating.
69       checkState(MoreElements.isType(element));
70       checkState(Processors.hasAnnotation(element, ClassName.get(annotation)));
71       return new AutoValue_BaseProcessor_ProcessingState(
72           ClassName.get(annotation),
73           ClassName.get(MoreElements.asType(element)));
74     }
75 
76     /** Returns the class name of the annotation. */
annotationClassName()77     abstract ClassName annotationClassName();
78 
79     /** Returns the type name of the annotated element. */
elementClassName()80     abstract ClassName elementClassName();
81 
82     /** Returns the annotation that triggered the processing. */
annotation(Elements elements)83     TypeElement annotation(Elements elements) {
84       return elements.getTypeElement(elementClassName().toString());
85     }
86 
87     /** Returns the annotated element to process. */
element(Elements elements)88     TypeElement element(Elements elements) {
89       return elements.getTypeElement(annotationClassName().toString());
90     }
91   }
92 
93   private final Set<ProcessingState> stateToReprocess = new LinkedHashSet<>();
94   private Elements elements;
95   private Types types;
96   private Messager messager;
97   private ProcessorErrorHandler errorHandler;
98 
99   /** Used to perform initialization before each round of processing. */
preRoundProcess(RoundEnvironment roundEnv)100   protected void preRoundProcess(RoundEnvironment roundEnv) {};
101 
102   /**
103    * Called for each element in a round that uses a supported annotation.
104    *
105    * Note that an exception can be thrown for each element in the round. This is usually preferred
106    * over throwing only the first exception in a round. Only throwing the first exception in the
107    * round can lead to flaky errors that are dependent on the non-deterministic ordering that the
108    * elements are processed in.
109    */
processEach(TypeElement annotation, Element element)110   protected void processEach(TypeElement annotation, Element element) throws Exception {};
111 
112   /**
113    * Used to perform post processing at the end of a round. This is especially useful for handling
114    * additional processing that depends on aggregate data, that cannot be handled in #processEach().
115    *
116    * <p>Note: this will not be called if an exception is thrown during #processEach() -- if we have
117    * already detected errors on an annotated element, performing post processing on an aggregate
118    * will just produce more (perhaps non-deterministic) errors.
119    */
postRoundProcess(RoundEnvironment roundEnv)120   protected void postRoundProcess(RoundEnvironment roundEnv) throws Exception {};
121 
122   /** @return true if you want to claim annotations after processing each round. Default false. */
claimAnnotations()123   protected boolean claimAnnotations() {
124     return false;
125   }
126 
127   /**
128    * @return true if you want to delay errors to the last round. Useful if the processor
129    * generates code for symbols used a lot in the user code. Delaying allows as much code to
130    * compile as possible for correctly configured types and reduces error spam.
131    */
delayErrors()132   protected boolean delayErrors() {
133     return false;
134   }
135 
136 
137   @Override
init(ProcessingEnvironment processingEnvironment)138   public synchronized void init(ProcessingEnvironment processingEnvironment) {
139     super.init(processingEnvironment);
140     this.messager = processingEnv.getMessager();
141     this.elements = processingEnv.getElementUtils();
142     this.types = processingEnv.getTypeUtils();
143     this.errorHandler = new ProcessorErrorHandler(processingEnvironment);
144   }
145 
146   @Override
getSupportedSourceVersion()147   public SourceVersion getSupportedSourceVersion() {
148     return SourceVersion.latestSupported();
149   }
150 
151   /**
152    * This should not be overridden, as it defines the order of the processing.
153    */
154   @Override
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)155   public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
156     preRoundProcess(roundEnv);
157 
158     boolean roundError = false;
159 
160     // Gather the set of new and deferred elements to process, grouped by annotation.
161     SetMultimap<TypeElement, Element> elementMultiMap = LinkedHashMultimap.create();
162     for (ProcessingState processingState : stateToReprocess) {
163       elementMultiMap.put(processingState.annotation(elements), processingState.element(elements));
164     }
165     for (TypeElement annotation : annotations) {
166       elementMultiMap.putAll(annotation, roundEnv.getElementsAnnotatedWith(annotation));
167     }
168 
169     // Clear the processing state before reprocessing.
170     stateToReprocess.clear();
171 
172     for (Map.Entry<TypeElement, Collection<Element>> entry : elementMultiMap.asMap().entrySet()) {
173       TypeElement annotation = entry.getKey();
174       for (Element element : entry.getValue()) {
175         try {
176           processEach(annotation, element);
177         } catch (Exception e) {
178           if (e instanceof ErrorTypeException && !roundEnv.processingOver()) {
179             // Allow an extra round to reprocess to try to resolve this type.
180             stateToReprocess.add(ProcessingState.of(annotation, element));
181           } else {
182             errorHandler.recordError(e);
183             roundError = true;
184           }
185         }
186       }
187     }
188 
189     if (!roundError) {
190       try {
191         postRoundProcess(roundEnv);
192       } catch (Exception e) {
193         errorHandler.recordError(e);
194       }
195     }
196 
197     if (!delayErrors() || roundEnv.processingOver()) {
198       errorHandler.checkErrors();
199     }
200 
201     return claimAnnotations();
202   }
203 
204   /** @return the error handle for the processor. */
getErrorHandler()205   protected final ProcessorErrorHandler getErrorHandler() {
206     return errorHandler;
207   }
208 
getProcessingEnv()209   public final ProcessingEnvironment getProcessingEnv() {
210     return processingEnv;
211   }
212 
getElementUtils()213   public final Elements getElementUtils() {
214     return elements;
215   }
216 
getTypeUtils()217   public final Types getTypeUtils() {
218     return types;
219   }
220 
getMessager()221   public final Messager getMessager() {
222     return messager;
223   }
224 }
225