aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorarmandgray <armandgray@google.com>2020-06-17 14:41:47 -0700
committerNick <565601+nick-someone@users.noreply.github.com>2020-06-18 10:23:16 -0400
commitdf5641b2340c8a437ab2c1c9579b97db27bc9fac (patch)
treefb8236d80dd9f5112e2c646c97d78a47b9b2ce5b
parente62e0abd2fbdfd2512c292ef95d4d152a5ca0691 (diff)
downloadauto-df5641b2340c8a437ab2c1c9579b97db27bc9fac.tar.gz
[ #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
-rw-r--r--common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java240
-rw-r--r--common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java387
2 files changed, 447 insertions, 180 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.
*
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 {