dagger.internal.codegen.BasicProcessor.java Source code

Java tutorial

Introduction

Here is the source code for dagger.internal.codegen.BasicProcessor.java

Source

/*
 * 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 dagger.internal.codegen;

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.auto.common.BasicAnnotationProcessor;
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.
 *
 * <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}.
 *
 * Any logic that needs to happen once per round can be specified by overriding
 * {@link #postRound(RoundEnvironment)}.
 *
 * <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.
 *
 * <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
 * similarly, except that their enclosed elements are not validated. See
 *
 * <p>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.
 *
 * <h3>Each {@code ProcessingStep} can defer elements</h3>
 *
 * <p>Each {@code ProcessingStep} can defer elements by including them in the set returned by
 * 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, 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.
 *
 * <p>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.
 *
 * <p>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 BasicProcessor extends AbstractProcessor {

    private final Set<ElementName> deferredElementNames = new LinkedHashSet<>();
    private final SetMultimap<ProcessingStep, ElementName> elementsDeferredBySteps = LinkedHashMultimap.create();
    private final String processorName = getClass().getCanonicalName();

    private Elements elements;
    private Messager messager;
    private ImmutableList<? extends ProcessingStep> 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<? extends ProcessingStep> 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<? extends Class<? extends Annotation>> getSupportedAnnotationClasses() {
        checkState(steps != null);
        ImmutableSet.Builder<Class<? extends Annotation>> 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}.
     */
    @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();
    }

    @Override
    public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        checkState(elements != null);
        checkState(messager != null);
        checkState(steps != null);

        ImmutableMap<String, Optional<? extends Element>> 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<String, Optional<? extends Element>> deferredElements() {
        ImmutableMap.Builder<String, Optional<? extends Element>> deferredElements = ImmutableMap.builder();
        for (ElementName elementName : deferredElementNames) {
            deferredElements.put(elementName.name(), elementName.getElement(elements));
        }
        return deferredElements.build();
    }

    private void reportMissingElements(Map<String, ? extends Optional<? extends Element>> missingElements,
            Collection<ElementName> missingElementNames) {
        if (!missingElementNames.isEmpty()) {
            ImmutableMap.Builder<String, Optional<? extends Element>> 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<String, ? extends Optional<? extends Element>> missingElementEntry : missingElements
                .entrySet()) {
            Optional<? extends Element> 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<Class<? extends Annotation>, Element> validElements(
            ImmutableMap<String, Optional<? extends Element>> deferredElements, RoundEnvironment roundEnv) {
        ImmutableSetMultimap.Builder<Class<? extends Annotation>, Element> deferredElementsByAnnotationBuilder = ImmutableSetMultimap
                .builder();
        for (Entry<String, Optional<? extends Element>> deferredTypeElementEntry : deferredElements.entrySet()) {
            Optional<? extends Element> deferredElement = deferredTypeElementEntry.getValue();
            if (deferredElement.isPresent()) {
                findAnnotatedElements(deferredElement.get(), getSupportedAnnotationClasses(),
                        deferredElementsByAnnotationBuilder);
            } else {
                deferredElementNames.add(ElementName.forTypeName(deferredTypeElementEntry.getKey()));
            }
        }

        ImmutableSetMultimap<Class<? extends Annotation>, Element> deferredElementsByAnnotation = deferredElementsByAnnotationBuilder
                .build();

        ImmutableSetMultimap.Builder<Class<? extends Annotation>, Element> validElements = ImmutableSetMultimap
                .builder();

        Set<ElementName> validElementNames = new LinkedHashSet<ElementName>();

        // 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());
            Set<? extends Element> elementsAnnotatedWith = (annotationType == null) ? ImmutableSet.<Element>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<Class<? extends Annotation>, Element> validElements) {
        boolean rejectedAnyElements = false;
        for (ProcessingStep step : steps) {
            ImmutableSetMultimap<Class<? extends Annotation>, Element> stepElements = new ImmutableSetMultimap.Builder<Class<? extends Annotation>, Element>()
                    .putAll(indexByAnnotation(elementsDeferredBySteps.get(step)))
                    .putAll(filterKeys(validElements, Predicates.<Object>in(step.annotations()))).build();
            if (stepElements.isEmpty()) {
                elementsDeferredBySteps.removeAll(step);
            } else {
                Set<? extends Element> rejectedElements = step.process(stepElements, rejectedAnyElements);
                if (!rejectedElements.isEmpty() && !rejectedAnyElements) {
                    rejectedAnyElements = true;
                }
                elementsDeferredBySteps.replaceValues(step,
                        transform(rejectedElements, new Function<Element, ElementName>() {
                            @Override
                            public ElementName apply(Element element) {
                                return ElementName.forAnnotatedElement(element);
                            }
                        }));
            }
        }
    }

    private ImmutableSetMultimap<Class<? extends Annotation>, Element> indexByAnnotation(
            Set<ElementName> annotatedElements) {
        ImmutableSet<? extends Class<? extends Annotation>> supportedAnnotationClasses = getSupportedAnnotationClasses();
        ImmutableSetMultimap.Builder<Class<? extends Annotation>, Element> deferredElements = ImmutableSetMultimap
                .builder();
        for (ElementName elementName : annotatedElements) {
            Optional<? extends Element> 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.
     *
     * <pre><code>
     *   {@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() {}
     *     }
     *   }
     * </code></pre>
     */
    private static void findAnnotatedElements(Element element,
            ImmutableSet<? extends Class<? extends Annotation>> annotationClasses,
            ImmutableSetMultimap.Builder<Class<? extends Annotation>, 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<? extends Annotation> 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<TypeElement, Void>() {
            @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);
    }

    /**
     * A package or type name.
     *
     * <p>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. :(
     *
     * <p>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<? extends Element> 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);
        }
    }

    public interface ProcessingStep {
        Set<? extends Class<? extends Annotation>> annotations();

        Set<Element> process(SetMultimap<Class<? extends Annotation>, Element> var1, boolean rejectedAnyElements);
    }

}