Skip to content

Commit

Permalink
Add capability to annotate individual AutoValue fields
Browse files Browse the repository at this point in the history
RELNOTES=Add capability to annotate individual AutoValue fields

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=199491872
  • Loading branch information
croyer authored and ronshapiro committed Jun 12, 2018
1 parent 41d78d2 commit 96370f3
Show file tree
Hide file tree
Showing 8 changed files with 445 additions and 148 deletions.
40 changes: 34 additions & 6 deletions value/src/main/java/com/google/auto/value/AutoValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
*
* <pre>
*
* &#64;AutoValue
* {@code @}AutoValue
* abstract class Person {
* static Person create(String name, int id) {
* return new AutoValue_Person(name, id);
Expand All @@ -53,7 +53,7 @@
*
* <pre>
*
* &#64;AutoValue
* {@code @}AutoValue
* abstract class Person {
* static Builder builder() {
* return new AutoValue_Person.Builder();
Expand All @@ -62,7 +62,7 @@
* abstract String name();
* abstract int id();
*
* &#64;AutoValue.Builder
* {@code @}AutoValue.Builder
* interface Builder {
* Builder name(String x);
* Builder id(int x);
Expand Down Expand Up @@ -95,20 +95,48 @@
* from the {@code @AutoValue} class itself to its implementation unless {@code @CopyAnnotations}
* is present.
*
* <p>If you want to copy annotations from your {@literal @}AutoValue-annotated class's methods to
* the generated fields in the AutoValue_... implementation, annotate your method
* with {@literal @}AutoValue.CopyAnnotations. For example, if Example.java is:<pre>
* {@code @}Immutable
* {@code @}AutoValue
* abstract class Example {
* {@code @}CopyAnnotations
* {@code @}SuppressWarnings("Immutable") // justification ...
* abstract Object getObject();
* // other details ...
* }</pre>
*
* <p>Then AutoValue will generate the following AutoValue_Example.java:<pre>
*
* final class AutoValue_Example extends Example {
* {@code @}SuppressWarnings("Immutable")
* private final Object object;
*
* {@code @}SuppressWarnings("Immutable")
* {@code @}Override
* Object getObject() {
* return object;
* }
*
* // other details ...
* }</pre>
*
* <p>When the <i>type</i> of an {@code @AutoValue} property method has annotations, those are
* part of the type, so they are always copied to the implementation of the method.
* {@code @CopyAnnotations} has no effect here. For example, suppose {@code @Confidential} is a
* {@link java.lang.annotation.ElementType#TYPE_USE TYPE_USE} annotation:
*
* <pre>
*
* &#64;AutoValue
* {@code @}AutoValue
* abstract class Person {
* static Person create(&#64;Confidential String name, int id) {
* static Person create({@code @}Confidential String name, int id) {
* return new AutoValue_Person(name, id);
* }
*
* abstract &#64;Confidential String name();
* abstract {@code @}Confidential String name();
* abstract int id();
* }</pre>
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ private void defineVarsForType(
AutoOneOfTemplateVars vars,
ImmutableSet<ExecutableElement> propertyMethods,
ExecutableElement kindGetter) {
vars.props = propertySet(type, propertyMethods, ImmutableListMultimap.of());
vars.props =
propertySet(type, propertyMethods, ImmutableListMultimap.of(), ImmutableListMultimap.of());
vars.kindGetter = kindGetter.getSimpleName().toString();
vars.kindType = TypeEncoder.encode(kindGetter.getReturnType());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@
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;
Expand All @@ -51,6 +53,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
Expand Down Expand Up @@ -138,7 +141,8 @@ public static class Property {
private final String identifier;
private final ExecutableElement method;
private final String type;
private final ImmutableList<String> annotations;
private final ImmutableList<String> fieldAnnotations;
private final ImmutableList<String> methodAnnotations;
private final Optional<String> nullableAnnotation;
private final Optionalish optional;

Expand All @@ -147,13 +151,15 @@ public static class Property {
String identifier,
ExecutableElement method,
String type,
ImmutableList<String> annotations,
ImmutableList<String> fieldAnnotations,
ImmutableList<String> methodAnnotations,
Optional<String> nullableAnnotation) {
this.name = name;
this.identifier = identifier;
this.method = method;
this.type = type;
this.annotations = annotations;
this.fieldAnnotations = fieldAnnotations;
this.methodAnnotations = methodAnnotations;
this.nullableAnnotation = nullableAnnotation;
TypeMirror propertyType = method.getReturnType();
this.optional = Optionalish.createIfOptional(propertyType);
Expand Down Expand Up @@ -200,8 +206,20 @@ public TypeKind getKind() {
return method.getReturnType().getKind();
}

public List<String> getAnnotations() {
return annotations;
/**
* 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;
}

/**
Expand Down Expand Up @@ -342,6 +360,7 @@ public final boolean process(Set<? extends TypeElement> annotations, RoundEnviro
final ImmutableSet<Property> propertySet(
TypeElement type,
ImmutableSet<ExecutableElement> propertyMethods,
ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyFields,
ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyMethods) {
ImmutableBiMap<ExecutableElement, String> methodToPropertyName =
propertyNameToMethodMap(propertyMethods).inverse();
Expand All @@ -358,17 +377,20 @@ final ImmutableSet<Property> propertySet(
String propertyType = TypeEncoder.encodeWithAnnotations(returnType);
String propertyName = methodToPropertyName.get(propertyMethod);
String identifier = methodToIdentifier.get(propertyMethod);
ImmutableList<AnnotationMirror> annotationMirrors =
ImmutableList<String> fieldAnnotations =
annotationStrings(annotatedPropertyFields.get(propertyMethod));
ImmutableList<AnnotationMirror> methodAnnotationMirrors =
annotatedPropertyMethods.get(propertyMethod);
ImmutableList<String> annotations = annotationStrings(annotationMirrors);
ImmutableList<String> methodAnnotations = annotationStrings(methodAnnotationMirrors);
Optional<String> nullableAnnotation = nullableAnnotationForMethod(propertyMethod);
Property p =
new Property(
propertyName,
identifier,
propertyMethod,
propertyType,
annotations,
fieldAnnotations,
methodAnnotations,
nullableAnnotation);
props.add(p);
if (p.isNullable() && returnType.getKind().isPrimitive()) {
Expand Down Expand Up @@ -863,19 +885,73 @@ private ImmutableList<AnnotationMirror> propertyMethodAnnotations(

// 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(getExcludedClasses(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
.getReturnType()
.getAnnotationMirrors()
.stream()
.map(a -> a.getAnnotationType().asElement())
.map(MoreElements::asType)
.filter(a -> !annotationAppliesToFields(a))
.map(e -> e.getQualifiedName().toString())
.collect(toSet());
Set<String> excluded = union(excludedAnnotations, returnTypeAnnotations);

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,9 +377,12 @@ private void defineVarsForType(
ImmutableList.copyOf(toBuilderMethods.stream().map(SimpleMethod::new).collect(toList()));
ImmutableBiMap<ExecutableElement, String> methodToPropertyName =
propertyNameToMethodMap(propertyMethods).inverse();
ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyFields =
propertyFieldAnnotationMap(type, propertyMethods);
ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyMethods =
propertyMethodAnnotationMap(type, propertyMethods);
vars.props = propertySet(type, propertyMethods, annotatedPropertyMethods);
vars.props =
propertySet(type, propertyMethods, annotatedPropertyFields, annotatedPropertyMethods);
vars.serialVersionUID = getSerialVersionUID(type);
// Check for @AutoValue.Builder and add appropriate variables if it is present.
if (builder.isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ ${modifiers}class $subclass$formalTypes extends $origClass$actualTypes {
## Fields

#foreach ($p in $props)
#foreach ($a in ${p.fieldAnnotations})

${a}##
#end

private final $p.type $p;
#end

Expand Down Expand Up @@ -91,7 +96,7 @@ ${modifiers}class $subclass$formalTypes extends $origClass$actualTypes {

#foreach ($p in $props)

#foreach ($a in ${p.annotations})
#foreach ($a in ${p.methodAnnotations})

${a}##
#end
Expand Down
Loading

0 comments on commit 96370f3

Please sign in to comment.