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