[ #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
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 d3f67aa..375a4cb 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 @@
 
 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.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 @@
  * 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 @@
  * <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 @@
  * 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 @@
 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.
    */
-  protected abstract Iterable<? extends ProcessingStep> initSteps();
+  @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 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 @@
     }
   }
 
-  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 @@
   }
 
   /** 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 @@
    * 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 @@
                     && 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 @@
     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 @@
 
   /**
    * 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 @@
    */
   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 @@
         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 @@
         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 59a135c..f753450 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 @@
 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 @@
 @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 @@
             }
 
             @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 @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 @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 @@
             }
 
             @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 @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 @@
             }
 
             @Override
-            public Set<? extends Class<? extends Annotation>> annotations() {
-              return ImmutableSet.of(CauseError.class);
+            public ImmutableSet<String> annotations() {
+              return ImmutableSet.of(ENCLOSING_CLASS_NAME + ".CauseError");
             }
           });
     }
   }
 
-  @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 {}");
+  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;
+    }
+  }
+
+  @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 @@
         .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 @@
     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 @@
             "  @" + 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 @@
     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 @@
         "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 @@
         .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 {