All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.auto.common.BasicAnnotationProcessor Maven / Gradle / Ivy

/*
 * Copyright (C) 2014 Google, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.auto.common;

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 javax.lang.model.element.ElementKind.PACKAGE;
import static javax.tools.Diagnostic.Kind.ERROR;

import com.google.common.base.Ascii;
import com.google.common.base.Function;
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.LinkedHashMultimap;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.ErrorType;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleElementVisitor6;

/**
 * An abstract {@link Processor} implementation that defers processing of {@link Element}s to later
 * rounds if they cannot be processed.
 *
 * 

Subclasses put their processing logic in {@link ProcessingStep} implementations. The * steps are passed to the processor by returning them in the {@link #initSteps()} method, and can * access the {@link ProcessingEnvironment} using {@link #processingEnv}. * * Any logic that needs to happen once per round can be specified by overriding * {@link #postRound(RoundEnvironment)}. * *

Ill-formed elements are deferred

* Any annotated element whose nearest enclosing type is not well-formed is deferred, and not passed * to any {@code ProcessingStep}. This helps processors to avoid many common pitfalls, such as * {@link ErrorType} instances, {@link ClassCastException}s and badly coerced types. * *

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 * similarly, except that their enclosed elements are not validated. See * {@link SuperficialValidation#validateElement(Element)} for details. * *

The primary disadvantage to this validation is that any element that forms a circular * dependency with a type generated by another {@code BasicAnnotationProcessor} will never compile * because the element will never be fully complete. All such compilations will fail with an error * message on the offending type that describes the issue. * *

Each {@code ProcessingStep} can defer elements

* *

Each {@code 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. * *

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, if an element {@code A} cannot be processed because processing it depends on the * existence of some class {@code B}, then {@code A} should be deferred until a later round of * processing, when {@code B} will have been generated by another processor. * *

If {@code A} directly references {@code B}, then the well-formedness check will correctly * defer processing of {@code A} until {@code B} has been generated. * *

However, if {@code A} references {@code B} only indirectly (for example, from within a method * body), then the well-formedness check will not defer processing {@code A}, but a processing step * can reject {@code A}. */ public abstract class BasicAnnotationProcessor extends AbstractProcessor { private final Set deferredElementNames = new LinkedHashSet(); private final SetMultimap elementsDeferredBySteps = LinkedHashMultimap.create(); private final String processorName = getClass().getCanonicalName(); private Elements elements; private Messager messager; private ImmutableList steps; @Override public final synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.elements = processingEnv.getElementUtils(); this.messager = processingEnv.getMessager(); this.steps = ImmutableList.copyOf(initSteps()); } /** * Creates {@linkplain ProcessingStep processing steps} for this processor. * {@link #processingEnv} is guaranteed to be set when this method is invoked. */ protected abstract Iterable initSteps(); /** * An optional hook for logic to be executed at the end of each round. * * @deprecated use {@link #postRound(RoundEnvironment)} instead */ @Deprecated protected void postProcess() {} /** An optional hook for logic to be executed at the end of each round. */ protected void postRound(RoundEnvironment roundEnv) { if (!roundEnv.processingOver()) { postProcess(); } } private ImmutableSet> getSupportedAnnotationClasses() { checkState(steps != null); ImmutableSet.Builder> builder = ImmutableSet.builder(); for (ProcessingStep step : steps) { builder.addAll(step.annotations()); } return builder.build(); } /** * Returns the set of supported annotation types as a collected from registered * {@linkplain ProcessingStep processing steps}. */ @Override public final ImmutableSet getSupportedAnnotationTypes() { ImmutableSet.Builder builder = ImmutableSet.builder(); for (Class annotationClass : getSupportedAnnotationClasses()) { builder.add(annotationClass.getCanonicalName()); } return builder.build(); } @Override public final boolean process(Set annotations, RoundEnvironment roundEnv) { checkState(elements != null); checkState(messager != null); checkState(steps != null); ImmutableMap> deferredElements = deferredElements(); deferredElementNames.clear(); // If this is the last round, report all of the missing elements if (roundEnv.processingOver()) { postRound(roundEnv); reportMissingElements(deferredElements, elementsDeferredBySteps.values()); return false; } process(validElements(deferredElements, roundEnv)); postRound(roundEnv); return false; } /** * Returns the previously deferred elements. */ private ImmutableMap> deferredElements() { ImmutableMap.Builder> deferredElements = ImmutableMap.builder(); for (ElementName elementName : deferredElementNames) { deferredElements.put(elementName.name(), elementName.getElement(elements)); } return deferredElements.build(); } private void reportMissingElements( Map> missingElements, Collection missingElementNames) { if (!missingElementNames.isEmpty()) { ImmutableMap.Builder> allMissingElements = ImmutableMap.builder(); allMissingElements.putAll(missingElements); for (ElementName missingElement : missingElementNames) { if (!missingElements.containsKey(missingElement.name())) { allMissingElements.put(missingElement.name(), missingElement.getElement(elements)); } } missingElements = allMissingElements.build(); } for (Entry> missingElementEntry : missingElements.entrySet()) { Optional missingElement = missingElementEntry.getValue(); if (missingElement.isPresent()) { processingEnv .getMessager() .printMessage( ERROR, processingErrorMessage( "this " + Ascii.toLowerCase(missingElement.get().getKind().name())), missingElement.get()); } else { processingEnv .getMessager() .printMessage(ERROR, processingErrorMessage(missingElementEntry.getKey())); } } } private String processingErrorMessage(String target) { return String.format( "%s was unable to process %s because not all of its dependencies could be resolved. Check " + "for compilation errors or a circular dependency with generated code.", processorName, target); } /** * Returns the valid annotated elements contained in all of the deferred elements. If none are * found for a deferred element, defers it again. */ private ImmutableSetMultimap, Element> validElements( ImmutableMap> deferredElements, RoundEnvironment roundEnv) { ImmutableSetMultimap.Builder, Element> deferredElementsByAnnotationBuilder = ImmutableSetMultimap.builder(); for (Entry> deferredTypeElementEntry : deferredElements.entrySet()) { Optional deferredElement = deferredTypeElementEntry.getValue(); if (deferredElement.isPresent()) { findAnnotatedElements( deferredElement.get(), getSupportedAnnotationClasses(), deferredElementsByAnnotationBuilder); } else { deferredElementNames.add(ElementName.forTypeName(deferredTypeElementEntry.getKey())); } } ImmutableSetMultimap, Element> deferredElementsByAnnotation = deferredElementsByAnnotationBuilder.build(); ImmutableSetMultimap.Builder, Element> validElements = ImmutableSetMultimap.builder(); Set validElementNames = new LinkedHashSet(); // Look at the elements we've found and the new elements from this round and validate them. for (Class annotationClass : getSupportedAnnotationClasses()) { // This should just call roundEnv.getElementsAnnotatedWith(Class) directly, but there is a bug // in some versions of eclipse that cause that method to crash. TypeElement annotationType = elements.getTypeElement(annotationClass.getCanonicalName()); Set elementsAnnotatedWith = (annotationType == null) ? ImmutableSet.of() : roundEnv.getElementsAnnotatedWith(annotationType); for (Element annotatedElement : Sets.union(elementsAnnotatedWith, deferredElementsByAnnotation.get(annotationClass))) { if (annotatedElement.getKind().equals(PACKAGE)) { PackageElement annotatedPackageElement = (PackageElement) annotatedElement; ElementName annotatedPackageName = ElementName.forPackageName(annotatedPackageElement.getQualifiedName().toString()); boolean validPackage = validElementNames.contains(annotatedPackageName) || (!deferredElementNames.contains(annotatedPackageName) && validateElement(annotatedPackageElement)); if (validPackage) { validElements.put(annotationClass, annotatedPackageElement); validElementNames.add(annotatedPackageName); } else { deferredElementNames.add(annotatedPackageName); } } else { TypeElement enclosingType = getEnclosingType(annotatedElement); ElementName enclosingTypeName = ElementName.forTypeName(enclosingType.getQualifiedName().toString()); boolean validEnclosingType = validElementNames.contains(enclosingTypeName) || (!deferredElementNames.contains(enclosingTypeName) && validateElement(enclosingType)); if (validEnclosingType) { validElements.put(annotationClass, annotatedElement); validElementNames.add(enclosingTypeName); } else { deferredElementNames.add(enclosingTypeName); } } } } return validElements.build(); } /** Processes the valid elements, including those previously deferred by each step. */ private void process(ImmutableSetMultimap, Element> validElements) { for (ProcessingStep step : steps) { ImmutableSetMultimap, Element> stepElements = new ImmutableSetMultimap.Builder, Element>() .putAll(indexByAnnotation(elementsDeferredBySteps.get(step))) .putAll(filterKeys(validElements, Predicates.in(step.annotations()))) .build(); if (stepElements.isEmpty()) { elementsDeferredBySteps.removeAll(step); } else { Set rejectedElements = step.process(stepElements); elementsDeferredBySteps.replaceValues( step, transform( rejectedElements, new Function() { @Override public ElementName apply(Element element) { return ElementName.forAnnotatedElement(element); } })); } } } private ImmutableSetMultimap, Element> indexByAnnotation( Set annotatedElements) { ImmutableSet> supportedAnnotationClasses = getSupportedAnnotationClasses(); ImmutableSetMultimap.Builder, Element> deferredElements = ImmutableSetMultimap.builder(); for (ElementName elementName : annotatedElements) { Optional element = elementName.getElement(elements); if (element.isPresent()) { findAnnotatedElements(element.get(), supportedAnnotationClasses, deferredElements); } } return deferredElements.build(); } /** * 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 * {@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. * *

   *   {@literal @}X class Outer {
   *     {@literal @}X Object foo;
   *     {@literal @}X void foo() {}
   *     {@literal @}X static class Inner {
   *       {@literal @}X Object bar;
   *       {@literal @}X void bar() {}
   *     }
   *   }
   * 
*/ private static void findAnnotatedElements( Element element, ImmutableSet> annotationClasses, ImmutableSetMultimap.Builder, Element> annotatedElements) { for (Element enclosedElement : element.getEnclosedElements()) { if (!enclosedElement.getKind().isClass() && !enclosedElement.getKind().isInterface()) { findAnnotatedElements(enclosedElement, annotationClasses, annotatedElements); } } // element.getEnclosedElements() does NOT return parameter elements if (element instanceof ExecutableElement) { for (Element parameterElement : ((ExecutableElement) element).getParameters()) { findAnnotatedElements(parameterElement, annotationClasses, annotatedElements); } } for (Class annotationClass : annotationClasses) { if (isAnnotationPresent(element, annotationClass)) { annotatedElements.put(annotationClass, element); } } } /** * 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 otherwise not enclosed by a type. */ // TODO(cgruber) move to MoreElements and make public. private static TypeElement getEnclosingType(Element element) { return element.accept(new SimpleElementVisitor6() { @Override protected TypeElement defaultAction(Element e, Void p) { return e.getEnclosingElement().accept(this, p); } @Override public TypeElement visitType(TypeElement e, Void p) { return e; } @Override public TypeElement visitPackage(PackageElement e, Void p) { throw new IllegalArgumentException(); } }, null); } /** * 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 ProcessingStep { /** The set of annotation types processed by this step. */ Set> annotations(); /** * The implementation of processing logic for the step. It is guaranteed that the keys in * {@code elementsByAnnotation} will be a subset of the set returned by {@link #annotations()}. * * @return the elements that this step is unable to process, possibly until a later processing * round. These elements will be passed back to this step at the next round of processing. */ Set process(SetMultimap, Element> elementsByAnnotation); } /** * A package or type name. * *

It's unfortunate that we have to track types and packages separately, but since there are * two different methods to look them up in {@link Elements}, we end up with a lot of parallel * logic. :( * *

Packages declared (and annotated) in {@code package-info.java} are tracked as deferred * packages, type elements are tracked directly, and all other elements are tracked via their * nearest enclosing type. */ private static final class ElementName { private enum Kind { PACKAGE_NAME, TYPE_NAME, } private final Kind kind; private final String name; private ElementName(Kind kind, String name) { this.kind = checkNotNull(kind); this.name = checkNotNull(name); } /** * An {@link ElementName} for a package. */ static ElementName forPackageName(String packageName) { return new ElementName(Kind.PACKAGE_NAME, packageName); } /** * An {@link ElementName} for a type. */ static ElementName forTypeName(String typeName) { return new ElementName(Kind.TYPE_NAME, typeName); } /** * An {@link ElementName} for an annotated element. If {@code element} is a package, uses the * fully qualified name of the package. If it's a type, uses its fully qualified name. * Otherwise, uses the fully-qualified name of the nearest enclosing type. */ static ElementName forAnnotatedElement(Element element) { return element.getKind() == PACKAGE ? ElementName.forPackageName(((PackageElement) element).getQualifiedName().toString()) : ElementName.forTypeName(getEnclosingType(element).getQualifiedName().toString()); } /** * The fully-qualified name of the element. */ String name() { return name; } /** * The {@link Element} whose fully-qualified name is {@link #name()}. Absent if the relevant * method on {@link Elements} returns {@code null}. */ Optional getElement(Elements elements) { return Optional.fromNullable( kind == Kind.PACKAGE_NAME ? elements.getPackageElement(name) : elements.getTypeElement(name)); } @Override public boolean equals(Object object) { if (!(object instanceof ElementName)) { return false; } ElementName that = (ElementName) object; return this.kind == that.kind && this.name.equals(that.name); } @Override public int hashCode() { return Objects.hash(kind, name); } } }