| /* |
| * Copyright 2018 Google LLC |
| * |
| * 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.GeneratedAnnotations.generatedAnnotation; |
| import static com.google.auto.common.MoreElements.getPackage; |
| import static com.google.auto.common.MoreElements.isAnnotationPresent; |
| import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_PACKAGE_NAME; |
| import static com.google.auto.value.processor.ClassNames.COPY_ANNOTATIONS_NAME; |
| import static com.google.common.collect.Iterables.getOnlyElement; |
| import static com.google.common.collect.Sets.union; |
| import static java.util.stream.Collectors.joining; |
| import static java.util.stream.Collectors.toCollection; |
| import static java.util.stream.Collectors.toList; |
| import static java.util.stream.Collectors.toSet; |
| |
| import com.google.auto.common.MoreElements; |
| import com.google.auto.common.MoreTypes; |
| import com.google.auto.common.Visibility; |
| import com.google.auto.value.processor.MissingTypes.MissingTypeException; |
| import com.google.common.base.Throwables; |
| import com.google.common.collect.ImmutableBiMap; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableListMultimap; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import java.io.IOException; |
| import java.io.Serializable; |
| import java.io.Writer; |
| import java.lang.annotation.ElementType; |
| import java.lang.annotation.Inherited; |
| import java.lang.annotation.Target; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.EnumMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.OptionalInt; |
| import java.util.Set; |
| import java.util.function.Predicate; |
| import javax.annotation.processing.AbstractProcessor; |
| import javax.annotation.processing.ProcessingEnvironment; |
| 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.QualifiedNameable; |
| 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.SimpleAnnotationValueVisitor8; |
| import javax.lang.model.util.Types; |
| import javax.tools.Diagnostic; |
| import javax.tools.JavaFileObject; |
| |
| /** |
| * Shared code between AutoValueProcessor and AutoOneOfProcessor. |
| * |
| * @author emcmanus@google.com (Éamonn McManus) |
| */ |
| abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { |
| private final String annotationClassName; |
| |
| /** |
| * Qualified names of {@code @AutoValue} or {@code AutoOneOf} 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<>(); |
| |
| AutoValueOrOneOfProcessor(String annotationClassName) { |
| this.annotationClassName = annotationClassName; |
| } |
| |
| /** The annotation we are processing, {@code AutoValue} or {@code AutoOneOf}. */ |
| private TypeElement annotationType; |
| /** The simple name of {@link #annotationType}. */ |
| private String simpleAnnotationName; |
| |
| private ErrorReporter errorReporter; |
| |
| @Override |
| public synchronized void init(ProcessingEnvironment processingEnv) { |
| super.init(processingEnv); |
| errorReporter = new ErrorReporter(processingEnv); |
| } |
| |
| final ErrorReporter errorReporter() { |
| return errorReporter; |
| } |
| |
| final Types typeUtils() { |
| return processingEnv.getTypeUtils(); |
| } |
| |
| final Elements elementUtils() { |
| return processingEnv.getElementUtils(); |
| } |
| |
| /** |
| * Qualified names of {@code @AutoValue} or {@code AutoOneOf} classes that we attempted to process |
| * but had to abandon because we needed other types that they referenced and those other types |
| * were missing. This is used by tests. |
| */ |
| final ImmutableList<String> deferredTypeNames() { |
| return ImmutableList.copyOf(deferredTypeNames); |
| } |
| |
| @Override |
| public final SourceVersion getSupportedSourceVersion() { |
| return SourceVersion.latestSupported(); |
| } |
| |
| /** |
| * A property of an {@code @AutoValue} or {@code @AutoOneOf} 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> fieldAnnotations; |
| private final ImmutableList<String> methodAnnotations; |
| private final Optional<String> nullableAnnotation; |
| private final Optionalish optional; |
| |
| Property( |
| String name, |
| String identifier, |
| ExecutableElement method, |
| String type, |
| ImmutableList<String> fieldAnnotations, |
| ImmutableList<String> methodAnnotations, |
| Optional<String> nullableAnnotation) { |
| this.name = name; |
| this.identifier = identifier; |
| this.method = method; |
| this.type = type; |
| this.fieldAnnotations = fieldAnnotations; |
| this.methodAnnotations = methodAnnotations; |
| this.nullableAnnotation = nullableAnnotation; |
| TypeMirror propertyType = method.getReturnType(); |
| this.optional = Optionalish.createIfOptional(propertyType); |
| } |
| |
| /** |
| * 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(); |
| } |
| |
| public TypeMirror getTypeMirror() { |
| return method.getReturnType(); |
| } |
| |
| public String getType() { |
| return type; |
| } |
| |
| public TypeKind getKind() { |
| return method.getReturnType().getKind(); |
| } |
| |
| /** |
| * Returns the annotations (in string form) that should be applied to the property's field |
| * declaration. |
| */ |
| public List<String> getFieldAnnotations() { |
| return fieldAnnotations; |
| } |
| |
| /** |
| * Returns the annotations (in string form) that should be applied to the property's method |
| * implementation. |
| */ |
| public List<String> getMethodAnnotations() { |
| return methodAnnotations; |
| } |
| |
| /** |
| * 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 a method 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 "@`javax.annotation.Nullable` "}, where the |
| * {@code ``} is the encoding used by {@link TypeEncoder}. If the property is nullable by virtue |
| * of its <i>type</i> rather than its method being {@code @Nullable}, this method returns the |
| * empty string, because the {@code @Nullable} will appear when the type is spelled out. In this |
| * case, {@link #nullableAnnotation} is present but empty. |
| */ |
| public final String getNullableAnnotation() { |
| return nullableAnnotation.orElse(""); |
| } |
| |
| public boolean isNullable() { |
| return nullableAnnotation.isPresent(); |
| } |
| |
| public String getAccess() { |
| return SimpleMethod.access(method); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return obj instanceof Property && ((Property) obj).method.equals(method); |
| } |
| |
| @Override |
| public int hashCode() { |
| return method.hashCode(); |
| } |
| } |
| |
| @Override |
| public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { |
| annotationType = elementUtils().getTypeElement(annotationClassName); |
| if (annotationType == null) { |
| // This should not happen. If the annotation type is not found, how did the processor get |
| // triggered? |
| processingEnv |
| .getMessager() |
| .printMessage( |
| Diagnostic.Kind.ERROR, |
| "Did not process @" |
| + annotationClassName |
| + " because the annotation class was not found"); |
| return false; |
| } |
| simpleAnnotationName = annotationType.getSimpleName().toString(); |
| List<TypeElement> deferredTypes = |
| deferredTypeNames |
| .stream() |
| .map(name -> elementUtils().getTypeElement(name)) |
| .collect(toList()); |
| 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( |
| type, |
| "Did not generate @%s class for %s because it references undefined types", |
| simpleAnnotationName, |
| type.getQualifiedName()); |
| } |
| return false; |
| } |
| Collection<? extends Element> annotatedElements = |
| roundEnv.getElementsAnnotatedWith(annotationType); |
| 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) { |
| String trace = Throwables.getStackTraceAsString(e); |
| errorReporter.reportError( |
| type, "@%s processor threw an exception: %s", simpleAnnotationName, trace); |
| throw e; |
| } |
| } |
| return false; // never claim annotation, because who knows what other processors want? |
| } |
| |
| /** |
| * Analyzes a single {@code @AutoValue} or {@code @AutoOneOf} class, and outputs the corresponding |
| * implementation class or classes. |
| * |
| * @param type the class with the {@code @AutoValue} or {@code @AutoOneOf} annotation. |
| */ |
| abstract void processType(TypeElement type); |
| |
| /** |
| * Returns the appropriate {@code @Nullable} annotation to put on the implementation of the given |
| * property method, and indicates whether the property is in fact nullable. The annotation in |
| * question is on the method, not its return type. If instead the return type is |
| * {@code @Nullable}, this method returns {@code Optional.of("")}, to indicate that the property |
| * is nullable but the <i>method</i> isn't. The {@code @Nullable} annotation will instead appear |
| * when the return type of the method is spelled out in the implementation. |
| */ |
| abstract Optional<String> nullableAnnotationForMethod(ExecutableElement propertyMethod); |
| |
| /** |
| * Returns the ordered set of {@link Property} definitions for the given {@code @AutoValue} or |
| * {@code AutoOneOf} type. |
| * |
| * @param annotatedPropertyMethods a map from property methods to the method annotations that |
| * should go on the implementation of those methods. These annotations are method annotations |
| * specifically. Type annotations do not appear because they are considered part of the return |
| * type and will appear when that is spelled out. Annotations that are excluded by {@code |
| * AutoValue.CopyAnnotations} also do not appear here. |
| */ |
| final ImmutableSet<Property> propertySet( |
| ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes, |
| ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyFields, |
| ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyMethods) { |
| ImmutableBiMap<ExecutableElement, String> methodToPropertyName = |
| propertyNameToMethodMap(propertyMethodsAndTypes.keySet()).inverse(); |
| Map<ExecutableElement, String> methodToIdentifier = new LinkedHashMap<>(methodToPropertyName); |
| fixReservedIdentifiers(methodToIdentifier); |
| |
| ImmutableSet.Builder<Property> props = ImmutableSet.builder(); |
| propertyMethodsAndTypes.forEach( |
| (propertyMethod, returnType) -> { |
| String propertyType = TypeEncoder.encodeWithAnnotations( |
| returnType, getExcludedAnnotationTypes(propertyMethod)); |
| String propertyName = methodToPropertyName.get(propertyMethod); |
| String identifier = methodToIdentifier.get(propertyMethod); |
| ImmutableList<String> fieldAnnotations = |
| annotationStrings(annotatedPropertyFields.get(propertyMethod)); |
| ImmutableList<AnnotationMirror> methodAnnotationMirrors = |
| annotatedPropertyMethods.get(propertyMethod); |
| ImmutableList<String> methodAnnotations = annotationStrings(methodAnnotationMirrors); |
| Optional<String> nullableAnnotation = nullableAnnotationForMethod(propertyMethod); |
| Property p = |
| new Property( |
| propertyName, |
| identifier, |
| propertyMethod, |
| propertyType, |
| fieldAnnotations, |
| methodAnnotations, |
| nullableAnnotation); |
| props.add(p); |
| if (p.isNullable() && returnType.getKind().isPrimitive()) { |
| errorReporter().reportError(propertyMethod, "Primitive types cannot be @Nullable"); |
| } |
| }); |
| return props.build(); |
| } |
| |
| /** Defines the template variables that are shared by AutoValue and AutoOneOf. */ |
| final void defineSharedVarsForType( |
| TypeElement type, |
| ImmutableSet<ExecutableElement> methods, |
| AutoValueOrOneOfTemplateVars vars) { |
| vars.pkg = TypeSimplifier.packageNameOf(type); |
| vars.origClass = TypeSimplifier.classNameOf(type); |
| vars.simpleClassName = TypeSimplifier.simpleNameOf(vars.origClass); |
| vars.generated = |
| generatedAnnotation(elementUtils(), processingEnv.getSourceVersion()) |
| .map(annotation -> TypeEncoder.encode(annotation.asType())) |
| .orElse(""); |
| vars.formalTypes = TypeEncoder.formalTypeParametersString(type); |
| vars.actualTypes = TypeSimplifier.actualTypeParametersString(type); |
| vars.wildcardTypes = wildcardTypeParametersString(type); |
| vars.annotations = copiedClassAnnotations(type); |
| Map<ObjectMethod, ExecutableElement> methodsToGenerate = |
| determineObjectMethodsToGenerate(methods); |
| vars.toString = methodsToGenerate.containsKey(ObjectMethod.TO_STRING); |
| vars.equals = methodsToGenerate.containsKey(ObjectMethod.EQUALS); |
| vars.hashCode = methodsToGenerate.containsKey(ObjectMethod.HASH_CODE); |
| vars.equalsParameterType = equalsParameterType(methodsToGenerate); |
| } |
| |
| /** Returns the spelling to be used in the generated code for the given list of annotations. */ |
| static ImmutableList<String> annotationStrings(List<? extends AnnotationMirror> annotations) { |
| // TODO(b/68008628): use ImmutableList.toImmutableList() when that works. |
| return ImmutableList.copyOf( |
| annotations.stream().map(AnnotationOutput::sourceFormForAnnotation).collect(toList())); |
| } |
| |
| /** |
| * Returns the name of the generated {@code @AutoValue} or {@code @AutoOneOf} class, for example |
| * {@code AutoOneOf_TaskResult} or {@code $$AutoValue_SimpleMethod}. |
| * |
| * @param type the name of the type bearing the {@code @AutoValue} or {@code @AutoOneOf} |
| * annotation. |
| * @param prefix the prefix to use in the generated class. This may start with one or more dollar |
| * signs, for an {@code @AutoValue} implementation where there are AutoValue extensions. |
| */ |
| static 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 static boolean isJavaLangObject(TypeElement type) { |
| return type.getSuperclass().getKind() == TypeKind.NONE && type.getKind() == ElementKind.CLASS; |
| } |
| |
| enum ObjectMethod { |
| NONE, |
| TO_STRING, |
| EQUALS, |
| HASH_CODE |
| } |
| |
| /** |
| * Determines which of the three public non-final methods from {@code java.lang.Object}, if any, |
| * is overridden by the given method. |
| */ |
| static ObjectMethod objectMethodToOverride(ExecutableElement method) { |
| String name = method.getSimpleName().toString(); |
| switch (method.getParameters().size()) { |
| case 0: |
| if (name.equals("toString")) { |
| return ObjectMethod.TO_STRING; |
| } else if (name.equals("hashCode")) { |
| return ObjectMethod.HASH_CODE; |
| } |
| break; |
| case 1: |
| if (name.equals("equals")) { |
| TypeMirror param = getOnlyElement(method.getParameters()).asType(); |
| if (param.getKind().equals(TypeKind.DECLARED)) { |
| TypeElement paramType = MoreTypes.asTypeElement(param); |
| if (paramType.getQualifiedName().contentEquals("java.lang.Object")) { |
| return ObjectMethod.EQUALS; |
| } |
| } |
| } |
| break; |
| default: |
| // No relevant Object methods have more than one parameter. |
| } |
| return ObjectMethod.NONE; |
| } |
| |
| /** Returns a bi-map between property names and the corresponding abstract property methods. */ |
| final ImmutableBiMap<String, ExecutableElement> propertyNameToMethodMap( |
| Set<ExecutableElement> propertyMethods) { |
| Map<String, ExecutableElement> map = new LinkedHashMap<>(); |
| Set<String> reportedDups = new HashSet<>(); |
| boolean allPrefixed = gettersAllPrefixed(propertyMethods); |
| for (ExecutableElement method : propertyMethods) { |
| String methodName = method.getSimpleName().toString(); |
| String name = allPrefixed ? nameWithoutPrefix(methodName) : methodName; |
| ExecutableElement old = map.put(name, method); |
| if (old != null) { |
| List<ExecutableElement> contexts = new ArrayList<>(Arrays.asList(method)); |
| if (reportedDups.add(name)) { |
| contexts.add(old); |
| } |
| // Report the error for both of the methods. If this is a third or further occurrence, |
| // reportedDups prevents us from reporting more than one error for the same method. |
| for (ExecutableElement context : contexts) { |
| errorReporter.reportError( |
| context, "More than one @%s property called %s", simpleAnnotationName, name); |
| } |
| } |
| } |
| return ImmutableBiMap.copyOf(map); |
| } |
| |
| private static boolean gettersAllPrefixed(Set<ExecutableElement> methods) { |
| return prefixedGettersIn(methods).size() == methods.size(); |
| } |
| |
| /** |
| * Returns an appropriate annotation spelling to indicate the nullability of an element. If the |
| * return value is a non-empty Optional, that indicates that the element is nullable, and the |
| * string should be used to annotate it. If the return value is an empty Optional, the element is |
| * not nullable. The return value can be {@code Optional.of("")}, which indicates that the element |
| * is nullable but that the nullability comes from a type annotation. In this case, the annotation |
| * will appear when the type is written, and must not be specified again. If the Optional contains |
| * a present non-empty string then that string will end with a space. |
| * |
| * @param element the element that might be {@code @Nullable}, either a method or a parameter. |
| * @param elementType the relevant type of the element: the return type for a method, or the |
| * parameter type for a parameter. |
| */ |
| static Optional<String> nullableAnnotationFor(Element element, TypeMirror elementType) { |
| List<? extends AnnotationMirror> typeAnnotations = elementType.getAnnotationMirrors(); |
| if (nullableAnnotationIndex(typeAnnotations).isPresent()) { |
| return Optional.of(""); |
| } |
| List<? extends AnnotationMirror> elementAnnotations = element.getAnnotationMirrors(); |
| OptionalInt nullableAnnotationIndex = nullableAnnotationIndex(elementAnnotations); |
| if (nullableAnnotationIndex.isPresent()) { |
| ImmutableList<String> annotations = annotationStrings(elementAnnotations); |
| return Optional.of(annotations.get(nullableAnnotationIndex.getAsInt()) + " "); |
| } else { |
| return Optional.empty(); |
| } |
| } |
| |
| private static OptionalInt nullableAnnotationIndex(List<? extends AnnotationMirror> annotations) { |
| for (int i = 0; i < annotations.size(); i++) { |
| if (isNullable(annotations.get(i))) { |
| return OptionalInt.of(i); |
| } |
| } |
| return OptionalInt.empty(); |
| } |
| |
| private static boolean isNullable(AnnotationMirror annotation) { |
| return annotation.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable"); |
| } |
| |
| /** |
| * Returns the subset of the given zero-arg methods whose names begin with {@code get}. Also |
| * includes {@code isFoo} methods if they return {@code boolean}. This corresponds to JavaBeans |
| * conventions. |
| */ |
| static ImmutableSet<ExecutableElement> prefixedGettersIn(Iterable<ExecutableElement> methods) { |
| ImmutableSet.Builder<ExecutableElement> getters = ImmutableSet.builder(); |
| for (ExecutableElement method : methods) { |
| String name = method.getSimpleName().toString(); |
| // Note that getfoo() (without a capital) is still a getter. |
| 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 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 static String nameWithoutPrefix(String name) { |
| if (name.startsWith("get")) { |
| name = name.substring(3); |
| } else { |
| assert name.startsWith("is"); |
| name = name.substring(2); |
| } |
| return PropertyNames.decapitalizeLikeJavaBeans(name); |
| } |
| |
| /** |
| * Checks that, if the given {@code @AutoValue} or {@code @AutoOneOf} class is nested, it is |
| * static and not private. This check is not necessary for correctness, since the generated code |
| * would not compile if the check fails, but it produces better error messages for the user. |
| */ |
| final void checkModifiersIfNested(TypeElement type) { |
| ElementKind enclosingKind = type.getEnclosingElement().getKind(); |
| if (enclosingKind.isClass() || enclosingKind.isInterface()) { |
| if (type.getModifiers().contains(Modifier.PRIVATE)) { |
| errorReporter.abortWithError( |
| type, "@%s class must not be private", simpleAnnotationName); |
| } else if (Visibility.effectiveVisibilityOfElement(type).equals(Visibility.PRIVATE)) { |
| // The previous case, where the class itself is private, is much commoner so it deserves |
| // its own error message, even though it would be caught by the test here too. |
| errorReporter.abortWithError( |
| type, "@%s class must not be nested in a private class", simpleAnnotationName); |
| } |
| if (!type.getModifiers().contains(Modifier.STATIC)) { |
| errorReporter.abortWithError( |
| type, "Nested @%s class must be static", simpleAnnotationName); |
| } |
| } |
| // 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. |
| } |
| |
| /** |
| * Modifies the values of the given map to avoid reserved words. If we have a getter called {@code |
| * getPackage()} then we can't use the identifier {@code package} to represent its value since |
| * that's a reserved word. |
| */ |
| static void fixReservedIdentifiers(Map<?, String> methodToIdentifier) { |
| for (Map.Entry<?, String> entry : methodToIdentifier.entrySet()) { |
| String name = entry.getValue(); |
| if (SourceVersion.isKeyword(name) || !Character.isJavaIdentifierStart(name.codePointAt(0))) { |
| entry.setValue(disambiguate(name, methodToIdentifier.values())); |
| } |
| } |
| } |
| |
| private static String disambiguate(String name, Collection<String> existingNames) { |
| if (!Character.isJavaIdentifierStart(name.codePointAt(0))) { |
| // You've defined a getter called get1st(). What were you thinking? |
| name = "_" + name; |
| if (!existingNames.contains(name)) { |
| return name; |
| } |
| } |
| for (int i = 0; ; i++) { |
| String candidate = name + i; |
| if (!existingNames.contains(candidate)) { |
| return candidate; |
| } |
| } |
| } |
| |
| /** |
| * Given a list of all methods defined in or inherited by a class, returns a map indicating which |
| * of equals, hashCode, and toString should be generated. Each value in the map is the method that |
| * will be overridden by the generated method, which might be a method in {@code Object} or an |
| * abstract method in the {@code @AutoValue} class or an ancestor. |
| */ |
| private static Map<ObjectMethod, ExecutableElement> determineObjectMethodsToGenerate( |
| Set<ExecutableElement> methods) { |
| Map<ObjectMethod, ExecutableElement> methodsToGenerate = new EnumMap<>(ObjectMethod.class); |
| for (ExecutableElement method : methods) { |
| ObjectMethod override = objectMethodToOverride(method); |
| boolean canGenerate = |
| method.getModifiers().contains(Modifier.ABSTRACT) |
| || isJavaLangObject((TypeElement) method.getEnclosingElement()); |
| if (!override.equals(ObjectMethod.NONE) && canGenerate) { |
| methodsToGenerate.put(override, method); |
| } |
| } |
| return methodsToGenerate; |
| } |
| |
| /** |
| * Returns the encoded parameter type of the {@code equals(Object)} method that is to be |
| * generated, or an empty string if the method is not being generated. The parameter type includes |
| * any type annotations, for example {@code @Nullable}. |
| */ |
| static String equalsParameterType(Map<ObjectMethod, ExecutableElement> methodsToGenerate) { |
| ExecutableElement equals = methodsToGenerate.get(ObjectMethod.EQUALS); |
| if (equals == null) { |
| return ""; // this will not be referenced because no equals method will be generated |
| } |
| TypeMirror parameterType = equals.getParameters().get(0).asType(); |
| return TypeEncoder.encodeWithAnnotations(parameterType); |
| } |
| |
| /** |
| * Returns the subset of all abstract methods in the given set of methods. A given method |
| * signature is only mentioned once, even if it is inherited on more than one path. If any of the |
| * abstract methods has a return type or parameter type that is not currently defined then this |
| * method will throw an exception that will cause us to defer processing of the current class |
| * until a later annotation-processing round. |
| */ |
| static ImmutableSet<ExecutableElement> abstractMethodsIn(Iterable<ExecutableElement> methods) { |
| Set<Name> noArgMethods = new HashSet<>(); |
| ImmutableSet.Builder<ExecutableElement> abstracts = ImmutableSet.builder(); |
| for (ExecutableElement method : methods) { |
| if (method.getModifiers().contains(Modifier.ABSTRACT)) { |
| MissingTypes.deferIfMissingTypesIn(method); |
| 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(); |
| } |
| |
| /** |
| * Returns the subset of property methods in the given set of abstract methods, with their actual |
| * return types. A property method has no arguments, is not void, and is not {@code hashCode()} or |
| * {@code toString()}. |
| */ |
| ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsIn( |
| Set<ExecutableElement> abstractMethods, |
| TypeElement autoValueOrOneOfType) { |
| DeclaredType declaredType = MoreTypes.asDeclared(autoValueOrOneOfType.asType()); |
| ImmutableSet.Builder<ExecutableElement> properties = ImmutableSet.builder(); |
| for (ExecutableElement method : abstractMethods) { |
| if (method.getParameters().isEmpty() |
| && (method.getReturnType().getKind() != TypeKind.VOID || propertiesCanBeVoid()) |
| && objectMethodToOverride(method) == ObjectMethod.NONE) { |
| properties.add(method); |
| } |
| } |
| return new EclipseHack(processingEnv).methodReturnTypes(properties.build(), declaredType); |
| } |
| |
| /** True if void properties are allowed. */ |
| boolean propertiesCanBeVoid() { |
| return false; |
| } |
| |
| /** |
| * Checks that the return type of the given property method is allowed. Currently, this means that |
| * it cannot be an array, unless it is a primitive array. |
| */ |
| final void 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); |
| } else { |
| errorReporter.reportError( |
| getter, |
| "An @%s class cannot define an array-valued property unless it is a primitive array", |
| simpleAnnotationName); |
| } |
| } |
| } |
| |
| private void warnAboutPrimitiveArrays(TypeElement autoValueClass, ExecutableElement getter) { |
| boolean suppressed = false; |
| Optional<AnnotationMirror> maybeAnnotation = |
| getAnnotationMirror(getter, "java.lang.SuppressWarnings"); |
| 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. |
| boolean sameClass = getter.getEnclosingElement().equals(autoValueClass); |
| Element element = sameClass ? getter : autoValueClass; |
| String context = sameClass ? "" : (" Method: " + getter.getEnclosingElement() + "." + getter); |
| errorReporter.reportWarning( |
| element, |
| "An @%s property that is a primitive array returns the original array, which can" |
| + " therefore be modified by the caller. If this is 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.%s", |
| simpleAnnotationName, |
| context); |
| } |
| } |
| |
| // 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 SimpleAnnotationValueVisitor8<Boolean, Void> { |
| @Override |
| public Boolean visitArray(List<? extends AnnotationValue> list, Void p) { |
| return list.stream().map(av -> av.getValue()).anyMatch("mutable"::equals); |
| } |
| } |
| |
| /** |
| * Returns a string like {@code "1234L"} if {@code type instanceof Serializable} and defines |
| * {@code serialVersionUID = 1234L}; otherwise {@code ""}. |
| */ |
| final String getSerialVersionUID(TypeElement type) { |
| TypeMirror serializable = elementUtils().getTypeElement(Serializable.class.getName()).asType(); |
| 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( |
| field, "serialVersionUID must be a static final long compile-time constant"); |
| break; |
| } |
| } |
| } |
| } |
| return ""; |
| } |
| |
| /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */ |
| ImmutableList<AnnotationMirror> annotationsToCopy( |
| Element autoValueType, Element typeOrMethod, Set<String> excludedAnnotations) { |
| ImmutableList.Builder<AnnotationMirror> result = ImmutableList.builder(); |
| for (AnnotationMirror annotation : typeOrMethod.getAnnotationMirrors()) { |
| String annotationFqName = getAnnotationFqName(annotation); |
| // To be included, the annotation should not be in com.google.auto.value, |
| // and it should not be in the excludedAnnotations set. |
| if (!isInAutoValuePackage(annotationFqName) |
| && !excludedAnnotations.contains(annotationFqName) |
| && annotationVisibleFrom(annotation, autoValueType)) { |
| result.add(annotation); |
| } |
| } |
| |
| return result.build(); |
| } |
| |
| /** |
| * True if the given class name is in the com.google.auto.value package or a subpackage. False if |
| * the class name contains {@code Test}, since many AutoValue tests under com.google.auto.value |
| * define their own annotations. |
| */ |
| private boolean isInAutoValuePackage(String className) { |
| return className.startsWith(AUTO_VALUE_PACKAGE_NAME) && !className.contains("Test"); |
| } |
| |
| ImmutableList<String> copiedClassAnnotations(TypeElement type) { |
| // Only copy annotations from a class if it has @AutoValue.CopyAnnotations. |
| if (hasAnnotationMirror(type, COPY_ANNOTATIONS_NAME)) { |
| Set<String> excludedAnnotations = |
| union(getExcludedAnnotationClassNames(type), getAnnotationsMarkedWithInherited(type)); |
| |
| return copyAnnotations(type, type, excludedAnnotations); |
| } else { |
| return ImmutableList.of(); |
| } |
| } |
| |
| /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */ |
| private ImmutableList<String> copyAnnotations( |
| Element autoValueType, Element typeOrMethod, Set<String> excludedAnnotations) { |
| ImmutableList<AnnotationMirror> annotationsToCopy = |
| annotationsToCopy(autoValueType, typeOrMethod, excludedAnnotations); |
| return annotationStrings(annotationsToCopy); |
| } |
| |
| /** |
| * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of |
| * {@code TypeMirror} where each type is an annotation type. |
| */ |
| private Set<TypeMirror> getExcludedAnnotationTypes(Element element) { |
| Optional<AnnotationMirror> maybeAnnotation = |
| getAnnotationMirror(element, COPY_ANNOTATIONS_NAME); |
| if (!maybeAnnotation.isPresent()) { |
| return ImmutableSet.of(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| List<AnnotationValue> excludedClasses = |
| (List<AnnotationValue>) getAnnotationValue(maybeAnnotation.get(), "exclude").getValue(); |
| return excludedClasses |
| .stream() |
| .map(annotationValue -> (DeclaredType) annotationValue.getValue()) |
| .collect(toCollection(TypeMirrorSet::new)); |
| } |
| |
| /** |
| * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of |
| * strings that are fully-qualified class names. |
| */ |
| private Set<String> getExcludedAnnotationClassNames(Element element) { |
| return getExcludedAnnotationTypes(element) |
| .stream() |
| .map(MoreTypes::asTypeElement) |
| .map(typeElement -> typeElement.getQualifiedName().toString()) |
| .collect(toSet()); |
| } |
| |
| private static Set<String> getAnnotationsMarkedWithInherited(Element element) { |
| return element |
| .getAnnotationMirrors() |
| .stream() |
| .filter(a -> isAnnotationPresent(a.getAnnotationType().asElement(), Inherited.class)) |
| .map(a -> getAnnotationFqName(a)) |
| .collect(toSet()); |
| } |
| |
| /** |
| * Returns the fully-qualified name of an annotation-mirror, e.g. |
| * "com.google.auto.value.AutoValue". |
| */ |
| private static String getAnnotationFqName(AnnotationMirror annotation) { |
| return ((QualifiedNameable) annotation.getAnnotationType().asElement()) |
| .getQualifiedName() |
| .toString(); |
| } |
| |
| final ImmutableListMultimap<ExecutableElement, AnnotationMirror> propertyMethodAnnotationMap( |
| TypeElement type, ImmutableSet<ExecutableElement> propertyMethods) { |
| ImmutableListMultimap.Builder<ExecutableElement, AnnotationMirror> builder = |
| ImmutableListMultimap.builder(); |
| for (ExecutableElement propertyMethod : propertyMethods) { |
| builder.putAll(propertyMethod, propertyMethodAnnotations(type, propertyMethod)); |
| } |
| return builder.build(); |
| } |
| |
| private ImmutableList<AnnotationMirror> propertyMethodAnnotations( |
| TypeElement type, ExecutableElement method) { |
| ImmutableSet<String> excludedAnnotations = |
| ImmutableSet.<String>builder() |
| .addAll(getExcludedAnnotationClassNames(method)) |
| .add(Override.class.getCanonicalName()) |
| .build(); |
| |
| // We need to exclude type annotations from the ones being output on the method, since |
| // they will be output as part of the method's return type. |
| Set<String> returnTypeAnnotations = getReturnTypeAnnotations(method, a -> true); |
| Set<String> excluded = union(excludedAnnotations, returnTypeAnnotations); |
| return annotationsToCopy(type, method, excluded); |
| } |
| |
| final ImmutableListMultimap<ExecutableElement, AnnotationMirror> propertyFieldAnnotationMap( |
| TypeElement type, ImmutableSet<ExecutableElement> propertyMethods) { |
| ImmutableListMultimap.Builder<ExecutableElement, AnnotationMirror> builder = |
| ImmutableListMultimap.builder(); |
| for (ExecutableElement propertyMethod : propertyMethods) { |
| builder.putAll(propertyMethod, propertyFieldAnnotations(type, propertyMethod)); |
| } |
| return builder.build(); |
| } |
| |
| private ImmutableList<AnnotationMirror> propertyFieldAnnotations( |
| TypeElement type, ExecutableElement method) { |
| if (!hasAnnotationMirror(method, COPY_ANNOTATIONS_NAME)) { |
| return ImmutableList.of(); |
| } |
| ImmutableSet<String> excludedAnnotations = |
| ImmutableSet.<String>builder() |
| .addAll(getExcludedAnnotationClassNames(method)) |
| .add(Override.class.getCanonicalName()) |
| .build(); |
| |
| // We need to exclude type annotations from the ones being output on the method, since |
| // they will be output as part of the field's type. |
| Set<String> returnTypeAnnotations = |
| getReturnTypeAnnotations(method, this::annotationAppliesToFields); |
| Set<String> nonFieldAnnotations = |
| method |
| .getAnnotationMirrors() |
| .stream() |
| .map(a -> a.getAnnotationType().asElement()) |
| .map(MoreElements::asType) |
| .filter(a -> !annotationAppliesToFields(a)) |
| .map(e -> e.getQualifiedName().toString()) |
| .collect(toSet()); |
| |
| Set<String> excluded = |
| ImmutableSet.<String>builder() |
| .addAll(excludedAnnotations) |
| .addAll(returnTypeAnnotations) |
| .addAll(nonFieldAnnotations) |
| .build(); |
| return annotationsToCopy(type, method, excluded); |
| } |
| |
| private Set<String> getReturnTypeAnnotations( |
| ExecutableElement method, Predicate<TypeElement> typeFilter) { |
| return method |
| .getReturnType() |
| .getAnnotationMirrors() |
| .stream() |
| .map(a -> a.getAnnotationType().asElement()) |
| .map(MoreElements::asType) |
| .filter(typeFilter) |
| .map(e -> e.getQualifiedName().toString()) |
| .collect(toSet()); |
| } |
| |
| private boolean annotationAppliesToFields(TypeElement annotation) { |
| Target target = annotation.getAnnotation(Target.class); |
| return target == null || Arrays.asList(target.value()).contains(ElementType.FIELD); |
| } |
| |
| private boolean annotationVisibleFrom(AnnotationMirror annotation, Element from) { |
| Element annotationElement = annotation.getAnnotationType().asElement(); |
| Visibility visibility = Visibility.effectiveVisibilityOfElement(annotationElement); |
| switch (visibility) { |
| case PUBLIC: |
| return true; |
| case PROTECTED: |
| // If the annotation is protected, it must be inside another class, call it C. If our |
| // @AutoValue class is Foo then, for the annotation to be visible, either Foo must be in the |
| // same package as C or Foo must be a subclass of C. If the annotation is visible from Foo |
| // then it is also visible from our generated subclass AutoValue_Foo. |
| // The protected case only applies to method annotations. An annotation on the AutoValue_Foo |
| // class itself can't be protected, even if AutoValue_Foo ultimately inherits from the |
| // class that defines the annotation. The JLS says "Access is permitted only within the |
| // body of a subclass": |
| // https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.6.2.1 |
| // AutoValue_Foo is a top-level class, so an annotation on it cannot be in the body of a |
| // subclass of anything. |
| return getPackage(annotationElement).equals(getPackage(from)) |
| || typeUtils() |
| .isSubtype(from.asType(), annotationElement.getEnclosingElement().asType()); |
| case DEFAULT: |
| return getPackage(annotationElement).equals(getPackage(from)); |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * Returns the {@code @AutoValue} or {@code @AutoOneOf} type parameters, with a ? for every type. |
| * If we have {@code @AutoValue abstract class Foo<T extends Something>} then this method will |
| * return just {@code <?>}. |
| */ |
| private static String wildcardTypeParametersString(TypeElement type) { |
| List<? extends TypeParameterElement> typeParameters = type.getTypeParameters(); |
| if (typeParameters.isEmpty()) { |
| return ""; |
| } else { |
| return typeParameters.stream().map(e -> "?").collect(joining(", ", "<", ">")); |
| } |
| } |
| |
| // TODO(emcmanus,ronshapiro): move to auto-common |
| static Optional<AnnotationMirror> getAnnotationMirror(Element element, String annotationName) { |
| for (AnnotationMirror annotation : element.getAnnotationMirrors()) { |
| TypeElement annotationElement = MoreTypes.asTypeElement(annotation.getAnnotationType()); |
| if (annotationElement.getQualifiedName().contentEquals(annotationName)) { |
| return Optional.of(annotation); |
| } |
| } |
| return Optional.empty(); |
| } |
| |
| static boolean hasAnnotationMirror(Element element, String annotationName) { |
| return getAnnotationMirror(element, annotationName).isPresent(); |
| } |
| |
| final void writeSourceFile(String className, String text, TypeElement originatingType) { |
| try { |
| JavaFileObject sourceFile = |
| processingEnv.getFiler().createSourceFile(className, originatingType); |
| try (Writer writer = sourceFile.openWriter()) { |
| writer.write(text); |
| } |
| } 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. |
| errorReporter.reportWarning( |
| originatingType, "Could not write generated class %s: %s", className, e); |
| } |
| } |
| } |