com.google.auto.value.processor.AutoValueProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.google.auto.value.processor.AutoValueProcessor.java

Source

/*
 * Copyright (C) 2012 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.value.processor;

import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
import static com.google.auto.common.MoreElements.getAnnotationMirror;
import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
import static com.google.auto.common.MoreElements.isAnnotationPresent;
import static com.google.common.collect.Sets.union;

import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.service.AutoService;
import com.google.auto.value.AutoValue;
import com.google.auto.value.extension.AutoValueExtension;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableBiMap;
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.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.beans.Introspector;
import java.io.IOException;
import java.io.Serializable;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.regex.Pattern;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleAnnotationValueVisitor6;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

/**
 * Javac annotation processor (compiler plugin) for value types; user code never references this
 * class.
 *
 * @see AutoValue
 * @see <a href="https://github.com/google/auto/tree/master/value">AutoValue User's Guide</a>
 * @author amonn McManus
 */
@AutoService(Processor.class)
public class AutoValueProcessor extends AbstractProcessor {
    public AutoValueProcessor() {
        this(AutoValueProcessor.class.getClassLoader());
    }

    @VisibleForTesting
    AutoValueProcessor(ClassLoader loaderForExtensions) {
        this.extensions = null;
        this.loaderForExtensions = loaderForExtensions;
    }

    @VisibleForTesting
    public AutoValueProcessor(Iterable<? extends AutoValueExtension> extensions) {
        this.extensions = ImmutableList.<AutoValueExtension>copyOf(extensions);
        this.loaderForExtensions = null;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return ImmutableSet.of(AutoValue.class.getName());
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * Used to test whether a fully-qualified name is AutoValue.class.getCanonicalName() or one of its
     * nested annotations.
     */
    private static final Pattern AUTO_VALUE_CLASSNAME_PATTERN = Pattern
            .compile(Pattern.quote(AutoValue.class.getCanonicalName()) + "(\\..*)?");

    private ErrorReporter errorReporter;

    /**
     * Qualified names of {@code @AutoValue} classes that we attempted to process but had to abandon
     * because we needed other types that they referenced and those other types were missing.
     */
    private final List<String> deferredTypeNames = new ArrayList<String>();

    // Depending on how this AutoValueProcessor was constructed, we might already have a list of
    // extensions when init() is run, or, if `extensions` is null, we have a ClassLoader that will be
    // used to get the list using the ServiceLoader API.
    private ImmutableList<AutoValueExtension> extensions;
    private final ClassLoader loaderForExtensions;

    private Types typeUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        errorReporter = new ErrorReporter(processingEnv);
        typeUtils = processingEnv.getTypeUtils();

        if (extensions == null) {
            try {
                extensions = ImmutableList
                        .copyOf(ServiceLoader.load(AutoValueExtension.class, loaderForExtensions));
                // ServiceLoader.load returns a lazily-evaluated Iterable, so evaluate it eagerly now
                // to discover any exceptions.
            } catch (Throwable t) {
                StringBuilder warning = new StringBuilder();
                warning.append("An exception occurred while looking for AutoValue extensions. "
                        + "No extensions will function.");
                if (t instanceof ServiceConfigurationError) {
                    warning.append(" This may be due to a corrupt jar file in the compiler's classpath.");
                }
                warning.append(" Exception: ").append(t);
                errorReporter.reportWarning(warning.toString(), null);
                extensions = ImmutableList.of();
            }
        }
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        List<TypeElement> deferredTypes = new ArrayList<TypeElement>();
        for (String deferred : deferredTypeNames) {
            deferredTypes.add(processingEnv.getElementUtils().getTypeElement(deferred));
        }
        if (roundEnv.processingOver()) {
            // This means that the previous round didn't generate any new sources, so we can't have found
            // any new instances of @AutoValue; and we can't have any new types that are the reason a type
            // was in deferredTypes.
            for (TypeElement type : deferredTypes) {
                errorReporter.reportError("Did not generate @AutoValue class for " + type.getQualifiedName()
                        + " because it references undefined types", type);
            }
            return false;
        }
        Collection<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(AutoValue.class);
        List<TypeElement> types = new ImmutableList.Builder<TypeElement>().addAll(deferredTypes)
                .addAll(ElementFilter.typesIn(annotatedElements)).build();
        deferredTypeNames.clear();
        for (TypeElement type : types) {
            try {
                processType(type);
            } catch (AbortProcessingException e) {
                // We abandoned this type; continue with the next.
            } catch (MissingTypeException e) {
                // We abandoned this type, but only because we needed another type that it references and
                // that other type was missing. It is possible that the missing type will be generated by
                // further annotation processing, so we will try again on the next round (perhaps failing
                // again and adding it back to the list). We save the name of the @AutoValue type rather
                // than its TypeElement because it is not guaranteed that it will be represented by
                // the same TypeElement on the next round.
                deferredTypeNames.add(type.getQualifiedName().toString());
            } catch (RuntimeException e) {
                // Don't propagate this exception, which will confusingly crash the compiler.
                // Instead, report a compiler error with the stack trace.
                String trace = Throwables.getStackTraceAsString(e);
                errorReporter.reportError("@AutoValue processor threw an exception: " + trace, type);
            }
        }
        return false; // never claim annotation, because who knows what other processors want?
    }

    private String generatedClassName(TypeElement type, String prefix) {
        String name = type.getSimpleName().toString();
        while (type.getEnclosingElement() instanceof TypeElement) {
            type = (TypeElement) type.getEnclosingElement();
            name = type.getSimpleName() + "_" + name;
        }
        String pkg = TypeSimplifier.packageNameOf(type);
        String dot = pkg.isEmpty() ? "" : ".";
        return pkg + dot + prefix + name;
    }

    private String generatedSubclassName(TypeElement type, int depth) {
        return generatedClassName(type, Strings.repeat("$", depth) + "AutoValue_");
    }

    /**
     * A property of an {@code @AutoValue} class, defined by one of its abstract methods.
     * An instance of this class is made available to the Velocity template engine for
     * each property. The public methods of this class define JavaBeans-style properties
     * that are accessible from templates. For example {@link #getType()} means we can
     * write {@code $p.type} for a Velocity variable {@code $p} that is a {@code Property}.
     */
    public static class Property {
        private final String name;
        private final String identifier;
        private final ExecutableElement method;
        private final String type;
        private final ImmutableList<String> annotations;
        private final Optionalish optional;

        Property(String name, String identifier, ExecutableElement method, String type,
                TypeSimplifier typeSimplifier, ImmutableSet<String> excludedAnnotations) {
            this.name = name;
            this.identifier = identifier;
            this.method = method;
            this.type = type;
            this.annotations = buildAnnotations(typeSimplifier, excludedAnnotations);
            TypeMirror propertyType = method.getReturnType();
            this.optional = Optionalish.createIfOptional(propertyType, typeSimplifier.simplifyRaw(propertyType));
        }

        private ImmutableList<String> buildAnnotations(TypeSimplifier typeSimplifier,
                ImmutableSet<String> excludedAnnotations) {
            ImmutableList.Builder<String> builder = ImmutableList.builder();

            builder.addAll(copyAnnotations(method, typeSimplifier, excludedAnnotations));

            for (AnnotationMirror annotationMirror : Java8Support.getAnnotationMirrors(method.getReturnType())) {
                AnnotationOutput annotationOutput = new AnnotationOutput(typeSimplifier);
                builder.add(annotationOutput.sourceFormForAnnotation(annotationMirror));
            }
            return builder.build();
        }

        /**
         * Returns the name of the property as it should be used when declaring identifiers (fields and
         * parameters). If the original getter method was {@code foo()} then this will be {@code foo}.
         * If it was {@code getFoo()} then it will be {@code foo}. If it was {@code getPackage()} then
         * it will be something like {@code package0}, since {@code package} is a reserved word.
         */
        @Override
        public String toString() {
            return identifier;
        }

        /**
         * Returns the name of the property as it should be used in strings visible to users. This is
         * usually the same as {@code toString()}, except that if we had to use an identifier like
         * "package0" because "package" is a reserved word, the name here will be the original
         * "package".
         */
        public String getName() {
            return name;
        }

        /**
         * Returns the name of the getter method for this property as defined by the {@code @AutoValue}
         * class. For property {@code foo}, this will be {@code foo} or {@code getFoo} or {@code isFoo}.
         */
        public String getGetter() {
            return method.getSimpleName().toString();
        }

        TypeElement getOwner() {
            return (TypeElement) method.getEnclosingElement();
        }

        public TypeMirror getTypeMirror() {
            return method.getReturnType();
        }

        public String getType() {
            return type;
        }

        public TypeKind getKind() {
            return method.getReturnType().getKind();
        }

        public List<String> getAnnotations() {
            return annotations;
        }

        /**
         * Returns an {@link Optionalish} representing the kind of Optional that this property's type
         * is, or null if the type is not an Optional of any kind.
         */
        public Optionalish getOptional() {
            return optional;
        }

        /**
         * Returns the string to use as an annotation to indicate the nullability of this property.
         * It is either the empty string, if the property is not nullable, or an annotation string
         * with a trailing space, such as {@code "@Nullable "} or {@code "@javax.annotation.Nullable "}.
         */
        public String getNullableAnnotation() {
            for (String annotationString : annotations) {
                if (annotationString.equals("@Nullable") || annotationString.endsWith(".Nullable")) {
                    return annotationString + " ";
                }
            }
            return "";
        }

        public boolean isNullable() {
            return !getNullableAnnotation().isEmpty();
        }

        public String getAccess() {
            return access(method);
        }

        @Override
        public boolean equals(Object obj) {
            return obj instanceof Property && ((Property) obj).method.equals(method);
        }

        @Override
        public int hashCode() {
            return method.hashCode();
        }
    }

    /**
     * A basic method on an @AutoValue class with no specific attached information, such as a {@code
     * toBuilder()} method, or a {@code build()} method, where only the name and access type is needed
     * in context.
     *
     * <p>It implements JavaBean-style getters akin to {@link Property}.
     */
    public static class SimpleMethod {
        private final String access;
        private final String name;

        SimpleMethod(ExecutableElement method) {
            this.access = access(method);
            this.name = method.getSimpleName().toString();
        }

        public String getAccess() {
            return access;
        }

        public String getName() {
            return name;
        }
    }

    static String access(ExecutableElement method) {
        Set<Modifier> mods = method.getModifiers();
        if (mods.contains(Modifier.PUBLIC)) {
            return "public ";
        } else if (mods.contains(Modifier.PROTECTED)) {
            return "protected ";
        } else {
            return "";
        }
    }

    private static boolean isJavaLangObject(TypeElement type) {
        return type.getSuperclass().getKind() == TypeKind.NONE && type.getKind() == ElementKind.CLASS;
    }

    private enum ObjectMethodToOverride {
        NONE, TO_STRING, EQUALS, HASH_CODE
    }

    private static ObjectMethodToOverride objectMethodToOverride(ExecutableElement method) {
        String name = method.getSimpleName().toString();
        switch (method.getParameters().size()) {
        case 0:
            if (name.equals("toString")) {
                return ObjectMethodToOverride.TO_STRING;
            } else if (name.equals("hashCode")) {
                return ObjectMethodToOverride.HASH_CODE;
            }
            break;
        case 1:
            if (name.equals("equals")
                    && method.getParameters().get(0).asType().toString().equals("java.lang.Object")) {
                return ObjectMethodToOverride.EQUALS;
            }
            break;
        default:
            // No relevant Object methods have more than one parameter.
        }
        return ObjectMethodToOverride.NONE;
    }

    private void processType(TypeElement type) {
        AutoValue autoValue = type.getAnnotation(AutoValue.class);
        if (autoValue == null) {
            // This shouldn't happen unless the compilation environment is buggy,
            // but it has happened in the past and can crash the compiler.
            errorReporter.abortWithError("annotation processor for @AutoValue was invoked with a type"
                    + " that does not have that annotation; this is probably a compiler bug", type);
        }
        if (type.getKind() != ElementKind.CLASS) {
            errorReporter.abortWithError("@" + AutoValue.class.getName() + " only applies to classes", type);
        }
        if (ancestorIsAutoValue(type)) {
            errorReporter.abortWithError("One @AutoValue class may not extend another", type);
        }
        if (implementsAnnotation(type)) {
            errorReporter.abortWithError("@AutoValue may not be used to implement an annotation"
                    + " interface; try using @AutoAnnotation instead", type);
        }
        checkModifiersIfNested(type);

        // We are going to classify the methods of the @AutoValue class into several categories.
        // This covers the methods in the class itself and the ones it inherits from supertypes.
        // First, the only concrete (non-abstract) methods we are interested in are overrides of
        // Object methods (equals, hashCode, toString), which signal that we should not generate
        // an implementation of those methods.
        // Then, each abstract method is one of the following:
        // (1) A property getter, like "abstract String foo()" or "abstract String getFoo()".
        // (2) A toBuilder() method, which is any abstract no-arg method returning the Builder for
        //     this @AutoValue class.
        // (3) An abstract method that will be consumed by an extension, such as
        //     Parcelable.describeContents() or Parcelable.writeToParcel(Parcel, int).
        // The describeContents() example shows a quirk here: initially we will identify it as a
        // property, which means that we need to reconstruct the list of properties after allowing
        // extensions to consume abstract methods.
        // If there are abstract methods that don't fit any of the categories above, that is an error
        // which we signal explicitly to avoid confusion.

        ImmutableSet<ExecutableElement> methods = getLocalAndInheritedMethods(type, processingEnv.getTypeUtils(),
                processingEnv.getElementUtils());
        ImmutableSet<ExecutableElement> abstractMethods = abstractMethodsIn(methods);

        BuilderSpec builderSpec = new BuilderSpec(type, processingEnv, errorReporter);
        Optional<BuilderSpec.Builder> builder = builderSpec.getBuilder();
        ImmutableSet<ExecutableElement> toBuilderMethods;
        if (builder.isPresent()) {
            toBuilderMethods = builder.get().toBuilderMethods(typeUtils, abstractMethods);
        } else {
            toBuilderMethods = ImmutableSet.of();
        }

        ImmutableSet<ExecutableElement> propertyMethods = propertyMethodsIn(
                immutableSetDifference(abstractMethods, toBuilderMethods));
        ImmutableBiMap<String, ExecutableElement> properties = propertyNameToMethodMap(propertyMethods);

        ExtensionContext context = new ExtensionContext(processingEnv, type, properties, abstractMethods);
        ImmutableList<AutoValueExtension> applicableExtensions = applicableExtensions(type, context);
        ImmutableSet<ExecutableElement> consumedMethods = methodsConsumedByExtensions(type, applicableExtensions,
                context, abstractMethods, properties);

        if (!consumedMethods.isEmpty()) {
            ImmutableSet<ExecutableElement> allAbstractMethods = abstractMethods;
            abstractMethods = immutableSetDifference(abstractMethods, consumedMethods);
            toBuilderMethods = immutableSetDifference(toBuilderMethods, consumedMethods);
            propertyMethods = propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods));
            properties = propertyNameToMethodMap(propertyMethods);
            context = new ExtensionContext(processingEnv, type, properties, allAbstractMethods);
        }

        boolean extensionsPresent = !applicableExtensions.isEmpty();
        validateMethods(type, abstractMethods, toBuilderMethods, propertyMethods, extensionsPresent);

        String finalSubclass = generatedSubclassName(type, 0);
        AutoValueTemplateVars vars = new AutoValueTemplateVars();
        vars.pkg = TypeSimplifier.packageNameOf(type);
        vars.origClass = TypeSimplifier.classNameOf(type);
        vars.simpleClassName = TypeSimplifier.simpleNameOf(vars.origClass);
        vars.finalSubclass = TypeSimplifier.simpleNameOf(finalSubclass);
        vars.types = processingEnv.getTypeUtils();
        determineObjectMethodsToGenerate(methods, vars);
        TypeSimplifier typeSimplifier = defineVarsForType(type, vars, toBuilderMethods, propertyMethods, builder);

        // Only copy annotations from a class if it has @AutoValue.CopyAnnotations.
        if (isAnnotationPresent(type, AutoValue.CopyAnnotations.class)) {
            Set<String> excludedAnnotations = union(getFieldOfClasses(type, AutoValue.CopyAnnotations.class,
                    "exclude", processingEnv.getElementUtils()), getAnnotationsMarkedWithInherited(type));

            vars.annotations = copyAnnotations(type, typeSimplifier, excludedAnnotations);
        } else {
            vars.annotations = ImmutableList.of();
        }

        GwtCompatibility gwtCompatibility = new GwtCompatibility(type);
        vars.gwtCompatibleAnnotation = gwtCompatibility.gwtCompatibleAnnotationString();

        int subclassDepth = writeExtensions(type, context, applicableExtensions);
        String subclass = generatedSubclassName(type, subclassDepth);
        vars.subclass = TypeSimplifier.simpleNameOf(subclass);
        vars.isFinal = (subclassDepth == 0);

        String text = vars.toText();
        text = Reformatter.fixup(text);
        writeSourceFile(subclass, text, type);
        GwtSerialization gwtSerialization = new GwtSerialization(gwtCompatibility, processingEnv, type);
        gwtSerialization.maybeWriteGwtSerializer(vars);
    }

    /** Implements the semantics of {@link AutoValue.CopyAnnotations}; see its javadoc. */
    private static ImmutableList<String> copyAnnotations(Element type, TypeSimplifier typeSimplifier,
            Set<String> excludedAnnotations) {
        ImmutableList.Builder<String> result = ImmutableList.builder();
        AnnotationOutput annotationOutput = new AnnotationOutput(typeSimplifier);
        for (AnnotationMirror annotation : type.getAnnotationMirrors()) {
            String annotationFqName = getAnnotationFqName(annotation);
            if (AUTO_VALUE_CLASSNAME_PATTERN.matcher(annotationFqName).matches()) {
                // Skip AutoValue itself (and any of its nested annotations).
                continue;
            }
            if (!excludedAnnotations.contains(annotationFqName)) {
                result.add(annotationOutput.sourceFormForAnnotation(annotation));
            }
        }

        return result.build();
    }

    /**
     * Returns the fully-qualified name of an annotation-mirror, e.g.
     * "com.google.auto.value.AutoValue".
     */
    private static String getAnnotationFqName(AnnotationMirror annotation) {
        return ((TypeElement) annotation.getAnnotationType().asElement()).getQualifiedName().toString();
    }

    /**
     * Returns the contents of a {@code Class[]}-typed field in an annotation.
     *
     * <p>This method is needed because directly reading the value of such a field from an
     * AnnotationMirror throws: <pre>
     * javax.lang.model.type.MirroredTypeException: Attempt to access Class object for TypeMirror Foo.
     * </pre>
     *
     * @param element The element on which the annotation is present. e.g. the class being processed
     *     by AutoValue.
     * @param annotation The class of the annotation to read from., e.g. {@link
     *     AutoValue.CopyAnnotations}.
     * @param fieldName The name of the field to read, e.g. "exclude".
     * @return a set of fully-qualified names of classes appearing in 'fieldName' on 'annotation' on
     *     'element'.
     */
    private ImmutableSet<String> getFieldOfClasses(Element element, Class<? extends Annotation> annotation,
            String fieldName, Elements elementUtils) {
        TypeElement annotationElement = elementUtils.getTypeElement(annotation.getCanonicalName());
        if (annotationElement == null) {
            // This can happen if the annotation is on the -processorpath but not on the -classpath.
            return ImmutableSet.of();
        }
        TypeMirror annotationMirror = annotationElement.asType();

        for (AnnotationMirror annot : element.getAnnotationMirrors()) {
            if (!typeUtils.isSameType(annot.getAnnotationType(), annotationMirror)) {
                continue;
            }
            for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annot.getElementValues()
                    .entrySet()) {
                if (fieldName.contentEquals(entry.getKey().getSimpleName())) {
                    ImmutableSet.Builder<String> result = ImmutableSet.builder();

                    @SuppressWarnings("unchecked")
                    List<AnnotationValue> annotationsToCopy = (List<AnnotationValue>) entry.getValue().getValue();
                    for (AnnotationValue annotationValue : annotationsToCopy) {
                        String qualifiedName = ((TypeElement) ((DeclaredType) annotationValue.getValue())
                                .asElement()).getQualifiedName().toString();
                        result.add(qualifiedName);
                    }
                    return result.build();
                }
            }
        }
        return ImmutableSet.of();
    }

    private ImmutableSet<String> getAnnotationsMarkedWithInherited(Element element) {
        ImmutableSet.Builder<String> result = ImmutableSet.builder();
        for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
            if (isAnnotationPresent(annotation.getAnnotationType().asElement(), Inherited.class)) {
                result.add(getAnnotationFqName(annotation));
            }
        }
        return result.build();
    }

    // Invokes each of the given extensions to generate its subclass, and returns the number of
    // hierarchy classes that extensions generated. This number is then the number of $ characters
    // that should precede the name of the AutoValue implementation class.
    // Assume the @AutoValue class is com.example.Foo.Bar. Then if there are no
    // extensions the returned value will be 0, so the AutoValue implementation will be
    // com.example.AutoValue_Foo_Bar. If there is one extension, it will be asked to
    // generate AutoValue_Foo_Bar with parent $AutoValue_Foo_Bar. If it does so (returns
    // non-null) then the returned value will be 1, so the AutoValue implementation will be
    // com.example.$AutoValue_Foo_Bar. Otherwise, the returned value will still be 0. Likewise,
    // if there is a second extension and both extensions return non-null, the first one will
    // generate AutoValue_Foo_Bar with parent $AutoValue_Foo_Bar, the second will generate
    // $AutoValue_Foo_Bar with parent $$AutoValue_Foo_Bar, and the returned value will be 2 for
    // com.example.$$AutoValue_Foo_Bar.
    private int writeExtensions(TypeElement type, ExtensionContext context,
            ImmutableList<AutoValueExtension> applicableExtensions) {
        int writtenSoFar = 0;
        for (AutoValueExtension extension : applicableExtensions) {
            String parentFqName = generatedSubclassName(type, writtenSoFar + 1);
            String parentSimpleName = TypeSimplifier.simpleNameOf(parentFqName);
            String classFqName = generatedSubclassName(type, writtenSoFar);
            String classSimpleName = TypeSimplifier.simpleNameOf(classFqName);
            boolean isFinal = (writtenSoFar == 0);
            String source = extension.generateClass(context, classSimpleName, parentSimpleName, isFinal);
            if (source != null) {
                source = Reformatter.fixup(source);
                writeSourceFile(classFqName, source, type);
                writtenSoFar++;
            }
        }
        return writtenSoFar;
    }

    private ImmutableList<AutoValueExtension> applicableExtensions(TypeElement type, ExtensionContext context) {
        List<AutoValueExtension> applicableExtensions = Lists.newArrayList();
        List<AutoValueExtension> finalExtensions = Lists.newArrayList();
        for (AutoValueExtension extension : extensions) {
            if (extension.applicable(context)) {
                if (extension.mustBeFinal(context)) {
                    finalExtensions.add(extension);
                } else {
                    applicableExtensions.add(extension);
                }
            }
        }
        switch (finalExtensions.size()) {
        case 0:
            break;
        case 1:
            applicableExtensions.add(0, finalExtensions.get(0));
            break;
        default:
            errorReporter.reportError("More than one extension wants to generate the final class: "
                    + FluentIterable.from(finalExtensions).transform(ExtensionName.INSTANCE).join(Joiner.on(", ")),
                    type);
            break;
        }
        return ImmutableList.copyOf(applicableExtensions);
    }

    private ImmutableSet<ExecutableElement> methodsConsumedByExtensions(TypeElement type,
            ImmutableList<AutoValueExtension> applicableExtensions, ExtensionContext context,
            ImmutableSet<ExecutableElement> abstractMethods, ImmutableBiMap<String, ExecutableElement> properties) {
        Set<ExecutableElement> consumed = Sets.newHashSet();
        for (AutoValueExtension extension : applicableExtensions) {
            Set<ExecutableElement> consumedHere = Sets.newHashSet();
            for (String consumedProperty : extension.consumeProperties(context)) {
                ExecutableElement propertyMethod = properties.get(consumedProperty);
                if (propertyMethod == null) {
                    errorReporter.reportError(
                            "Extension " + extensionName(extension)
                                    + " wants to consume a property that does not exist: " + consumedProperty,
                            type);
                } else {
                    consumedHere.add(propertyMethod);
                }
            }
            for (ExecutableElement consumedMethod : extension.consumeMethods(context)) {
                if (!abstractMethods.contains(consumedMethod)) {
                    errorReporter.reportError("Extension " + extensionName(extension)
                            + " wants to consume a method that is not one of the abstract methods in this"
                            + " class: " + consumedMethod, type);
                } else {
                    consumedHere.add(consumedMethod);
                }
            }
            for (ExecutableElement repeat : Sets.intersection(consumed, consumedHere)) {
                errorReporter.reportError("Extension " + extensionName(extension)
                        + " wants to consume a method that was already" + " consumed by another extension", repeat);
            }
            consumed.addAll(consumedHere);
        }
        return ImmutableSet.copyOf(consumed);
    }

    private void validateMethods(TypeElement type, ImmutableSet<ExecutableElement> abstractMethods,
            ImmutableSet<ExecutableElement> toBuilderMethods, ImmutableSet<ExecutableElement> propertyMethods,
            boolean extensionsPresent) {
        boolean ok = true;
        for (ExecutableElement method : abstractMethods) {
            if (propertyMethods.contains(method)) {
                ok &= checkReturnType(type, method);
            } else if (!toBuilderMethods.contains(method)
                    && objectMethodToOverride(method) == ObjectMethodToOverride.NONE) {
                // This could reasonably be an error, were it not for an Eclipse bug in
                // ElementUtils.override that sometimes fails to recognize that one method overrides
                // another, and therefore leaves us with both an abstract method and the subclass method
                // that overrides it. This shows up in AutoValueTest.LukesBase for example.
                String message = "Abstract method is neither a property getter nor a Builder converter";
                if (extensionsPresent) {
                    message += ", and no extension consumed it";
                }
                errorReporter.reportWarning(message, method);
            }
        }
        if (!ok) {
            throw new AbortProcessingException();
        }
    }

    private static String extensionName(AutoValueExtension extension) {
        return extension.getClass().getName();
    }

    private enum ExtensionName implements Function<AutoValueExtension, String> {
        INSTANCE;

        @Override
        public String apply(AutoValueExtension input) {
            return extensionName(input);
        }
    }

    private enum SimpleMethodFunction implements Function<ExecutableElement, SimpleMethod> {
        INSTANCE;

        @Override
        public SimpleMethod apply(ExecutableElement input) {
            return new SimpleMethod(input);
        }
    }

    private TypeSimplifier defineVarsForType(TypeElement type, AutoValueTemplateVars vars,
            ImmutableSet<ExecutableElement> toBuilderMethods, ImmutableSet<ExecutableElement> propertyMethods,
            Optional<BuilderSpec.Builder> builder) {
        DeclaredType declaredType = MoreTypes.asDeclared(type.asType());
        Set<TypeMirror> types = new TypeMirrorSet();
        types.addAll(returnTypesOf(propertyMethods));
        if (builder.isPresent()) {
            types.addAll(builder.get().referencedTypes());
        }
        TypeElement generatedTypeElement = processingEnv.getElementUtils()
                .getTypeElement("javax.annotation.Generated");
        if (generatedTypeElement != null) {
            types.add(generatedTypeElement.asType());
        }
        TypeMirror javaUtilArrays = getTypeMirror(Arrays.class);
        if (containsArrayType(types)) {
            // If there are array properties then we will be referencing java.util.Arrays.
            // Arrange to import it unless that would introduce ambiguity.
            types.add(javaUtilArrays);
        }
        vars.toBuilderMethods = FluentIterable.from(toBuilderMethods).transform(SimpleMethodFunction.INSTANCE)
                .toList();
        ImmutableSetMultimap<ExecutableElement, String> excludedAnnotationsMap = allMethodExcludedAnnotations(
                propertyMethods);
        types.addAll(allMethodAnnotationTypes(propertyMethods, excludedAnnotationsMap));
        String pkg = TypeSimplifier.packageNameOf(type);
        TypeSimplifier typeSimplifier = new TypeSimplifier(typeUtils, pkg, types, declaredType);
        vars.imports = typeSimplifier.typesToImport();
        vars.generated = generatedTypeElement == null ? "" : typeSimplifier.simplify(generatedTypeElement.asType());
        vars.arrays = typeSimplifier.simplify(javaUtilArrays);
        ImmutableBiMap<ExecutableElement, String> methodToPropertyName = propertyNameToMethodMap(propertyMethods)
                .inverse();
        Map<ExecutableElement, String> methodToIdentifier = Maps.newLinkedHashMap(methodToPropertyName);
        fixReservedIdentifiers(methodToIdentifier);
        List<Property> props = new ArrayList<Property>();
        EclipseHack eclipseHack = eclipseHack();
        ImmutableMap<ExecutableElement, TypeMirror> returnTypes = eclipseHack.methodReturnTypes(propertyMethods,
                declaredType);
        for (ExecutableElement method : propertyMethods) {
            TypeMirror returnType = returnTypes.get(method);
            String propertyType = typeSimplifier.simplify(returnType);
            String propertyName = methodToPropertyName.get(method);
            String identifier = methodToIdentifier.get(method);
            ImmutableSet<String> excludedAnnotations = ImmutableSet.<String>builder()
                    .addAll(excludedAnnotationsMap.get(method)).add(Override.class.getCanonicalName()).build();
            Property p = new Property(propertyName, identifier, method, propertyType, typeSimplifier,
                    excludedAnnotations);
            props.add(p);
            if (p.isNullable() && returnType.getKind().isPrimitive()) {
                errorReporter.reportError("Primitive types cannot be @Nullable", method);
            }
        }
        vars.props = ImmutableSet.copyOf(props);
        vars.serialVersionUID = getSerialVersionUID(type);
        vars.formalTypes = typeSimplifier.formalTypeParametersString(type);
        vars.actualTypes = TypeSimplifier.actualTypeParametersString(type);
        vars.wildcardTypes = wildcardTypeParametersString(type);
        // Check for @AutoValue.Builder and add appropriate variables if it is present.
        if (builder.isPresent()) {
            builder.get().defineVars(vars, typeSimplifier, methodToPropertyName);
        }
        return typeSimplifier;
    }

    private ImmutableSetMultimap<ExecutableElement, String> allMethodExcludedAnnotations(
            Iterable<ExecutableElement> methods) {
        ImmutableSetMultimap.Builder<ExecutableElement, String> result = ImmutableSetMultimap.builder();
        for (ExecutableElement method : methods) {
            result.putAll(method, getFieldOfClasses(method, AutoValue.CopyAnnotations.class, "exclude",
                    processingEnv.getElementUtils()));
        }
        return result.build();
    }

    private ImmutableBiMap<String, ExecutableElement> propertyNameToMethodMap(
            Set<ExecutableElement> propertyMethods) {
        Map<String, ExecutableElement> map = Maps.newLinkedHashMap();
        boolean allPrefixed = gettersAllPrefixed(propertyMethods);
        for (ExecutableElement method : propertyMethods) {
            String methodName = method.getSimpleName().toString();
            String name = allPrefixed ? nameWithoutPrefix(methodName) : methodName;
            Object old = map.put(name, method);
            if (old != null) {
                errorReporter.reportError("More than one @AutoValue property called " + name, method);
            }
        }
        return ImmutableBiMap.copyOf(map);
    }

    private static boolean gettersAllPrefixed(Set<ExecutableElement> methods) {
        return prefixedGettersIn(methods).size() == methods.size();
    }

    static ImmutableSet<ExecutableElement> prefixedGettersIn(Iterable<ExecutableElement> methods) {
        ImmutableSet.Builder<ExecutableElement> getters = ImmutableSet.builder();
        for (ExecutableElement method : methods) {
            String name = method.getSimpleName().toString();
            // TODO(emcmanus): decide whether getfoo() (without a capital) is a getter. Currently it is.
            boolean get = name.startsWith("get") && !name.equals("get");
            boolean is = name.startsWith("is") && !name.equals("is")
                    && method.getReturnType().getKind() == TypeKind.BOOLEAN;
            if (get || is) {
                getters.add(method);
            }
        }
        return getters.build();
    }

    /**
     * Returns all method annotations that should be imported in the generated class. Doesn't include
     * AutoValue itself (and its nested annotations), any @Inherited annotations, and anything that's
     * in excludedAnnotationsMap.
     */
    private Set<TypeMirror> allMethodAnnotationTypes(Iterable<ExecutableElement> methods,
            ImmutableSetMultimap<ExecutableElement, String> excludedAnnotationsMap) {
        Set<TypeMirror> annotationTypes = new TypeMirrorSet();
        for (ExecutableElement method : methods) {
            ImmutableSet<String> excludedAnnotations = excludedAnnotationsMap.get(method);
            for (AnnotationMirror annotationMirror : method.getAnnotationMirrors()) {
                String annotationFqName = getAnnotationFqName(annotationMirror);
                if (excludedAnnotations.contains(annotationFqName)) {
                    continue;
                }
                if (isAnnotationPresent(annotationMirror.getAnnotationType().asElement(), Inherited.class)) {
                    continue;
                }
                if (AUTO_VALUE_CLASSNAME_PATTERN.matcher(annotationFqName).matches()) {
                    continue;
                }
                annotationTypes.add(annotationMirror.getAnnotationType());
            }
            for (AnnotationMirror annotationMirror : Java8Support.getAnnotationMirrors(method.getReturnType())) {
                annotationTypes.add(annotationMirror.getAnnotationType());
            }
        }
        return annotationTypes;
    }

    /**
     * Returns the name of the property defined by the given getter. A getter called {@code getFoo()}
     * or {@code isFoo()} defines a property called {@code foo}. For consistency with JavaBeans, a
     * getter called {@code getHTMLPage()} defines a property called {@code HTMLPage}. The
     * <a href="https://docs.oracle.com/javase/8/docs/api/java/beans/Introspector.html#decapitalize-java.lang.String-">
     * rule</a> is: the name of the property is the part after {@code get} or {@code is}, with the
     * first letter lowercased <i>unless</i> the first two letters are uppercase. This works well
     * for the {@code HTMLPage} example, but in these more enlightened times we use {@code HtmlPage}
     * anyway, so the special behaviour is not useful, and of course it behaves poorly with examples
     * like {@code OAuth}.
     */
    private String nameWithoutPrefix(String name) {
        if (name.startsWith("get")) {
            name = name.substring(3);
        } else {
            assert name.startsWith("is");
            name = name.substring(2);
        }
        return Introspector.decapitalize(name);
    }

    private void checkModifiersIfNested(TypeElement type) {
        ElementKind enclosingKind = type.getEnclosingElement().getKind();
        if (enclosingKind.isClass() || enclosingKind.isInterface()) {
            if (type.getModifiers().contains(Modifier.PRIVATE)) {
                errorReporter.abortWithError("@AutoValue class must not be private", type);
            }
            if (!type.getModifiers().contains(Modifier.STATIC)) {
                errorReporter.abortWithError("Nested @AutoValue class must be static", type);
            }
        }
        // In principle type.getEnclosingElement() could be an ExecutableElement (for a class
        // declared inside a method), but since RoundEnvironment.getElementsAnnotatedWith doesn't
        // return such classes we won't see them here.
    }

    // If we have a getter called getPackage() then we can't use the identifier "package" to represent
    // its value since that's a reserved word.
    private void fixReservedIdentifiers(Map<ExecutableElement, String> methodToIdentifier) {
        for (Map.Entry<ExecutableElement, String> entry : methodToIdentifier.entrySet()) {
            if (SourceVersion.isKeyword(entry.getValue())) {
                entry.setValue(disambiguate(entry.getValue(), methodToIdentifier.values()));
            }
        }
    }

    private String disambiguate(String name, Collection<String> existingNames) {
        for (int i = 0;; i++) {
            String candidate = name + i;
            if (!existingNames.contains(candidate)) {
                return candidate;
            }
        }
    }

    private Set<TypeMirror> returnTypesOf(Iterable<ExecutableElement> methods) {
        Set<TypeMirror> returnTypes = new TypeMirrorSet();
        for (ExecutableElement method : methods) {
            returnTypes.add(method.getReturnType());
        }
        return returnTypes;
    }

    private static boolean containsArrayType(Set<TypeMirror> types) {
        for (TypeMirror type : types) {
            if (type.getKind() == TypeKind.ARRAY) {
                return true;
            }
        }
        return false;
    }

    /**
     * Given a list of all methods defined in or inherited by a class, sets the equals, hashCode, and
     * toString fields of vars according as the corresponding methods should be generated.
     */
    private static void determineObjectMethodsToGenerate(Set<ExecutableElement> methods,
            AutoValueTemplateVars vars) {
        // The defaults here only come into play when an ancestor class doesn't exist.
        // Compilation will fail in that case, but we don't want it to crash the compiler with
        // an exception before it does. If all ancestors do exist then we will definitely find
        // definitions of these three methods (perhaps the ones in Object) so we will overwrite these:
        vars.equals = false;
        vars.hashCode = false;
        vars.toString = false;
        for (ExecutableElement method : methods) {
            ObjectMethodToOverride override = objectMethodToOverride(method);
            boolean canGenerate = method.getModifiers().contains(Modifier.ABSTRACT)
                    || isJavaLangObject((TypeElement) method.getEnclosingElement());
            switch (override) {
            case EQUALS:
                vars.equals = canGenerate;
                break;
            case HASH_CODE:
                vars.hashCode = canGenerate;
                break;
            case TO_STRING:
                vars.toString = canGenerate;
                break;
            default:
                // Not a method from Object, nothing to do.
            }
        }
    }

    private ImmutableSet<ExecutableElement> abstractMethodsIn(ImmutableSet<ExecutableElement> methods) {
        Set<Name> noArgMethods = Sets.newHashSet();
        ImmutableSet.Builder<ExecutableElement> abstracts = ImmutableSet.builder();
        for (ExecutableElement method : methods) {
            if (method.getModifiers().contains(Modifier.ABSTRACT)) {
                boolean hasArgs = !method.getParameters().isEmpty();
                if (hasArgs || noArgMethods.add(method.getSimpleName())) {
                    // If an abstract method with the same signature is inherited on more than one path,
                    // we only add it once. At the moment we only do this check for no-arg methods. All
                    // methods that AutoValue will implement are either no-arg methods or equals(Object).
                    // The former is covered by this check and the latter will lead to vars.equals being
                    // set to true, regardless of how many times it appears. So the only case that is
                    // covered imperfectly here is that of a method that is inherited on more than one path
                    // and that will be consumed by an extension. We could check parameters as well, but that
                    // can be a bit tricky if any of the parameters are generic.
                    abstracts.add(method);
                }
            }
        }
        return abstracts.build();
    }

    private ImmutableSet<ExecutableElement> propertyMethodsIn(ImmutableSet<ExecutableElement> abstractMethods) {
        ImmutableSet.Builder<ExecutableElement> properties = ImmutableSet.builder();
        for (ExecutableElement method : abstractMethods) {
            if (method.getParameters().isEmpty() && method.getReturnType().getKind() != TypeKind.VOID
                    && objectMethodToOverride(method) == ObjectMethodToOverride.NONE) {
                properties.add(method);
            }
        }
        return properties.build();
    }

    private boolean checkReturnType(TypeElement autoValueClass, ExecutableElement getter) {
        TypeMirror type = getter.getReturnType();
        if (type.getKind() == TypeKind.ARRAY) {
            TypeMirror componentType = ((ArrayType) type).getComponentType();
            if (componentType.getKind().isPrimitive()) {
                warnAboutPrimitiveArrays(autoValueClass, getter);
                return true;
            } else {
                errorReporter.reportError("An @AutoValue class cannot define an array-valued property"
                        + " unless it is a primitive array", getter);
                return false;
            }
        } else {
            return true;
        }
    }

    // Detects whether the visited AnnotationValue is an array that contains the string "mutable".
    // The simpler approach using Element.getAnnotation(SuppressWarnings.class) doesn't work if
    // the annotation has an undefined reference, like @SuppressWarnings(UNDEFINED).
    // TODO(emcmanus): replace with a method from auto-common when that is available.
    private static class ContainsMutableVisitor extends SimpleAnnotationValueVisitor6<Boolean, Void> {
        @Override
        public Boolean visitArray(List<? extends AnnotationValue> list, Void p) {
            for (AnnotationValue value : list) {
                if ("mutable".equals(value.getValue())) {
                    return true;
                }
            }
            return false;
        }
    }

    private void warnAboutPrimitiveArrays(TypeElement autoValueClass, ExecutableElement getter) {
        boolean suppressed = false;
        Optional<AnnotationMirror> maybeAnnotation = getAnnotationMirror(getter, SuppressWarnings.class);
        if (maybeAnnotation.isPresent()) {
            AnnotationValue listValue = getAnnotationValue(maybeAnnotation.get(), "value");
            suppressed = listValue.accept(new ContainsMutableVisitor(), null);
        }
        if (!suppressed) {
            // If the primitive-array property method is defined directly inside the @AutoValue class,
            // then our error message should point directly to it. But if it is inherited, we don't
            // want to try to make the error message point to the inherited definition, since that would
            // be confusing (there is nothing wrong with the definition itself), and won't work if the
            // inherited class is not being recompiled. Instead, in this case we point to the @AutoValue
            // class itself, and we include extra text in the error message that shows the full name of
            // the inherited method.
            String warning = "An @AutoValue property that is a primitive array returns the original array, "
                    + "which can therefore be modified by the caller. If this OK, you can "
                    + "suppress this warning with @SuppressWarnings(\"mutable\"). Otherwise, you "
                    + "should replace the property with an immutable type, perhaps a simple wrapper "
                    + "around the original array.";
            boolean sameClass = getter.getEnclosingElement().equals(autoValueClass);
            if (sameClass) {
                errorReporter.reportWarning(warning, getter);
            } else {
                errorReporter.reportWarning(warning + " Method: " + getter.getEnclosingElement() + "." + getter,
                        autoValueClass);
            }
        }
    }

    private void writeSourceFile(String className, String text, TypeElement originatingType) {
        try {
            JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(className, originatingType);
            Writer writer = sourceFile.openWriter();
            try {
                writer.write(text);
            } finally {
                writer.close();
            }
        } catch (IOException e) {
            // This should really be an error, but we make it a warning in the hope of resisting Eclipse
            // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599. If that bug manifests, we may get
            // invoked more than once for the same file, so ignoring the ability to overwrite it is the
            // right thing to do. If we are unable to write for some other reason, we should get a compile
            // error later because user code will have a reference to the code we were supposed to
            // generate (new AutoValue_Foo() or whatever) and that reference will be undefined.
            processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
                    "Could not write generated class " + className + ": " + e);
        }
    }

    private boolean ancestorIsAutoValue(TypeElement type) {
        while (true) {
            TypeMirror parentMirror = type.getSuperclass();
            if (parentMirror.getKind() == TypeKind.NONE) {
                return false;
            }
            TypeElement parentElement = (TypeElement) typeUtils.asElement(parentMirror);
            if (MoreElements.isAnnotationPresent(parentElement, AutoValue.class)) {
                return true;
            }
            type = parentElement;
        }
    }

    private boolean implementsAnnotation(TypeElement type) {
        return typeUtils.isAssignable(type.asType(), getTypeMirror(Annotation.class));
    }

    // Return a string like "1234L" if type instanceof Serializable and defines
    // serialVersionUID = 1234L, otherwise "".
    private String getSerialVersionUID(TypeElement type) {
        TypeMirror serializable = getTypeMirror(Serializable.class);
        if (typeUtils.isAssignable(type.asType(), serializable)) {
            List<VariableElement> fields = ElementFilter.fieldsIn(type.getEnclosedElements());
            for (VariableElement field : fields) {
                if (field.getSimpleName().contentEquals("serialVersionUID")) {
                    Object value = field.getConstantValue();
                    if (field.getModifiers().containsAll(Arrays.asList(Modifier.STATIC, Modifier.FINAL))
                            && field.asType().getKind() == TypeKind.LONG && value != null) {
                        return value + "L";
                    } else {
                        errorReporter.reportError(
                                "serialVersionUID must be a static final long compile-time constant", field);
                        break;
                    }
                }
            }
        }
        return "";
    }

    private TypeMirror getTypeMirror(Class<?> c) {
        return processingEnv.getElementUtils().getTypeElement(c.getName()).asType();
    }

    // The @AutoValue type, with a ? for every type.
    // If we have @AutoValue abstract class Foo<T extends Something> then this method will return
    // just <?>.
    private static String wildcardTypeParametersString(TypeElement type) {
        List<? extends TypeParameterElement> typeParameters = type.getTypeParameters();
        if (typeParameters.isEmpty()) {
            return "";
        } else {
            return "<"
                    + Joiner.on(", ").join(FluentIterable.from(typeParameters).transform(Functions.constant("?")))
                    + ">";
        }
    }

    private static <E> ImmutableSet<E> immutableSetDifference(ImmutableSet<E> a, ImmutableSet<E> b) {
        if (Collections.disjoint(a, b)) {
            return a;
        } else {
            return ImmutableSet.copyOf(Sets.difference(a, b));
        }
    }

    private EclipseHack eclipseHack() {
        return new EclipseHack(processingEnv);
    }
}