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