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