From df5641b2340c8a437ab2c1c9579b97db27bc9fac Mon Sep 17 00:00:00 2001 From: armandgray Date: Wed, 17 Jun 2020 14:41:47 -0700 Subject: [ #HiltMigration ] Updating BasicAnnotationProcessor to support a String-based API. Context: Per discussion in [] this change adds a String-based `Step` class as a replacement for `ProcessingStep`. The new `Step` contains the same two methods (annotations() & process()) which return and accept fully-qualified annotation names. To support the potential absence of the annotation classes from the processor classpath the corresponding methods were changed to no longer depend on explicit Class types. RELNOTES=Adding String-based `Step` as a replacement for `ProcessingStep` to BasicAnnotationProcessor. Allows for fully-qualified Annotation names to be specified in a processing Step in order to remove the requirement that implementation processors depend directly on their Annotation classes. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=316968142 --- .../auto/common/BasicAnnotationProcessor.java | 240 +++++++++---- .../auto/common/BasicAnnotationProcessorTest.java | 387 ++++++++++++++------- 2 files changed, 447 insertions(+), 180 deletions(-) (limited to 'common') 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. * - *

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}. + *

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}. * *

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; *

Ill-formed elements are deferred

* * 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. * *

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. * - *

Each {@code ProcessingStep} can defer elements

+ *

Each {@code Step} can defer elements

* - *

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. + *

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. * *

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 deferredElementNames = new LinkedHashSet<>(); - private final SetMultimap elementsDeferredBySteps = + private final SetMultimap elementsDeferredBySteps = LinkedHashMultimap.create(); private Elements elements; private Messager messager; - private ImmutableList steps; + private ImmutableList 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 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. + * + *

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 initSteps(); + protected Iterable 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> getSupportedAnnotationClasses() { + private ImmutableSet getSupportedAnnotationTypeElements() { checkState(steps != null); - ImmutableSet.Builder> 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 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 getSupportedAnnotationTypes() { - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (Class 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, Element> validElements) { - for (ProcessingStep step : steps) { - ImmutableSetMultimap, Element> stepElements = - new ImmutableSetMultimap.Builder, Element>() - .putAll(indexByAnnotation(elementsDeferredBySteps.get(step), step.annotations())) - .putAll(filterKeys(validElements, Predicates.in(step.annotations()))) + private void process(ImmutableSetMultimap validElements) { + for (Step step : steps) { + ImmutableSet annotationTypes = getSupportedAnnotationTypeElements(step); + ImmutableSetMultimap stepElements = + new ImmutableSetMultimap.Builder() + .putAll(indexByAnnotation(elementsDeferredBySteps.get(step), annotationTypes)) + .putAll(filterKeys(validElements, Predicates.in(annotationTypes))) .build(); if (stepElements.isEmpty()) { elementsDeferredBySteps.removeAll(step); } else { - Set rejectedElements = step.process(stepElements); + Set 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, Element> validElements( - RoundEnvironment roundEnv) { + private ImmutableSetMultimap validElements(RoundEnvironment roundEnv) { ImmutableSet prevDeferredElementNames = ImmutableSet.copyOf(deferredElementNames); deferredElementNames.clear(); - ImmutableSetMultimap.Builder, Element> - deferredElementsByAnnotationBuilder = ImmutableSetMultimap.builder(); + ImmutableSetMultimap.Builder deferredElementsByAnnotationBuilder = + ImmutableSetMultimap.builder(); for (ElementName deferredElementName : prevDeferredElementNames) { Optional deferredElement = deferredElementName.getElement(elements); if (deferredElement.isPresent()) { findAnnotatedElements( deferredElement.get(), - getSupportedAnnotationClasses(), + getSupportedAnnotationTypeElements(), deferredElementsByAnnotationBuilder); } else { deferredElementNames.add(deferredElementName); } } - ImmutableSetMultimap, Element> deferredElementsByAnnotation = + ImmutableSetMultimap deferredElementsByAnnotation = deferredElementsByAnnotationBuilder.build(); - ImmutableSetMultimap.Builder, Element> validElements = + ImmutableSetMultimap.Builder validElements = ImmutableSetMultimap.builder(); Set validElementNames = new LinkedHashSet<>(); // Look at the elements we've found and the new elements from this round and validate them. - for (Class 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 roundElements = (annotationType == null) - ? ImmutableSet.of() + ? ImmutableSet.of() : roundEnv.getElementsAnnotatedWith(annotationType); - ImmutableSet prevRoundElements = deferredElementsByAnnotation.get(annotationClass); + ImmutableSet 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, Element> indexByAnnotation( - Set annotatedElements, - Set> annotationClasses) { - ImmutableSetMultimap.Builder, Element> deferredElements = + private ImmutableSetMultimap indexByAnnotation( + Set annotatedElements, ImmutableSet annotationTypes) { + ImmutableSetMultimap.Builder deferredElements = ImmutableSetMultimap.builder(); for (ElementName elementName : annotatedElements) { Optional 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> annotationClasses, - ImmutableSetMultimap.Builder, Element> annotatedElements) { + ImmutableSet annotationTypes, + ImmutableSetMultimap.Builder 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 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 toClassNameKeyedMultimap( + SetMultimap elements) { + ImmutableSetMultimap.Builder 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. + * + *

Warning: If the returned names are not names of annotations, they'll be ignored. + */ + Set 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 process(ImmutableSetMultimap 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> annotations(); @@ -392,6 +468,46 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { SetMultimap, Element> elementsByAnnotation); } + private static class ProcessingStepAsStep implements Step { + + private final ProcessingStep processingStep; + private final ImmutableMap> annotationsByName; + + ProcessingStepAsStep(ProcessingStep processingStep) { + this.processingStep = processingStep; + this.annotationsByName = + processingStep.annotations().stream() + .collect( + collectingAndThen( + toMap( + Class::getCanonicalName, (Class aClass) -> aClass), + ImmutableMap::copyOf)); + } + + @Override + public Set annotations() { + return annotationsByName.keySet(); + } + + @Override + public Set process( + ImmutableSetMultimap elementsByAnnotation) { + return processingStep.process(toClassKeyedMultimap(elementsByAnnotation)); + } + + private ImmutableSetMultimap, Element> toClassKeyedMultimap( + SetMultimap elements) { + ImmutableSetMultimap.Builder, Element> builder = + ImmutableSetMultimap.builder(); + elements + .asMap() + .forEach( + (annotation, annotatedElements) -> + builder.putAll(annotationsByName.get(annotation), annotatedElements)); + return builder.build(); + } + } + /** * A package or type name. * diff --git a/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java b/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java index 59a135c4..f7534503 100644 --- a/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java +++ b/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java @@ -18,28 +18,35 @@ package com.google.auto.common; import static com.google.common.collect.Multimaps.transformValues; import static com.google.common.truth.Truth.assertAbout; import static com.google.common.truth.Truth.assertThat; +import static com.google.testing.compile.CompilationSubject.assertThat; +import static com.google.testing.compile.Compiler.javac; import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; import static javax.tools.Diagnostic.Kind.ERROR; import static javax.tools.StandardLocation.SOURCE_OUTPUT; +import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; +import com.google.auto.common.BasicAnnotationProcessor.Step; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.SetMultimap; import com.google.common.truth.Correspondence; +import com.google.testing.compile.Compilation; +import com.google.testing.compile.CompilationRule; import com.google.testing.compile.JavaFileObjects; import java.io.IOException; import java.io.PrintWriter; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Set; import javax.annotation.processing.Filer; 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.tools.JavaFileObject; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -47,30 +54,36 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class BasicAnnotationProcessorTest { + private abstract static class BaseAnnotationProcessor extends BasicAnnotationProcessor { + + static final String ENCLOSING_CLASS_NAME = + BasicAnnotationProcessorTest.class.getCanonicalName(); + + @Override + public final SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + } + @Retention(RetentionPolicy.SOURCE) public @interface RequiresGeneratedCode {} /** * Rejects elements unless the class generated by {@link GeneratesCode}'s processor is present. */ - private static final class RequiresGeneratedCodeProcessor extends BasicAnnotationProcessor { + private static class RequiresGeneratedCodeProcessor extends BaseAnnotationProcessor { int rejectedRounds; - final ImmutableList.Builder, Element>> - processArguments = ImmutableList.builder(); + final ImmutableList.Builder> processArguments = + ImmutableList.builder(); @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); - } - - @Override - protected Iterable initSteps() { + protected Iterable steps() { return ImmutableSet.of( - new ProcessingStep() { + new Step() { @Override - public Set process( - SetMultimap, Element> elementsByAnnotation) { + public ImmutableSet process( + ImmutableSetMultimap elementsByAnnotation) { processArguments.add(ImmutableSetMultimap.copyOf(elementsByAnnotation)); TypeElement requiredClass = processingEnv.getElementUtils().getTypeElement("test.SomeGeneratedClass"); @@ -83,25 +96,25 @@ public class BasicAnnotationProcessorTest { } @Override - public Set> annotations() { - return ImmutableSet.of(RequiresGeneratedCode.class); + public ImmutableSet annotations() { + return ImmutableSet.of(ENCLOSING_CLASS_NAME + ".RequiresGeneratedCode"); } }, - new ProcessingStep() { + new Step() { @Override - public Set> annotations() { - return ImmutableSet.of(AnAnnotation.class); + public ImmutableSet process( + ImmutableSetMultimap elementsByAnnotation) { + return ImmutableSet.of(); } @Override - public Set process( - SetMultimap, Element> elementsByAnnotation) { - return ImmutableSet.of(); + public ImmutableSet annotations() { + return ImmutableSet.of(ENCLOSING_CLASS_NAME + ".AnAnnotation"); } }); } - ImmutableList, Element>> processArguments() { + ImmutableList> processArguments() { return processArguments.build(); } } @@ -110,27 +123,21 @@ public class BasicAnnotationProcessorTest { public @interface GeneratesCode {} /** Generates a class called {@code test.SomeGeneratedClass}. */ - public class GeneratesCodeProcessor extends BasicAnnotationProcessor { - + public static class GeneratesCodeProcessor extends BaseAnnotationProcessor { @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); - } - - @Override - protected Iterable initSteps() { + protected Iterable steps() { return ImmutableSet.of( - new ProcessingStep() { + new Step() { @Override - public Set process( - SetMultimap, Element> elementsByAnnotation) { + public ImmutableSet process( + ImmutableSetMultimap elementsByAnnotation) { generateClass(processingEnv.getFiler(), "SomeGeneratedClass"); return ImmutableSet.of(); } @Override - public Set> annotations() { - return ImmutableSet.of(GeneratesCode.class); + public ImmutableSet annotations() { + return ImmutableSet.of(ENCLOSING_CLASS_NAME + ".GeneratesCode"); } }); } @@ -139,20 +146,15 @@ public class BasicAnnotationProcessorTest { public @interface AnAnnotation {} /** When annotating a type {@code Foo}, generates a class called {@code FooXYZ}. */ - public class AnAnnotationProcessor extends BasicAnnotationProcessor { + public static class AnAnnotationProcessor extends BaseAnnotationProcessor { @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); - } - - @Override - protected Iterable initSteps() { + protected Iterable steps() { return ImmutableSet.of( - new ProcessingStep() { + new Step() { @Override - public Set process( - SetMultimap, Element> elementsByAnnotation) { + public ImmutableSet process( + ImmutableSetMultimap elementsByAnnotation) { for (Element element : elementsByAnnotation.values()) { generateClass(processingEnv.getFiler(), element.getSimpleName() + "XYZ"); } @@ -160,8 +162,8 @@ public class BasicAnnotationProcessorTest { } @Override - public Set> annotations() { - return ImmutableSet.of(AnAnnotation.class); + public ImmutableSet annotations() { + return ImmutableSet.of(ENCLOSING_CLASS_NAME + ".AnAnnotation"); } }); } @@ -171,19 +173,15 @@ public class BasicAnnotationProcessorTest { public @interface CauseError {} /** Report an error for any class annotated. */ - public static class CauseErrorProcessor extends BasicAnnotationProcessor { - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); - } + public static class CauseErrorProcessor extends BaseAnnotationProcessor { @Override - protected Iterable initSteps() { + protected Iterable steps() { return ImmutableSet.of( - new ProcessingStep() { + new Step() { @Override - public Set process( - SetMultimap, Element> elementsByAnnotation) { + public ImmutableSet process( + ImmutableSetMultimap elementsByAnnotation) { for (Element e : elementsByAnnotation.values()) { processingEnv.getMessager().printMessage(ERROR, "purposeful error", e); } @@ -191,26 +189,91 @@ public class BasicAnnotationProcessorTest { } @Override - public Set> annotations() { - return ImmutableSet.of(CauseError.class); + public ImmutableSet annotations() { + return ImmutableSet.of(ENCLOSING_CLASS_NAME + ".CauseError"); + } + }); + } + } + + public static class MissingAnnotationProcessor extends BaseAnnotationProcessor { + + private ImmutableSetMultimap elementsByAnnotation; + + @Override + protected Iterable steps() { + return ImmutableSet.of( + new Step() { + @Override + public ImmutableSet process( + ImmutableSetMultimap elementsByAnnotation) { + MissingAnnotationProcessor.this.elementsByAnnotation = elementsByAnnotation; + for (Element element : elementsByAnnotation.values()) { + generateClass(processingEnv.getFiler(), element.getSimpleName() + "XYZ"); + } + return ImmutableSet.of(); + } + + @Override + public ImmutableSet annotations() { + return ImmutableSet.of( + "test.SomeNonExistentClass", ENCLOSING_CLASS_NAME + ".AnAnnotation"); } }); } + + ImmutableSetMultimap getElementsByAnnotation() { + return elementsByAnnotation; + } } - @Test public void properlyDefersProcessing_typeElement() { - JavaFileObject classAFileObject = JavaFileObjects.forSourceLines("test.ClassA", - "package test;", - "", - "@" + RequiresGeneratedCode.class.getCanonicalName(), - "public class ClassA {", - " SomeGeneratedClass sgc;", - "}"); - JavaFileObject classBFileObject = JavaFileObjects.forSourceLines("test.ClassB", - "package test;", - "", - "@" + GeneratesCode.class.getCanonicalName(), - "public class ClassB {}"); + @SuppressWarnings("deprecation") // Deprecated ProcessingStep is being explicitly tested. + static final class MultiAnnotationProcessingStep implements ProcessingStep { + + private SetMultimap, Element> elementsByAnnotation; + + @Override + public ImmutableSet> annotations() { + return ImmutableSet.of(AnAnnotation.class, ReferencesAClass.class); + } + + @Override + public ImmutableSet process( + SetMultimap, Element> elementsByAnnotation) { + this.elementsByAnnotation = elementsByAnnotation; + return ImmutableSet.of(); + } + + SetMultimap, Element> getElementsByAnnotation() { + return elementsByAnnotation; + } + } + + @Retention(RetentionPolicy.SOURCE) + public @interface ReferencesAClass { + Class value(); + } + + @Rule public CompilationRule compilation = new CompilationRule(); + + @Test + public void properlyDefersProcessing_typeElement() { + JavaFileObject classAFileObject = + JavaFileObjects.forSourceLines( + "test.ClassA", + "package test;", + "", + "@" + RequiresGeneratedCode.class.getCanonicalName(), + "public class ClassA {", + " SomeGeneratedClass sgc;", + "}"); + JavaFileObject classBFileObject = + JavaFileObjects.forSourceLines( + "test.ClassB", + "package test;", + "", + "@" + GeneratesCode.class.getCanonicalName(), + "public class ClassB {}"); RequiresGeneratedCodeProcessor requiresGeneratedCodeProcessor = new RequiresGeneratedCodeProcessor(); assertAbout(javaSources()) @@ -244,22 +307,22 @@ public class BasicAnnotationProcessorTest { .generatesFileNamed(SOURCE_OUTPUT, "test", "ValidInRound2XYZ.java"); } - @Retention(RetentionPolicy.SOURCE) - public @interface ReferencesAClass { - Class value(); - } - - @Test public void properlyDefersProcessing_packageElement() { - JavaFileObject classAFileObject = JavaFileObjects.forSourceLines("test.ClassA", - "package test;", - "", - "@" + GeneratesCode.class.getCanonicalName(), - "public class ClassA {", - "}"); - JavaFileObject packageFileObject = JavaFileObjects.forSourceLines("test.package-info", - "@" + RequiresGeneratedCode.class.getCanonicalName(), - "@" + ReferencesAClass.class.getCanonicalName() + "(SomeGeneratedClass.class)", - "package test;"); + @Test + public void properlyDefersProcessing_packageElement() { + JavaFileObject classAFileObject = + JavaFileObjects.forSourceLines( + "test.ClassA", + "package test;", + "", + "@" + GeneratesCode.class.getCanonicalName(), + "public class ClassA {", + "}"); + JavaFileObject packageFileObject = + JavaFileObjects.forSourceLines( + "test.package-info", + "@" + RequiresGeneratedCode.class.getCanonicalName(), + "@" + ReferencesAClass.class.getCanonicalName() + "(SomeGeneratedClass.class)", + "package test;"); RequiresGeneratedCodeProcessor requiresGeneratedCodeProcessor = new RequiresGeneratedCodeProcessor(); assertAbout(javaSources()) @@ -272,21 +335,28 @@ public class BasicAnnotationProcessorTest { assertThat(requiresGeneratedCodeProcessor.rejectedRounds).isEqualTo(0); } - @Test public void properlyDefersProcessing_argumentElement() { - JavaFileObject classAFileObject = JavaFileObjects.forSourceLines("test.ClassA", - "package test;", - "", - "public class ClassA {", - " SomeGeneratedClass sgc;", - " public void myMethod(@" + RequiresGeneratedCode.class.getCanonicalName() + " int myInt)", - " {}", - "}"); - JavaFileObject classBFileObject = JavaFileObjects.forSourceLines("test.ClassB", - "package test;", - "", - "public class ClassB {", - " public void myMethod(@" + GeneratesCode.class.getCanonicalName() + " int myInt) {}", - "}"); + @Test + public void properlyDefersProcessing_argumentElement() { + JavaFileObject classAFileObject = + JavaFileObjects.forSourceLines( + "test.ClassA", + "package test;", + "", + "public class ClassA {", + " SomeGeneratedClass sgc;", + " public void myMethod(@" + + RequiresGeneratedCode.class.getCanonicalName() + + " int myInt)", + " {}", + "}"); + JavaFileObject classBFileObject = + JavaFileObjects.forSourceLines( + "test.ClassB", + "package test;", + "", + "public class ClassB {", + " public void myMethod(@" + GeneratesCode.class.getCanonicalName() + " int myInt) {}", + "}"); RequiresGeneratedCodeProcessor requiresGeneratedCodeProcessor = new RequiresGeneratedCodeProcessor(); assertAbout(javaSources()) @@ -311,11 +381,13 @@ public class BasicAnnotationProcessorTest { " @" + AnAnnotation.class.getCanonicalName(), " public void method() {}", "}"); - JavaFileObject classBFileObject = JavaFileObjects.forSourceLines("test.ClassB", - "package test;", - "", - "@" + GeneratesCode.class.getCanonicalName(), - "public class ClassB {}"); + JavaFileObject classBFileObject = + JavaFileObjects.forSourceLines( + "test.ClassB", + "package test;", + "", + "@" + GeneratesCode.class.getCanonicalName(), + "public class ClassB {}"); RequiresGeneratedCodeProcessor requiresGeneratedCodeProcessor = new RequiresGeneratedCodeProcessor(); assertAbout(javaSources()) @@ -332,8 +404,8 @@ public class BasicAnnotationProcessorTest { assertThat(requiresGeneratedCodeProcessor.processArguments()) .comparingElementsUsing(setMultimapValuesByString()) .containsExactly( - ImmutableSetMultimap.of(RequiresGeneratedCode.class, "test.ClassA"), - ImmutableSetMultimap.of(RequiresGeneratedCode.class, "test.ClassA")) + ImmutableSetMultimap.of(RequiresGeneratedCode.class.getCanonicalName(), "test.ClassA"), + ImmutableSetMultimap.of(RequiresGeneratedCode.class.getCanonicalName(), "test.ClassA")) .inOrder(); } @@ -345,20 +417,60 @@ public class BasicAnnotationProcessorTest { "is equivalent comparing multimap values by `toString()` to"); } - @Test public void reportsMissingType() { - JavaFileObject classAFileObject = JavaFileObjects.forSourceLines("test.ClassA", - "package test;", - "", - "@" + RequiresGeneratedCode.class.getCanonicalName(), - "public class ClassA {", - " SomeGeneratedClass bar;", - "}"); + @Test + public void properlySkipsMissingAnnotations_generatesClass() { + JavaFileObject source = + JavaFileObjects.forSourceLines( + "test.ValidInRound2", + "package test;", + "", + "@" + AnAnnotation.class.getCanonicalName(), + "public class ValidInRound2 {", + " ValidInRound1XYZ vir1xyz;", + " @" + AnAnnotation.class.getCanonicalName(), + " static class ValidInRound1 {}", + "}"); + Compilation compilation = + javac().withProcessors(new MissingAnnotationProcessor()).compile(source); + assertThat(compilation).succeeded(); + assertThat(compilation).generatedSourceFile("test.ValidInRound2XYZ"); + } + + @Test + public void properlySkipsMissingAnnotations_passesValidAnnotationsToProcess() { + JavaFileObject source = + JavaFileObjects.forSourceLines( + "test.ClassA", + "package test;", + "", + "@" + AnAnnotation.class.getCanonicalName(), + "public class ClassA {", + "}"); + MissingAnnotationProcessor missingAnnotationProcessor = new MissingAnnotationProcessor(); + assertThat(javac().withProcessors(missingAnnotationProcessor).compile(source)).succeeded(); + assertThat(missingAnnotationProcessor.getElementsByAnnotation().keySet()) + .containsExactly(AnAnnotation.class.getCanonicalName()); + assertThat(missingAnnotationProcessor.getElementsByAnnotation().values()).hasSize(1); + } + + @Test + public void reportsMissingType() { + JavaFileObject classAFileObject = + JavaFileObjects.forSourceLines( + "test.ClassA", + "package test;", + "", + "@" + RequiresGeneratedCode.class.getCanonicalName(), + "public class ClassA {", + " SomeGeneratedClass bar;", + "}"); assertAbout(javaSources()) .that(ImmutableList.of(classAFileObject)) .processedWith(new RequiresGeneratedCodeProcessor()) .failsToCompile() .withErrorContaining(RequiresGeneratedCodeProcessor.class.getCanonicalName()) - .in(classAFileObject).onLine(4); + .in(classAFileObject) + .onLine(4); } @Test @@ -378,6 +490,45 @@ public class BasicAnnotationProcessorTest { .withErrorContaining("purposeful"); } + @Test + public void processingStepAsStepAnnotationsNamesMatchClasses() { + Step step = BasicAnnotationProcessor.asStep(new MultiAnnotationProcessingStep()); + + assertThat(step.annotations()) + .containsExactly( + AnAnnotation.class.getCanonicalName(), ReferencesAClass.class.getCanonicalName()); + } + + /** + * Tests that a {@link ProcessingStep} passed to {@link + * BasicAnnotationProcessor#asStep(ProcessingStep)} still gets passed the correct arguments to + * {@link Step#process(ImmutableSetMultimap)}. + */ + @Test + public void processingStepAsStepProcessElementsMatchClasses() { + Elements elements = compilation.getElements(); + String anAnnotationName = AnAnnotation.class.getCanonicalName(); + String referencesAClassName = ReferencesAClass.class.getCanonicalName(); + TypeElement anAnnotationElement = elements.getTypeElement(anAnnotationName); + TypeElement referencesAClassElement = elements.getTypeElement(referencesAClassName); + MultiAnnotationProcessingStep processingStep = new MultiAnnotationProcessingStep(); + + BasicAnnotationProcessor.asStep(processingStep) + .process( + ImmutableSetMultimap.of( + anAnnotationName, + anAnnotationElement, + referencesAClassName, + referencesAClassElement)); + + assertThat(processingStep.getElementsByAnnotation()) + .containsExactly( + AnAnnotation.class, + anAnnotationElement, + ReferencesAClass.class, + referencesAClassElement); + } + private static void generateClass(Filer filer, String generatedClassName) { PrintWriter writer = null; try { -- cgit v1.2.3 From 6c39b13727ce18e218c2b0ca47d6eb47b62164ab Mon Sep 17 00:00:00 2001 From: bcorso Date: Mon, 27 Jul 2020 13:13:45 -0700 Subject: Open SuperficialValidation#validateType(TypeMirror) to public visibility so it can be used from other internal code. RELNOTES=Open SuperficialValidation#validateType(TypeMirror) to public visibility ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=323425711 --- common/src/main/java/com/google/auto/common/SuperficialValidation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'common') diff --git a/common/src/main/java/com/google/auto/common/SuperficialValidation.java b/common/src/main/java/com/google/auto/common/SuperficialValidation.java index dddb1be9..83870754 100644 --- a/common/src/main/java/com/google/auto/common/SuperficialValidation.java +++ b/common/src/main/java/com/google/auto/common/SuperficialValidation.java @@ -163,7 +163,7 @@ public final class SuperficialValidation { } }; - private static boolean validateType(TypeMirror type) { + public static boolean validateType(TypeMirror type) { return type.accept(TYPE_VALIDATING_VISITOR, null); } -- cgit v1.2.3 From 5384b30905c47991eed5e7d6345cfd29343209b3 Mon Sep 17 00:00:00 2001 From: emcmanus Date: Tue, 28 Jul 2020 16:55:12 -0700 Subject: Add javadoc to the methods in SuperficialValidation. Also add a private constructor, and rewrite some code to use streams. RELNOTES=Add javadoc for SuperficialValidation methods. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=323682147 --- .../google/auto/common/SuperficialValidation.java | 54 +++++++++++----------- 1 file changed, 28 insertions(+), 26 deletions(-) (limited to 'common') diff --git a/common/src/main/java/com/google/auto/common/SuperficialValidation.java b/common/src/main/java/com/google/auto/common/SuperficialValidation.java index 83870754..5ef4dbf2 100644 --- a/common/src/main/java/com/google/auto/common/SuperficialValidation.java +++ b/common/src/main/java/com/google/auto/common/SuperficialValidation.java @@ -17,6 +17,7 @@ package com.google.auto.common; import java.util.List; import java.util.Map; +import java.util.stream.StreamSupport; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.AnnotationValueVisitor; @@ -46,13 +47,12 @@ import javax.lang.model.util.SimpleTypeVisitor8; * @author Gregory Kick */ public final class SuperficialValidation { + /** + * Returns true if all of the given elements return true from {@link #validateElement(Element)}. + */ public static boolean validateElements(Iterable elements) { - for (Element element : elements) { - if (!validateElement(element)) { - return false; - } - } - return true; + return StreamSupport.stream(elements.spliterator(), false) + .allMatch(SuperficialValidation::validateElement); } private static final ElementVisitor ELEMENT_VALIDATING_VISITOR = @@ -94,6 +94,12 @@ public final class SuperficialValidation { } }; + /** + * Returns true if all types referenced by the given element are defined. The exact meaning of + * this depends on the kind of element. For packages, it means that all annotations on the package + * are fully defined. For other element kinds, it means that types referenced by the element, + * anything it contains, and any of its annotations element are all defined. + */ public static boolean validateElement(Element element) { return element.accept(ELEMENT_VALIDATING_VISITOR, null); } @@ -163,6 +169,12 @@ public final class SuperficialValidation { } }; + /** + * Returns true if the given type is fully defined. This means that the type itself is defined, as + * are any types it references, such as any type arguments or type bounds. For an {@link + * ExecutableType}, the parameter and return types must be fully defined, as must types declared + * in a {@code throws} clause or in the bounds of any type parameters. + */ public static boolean validateType(TypeMirror type) { return type.accept(TYPE_VALIDATING_VISITOR, null); } @@ -182,17 +194,14 @@ public final class SuperficialValidation { && validateAnnotationValues(annotationMirror.getElementValues()); } - @SuppressWarnings("unused") private static boolean validateAnnotationValues( Map valueMap) { - for (Map.Entry valueEntry : - valueMap.entrySet()) { - TypeMirror expectedType = valueEntry.getKey().getReturnType(); - if (!validateAnnotationValue(valueEntry.getValue(), expectedType)) { - return false; - } - } - return true; + return valueMap.entrySet().stream() + .allMatch( + valueEntry -> { + TypeMirror expectedType = valueEntry.getKey().getReturnType(); + return validateAnnotationValue(valueEntry.getValue(), expectedType); + }); } private static final AnnotationValueVisitor VALUE_VALIDATING_VISITOR = @@ -216,17 +225,8 @@ public final class SuperficialValidation { if (!expectedType.getKind().equals(TypeKind.ARRAY)) { return false; } - try { - expectedType = MoreTypes.asArray(expectedType).getComponentType(); - } catch (IllegalArgumentException e) { - return false; // Not an array expected, ergo invalid. - } - for (AnnotationValue value : values) { - if (!value.accept(this, expectedType)) { - return false; - } - } - return true; + TypeMirror componentType = MoreTypes.asArray(expectedType).getComponentType(); + return values.stream().allMatch(value -> value.accept(this, componentType)); } @Override @@ -280,4 +280,6 @@ public final class SuperficialValidation { AnnotationValue annotationValue, TypeMirror expectedType) { return annotationValue.accept(VALUE_VALIDATING_VISITOR, expectedType); } + + private SuperficialValidation() {} } -- cgit v1.2.3 From 9f870ccfcbc22bf8c64084011f7d2d7992bdf06f Mon Sep 17 00:00:00 2001 From: dpb Date: Thu, 6 Aug 2020 13:01:25 -0700 Subject: Update dependency versions. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=325291991 --- common/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'common') diff --git a/common/pom.xml b/common/pom.xml index 7d1c8773..49295b57 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -36,7 +36,7 @@ UTF-8 1.8 - 27.0.1-jre + 29.0-jre 1.0.1 @@ -75,7 +75,7 @@ If you use JavaPoet, you can use GeneratedAnnotationSpecs. --> com.squareup javapoet - 1.9.0 + 1.13.0 true @@ -107,7 +107,7 @@ org.eclipse.jdt ecj - 3.20.0 + 3.22.0 test -- cgit v1.2.3 From 123db863068570167ecf3ca7b64cf21c46e9664d Mon Sep 17 00:00:00 2001 From: Ron Shapiro Date: Tue, 11 Aug 2020 18:55:59 +0300 Subject: Set version number for auto-common to 0.11. --- common/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'common') diff --git a/common/pom.xml b/common/pom.xml index 49295b57..e4a23e02 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -26,7 +26,7 @@ com.google.auto auto-common - HEAD-SNAPSHOT + 0.11 Auto Common Libraries Common utilities for creating annotation processors. -- cgit v1.2.3