| /* |
| * Copyright (C) 2019 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.hilt.processor.internal; |
| |
| import static com.google.common.base.Preconditions.checkState; |
| |
| import com.google.auto.common.MoreElements; |
| import com.google.auto.value.AutoValue; |
| import com.google.common.collect.LinkedHashMultimap; |
| import com.google.common.collect.SetMultimap; |
| import com.squareup.javapoet.ClassName; |
| import java.util.Collection; |
| import java.util.LinkedHashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import javax.annotation.processing.AbstractProcessor; |
| import javax.annotation.processing.Messager; |
| import javax.annotation.processing.ProcessingEnvironment; |
| import javax.annotation.processing.RoundEnvironment; |
| import javax.lang.model.SourceVersion; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.util.Elements; |
| import javax.lang.model.util.Types; |
| |
| /** |
| * Implements default configurations for Processors, and provides structure for exception handling. |
| * |
| * <p>By default #process() will do the following: |
| * |
| * <ol> |
| * <li> #preRoundProcess() |
| * <li> foreach element: |
| * <ul><li> #processEach()</ul> |
| * </li> |
| * <li> #postRoundProcess() |
| * <li> #claimAnnotation() |
| * </ol> |
| * |
| * <p>#processEach() allows each element to be processed, even if exceptions are thrown. Due to the |
| * non-deterministic ordering of the processed elements, this is needed to ensure a consistent set |
| * of exceptions are thrown with each build. |
| */ |
| public abstract class BaseProcessor extends AbstractProcessor { |
| /** Stores the state of processing for a given annotation and element. */ |
| @AutoValue |
| abstract static class ProcessingState { |
| private static ProcessingState of(TypeElement annotation, Element element) { |
| // We currently only support TypeElements directly annotated with the annotation. |
| // TODO(bcorso): Switch to using BasicAnnotationProcessor if we need more than this. |
| // Note: Switching to BasicAnnotationProcessor is currently not possible because of cyclic |
| // references to generated types in our API. For example, an @AndroidEntryPoint annotated |
| // element will indefinitely defer its own processing because it extends a generated type |
| // that it's responsible for generating. |
| checkState(MoreElements.isType(element)); |
| checkState(Processors.hasAnnotation(element, ClassName.get(annotation))); |
| return new AutoValue_BaseProcessor_ProcessingState( |
| ClassName.get(annotation), |
| ClassName.get(MoreElements.asType(element))); |
| } |
| |
| /** Returns the class name of the annotation. */ |
| abstract ClassName annotationClassName(); |
| |
| /** Returns the type name of the annotated element. */ |
| abstract ClassName elementClassName(); |
| |
| /** Returns the annotation that triggered the processing. */ |
| TypeElement annotation(Elements elements) { |
| return elements.getTypeElement(elementClassName().toString()); |
| } |
| |
| /** Returns the annotated element to process. */ |
| TypeElement element(Elements elements) { |
| return elements.getTypeElement(annotationClassName().toString()); |
| } |
| } |
| |
| private final Set<ProcessingState> stateToReprocess = new LinkedHashSet<>(); |
| private Elements elements; |
| private Types types; |
| private Messager messager; |
| private ProcessorErrorHandler errorHandler; |
| |
| @Override |
| public final Set<String> getSupportedOptions() { |
| // This is declared here rather than in the actual processors because KAPT will issue a |
| // warning if any used option is not unsupported. This can happen when there is a module |
| // which uses Hilt but lacks any @AndroidEntryPoint annotations. |
| // See: https://github.com/google/dagger/issues/2040 |
| return HiltCompilerOptions.getProcessorOptions(); |
| } |
| |
| /** Used to perform initialization before each round of processing. */ |
| protected void preRoundProcess(RoundEnvironment roundEnv) {}; |
| |
| /** |
| * Called for each element in a round that uses a supported annotation. |
| * |
| * Note that an exception can be thrown for each element in the round. This is usually preferred |
| * over throwing only the first exception in a round. Only throwing the first exception in the |
| * round can lead to flaky errors that are dependent on the non-deterministic ordering that the |
| * elements are processed in. |
| */ |
| protected void processEach(TypeElement annotation, Element element) throws Exception {}; |
| |
| /** |
| * Used to perform post processing at the end of a round. This is especially useful for handling |
| * additional processing that depends on aggregate data, that cannot be handled in #processEach(). |
| * |
| * <p>Note: this will not be called if an exception is thrown during #processEach() -- if we have |
| * already detected errors on an annotated element, performing post processing on an aggregate |
| * will just produce more (perhaps non-deterministic) errors. |
| */ |
| protected void postRoundProcess(RoundEnvironment roundEnv) throws Exception {}; |
| |
| /** @return true if you want to claim annotations after processing each round. Default false. */ |
| protected boolean claimAnnotations() { |
| return false; |
| } |
| |
| /** |
| * @return true if you want to delay errors to the last round. Useful if the processor |
| * generates code for symbols used a lot in the user code. Delaying allows as much code to |
| * compile as possible for correctly configured types and reduces error spam. |
| */ |
| protected boolean delayErrors() { |
| return false; |
| } |
| |
| |
| @Override |
| public synchronized void init(ProcessingEnvironment processingEnvironment) { |
| super.init(processingEnvironment); |
| this.messager = processingEnv.getMessager(); |
| this.elements = processingEnv.getElementUtils(); |
| this.types = processingEnv.getTypeUtils(); |
| this.errorHandler = new ProcessorErrorHandler(processingEnvironment); |
| } |
| |
| @Override |
| public SourceVersion getSupportedSourceVersion() { |
| return SourceVersion.latestSupported(); |
| } |
| |
| /** |
| * This should not be overridden, as it defines the order of the processing. |
| */ |
| @Override |
| public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { |
| preRoundProcess(roundEnv); |
| |
| boolean roundError = false; |
| |
| // Gather the set of new and deferred elements to process, grouped by annotation. |
| SetMultimap<TypeElement, Element> elementMultiMap = LinkedHashMultimap.create(); |
| for (ProcessingState processingState : stateToReprocess) { |
| elementMultiMap.put(processingState.annotation(elements), processingState.element(elements)); |
| } |
| for (TypeElement annotation : annotations) { |
| elementMultiMap.putAll(annotation, roundEnv.getElementsAnnotatedWith(annotation)); |
| } |
| |
| // Clear the processing state before reprocessing. |
| stateToReprocess.clear(); |
| |
| for (Map.Entry<TypeElement, Collection<Element>> entry : elementMultiMap.asMap().entrySet()) { |
| TypeElement annotation = entry.getKey(); |
| for (Element element : entry.getValue()) { |
| try { |
| processEach(annotation, element); |
| } catch (Exception e) { |
| if (e instanceof ErrorTypeException && !roundEnv.processingOver()) { |
| // Allow an extra round to reprocess to try to resolve this type. |
| stateToReprocess.add(ProcessingState.of(annotation, element)); |
| } else { |
| errorHandler.recordError(e); |
| roundError = true; |
| } |
| } |
| } |
| } |
| |
| if (!roundError) { |
| try { |
| postRoundProcess(roundEnv); |
| } catch (Exception e) { |
| errorHandler.recordError(e); |
| } |
| } |
| |
| if (!delayErrors() || roundEnv.processingOver()) { |
| errorHandler.checkErrors(); |
| } |
| |
| return claimAnnotations(); |
| } |
| |
| /** @return the error handle for the processor. */ |
| protected final ProcessorErrorHandler getErrorHandler() { |
| return errorHandler; |
| } |
| |
| public final ProcessingEnvironment getProcessingEnv() { |
| return processingEnv; |
| } |
| |
| public final Elements getElementUtils() { |
| return elements; |
| } |
| |
| public final Types getTypeUtils() { |
| return types; |
| } |
| |
| public final Messager getMessager() { |
| return messager; |
| } |
| } |