diff options
Diffstat (limited to 'common')
4 files changed, 480 insertions, 211 deletions
diff --git a/common/pom.xml b/common/pom.xml index 7d1c8773..e4a23e02 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -26,7 +26,7 @@ <groupId>com.google.auto</groupId> <artifactId>auto-common</artifactId> - <version>HEAD-SNAPSHOT</version> + <version>0.11</version> <name>Auto Common Libraries</name> <description> Common utilities for creating annotation processors. @@ -36,7 +36,7 @@ <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> - <guava.version>27.0.1-jre</guava.version> + <guava.version>29.0-jre</guava.version> <truth.version>1.0.1</truth.version> </properties> @@ -75,7 +75,7 @@ If you use JavaPoet, you can use GeneratedAnnotationSpecs. --> <groupId>com.squareup</groupId> <artifactId>javapoet</artifactId> - <version>1.9.0</version> + <version>1.13.0</version> <optional>true</optional> </dependency> @@ -107,7 +107,7 @@ <dependency> <groupId>org.eclipse.jdt</groupId> <artifactId>ecj</artifactId> - <version>3.20.0</version> + <version>3.22.0</version> <scope>test</scope> </dependency> </dependencies> 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. * 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..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<? extends Element> 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<Boolean, Void> 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,7 +169,13 @@ public final class SuperficialValidation { } }; - private static boolean validateType(TypeMirror type) { + /** + * 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<? extends ExecutableElement, ? extends AnnotationValue> valueMap) { - for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> 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<Boolean, TypeMirror> 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() {} } 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<ImmutableSetMultimap<Class<? extends Annotation>, Element>> - processArguments = ImmutableList.builder(); + final ImmutableList.Builder<ImmutableSetMultimap<String, Element>> processArguments = + ImmutableList.builder(); @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); - } - - @Override - protected Iterable<? extends ProcessingStep> initSteps() { + protected Iterable<? extends Step> steps() { return ImmutableSet.of( - new ProcessingStep() { + new Step() { @Override - public Set<Element> process( - SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { + public ImmutableSet<? extends Element> process( + ImmutableSetMultimap<String, Element> elementsByAnnotation) { processArguments.add(ImmutableSetMultimap.copyOf(elementsByAnnotation)); TypeElement requiredClass = processingEnv.getElementUtils().getTypeElement("test.SomeGeneratedClass"); @@ -83,25 +96,25 @@ public class BasicAnnotationProcessorTest { } @Override - public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.of(RequiresGeneratedCode.class); + public ImmutableSet<String> annotations() { + return ImmutableSet.of(ENCLOSING_CLASS_NAME + ".RequiresGeneratedCode"); } }, - new ProcessingStep() { + new Step() { @Override - public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.of(AnAnnotation.class); + public ImmutableSet<? extends Element> process( + ImmutableSetMultimap<String, Element> elementsByAnnotation) { + return ImmutableSet.of(); } @Override - public Set<? extends Element> process( - SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { - return ImmutableSet.of(); + public ImmutableSet<String> annotations() { + return ImmutableSet.of(ENCLOSING_CLASS_NAME + ".AnAnnotation"); } }); } - ImmutableList<ImmutableSetMultimap<Class<? extends Annotation>, Element>> processArguments() { + ImmutableList<ImmutableSetMultimap<String, Element>> 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<? extends ProcessingStep> initSteps() { + protected Iterable<? extends Step> steps() { return ImmutableSet.of( - new ProcessingStep() { + new Step() { @Override - public Set<Element> process( - SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { + public ImmutableSet<? extends Element> process( + ImmutableSetMultimap<String, Element> elementsByAnnotation) { generateClass(processingEnv.getFiler(), "SomeGeneratedClass"); return ImmutableSet.of(); } @Override - public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.of(GeneratesCode.class); + public ImmutableSet<String> 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<? extends ProcessingStep> initSteps() { + protected Iterable<? extends Step> steps() { return ImmutableSet.of( - new ProcessingStep() { + new Step() { @Override - public Set<Element> process( - SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { + public ImmutableSet<Element> process( + ImmutableSetMultimap<String, Element> elementsByAnnotation) { for (Element element : elementsByAnnotation.values()) { generateClass(processingEnv.getFiler(), element.getSimpleName() + "XYZ"); } @@ -160,8 +162,8 @@ public class BasicAnnotationProcessorTest { } @Override - public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.of(AnAnnotation.class); + public ImmutableSet<String> 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<? extends ProcessingStep> initSteps() { + protected Iterable<? extends Step> steps() { return ImmutableSet.of( - new ProcessingStep() { + new Step() { @Override - public Set<Element> process( - SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { + public ImmutableSet<Element> process( + ImmutableSetMultimap<String, Element> elementsByAnnotation) { for (Element e : elementsByAnnotation.values()) { processingEnv.getMessager().printMessage(ERROR, "purposeful error", e); } @@ -191,26 +189,91 @@ public class BasicAnnotationProcessorTest { } @Override - public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.of(CauseError.class); + public ImmutableSet<String> annotations() { + return ImmutableSet.of(ENCLOSING_CLASS_NAME + ".CauseError"); + } + }); + } + } + + public static class MissingAnnotationProcessor extends BaseAnnotationProcessor { + + private ImmutableSetMultimap<String, Element> elementsByAnnotation; + + @Override + protected Iterable<? extends Step> steps() { + return ImmutableSet.of( + new Step() { + @Override + public ImmutableSet<Element> process( + ImmutableSetMultimap<String, Element> elementsByAnnotation) { + MissingAnnotationProcessor.this.elementsByAnnotation = elementsByAnnotation; + for (Element element : elementsByAnnotation.values()) { + generateClass(processingEnv.getFiler(), element.getSimpleName() + "XYZ"); + } + return ImmutableSet.of(); + } + + @Override + public ImmutableSet<String> annotations() { + return ImmutableSet.of( + "test.SomeNonExistentClass", ENCLOSING_CLASS_NAME + ".AnAnnotation"); } }); } + + ImmutableSetMultimap<String, Element> 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<Class<? extends Annotation>, Element> elementsByAnnotation; + + @Override + public ImmutableSet<? extends Class<? extends Annotation>> annotations() { + return ImmutableSet.of(AnAnnotation.class, ReferencesAClass.class); + } + + @Override + public ImmutableSet<? extends Element> process( + SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { + this.elementsByAnnotation = elementsByAnnotation; + return ImmutableSet.of(); + } + + SetMultimap<Class<? extends Annotation>, 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 { |