diff --git a/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java b/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java index 343645ae27..375864e30f 100644 --- a/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java +++ b/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java @@ -15,13 +15,16 @@ */ package com.google.auto.value.extension; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.SupportedOptions; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; @@ -156,6 +159,32 @@ default Map propertyTypes() { */ Set abstractMethods(); + /** + * Returns the complete list of annotations defined on the {@code classToCopyFrom} that should + * be added to any generated subclass. Only annotations visible to the {@code @AutoValue} will + * be present. See {@link com.google.auto.value.AutoValue.CopyAnnotations + * AutoValue.CopyAnnotations} for more information. + * + *

The default implementation of this method returns an empty list for compatibility with + * extensions which may have implemented this interface themselves. + */ + default List classAnnotationsToCopy(TypeElement classToCopyFrom) { + return ImmutableList.of(); + } + + /** + * Returns the complete list of annotations defined on the {@code method} that should be applied + * to any override of that method. Only annotations visible to the {@code @AutoValue} will be + * present. See {@link com.google.auto.value.AutoValue.CopyAnnotations + * AutoValue.CopyAnnotations} for more information. + * + *

The default implementation of this method returns an empty list for compatibility with + * extensions which may have implemented this interface themselves. + */ + default List methodAnnotationsToCopy(ExecutableElement method) { + return ImmutableList.of(); + } + /** * Returns a representation of the {@code Builder} associated with the {@code @AutoValue} class, * if there is one. diff --git a/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java index acbe1c032c..abcc5ccd76 100644 --- a/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java +++ b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java @@ -15,10 +15,7 @@ */ package com.google.auto.value.extension.memoized.processor; -import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec; -import static com.google.auto.common.MoreElements.getPackage; -import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.auto.common.MoreStreams.toImmutableList; import static com.google.auto.common.MoreStreams.toImmutableSet; import static com.google.auto.value.extension.memoized.processor.ClassNames.MEMOIZED_NAME; @@ -27,13 +24,11 @@ import static com.google.common.base.Predicates.not; import static com.google.common.collect.Iterables.filter; import static com.google.common.collect.Iterables.getOnlyElement; -import static com.google.common.collect.Sets.union; import static com.squareup.javapoet.MethodSpec.constructorBuilder; import static com.squareup.javapoet.MethodSpec.methodBuilder; import static com.squareup.javapoet.TypeSpec.classBuilder; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; @@ -46,11 +41,8 @@ import static javax.tools.Diagnostic.Kind.ERROR; import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; -import com.google.auto.common.Visibility; import com.google.auto.service.AutoService; import com.google.auto.value.extension.AutoValueExtension; -import com.google.common.base.Equivalence.Wrapper; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.FormatMethod; @@ -64,19 +56,14 @@ import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; -import java.lang.annotation.Inherited; import java.util.List; import java.util.Optional; -import java.util.Set; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; 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.ExecutableElement; import javax.lang.model.element.Modifier; -import javax.lang.model.element.QualifiedNameable; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; @@ -92,11 +79,6 @@ public final class MemoizeExtension extends AutoValueExtension { private static final ImmutableSet DO_NOT_PULL_DOWN_ANNOTATIONS = ImmutableSet.of(Override.class.getCanonicalName(), MEMOIZED_NAME); - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private static final String AUTO_VALUE_PACKAGE_NAME = "com.google.auto.value."; - private static final String AUTO_VALUE_NAME = AUTO_VALUE_PACKAGE_NAME + "AutoValue"; - private static final String COPY_ANNOTATIONS_NAME = AUTO_VALUE_NAME + ".CopyAnnotations"; - // Maven is configured to shade (rewrite) com.google packages to prevent dependency conflicts. // Split up the package here with a call to concat to prevent Maven from finding and rewriting it, // so that this will be able to find the LazyInit annotation if it's on the classpath. @@ -156,7 +138,10 @@ String generate() { TypeSpec.Builder generated = classBuilder(className) .superclass(superType()) - .addAnnotations(copiedClassAnnotations(context.autoValueClass())) + .addAnnotations( + context.classAnnotationsToCopy(context.autoValueClass()).stream() + .map(AnnotationSpec::get) + .collect(toImmutableList())) .addTypeVariables(annotatedTypeVariableNames()) .addModifiers(isFinal ? FINAL : ABSTRACT) .addMethod(constructor()); @@ -252,146 +237,6 @@ private MethodSpec equalsWithHashCodeCheck() { .build(); } - // LINT.IfChange - /** - * 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. - */ - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private boolean isInAutoValuePackage(String className) { - return className.startsWith(AUTO_VALUE_PACKAGE_NAME) && !className.contains("Test"); - } - - /** - * Returns the fully-qualified name of an annotation-mirror, e.g. - * "com.google.auto.value.AutoValue". - */ - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private static String getAnnotationFqName(AnnotationMirror annotation) { - return ((QualifiedNameable) annotation.getAnnotationType().asElement()) - .getQualifiedName() - .toString(); - } - - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - 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)) - || types.isSubtype(from.asType(), annotationElement.getEnclosingElement().asType()); - case DEFAULT: - return getPackage(annotationElement).equals(getPackage(from)); - default: - return false; - } - } - - /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */ - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private ImmutableList annotationsToCopy( - Element autoValueType, Element typeOrMethod, Set excludedAnnotations) { - ImmutableList.Builder 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(); - } - - /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */ - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private ImmutableList copyAnnotations( - Element autoValueType, Element typeOrMethod, Set excludedAnnotations) { - ImmutableList annotationsToCopy = - annotationsToCopy(autoValueType, typeOrMethod, excludedAnnotations); - return annotationsToCopy.stream().map(AnnotationSpec::get).collect(toImmutableList()); - } - - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private static boolean hasAnnotationMirror(Element element, String annotationName) { - return getAnnotationMirror(element, annotationName).isPresent(); - } - - /** - * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of - * {@code TypeMirror} where each type is an annotation type. - */ - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private ImmutableSet getExcludedAnnotationTypes(Element element) { - Optional maybeAnnotation = - getAnnotationMirror(element, COPY_ANNOTATIONS_NAME); - if (!maybeAnnotation.isPresent()) { - return ImmutableSet.of(); - } - - @SuppressWarnings("unchecked") - List excludedClasses = - (List) getAnnotationValue(maybeAnnotation.get(), "exclude").getValue(); - return excludedClasses.stream() - .map( - annotationValue -> - MoreTypes.equivalence().wrap((TypeMirror) annotationValue.getValue())) - // TODO(b/122509249): Move TypeMirrorSet to common package instead of doing this. - .distinct() - .map(Wrapper::get) - .collect(toImmutableSet()); - } - - /** - * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of - * strings that are fully-qualified class names. - */ - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private Set getExcludedAnnotationClassNames(Element element) { - return getExcludedAnnotationTypes(element).stream() - .map(MoreTypes::asTypeElement) - .map(typeElement -> typeElement.getQualifiedName().toString()) - .collect(toSet()); - } - - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private static Set getAnnotationsMarkedWithInherited(Element element) { - return element.getAnnotationMirrors().stream() - .filter(a -> isAnnotationPresent(a.getAnnotationType().asElement(), Inherited.class)) - .map(Generator::getAnnotationFqName) - .collect(toSet()); - } - - private ImmutableList copiedClassAnnotations(TypeElement type) { - // Only copy annotations from a class if it has @AutoValue.CopyAnnotations. - if (hasAnnotationMirror(type, COPY_ANNOTATIONS_NAME)) { - Set excludedAnnotations = - union(getExcludedAnnotationClassNames(type), getAnnotationsMarkedWithInherited(type)); - - return copyAnnotations(type, type, excludedAnnotations); - } else { - return ImmutableList.of(); - } - } - /** * Determines the required fields and overriding method for a {@link * com.google.auto.value.extension.memoized.Memoized @Memoized} method. @@ -416,7 +261,7 @@ private final class MethodOverrider { .addExceptions( method.getThrownTypes().stream().map(TypeName::get).collect(toList())) .addModifiers(filter(method.getModifiers(), not(equalTo(ABSTRACT)))); - for (AnnotationMirror annotation : method.getAnnotationMirrors()) { + for (AnnotationMirror annotation : context.methodAnnotationsToCopy(method)) { AnnotationSpec annotationSpec = AnnotationSpec.get(annotation); if (pullDownMethodAnnotation(annotation)) { override.addAnnotation(annotationSpec); diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java index e2381f7eff..e7578911bd 100644 --- a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java +++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java @@ -16,29 +16,18 @@ package com.google.auto.value.extension.toprettystring.processor; -import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec; -import static com.google.auto.common.MoreElements.getPackage; -import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.auto.common.MoreStreams.toImmutableList; -import static com.google.auto.common.MoreStreams.toImmutableSet; -import static com.google.auto.value.extension.toprettystring.processor.Annotations.getAnnotationMirror; -import static com.google.common.collect.Sets.union; import static com.squareup.javapoet.MethodSpec.constructorBuilder; import static com.squareup.javapoet.TypeSpec.classBuilder; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.FINAL; -import com.google.auto.common.MoreTypes; -import com.google.auto.common.Visibility; import com.google.auto.value.extension.AutoValueExtension; import com.google.auto.value.extension.AutoValueExtension.Context; -import com.google.common.base.Equivalence.Wrapper; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; @@ -46,19 +35,10 @@ import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; -import java.lang.annotation.Inherited; import java.util.List; -import java.util.Optional; -import java.util.Set; 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.QualifiedNameable; -import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; /** * A factory for {@link TypeSpec}s used in {@link AutoValueExtension} implementations. @@ -68,15 +48,11 @@ * location to consolidate the code. */ final class ExtensionClassTypeSpecBuilder { - private static final String AUTO_VALUE_PACKAGE_NAME = "com.google.auto.value."; - private static final String AUTO_VALUE_NAME = AUTO_VALUE_PACKAGE_NAME + "AutoValue"; - private static final String COPY_ANNOTATIONS_NAME = AUTO_VALUE_NAME + ".CopyAnnotations"; private final Context context; private final String className; private final String classToExtend; private final boolean isFinal; - private final Types types; private final Elements elements; private final SourceVersion sourceVersion; @@ -86,7 +62,6 @@ private ExtensionClassTypeSpecBuilder( this.className = className; this.classToExtend = classToExtend; this.isFinal = isFinal; - this.types = context.processingEnvironment().getTypeUtils(); this.elements = context.processingEnvironment().getElementUtils(); this.sourceVersion = context.processingEnvironment().getSourceVersion(); } @@ -101,7 +76,10 @@ TypeSpec.Builder extensionClassBuilder() { TypeSpec.Builder builder = classBuilder(className) .superclass(superType()) - .addAnnotations(copiedClassAnnotations(context.autoValueClass())) + .addAnnotations( + context.classAnnotationsToCopy(context.autoValueClass()).stream() + .map(AnnotationSpec::get) + .collect(toImmutableList())) .addTypeVariables(annotatedTypeVariableNames()) .addModifiers(isFinal ? FINAL : ABSTRACT) .addMethod(constructor()); @@ -148,145 +126,6 @@ private MethodSpec constructor() { return constructor.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. - */ - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private boolean isInAutoValuePackage(String className) { - return className.startsWith(AUTO_VALUE_PACKAGE_NAME) && !className.contains("Test"); - } - - /** - * Returns the fully-qualified name of an annotation-mirror, e.g. - * "com.google.auto.value.AutoValue". - */ - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private static String getAnnotationFqName(AnnotationMirror annotation) { - return ((QualifiedNameable) annotation.getAnnotationType().asElement()) - .getQualifiedName() - .toString(); - } - - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - 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)) - || types.isSubtype(from.asType(), annotationElement.getEnclosingElement().asType()); - case DEFAULT: - return getPackage(annotationElement).equals(getPackage(from)); - default: - return false; - } - } - - /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */ - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private ImmutableList annotationsToCopy( - Element autoValueType, Element typeOrMethod, Set excludedAnnotations) { - ImmutableList.Builder 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(); - } - - /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */ - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private ImmutableList copyAnnotations( - Element autoValueType, Element typeOrMethod, Set excludedAnnotations) { - ImmutableList annotationsToCopy = - annotationsToCopy(autoValueType, typeOrMethod, excludedAnnotations); - return annotationsToCopy.stream().map(AnnotationSpec::get).collect(toImmutableList()); - } - - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private static boolean hasAnnotationMirror(Element element, String annotationName) { - return getAnnotationMirror(element, annotationName).isPresent(); - } - - /** - * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of - * {@code TypeMirror} where each type is an annotation type. - */ - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private ImmutableSet getExcludedAnnotationTypes(Element element) { - Optional maybeAnnotation = - getAnnotationMirror(element, COPY_ANNOTATIONS_NAME); - if (!maybeAnnotation.isPresent()) { - return ImmutableSet.of(); - } - - @SuppressWarnings("unchecked") - List excludedClasses = - (List) getAnnotationValue(maybeAnnotation.get(), "exclude").getValue(); - return excludedClasses.stream() - .map( - annotationValue -> - MoreTypes.equivalence().wrap((TypeMirror) annotationValue.getValue())) - // TODO(b/122509249): Move TypeMirrorSet to common package instead of doing this. - .distinct() - .map(Wrapper::get) - .collect(toImmutableSet()); - } - - /** - * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of - * strings that are fully-qualified class names. - */ - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private Set getExcludedAnnotationClassNames(Element element) { - return getExcludedAnnotationTypes(element).stream() - .map(MoreTypes::asTypeElement) - .map(typeElement -> typeElement.getQualifiedName().toString()) - .collect(toSet()); - } - - // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common. - private static Set getAnnotationsMarkedWithInherited(Element element) { - return element.getAnnotationMirrors().stream() - .filter(a -> isAnnotationPresent(a.getAnnotationType().asElement(), Inherited.class)) - .map(ExtensionClassTypeSpecBuilder::getAnnotationFqName) - .collect(toSet()); - } - - private ImmutableList copiedClassAnnotations(TypeElement type) { - // Only copy annotations from a class if it has @AutoValue.CopyAnnotations. - if (hasAnnotationMirror(type, COPY_ANNOTATIONS_NAME)) { - Set excludedAnnotations = - union(getExcludedAnnotationClassNames(type), getAnnotationsMarkedWithInherited(type)); - - return copyAnnotations(type, type, excludedAnnotations); - } else { - return ImmutableList.of(); - } - } - /** Translate a {@link TypeMirror} into a {@link TypeName}, including type annotations. */ private static TypeName annotatedType(TypeMirror type) { List annotations = diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java index d25b7bec04..73815f283a 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java @@ -438,7 +438,7 @@ private void defineVarsForType( ImmutableListMultimap annotatedPropertyFields = propertyFieldAnnotationMap(type, propertyMethods); ImmutableListMultimap annotatedPropertyMethods = - propertyMethodAnnotationMap(type, propertyMethods); + propertyMethodAnnotationMap(type, propertyMethods, typeUtils()); vars.props = propertySet( propertyMethodsAndTypes, diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java index 70ce8dae08..2cb5a165cb 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java @@ -1031,8 +1031,11 @@ final String getSerialVersionUID(TypeElement type) { } /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */ - ImmutableList annotationsToCopy( - Element autoValueType, Element typeOrMethod, Set excludedAnnotations) { + static ImmutableList annotationsToCopy( + Element autoValueType, + Element typeOrMethod, + Set excludedAnnotations, + Types typeUtils) { ImmutableList.Builder result = ImmutableList.builder(); for (AnnotationMirror annotation : typeOrMethod.getAnnotationMirrors()) { String annotationFqName = getAnnotationFqName(annotation); @@ -1040,7 +1043,7 @@ ImmutableList annotationsToCopy( // and it should not be in the excludedAnnotations set. if (!isInAutoValuePackage(annotationFqName) && !excludedAnnotations.contains(annotationFqName) - && annotationVisibleFrom(annotation, autoValueType)) { + && annotationVisibleFrom(annotation, autoValueType, typeUtils)) { result.add(annotation); } } @@ -1053,7 +1056,7 @@ && annotationVisibleFrom(annotation, autoValueType)) { * the class name contains {@code Test}, since many AutoValue tests under com.google.auto.value * define their own annotations. */ - private boolean isInAutoValuePackage(String className) { + private static boolean isInAutoValuePackage(String className) { return className.startsWith(AUTO_VALUE_PACKAGE_NAME) && !className.contains("Test"); } @@ -1088,10 +1091,10 @@ ImmutableList copiedClassAnnotations(TypeElement type) { } /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */ - private ImmutableList copyAnnotations( + ImmutableList copyAnnotations( Element autoValueType, Element typeOrMethod, Set excludedAnnotations) { ImmutableList annotationsToCopy = - annotationsToCopy(autoValueType, typeOrMethod, excludedAnnotations); + annotationsToCopy(autoValueType, typeOrMethod, excludedAnnotations, typeUtils()); return annotationStrings(annotationsToCopy); } @@ -1099,7 +1102,7 @@ private ImmutableList copyAnnotations( * 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 getExcludedAnnotationTypes(Element element) { + private static Set getExcludedAnnotationTypes(Element element) { Optional maybeAnnotation = getAnnotationMirror(element, COPY_ANNOTATIONS_NAME); if (!maybeAnnotation.isPresent()) { @@ -1118,14 +1121,14 @@ private Set getExcludedAnnotationTypes(Element element) { * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of * strings that are fully-qualified class names. */ - private Set getExcludedAnnotationClassNames(Element element) { + static Set getExcludedAnnotationClassNames(Element element) { return getExcludedAnnotationTypes(element).stream() .map(MoreTypes::asTypeElement) .map(typeElement -> typeElement.getQualifiedName().toString()) .collect(toSet()); } - private static Set getAnnotationsMarkedWithInherited(Element element) { + static Set getAnnotationsMarkedWithInherited(Element element) { return element.getAnnotationMirrors().stream() .filter(a -> isAnnotationPresent(a.getAnnotationType().asElement(), Inherited.class)) .map(a -> getAnnotationFqName(a)) @@ -1142,18 +1145,18 @@ private static String getAnnotationFqName(AnnotationMirror annotation) { .toString(); } - final ImmutableListMultimap propertyMethodAnnotationMap( - TypeElement type, ImmutableSet propertyMethods) { + static ImmutableListMultimap propertyMethodAnnotationMap( + TypeElement type, ImmutableSet propertyMethods, Types typeUtils) { ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); for (ExecutableElement propertyMethod : propertyMethods) { - builder.putAll(propertyMethod, propertyMethodAnnotations(type, propertyMethod)); + builder.putAll(propertyMethod, propertyMethodAnnotations(type, propertyMethod, typeUtils)); } return builder.build(); } - private ImmutableList propertyMethodAnnotations( - TypeElement type, ExecutableElement method) { + static ImmutableList propertyMethodAnnotations( + TypeElement type, ExecutableElement method, Types typeUtils) { ImmutableSet excludedAnnotations = ImmutableSet.builder() .addAll(getExcludedAnnotationClassNames(method)) @@ -1164,7 +1167,7 @@ private ImmutableList propertyMethodAnnotations( // they will be output as part of the method's return type. Set returnTypeAnnotations = getReturnTypeAnnotations(method, a -> true); Set excluded = union(excludedAnnotations, returnTypeAnnotations); - return annotationsToCopy(type, method, excluded); + return annotationsToCopy(type, method, excluded, typeUtils); } final ImmutableListMultimap propertyFieldAnnotationMap( @@ -1206,10 +1209,10 @@ private ImmutableList propertyFieldAnnotations( .addAll(returnTypeAnnotations) .addAll(nonFieldAnnotations) .build(); - return annotationsToCopy(type, method, excluded); + return annotationsToCopy(type, method, excluded, typeUtils()); } - private Set getReturnTypeAnnotations( + private static Set getReturnTypeAnnotations( ExecutableElement method, Predicate typeFilter) { return method.getReturnType().getAnnotationMirrors().stream() .map(a -> a.getAnnotationType().asElement()) @@ -1224,7 +1227,8 @@ private boolean annotationAppliesToFields(TypeElement annotation) { return target == null || Arrays.asList(target.value()).contains(ElementType.FIELD); } - private boolean annotationVisibleFrom(AnnotationMirror annotation, Element from) { + private static boolean annotationVisibleFrom( + AnnotationMirror annotation, Element from, Types typeUtils) { Element annotationElement = annotation.getAnnotationType().asElement(); Visibility visibility = Visibility.effectiveVisibilityOfElement(annotationElement); switch (visibility) { @@ -1243,8 +1247,7 @@ private boolean annotationVisibleFrom(AnnotationMirror annotation, Element from) // 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()); + || typeUtils.isSubtype(from.asType(), annotationElement.getEnclosingElement().asType()); case DEFAULT: return getPackage(annotationElement).equals(getPackage(from)); default: diff --git a/value/src/main/java/com/google/auto/value/processor/ExtensionContext.java b/value/src/main/java/com/google/auto/value/processor/ExtensionContext.java index da844b0eed..ca48fdbeac 100644 --- a/value/src/main/java/com/google/auto/value/processor/ExtensionContext.java +++ b/value/src/main/java/com/google/auto/value/processor/ExtensionContext.java @@ -15,15 +15,20 @@ */ package com.google.auto.value.processor; +import static com.google.auto.value.processor.ClassNames.COPY_ANNOTATIONS_NAME; + import com.google.auto.value.extension.AutoValueExtension; import com.google.auto.value.extension.AutoValueExtension.BuilderContext; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; @@ -90,6 +95,44 @@ public Set abstractMethods() { return abstractMethods; } + @Override + public List classAnnotationsToCopy(TypeElement classToCopyFrom) { + // Only copy annotations from a class if it has @AutoValue.CopyAnnotations. + if (!AutoValueishProcessor.hasAnnotationMirror(classToCopyFrom, COPY_ANNOTATIONS_NAME)) { + return ImmutableList.of(); + } + + ImmutableSet excludedAnnotations = + ImmutableSet.builder() + .addAll(AutoValueishProcessor.getExcludedAnnotationClassNames(classToCopyFrom)) + .addAll(AutoValueishProcessor.getAnnotationsMarkedWithInherited(classToCopyFrom)) + // + // Kotlin classes have an intrinsic @Metadata annotation generated + // onto them by kotlinc. This annotation is specific to the annotated + // class and should not be implicitly copied. Doing so can mislead + // static analysis or metaprogramming tooling that reads the data + // contained in these annotations. + // + // It may be surprising to see AutoValue classes written in Kotlin + // when they could be written as Kotlin data classes, but this can + // come up in cases where consumers rely on AutoValue features or + // extensions that are not available in data classes. + // + // See: https://github.com/google/auto/issues/1087 + // + .add(ClassNames.KOTLIN_METADATA_NAME) + .build(); + + return AutoValueishProcessor.annotationsToCopy( + autoValueClass, classToCopyFrom, excludedAnnotations, processingEnvironment.getTypeUtils()); + } + + @Override + public List methodAnnotationsToCopy(ExecutableElement method) { + return AutoValueishProcessor.propertyMethodAnnotations( + autoValueClass, method, processingEnvironment.getTypeUtils()); + } + @Override public Optional builder() { return builderContext; diff --git a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java index 5beb686b32..e516b087ce 100644 --- a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java +++ b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java @@ -59,10 +59,25 @@ boolean getMemoizedNative0() { @CopyAnnotations @javax.annotation.Nullable abstract static class ValueWithCopyAnnotations { - abstract boolean getNative(); + abstract String getNative(); @Memoized - boolean getMemoizedNative() { + @javax.annotation.Nullable + public String getMemoizedNative() { + return getNative(); + } + } + + @AutoValue + @CopyAnnotations(exclude = javax.annotation.Nullable.class) + @javax.annotation.Nullable + abstract static class ValueWithExcludedCopyAnnotations { + abstract String getNative(); + + @Memoized + @CopyAnnotations(exclude = javax.annotation.Nullable.class) + @javax.annotation.Nullable + public String getMemoizedNative() { return getNative(); } } @@ -70,10 +85,11 @@ boolean getMemoizedNative() { @AutoValue @javax.annotation.Nullable abstract static class ValueWithoutCopyAnnotations { - abstract boolean getNative(); + abstract String getNative(); @Memoized - boolean getMemoizedNative() { + @javax.annotation.Nullable + public String getMemoizedNative() { return getNative(); } } @@ -351,20 +367,80 @@ public void keywords() { } @Test - public void copyAnnotations() { + public void copyClassAnnotations_valueWithCopyAnnotations_copiesAnnotation() throws Exception { ValueWithCopyAnnotations valueWithCopyAnnotations = - new AutoValue_MemoizedTest_ValueWithCopyAnnotations(true); + new AutoValue_MemoizedTest_ValueWithCopyAnnotations("test"); + + assertThat( + valueWithCopyAnnotations + .getClass() + .isAnnotationPresent(javax.annotation.Nullable.class)) + .isTrue(); + } + + @Test + public void copyClassAnnotations_valueWithoutCopyAnnotations_doesNotCopyAnnotation() + throws Exception { ValueWithoutCopyAnnotations valueWithoutCopyAnnotations = - new AutoValue_MemoizedTest_ValueWithoutCopyAnnotations(true); + new AutoValue_MemoizedTest_ValueWithoutCopyAnnotations("test"); + + assertThat( + valueWithoutCopyAnnotations + .getClass() + .isAnnotationPresent(javax.annotation.Nullable.class)) + .isFalse(); + } + + @Test + public void copyClassAnnotations_valueWithExcludedCopyAnnotations_doesNotCopyAnnotation() + throws Exception { + ValueWithExcludedCopyAnnotations valueWithExcludedCopyAnnotations = + new AutoValue_MemoizedTest_ValueWithExcludedCopyAnnotations("test"); + + assertThat( + valueWithExcludedCopyAnnotations + .getClass() + .isAnnotationPresent(javax.annotation.Nullable.class)) + .isFalse(); + } + + @Test + public void copyMethodAnnotations_valueWithCopyAnnotations_copiesAnnotation() throws Exception { + ValueWithCopyAnnotations valueWithCopyAnnotations = + new AutoValue_MemoizedTest_ValueWithCopyAnnotations("test"); assertThat( valueWithCopyAnnotations .getClass() + .getMethod("getMemoizedNative") .isAnnotationPresent(javax.annotation.Nullable.class)) .isTrue(); + } + + @Test + public void copyMethodAnnotations_valueWithoutCopyAnnotations_copiesAnnotation() + throws Exception { + ValueWithoutCopyAnnotations valueWithoutCopyAnnotations = + new AutoValue_MemoizedTest_ValueWithoutCopyAnnotations("test"); + assertThat( valueWithoutCopyAnnotations .getClass() + .getMethod("getMemoizedNative") + .isAnnotationPresent(javax.annotation.Nullable.class)) + .isTrue(); + } + + @Test + public void copyMethodAnnotations_valueWithExcludedCopyAnnotations_doesNotCopyAnnotation() + throws Exception { + ValueWithExcludedCopyAnnotations valueWithExcludedCopyAnnotations = + new AutoValue_MemoizedTest_ValueWithExcludedCopyAnnotations("test"); + + assertThat( + valueWithExcludedCopyAnnotations + .getClass() + .getMethod("getMemoizedNative") .isAnnotationPresent(javax.annotation.Nullable.class)) .isFalse(); }