aboutsummaryrefslogtreecommitdiff
path: root/value/src/main/java/com
diff options
context:
space:
mode:
authoremcmanus <emcmanus@google.com>2019-09-30 10:16:42 -0700
committerChris Povirk <beigetangerine@gmail.com>2019-09-30 14:21:51 -0400
commit86f456371a23353acfa6dc5aacf880eb10439fad (patch)
treeb317e8283dc401c5b18cab6de01b223fad68612b /value/src/main/java/com
parent7646889ded2a74c4377ac8decece3b879209cf35 (diff)
downloadauto-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')
-rw-r--r--value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java131
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java1
-rw-r--r--value/src/main/java/com/google/auto/value/processor/BuilderSpec.java85
-rw-r--r--value/src/main/java/com/google/auto/value/processor/ExtensionContext.java12
-rw-r--r--value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java2
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");
}
- };
+ }
}