diff options
Diffstat (limited to 'common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java')
-rw-r--r-- | common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java | 240 |
1 files changed, 178 insertions, 62 deletions
diff --git a/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java b/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java index d3f67aa5..375a4cb8 100644 --- a/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java +++ b/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java @@ -17,12 +17,14 @@ package com.google.auto.common; import static com.google.auto.common.MoreElements.asExecutable; import static com.google.auto.common.MoreElements.asPackage; -import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.auto.common.SuperficialValidation.validateElement; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Multimaps.filterKeys; +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; import static javax.lang.model.element.ElementKind.PACKAGE; import static javax.tools.Diagnostic.Kind.ERROR; @@ -30,8 +32,10 @@ import com.google.common.base.Ascii; import com.google.common.base.Optional; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; @@ -57,9 +61,9 @@ import javax.lang.model.util.SimpleElementVisitor8; * An abstract {@link Processor} implementation that defers processing of {@link Element}s to later * rounds if they cannot be processed. * - * <p>Subclasses put their processing logic in {@link ProcessingStep} implementations. The steps are - * passed to the processor by returning them in the {@link #initSteps()} method, and can access the - * {@link ProcessingEnvironment} using {@link #processingEnv}. + * <p>Subclasses put their processing logic in {@link Step} implementations. The steps are passed to + * the processor by returning them in the {@link #steps()} method, and can access the {@link + * ProcessingEnvironment} using {@link #processingEnv}. * * <p>Any logic that needs to happen once per round can be specified by overriding {@link * #postRound(RoundEnvironment)}. @@ -67,8 +71,8 @@ import javax.lang.model.util.SimpleElementVisitor8; * <h3>Ill-formed elements are deferred</h3> * * Any annotated element whose nearest enclosing type is not well-formed is deferred, and not passed - * to any {@code ProcessingStep}. This helps processors to avoid many common pitfalls, such as - * {@link ErrorType} instances, {@link ClassCastException}s and badly coerced types. + * to any {@code Step}. This helps processors to avoid many common pitfalls, such as {@link + * ErrorType} instances, {@link ClassCastException}s and badly coerced types. * * <p>A non-package element is considered well-formed if its type, type parameters, parameters, * default values, supertypes, annotations, and enclosed elements are. Package elements are treated @@ -80,11 +84,11 @@ import javax.lang.model.util.SimpleElementVisitor8; * because the element will never be fully complete. All such compilations will fail with an error * message on the offending type that describes the issue. * - * <h3>Each {@code ProcessingStep} can defer elements</h3> + * <h3>Each {@code Step} can defer elements</h3> * - * <p>Each {@code ProcessingStep} can defer elements by including them in the set returned by {@link - * ProcessingStep#process(SetMultimap)}; elements deferred by a step will be passed back to that - * step in a later round of processing. + * <p>Each {@code Step} can defer elements by including them in the set returned by {@link + * Step#process(ImmutableSetMultimap)}; elements deferred by a step will be passed back to that step + * in a later round of processing. * * <p>This feature is useful when one processor may depend on code generated by another, independent * processor, in a way that isn't caught by the well-formedness check described above. For example, @@ -102,26 +106,42 @@ import javax.lang.model.util.SimpleElementVisitor8; public abstract class BasicAnnotationProcessor extends AbstractProcessor { private final Set<ElementName> deferredElementNames = new LinkedHashSet<>(); - private final SetMultimap<ProcessingStep, ElementName> elementsDeferredBySteps = + private final SetMultimap<Step, ElementName> elementsDeferredBySteps = LinkedHashMultimap.create(); private Elements elements; private Messager messager; - private ImmutableList<? extends ProcessingStep> steps; + private ImmutableList<? extends Step> steps; @Override public final synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.elements = processingEnv.getElementUtils(); this.messager = processingEnv.getMessager(); - this.steps = ImmutableList.copyOf(initSteps()); + this.steps = ImmutableList.copyOf(steps()); } /** * Creates {@linkplain ProcessingStep processing steps} for this processor. {@link #processingEnv} * is guaranteed to be set when this method is invoked. + * + * @deprecated Implement {@link #steps()} instead. + */ + @Deprecated + protected Iterable<? extends ProcessingStep> initSteps() { + throw new AssertionError("If steps() is not implemented, initSteps() must be."); + } + + /** + * Creates {@linkplain Step processing steps} for this processor. {@link #processingEnv} is + * guaranteed to be set when this method is invoked. + * + * <p>Note: If you are migrating some steps from {@link ProcessingStep} to {@link Step}, then you + * can call {@link #asStep(ProcessingStep)} on any unmigrated steps. */ - protected abstract Iterable<? extends ProcessingStep> initSteps(); + protected Iterable<? extends Step> steps() { + return Iterables.transform(initSteps(), BasicAnnotationProcessor::asStep); + } /** * An optional hook for logic to be executed at the end of each round. @@ -138,26 +158,30 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { } } - private ImmutableSet<? extends Class<? extends Annotation>> getSupportedAnnotationClasses() { + private ImmutableSet<TypeElement> getSupportedAnnotationTypeElements() { checkState(steps != null); - ImmutableSet.Builder<Class<? extends Annotation>> builder = ImmutableSet.builder(); - for (ProcessingStep step : steps) { - builder.addAll(step.annotations()); - } - return builder.build(); + return steps.stream() + .flatMap(step -> getSupportedAnnotationTypeElements(step).stream()) + .collect(collectingAndThen(toList(), ImmutableSet::copyOf)); + } + + private ImmutableSet<TypeElement> getSupportedAnnotationTypeElements(Step step) { + return step.annotations().stream() + .map(elements::getTypeElement) + .filter(Objects::nonNull) + .collect(collectingAndThen(toList(), ImmutableSet::copyOf)); } /** - * Returns the set of supported annotation types as a collected from registered {@linkplain - * ProcessingStep processing steps}. + * Returns the set of supported annotation types as collected from registered {@linkplain Step + * processing steps}. */ @Override public final ImmutableSet<String> getSupportedAnnotationTypes() { - ImmutableSet.Builder<String> builder = ImmutableSet.builder(); - for (Class<? extends Annotation> annotationClass : getSupportedAnnotationClasses()) { - builder.add(annotationClass.getCanonicalName()); - } - return builder.build(); + checkState(steps != null); + return steps.stream() + .flatMap(step -> step.annotations().stream()) + .collect(collectingAndThen(toList(), ImmutableSet::copyOf)); } @Override @@ -189,17 +213,19 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { } /** Processes the valid elements, including those previously deferred by each step. */ - private void process(ImmutableSetMultimap<Class<? extends Annotation>, Element> validElements) { - for (ProcessingStep step : steps) { - ImmutableSetMultimap<Class<? extends Annotation>, Element> stepElements = - new ImmutableSetMultimap.Builder<Class<? extends Annotation>, Element>() - .putAll(indexByAnnotation(elementsDeferredBySteps.get(step), step.annotations())) - .putAll(filterKeys(validElements, Predicates.<Object>in(step.annotations()))) + private void process(ImmutableSetMultimap<TypeElement, Element> validElements) { + for (Step step : steps) { + ImmutableSet<TypeElement> annotationTypes = getSupportedAnnotationTypeElements(step); + ImmutableSetMultimap<TypeElement, Element> stepElements = + new ImmutableSetMultimap.Builder<TypeElement, Element>() + .putAll(indexByAnnotation(elementsDeferredBySteps.get(step), annotationTypes)) + .putAll(filterKeys(validElements, Predicates.in(annotationTypes))) .build(); if (stepElements.isEmpty()) { elementsDeferredBySteps.removeAll(step); } else { - Set<? extends Element> rejectedElements = step.process(stepElements); + Set<? extends Element> rejectedElements = + step.process(toClassNameKeyedMultimap(stepElements)); elementsDeferredBySteps.replaceValues( step, transform(rejectedElements, ElementName::forAnnotatedElement)); } @@ -233,43 +259,39 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { * Returns the valid annotated elements contained in all of the deferred elements. If none are * found for a deferred element, defers it again. */ - private ImmutableSetMultimap<Class<? extends Annotation>, Element> validElements( - RoundEnvironment roundEnv) { + private ImmutableSetMultimap<TypeElement, Element> validElements(RoundEnvironment roundEnv) { ImmutableSet<ElementName> prevDeferredElementNames = ImmutableSet.copyOf(deferredElementNames); deferredElementNames.clear(); - ImmutableSetMultimap.Builder<Class<? extends Annotation>, Element> - deferredElementsByAnnotationBuilder = ImmutableSetMultimap.builder(); + ImmutableSetMultimap.Builder<TypeElement, Element> deferredElementsByAnnotationBuilder = + ImmutableSetMultimap.builder(); for (ElementName deferredElementName : prevDeferredElementNames) { Optional<? extends Element> deferredElement = deferredElementName.getElement(elements); if (deferredElement.isPresent()) { findAnnotatedElements( deferredElement.get(), - getSupportedAnnotationClasses(), + getSupportedAnnotationTypeElements(), deferredElementsByAnnotationBuilder); } else { deferredElementNames.add(deferredElementName); } } - ImmutableSetMultimap<Class<? extends Annotation>, Element> deferredElementsByAnnotation = + ImmutableSetMultimap<TypeElement, Element> deferredElementsByAnnotation = deferredElementsByAnnotationBuilder.build(); - ImmutableSetMultimap.Builder<Class<? extends Annotation>, Element> validElements = + ImmutableSetMultimap.Builder<TypeElement, Element> validElements = ImmutableSetMultimap.builder(); Set<ElementName> validElementNames = new LinkedHashSet<>(); // Look at the elements we've found and the new elements from this round and validate them. - for (Class<? extends Annotation> annotationClass : getSupportedAnnotationClasses()) { - // This should just call roundEnv.getElementsAnnotatedWith(Class) directly, but there is a bug - // in some versions of eclipse that cause that method to crash. - TypeElement annotationType = elements.getTypeElement(annotationClass.getCanonicalName()); + for (TypeElement annotationType : getSupportedAnnotationTypeElements()) { Set<? extends Element> roundElements = (annotationType == null) - ? ImmutableSet.<Element>of() + ? ImmutableSet.of() : roundEnv.getElementsAnnotatedWith(annotationType); - ImmutableSet<Element> prevRoundElements = deferredElementsByAnnotation.get(annotationClass); + ImmutableSet<Element> prevRoundElements = deferredElementsByAnnotation.get(annotationType); for (Element element : Sets.union(roundElements, prevRoundElements)) { ElementName elementName = ElementName.forAnnotatedElement(element); boolean isValidElement = @@ -278,7 +300,7 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { && validateElement( element.getKind().equals(PACKAGE) ? element : getEnclosingType(element))); if (isValidElement) { - validElements.put(annotationClass, element); + validElements.put(annotationType, element); validElementNames.add(elementName); } else { deferredElementNames.add(elementName); @@ -289,15 +311,14 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { return validElements.build(); } - private ImmutableSetMultimap<Class<? extends Annotation>, Element> indexByAnnotation( - Set<ElementName> annotatedElements, - Set<? extends Class<? extends Annotation>> annotationClasses) { - ImmutableSetMultimap.Builder<Class<? extends Annotation>, Element> deferredElements = + private ImmutableSetMultimap<TypeElement, Element> indexByAnnotation( + Set<ElementName> annotatedElements, ImmutableSet<TypeElement> annotationTypes) { + ImmutableSetMultimap.Builder<TypeElement, Element> deferredElements = ImmutableSetMultimap.builder(); for (ElementName elementName : annotatedElements) { Optional<? extends Element> element = elementName.getElement(elements); if (element.isPresent()) { - findAnnotatedElements(element.get(), annotationClasses, deferredElements); + findAnnotatedElements(element.get(), annotationTypes, deferredElements); } } return deferredElements.build(); @@ -305,8 +326,8 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { /** * Adds {@code element} and its enclosed elements to {@code annotatedElements} if they are - * annotated with any annotations in {@code annotationClasses}. Does not traverse to member types - * of {@code element}, so that if {@code Outer} is passed in the example below, looking for + * annotated with any annotations in {@code annotationTypes}. Does not traverse to member types of + * {@code element}, so that if {@code Outer} is passed in the example below, looking for * {@code @X}, then {@code Outer}, {@code Outer.foo}, and {@code Outer.foo()} will be added to the * multimap, but neither {@code Inner} nor its members will. * @@ -323,27 +344,33 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { */ private static void findAnnotatedElements( Element element, - Set<? extends Class<? extends Annotation>> annotationClasses, - ImmutableSetMultimap.Builder<Class<? extends Annotation>, Element> annotatedElements) { + ImmutableSet<TypeElement> annotationTypes, + ImmutableSetMultimap.Builder<TypeElement, Element> annotatedElements) { for (Element enclosedElement : element.getEnclosedElements()) { if (!enclosedElement.getKind().isClass() && !enclosedElement.getKind().isInterface()) { - findAnnotatedElements(enclosedElement, annotationClasses, annotatedElements); + findAnnotatedElements(enclosedElement, annotationTypes, annotatedElements); } } // element.getEnclosedElements() does NOT return parameter elements if (element instanceof ExecutableElement) { for (Element parameterElement : asExecutable(element).getParameters()) { - findAnnotatedElements(parameterElement, annotationClasses, annotatedElements); + findAnnotatedElements(parameterElement, annotationTypes, annotatedElements); } } - for (Class<? extends Annotation> annotationClass : annotationClasses) { - if (isAnnotationPresent(element, annotationClass)) { - annotatedElements.put(annotationClass, element); + for (TypeElement annotationType : annotationTypes) { + if (isAnnotationPresent(element, annotationType)) { + annotatedElements.put(annotationType, element); } } } + private static boolean isAnnotationPresent(Element element, TypeElement annotationType) { + return element.getAnnotationMirrors().stream() + .anyMatch( + mirror -> MoreTypes.asTypeElement(mirror.getAnnotationType()).equals(annotationType)); + } + /** * Returns the nearest enclosing {@link TypeElement} to the current element, throwing an {@link * IllegalArgumentException} if the provided {@link Element} is a {@link PackageElement} or is @@ -371,12 +398,61 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { null); } + private static ImmutableSetMultimap<String, Element> toClassNameKeyedMultimap( + SetMultimap<TypeElement, Element> elements) { + ImmutableSetMultimap.Builder<String, Element> builder = ImmutableSetMultimap.builder(); + elements + .asMap() + .forEach( + (annotation, element) -> + builder.putAll(annotation.getQualifiedName().toString(), element)); + return builder.build(); + } + + /** + * Wraps the passed {@link ProcessingStep} in a {@link Step}. This is a convenience method to + * allow incremental migration to a String-based API. This method can be used to return a not yet + * converted {@link ProcessingStep} from {@link BasicAnnotationProcessor#steps()}. + */ + protected static Step asStep(ProcessingStep processingStep) { + return new ProcessingStepAsStep(processingStep); + } + /** * The unit of processing logic that runs under the guarantee that all elements are complete and * well-formed. A step may reject elements that are not ready for processing but may be at a later * round. */ + public interface Step { + + /** + * The set of fully-qualified annotation type names processed by this step. + * + * <p>Warning: If the returned names are not names of annotations, they'll be ignored. + */ + Set<String> annotations(); + + /** + * The implementation of processing logic for the step. It is guaranteed that the keys in {@code + * elementsByAnnotation} will be a subset of the set returned by {@link #annotations()}. + * + * @return the elements (a subset of the values of {@code elementsByAnnotation}) that this step + * is unable to process, possibly until a later processing round. These elements will be + * passed back to this step at the next round of processing. + */ + Set<? extends Element> process(ImmutableSetMultimap<String, Element> elementsByAnnotation); + } + + /** + * The unit of processing logic that runs under the guarantee that all elements are complete and + * well-formed. A step may reject elements that are not ready for processing but may be at a later + * round. + * + * @deprecated Implement {@link Step} instead. See {@link BasicAnnotationProcessor#steps()}. + */ + @Deprecated public interface ProcessingStep { + /** The set of annotation types processed by this step. */ Set<? extends Class<? extends Annotation>> annotations(); @@ -392,6 +468,46 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation); } + private static class ProcessingStepAsStep implements Step { + + private final ProcessingStep processingStep; + private final ImmutableMap<String, Class<? extends Annotation>> annotationsByName; + + ProcessingStepAsStep(ProcessingStep processingStep) { + this.processingStep = processingStep; + this.annotationsByName = + processingStep.annotations().stream() + .collect( + collectingAndThen( + toMap( + Class::getCanonicalName, (Class<? extends Annotation> aClass) -> aClass), + ImmutableMap::copyOf)); + } + + @Override + public Set<String> annotations() { + return annotationsByName.keySet(); + } + + @Override + public Set<? extends Element> process( + ImmutableSetMultimap<String, Element> elementsByAnnotation) { + return processingStep.process(toClassKeyedMultimap(elementsByAnnotation)); + } + + private ImmutableSetMultimap<Class<? extends Annotation>, Element> toClassKeyedMultimap( + SetMultimap<String, Element> elements) { + ImmutableSetMultimap.Builder<Class<? extends Annotation>, Element> builder = + ImmutableSetMultimap.builder(); + elements + .asMap() + .forEach( + (annotation, annotatedElements) -> + builder.putAll(annotationsByName.get(annotation), annotatedElements)); + return builder.build(); + } + } + /** * A package or type name. * |