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

com.google.auto.value.processor.AutoValueProcessor Maven / Gradle / Ivy

/*
 * 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.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.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 AutoValue User's Guide
 * @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 extensions) {
    this.extensions = ImmutableList.copyOf(extensions);
    this.loaderForExtensions = null;
  }

  @Override
  public Set 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 deferredTypeNames = new ArrayList();

  // 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 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 annotations, RoundEnvironment roundEnv) {
    List deferredTypes = new ArrayList();
    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 annotatedElements =
        roundEnv.getElementsAnnotatedWith(AutoValue.class);
    List types = new ImmutableList.Builder()
        .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 annotations;
    private final Optionalish optional;

    Property(
        String name,
        String identifier,
        ExecutableElement method,
        String type,
        TypeSimplifier typeSimplifier,
        ImmutableSet 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 buildAnnotations(
        TypeSimplifier typeSimplifier, ImmutableSet excludedAnnotations) {
      ImmutableList.Builder 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 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();
    }

    /**
     * Returns an expression to check if the property is able to be retrieved as a non-null value
     * using {@link #getValueRetrieval()}.
     */
    public String getPresenseCheck() {
      if (optional == null) {
        return identifier + " != null";
      } else {
        return identifier + ".isPresent()";
      }
    }

    /**
     * Returns an expression to get the value of the property, unwrapping it if it is an Optional.
     */
    public String getValueRetrieval() {
      if (optional == null) {
        return identifier;
      } else {
        return identifier + ".get()";
      }
    }

    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.
   *
   * 

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 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 methods = getLocalAndInheritedMethods( type, processingEnv.getTypeUtils(), processingEnv.getElementUtils()); ImmutableSet abstractMethods = abstractMethodsIn(methods); BuilderSpec builderSpec = new BuilderSpec(type, processingEnv, errorReporter); Optional builder = builderSpec.getBuilder(); ImmutableSet toBuilderMethods; if (builder.isPresent()) { toBuilderMethods = builder.get().toBuilderMethods(typeUtils, abstractMethods); } else { toBuilderMethods = ImmutableSet.of(); } ImmutableSet propertyMethods = propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods)); ImmutableBiMap properties = propertyNameToMethodMap(propertyMethods); ExtensionContext context = new ExtensionContext(processingEnv, type, properties, abstractMethods); ImmutableList applicableExtensions = applicableExtensions(type, context); ImmutableSet consumedMethods = methodsConsumedByExtensions( type, applicableExtensions, context, abstractMethods, properties); if (!consumedMethods.isEmpty()) { ImmutableSet 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 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 copyAnnotations( Element type, TypeSimplifier typeSimplifier, Set excludedAnnotations) { ImmutableList.Builder 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. * *

This method is needed because directly reading the value of such a field from an * AnnotationMirror throws:

   * javax.lang.model.type.MirroredTypeException: Attempt to access Class object for TypeMirror Foo.
   * 
* * @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 getFieldOfClasses( Element element, Class 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 entry : annot.getElementValues().entrySet()) { if (fieldName.contentEquals(entry.getKey().getSimpleName())) { ImmutableSet.Builder result = ImmutableSet.builder(); @SuppressWarnings("unchecked") List annotationsToCopy = (List) 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 getAnnotationsMarkedWithInherited(Element element) { ImmutableSet.Builder 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 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 applicableExtensions( TypeElement type, ExtensionContext context) { List applicableExtensions = Lists.newArrayList(); List 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 methodsConsumedByExtensions( TypeElement type, ImmutableList applicableExtensions, ExtensionContext context, ImmutableSet abstractMethods, ImmutableBiMap properties) { Set consumed = Sets.newHashSet(); for (AutoValueExtension extension : applicableExtensions) { Set 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 abstractMethods, ImmutableSet toBuilderMethods, ImmutableSet 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 { INSTANCE; @Override public String apply(AutoValueExtension input) { return extensionName(input); } } private enum SimpleMethodFunction implements Function { INSTANCE; @Override public SimpleMethod apply(ExecutableElement input) { return new SimpleMethod(input); } } private TypeSimplifier defineVarsForType( TypeElement type, AutoValueTemplateVars vars, ImmutableSet toBuilderMethods, ImmutableSet propertyMethods, Optional builder) { DeclaredType declaredType = MoreTypes.asDeclared(type.asType()); Set 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 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 methodToPropertyName = propertyNameToMethodMap(propertyMethods).inverse(); Map methodToIdentifier = Maps.newLinkedHashMap(methodToPropertyName); fixReservedIdentifiers(methodToIdentifier); List props = new ArrayList(); EclipseHack eclipseHack = eclipseHack(); ImmutableMap 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 excludedAnnotations = ImmutableSet.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); } } // If we are running from Eclipse, undo the work of its compiler which sorts methods. eclipseHack.reorderProperties(props); 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 allMethodExcludedAnnotations( Iterable methods) { ImmutableSetMultimap.Builder result = ImmutableSetMultimap.builder(); for (ExecutableElement method : methods) { result.putAll( method, getFieldOfClasses( method, AutoValue.CopyAnnotations.class, "exclude", processingEnv.getElementUtils())); } return result.build(); } private ImmutableBiMap propertyNameToMethodMap( Set propertyMethods) { Map 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 methods) { return prefixedGettersIn(methods).size() == methods.size(); } static ImmutableSet prefixedGettersIn(Iterable methods) { ImmutableSet.Builder 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 allMethodAnnotationTypes( Iterable methods, ImmutableSetMultimap excludedAnnotationsMap) { Set annotationTypes = new TypeMirrorSet(); for (ExecutableElement method : methods) { ImmutableSet 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 * * rule is: the name of the property is the part after {@code get} or {@code is}, with the * first letter lowercased unless 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 methodToIdentifier) { for (Map.Entry entry : methodToIdentifier.entrySet()) { if (SourceVersion.isKeyword(entry.getValue())) { entry.setValue(disambiguate(entry.getValue(), methodToIdentifier.values())); } } } private String disambiguate(String name, Collection existingNames) { for (int i = 0; ; i++) { String candidate = name + i; if (!existingNames.contains(candidate)) { return candidate; } } } private Set returnTypesOf(Iterable methods) { Set returnTypes = new TypeMirrorSet(); for (ExecutableElement method : methods) { returnTypes.add(method.getReturnType()); } return returnTypes; } private static boolean containsArrayType(Set 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 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 abstractMethodsIn( ImmutableSet methods) { Set noArgMethods = Sets.newHashSet(); ImmutableSet.Builder 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 propertyMethodsIn( ImmutableSet abstractMethods) { ImmutableSet.Builder 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; } } private void warnAboutPrimitiveArrays(TypeElement autoValueClass, ExecutableElement getter) { SuppressWarnings suppressWarnings = getter.getAnnotation(SuppressWarnings.class); if (suppressWarnings == null || !Arrays.asList(suppressWarnings.value()).contains("mutable")) { // 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 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 then this method will return // just . private static String wildcardTypeParametersString(TypeElement type) { List typeParameters = type.getTypeParameters(); if (typeParameters.isEmpty()) { return ""; } else { return "<" + Joiner.on(", ").join( FluentIterable.from(typeParameters).transform(Functions.constant("?"))) + ">"; } } private static ImmutableSet immutableSetDifference(ImmutableSet a, ImmutableSet b) { if (Collections.disjoint(a, b)) { return a; } else { return ImmutableSet.copyOf(Sets.difference(a, b)); } } private EclipseHack eclipseHack() { return new EclipseHack(processingEnv); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy