Skip to content

Commit

Permalink
Make it easier to support @CopyAnnotations in AutoValueExtensions
Browse files Browse the repository at this point in the history
AutoValueExtension.Context now provides methods to lookup the annotations that should be copied to the class or methods.

RELNOTES=Make it easier to support @CopyAnnotations in AutoValueExtensions
PiperOrigin-RevId: 495638296
  • Loading branch information
java-team-github-bot authored and Google Java Core Libraries committed Dec 15, 2022
1 parent 0436f53 commit a3f218d
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 353 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -156,6 +159,32 @@ default Map<String, TypeMirror> propertyTypes() {
*/
Set<ExecutableElement> 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.
*
* <p>The default implementation of this method returns an empty list for compatibility with
* extensions which may have implemented this interface themselves.
*/
default List<AnnotationMirror> 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.
*
* <p>The default implementation of this method returns an empty list for compatibility with
* extensions which may have implemented this interface themselves.
*/
default List<AnnotationMirror> methodAnnotationsToCopy(ExecutableElement method) {
return ImmutableList.of();
}

/**
* Returns a representation of the {@code Builder} associated with the {@code @AutoValue} class,
* if there is one.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -92,11 +79,6 @@ public final class MemoizeExtension extends AutoValueExtension {
private static final ImmutableSet<String> 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.
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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<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();
}

/** 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<AnnotationSpec> copyAnnotations(
Element autoValueType, Element typeOrMethod, Set<String> excludedAnnotations) {
ImmutableList<AnnotationMirror> 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<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 ->
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<String> 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<String> getAnnotationsMarkedWithInherited(Element element) {
return element.getAnnotationMirrors().stream()
.filter(a -> isAnnotationPresent(a.getAnnotationType().asElement(), Inherited.class))
.map(Generator::getAnnotationFqName)
.collect(toSet());
}

private ImmutableList<AnnotationSpec> 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();
}
}

/**
* Determines the required fields and overriding method for a {@link
* com.google.auto.value.extension.memoized.Memoized @Memoized} method.
Expand All @@ -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);
Expand Down
Loading

0 comments on commit a3f218d

Please sign in to comment.