diff options
author | emcmanus <emcmanus@google.com> | 2019-09-30 10:16:42 -0700 |
---|---|---|
committer | Chris Povirk <beigetangerine@gmail.com> | 2019-09-30 14:21:51 -0400 |
commit | 86f456371a23353acfa6dc5aacf880eb10439fad (patch) | |
tree | b317e8283dc401c5b18cab6de01b223fad68612b /value/src/main/java/com | |
parent | 7646889ded2a74c4377ac8decece3b879209cf35 (diff) | |
download | auto-86f456371a23353acfa6dc5aacf880eb10439fad.tar.gz |
Add an API to allow extensions to find out about builders.
Fixes https://github.com/google/auto/issues/421.
RELNOTES=Add an API to allow AutoValue extensions to find out about builders.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=272009461
Diffstat (limited to 'value/src/main/java/com')
5 files changed, 216 insertions, 15 deletions
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 38783fe2..045abc23 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 @@ -17,6 +17,7 @@ package com.google.auto.value.extension; import com.google.common.collect.ImmutableSet; import java.util.Map; +import java.util.Optional; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; @@ -142,6 +143,129 @@ public abstract class AutoValueExtension { * method even if it has been consumed by this or another Extension. */ Set<ExecutableElement> abstractMethods(); + + /** + * Returns a representation of the {@code Builder} associated with the {@code @AutoValue} class, + * if there is one. + * + * <p>This method returns {@link Optional#empty()} if called from within the {@link #applicable} + * method. If an Extension needs {@code Builder} information to decide whether it is applicable, + * it should return {@code true} from the {@link #applicable} method and then return {@code + * null} from the {@link #generateClass} method if it does not need to generate a class after + * all. + * + * <p>The default implementation of this method returns {@link Optional#empty()} for + * compatibility with extensions which may have implemented this interface themselves. + */ + default Optional<BuilderContext> builder() { + return Optional.empty(); + } + } + + /** + * Represents a {@code Builder} associated with an {@code @AutoValue} class. + */ + public interface BuilderContext { + /** + * Returns the {@code @AutoValue.Builder} interface or abstract class that this object + * represents. + */ + TypeElement builderType(); + + /** + * Returns abstract no-argument methods in the {@code @AutoValue} class that return the builder + * type. + * + * <p>Consider a class like this: + * <pre> + * {@code @AutoValue} abstract class Foo { + * abstract String bar(); + * + * abstract Builder toBuilder(); + * + * ... + * {@code @AutoValue.Builder} + * abstract static class Builder {...} + * } + * </pre> + * + * <p>Here {@code toBuilderMethods()} will return a set containing the method + * {@code Foo.toBuilder()}. + */ + Set<ExecutableElement> toBuilderMethods(); + + /** + * Returns static no-argument methods in the {@code @AutoValue} class that return the builder + * type. + * + * <p>Consider a class like this: + * <pre> + * {@code @AutoValue} abstract class Foo { + * abstract String bar(); + * + * static Builder builder() { + * return new AutoValue_Foo.Builder() + * .setBar("default bar"); + * } + * + * {@code @AutoValue.Builder} + * abstract class Builder { + * abstract Builder setBar(String x); + * abstract Foo build(); + * } + * } + * </pre> + * + * <p>Here {@code builderMethods()} will return a set containing the method + * {@code Foo.builder()}. Generated code should usually call this method in preference to + * constructing {@code AutoValue_Foo.Builder()} directly, because this method can establish + * default values for properties, as it does here. + */ + Set<ExecutableElement> builderMethods(); + + /** + * Returns the method {@code build()} in the builder class, if it exists and returns the + * {@code @AutoValue} type. This is the method that generated code for + * {@code @AutoValue class Foo} should call in order to get an instance of {@code Foo} from its + * builder. The returned method is called {@code build()}; if the builder uses some other name + * then extensions have no good way to guess how they should build. + * + * <p>A common convention is for {@code build()} to be a concrete method in the + * {@code @AutoValue.Builder} class, which calls an abstract method {@code autoBuild()} that is + * implemented in the generated subclass. The {@code build()} method can then do validation, + * defaulting, and so on. + */ + Optional<ExecutableElement> buildMethod(); + + /** + * Returns the abstract build method. If the {@code @AutoValue} class is {@code Foo}, this is an + * abstract no-argument method in the builder class that returns {@code Foo}. This might be + * called {@code build()}, or, following a common convention, it might be called + * {@code autoBuild()} and used in the implementation of a {@code build()} method that is + * defined in the builder class. + * + * <p>Extensions should call the {@code build()} method in preference to this one. But they + * should override this one if they want to customize build-time behaviour. + */ + ExecutableElement autoBuildMethod(); + + /** + * Returns a map from property names to the corresponding setters. A property may have more than + * one setter. For example, an {@code ImmutableList<String>} might be set by + * {@code setFoo(ImmutableList<String>)} and {@code setFoo(String[])}. + */ + Map<String, Set<ExecutableElement>> setters(); + + /** + * Returns a map from property names to property builders. For example, if there is a property + * {@code foo} defined by {@code abstract ImmutableList<String> foo();} or + * {@code abstract ImmutableList<String> getFoo();} in the {@code @AutoValue} class, + * then there can potentially be a builder defined by + * {@code abstract ImmutableList.Builder<String> fooBuilder();} in the + * {@code @AutoValue.Builder} class. This map would then map {@code "foo"} to the + * {@link ExecutableElement} representing {@code fooBuilder()}. + */ + Map<String, ExecutableElement> propertyBuilders(); } /** @@ -221,11 +345,12 @@ public abstract class AutoValueExtension { } /** - * Determines whether this Extension applies to the given context. + * Determines whether this Extension applies to the given context. If an Extension returns {@code + * false} for a given class, it will not be called again during the processing of that class. An + * Extension can return {@code true} and still choose not to generate any code for the class, by + * returning {@code null} from {@link #generateClass}. That is often a more flexible approach. * * @param context The Context of the code generation for this class. - * @return true if this Extension should be applied in the given context. If an Extension returns - * false for a given class, it will not be called again during the processing of that class. */ public boolean applicable(Context context) { return false; 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 3c2bbd78..84b61462 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 @@ -250,6 +250,7 @@ public class AutoValueProcessor extends AutoValueOrOneOfProcessor { GwtCompatibility gwtCompatibility = new GwtCompatibility(type); vars.gwtCompatibleAnnotation = gwtCompatibility.gwtCompatibleAnnotationString(); + builder.ifPresent(context::setBuilderContext); int subclassDepth = writeExtensions(type, context, applicableExtensions); String subclass = generatedSubclassName(type, subclassDepth); vars.subclass = TypeSimplifier.simpleNameOf(subclass); diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java index 9cebcad1..f51e895c 100644 --- a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java +++ b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java @@ -20,15 +20,22 @@ import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.hasAnnot import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_BUILDER_NAME; import static com.google.common.collect.Sets.immutableEnumSet; import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static javax.lang.model.util.ElementFilter.methodsIn; +import static javax.lang.model.util.ElementFilter.typesIn; import com.google.auto.common.MoreTypes; +import com.google.auto.value.extension.AutoValueExtension; import com.google.auto.value.processor.AutoValueOrOneOfProcessor.Property; +import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -43,7 +50,6 @@ import javax.lang.model.element.VariableElement; 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.Types; /** @@ -75,7 +81,7 @@ class BuilderSpec { */ Optional<Builder> getBuilder() { Optional<TypeElement> builderTypeElement = Optional.empty(); - for (TypeElement containedClass : ElementFilter.typesIn(autoValueClass.getEnclosedElements())) { + for (TypeElement containedClass : typesIn(autoValueClass.getEnclosedElements())) { if (hasAnnotationMirror(containedClass, AUTO_VALUE_BUILDER_NAME)) { if (!CLASS_OR_INTERFACE.contains(containedClass.getKind())) { errorReporter.reportError( @@ -101,14 +107,75 @@ class BuilderSpec { } /** Representation of an {@code AutoValue.Builder} class or interface. */ - class Builder { + class Builder implements AutoValueExtension.BuilderContext { private final TypeElement builderTypeElement; private ImmutableSet<ExecutableElement> toBuilderMethods; + private ExecutableElement buildMethod; + private BuilderMethodClassifier classifier; Builder(TypeElement builderTypeElement) { this.builderTypeElement = builderTypeElement; } + @Override + public TypeElement builderType() { + return builderTypeElement; + } + + @Override + public Set<ExecutableElement> builderMethods() { + return methodsIn(autoValueClass.getEnclosedElements()).stream() + .filter( + m -> + m.getParameters().isEmpty() + && m.getModifiers().contains(Modifier.STATIC) + && !m.getModifiers().contains(Modifier.PRIVATE) + && erasedTypeIs(m.getReturnType(), builderTypeElement)) + .collect(toSet()); + } + + @Override + public Optional<ExecutableElement> buildMethod() { + return methodsIn(builderTypeElement.getEnclosedElements()).stream() + .filter( + m -> + m.getSimpleName().contentEquals("build") + && !m.getModifiers().contains(Modifier.PRIVATE) + && !m.getModifiers().contains(Modifier.STATIC) + && m.getParameters().isEmpty() + && erasedTypeIs(m.getReturnType(), autoValueClass)) + .findFirst(); + } + + @Override + public ExecutableElement autoBuildMethod() { + return buildMethod; + } + + @Override + public Map<String, Set<ExecutableElement>> setters() { + return Maps.transformValues( + classifier.propertyNameToSetters().asMap(), + propertySetters -> + propertySetters.stream().map(PropertySetter::getSetter).collect(toSet())); + } + + @Override + public Map<String, ExecutableElement> propertyBuilders() { + return Maps.transformValues( + classifier.propertyNameToPropertyBuilder(), PropertyBuilder::getPropertyBuilderMethod); + } + + private boolean erasedTypeIs(TypeMirror type, TypeElement baseType) { + return type.getKind().equals(TypeKind.DECLARED) + && MoreTypes.asDeclared(type).asElement().equals(baseType); + } + + @Override + public Set<ExecutableElement> toBuilderMethods() { + return toBuilderMethods; + } + /** * Finds any methods in the set that return the builder type. If the builder has type parameters * {@code <A, B>}, then the return type of the method must be {@code Builder<A, B>} with the @@ -131,9 +198,7 @@ class BuilderSpec { Types typeUtils, Set<ExecutableElement> abstractMethods) { List<String> builderTypeParamNames = - builderTypeElement - .getTypeParameters() - .stream() + builderTypeElement.getTypeParameters().stream() .map(e -> e.getSimpleName().toString()) .collect(toList()); @@ -143,9 +208,7 @@ class BuilderSpec { methods.add(method); DeclaredType returnType = MoreTypes.asDeclared(method.getReturnType()); List<String> typeArguments = - returnType - .getTypeArguments() - .stream() + returnType.getTypeArguments().stream() .filter(t -> t.getKind().equals(TypeKind.TYPEVAR)) .map(t -> typeUtils.asElement(t).getSimpleName().toString()) .collect(toList()); @@ -192,7 +255,7 @@ class BuilderSpec { if (!optionalClassifier.isPresent()) { return; } - BuilderMethodClassifier classifier = optionalClassifier.get(); + this.classifier = optionalClassifier.get(); Set<ExecutableElement> buildMethods = classifier.buildMethods(); if (buildMethods.size() != 1) { Set<? extends Element> errorElements = @@ -206,7 +269,7 @@ class BuilderSpec { } return; } - ExecutableElement buildMethod = Iterables.getOnlyElement(buildMethods); + this.buildMethod = Iterables.getOnlyElement(buildMethods); vars.builderIsInterface = builderTypeElement.getKind() == ElementKind.INTERFACE; vars.builderTypeName = TypeSimplifier.classNameOf(builderTypeElement); vars.builderFormalTypes = TypeEncoder.formalTypeParametersString(builderTypeElement); 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 f789655e..47b4efcd 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 @@ -16,10 +16,12 @@ package com.google.auto.value.processor; import com.google.auto.value.extension.AutoValueExtension; +import com.google.auto.value.extension.AutoValueExtension.BuilderContext; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import java.util.Map; +import java.util.Optional; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; @@ -33,6 +35,7 @@ class ExtensionContext implements AutoValueExtension.Context { private final ImmutableMap<String, ExecutableElement> properties; private final ImmutableMap<String, TypeMirror> propertyTypes; private final ImmutableSet<ExecutableElement> abstractMethods; + private Optional<BuilderContext> builderContext = Optional.empty(); ExtensionContext( ProcessingEnvironment processingEnvironment, @@ -48,6 +51,10 @@ class ExtensionContext implements AutoValueExtension.Context { this.abstractMethods = abstractMethods; } + void setBuilderContext(BuilderContext builderContext) { + this.builderContext = Optional.of(builderContext); + } + @Override public ProcessingEnvironment processingEnvironment() { return processingEnvironment; @@ -77,4 +84,9 @@ class ExtensionContext implements AutoValueExtension.Context { public Set<ExecutableElement> abstractMethods() { return abstractMethods; } + + @Override + public Optional<BuilderContext> builder() { + return builderContext; + } } diff --git a/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java b/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java index cc9d3e25..6598aded 100644 --- a/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java +++ b/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java @@ -379,5 +379,5 @@ final class TypeSimplifier { TypeElement typeElement = (TypeElement) declaredType.asElement(); return typeElement.getQualifiedName().contentEquals("java.lang.Object"); } - }; + } } |