aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-rw-r--r--value/src/test/java/com/google/auto/value/processor/ExtensionTest.java210
6 files changed, 426 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");
}
- };
+ }
}
diff --git a/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java b/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java
index a12bc441..d095e504 100644
--- a/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java
@@ -16,14 +16,18 @@
package com.google.auto.value.processor;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
import static com.google.testing.compile.JavaSourcesSubject.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import com.google.auto.common.MoreTypes;
import com.google.auto.value.extension.AutoValueExtension;
+import com.google.auto.value.extension.AutoValueExtension.BuilderContext;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
import com.google.common.truth.Truth;
import com.google.testing.compile.CompileTester.SuccessfulCompilationClause;
import com.google.testing.compile.JavaFileObjects;
@@ -35,16 +39,20 @@ import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.Map;
+import java.util.Optional;
import java.util.ServiceConfigurationError;
import java.util.Set;
+import java.util.function.Consumer;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import javax.annotation.processing.Filer;
import javax.annotation.processing.SupportedOptions;
import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
@@ -875,4 +883,206 @@ public class ExtensionTest {
+ ") {}";
}
}
+
+ @Test
+ public void propertyTypes() {
+ JavaFileObject parent = JavaFileObjects.forSourceLines(
+ "foo.bar.Parent",
+ "package foo.bar;",
+ "",
+ "import java.util.List;",
+ "",
+ "interface Parent<T> {",
+ " T thing();",
+ " List<T> list();",
+ "}");
+ JavaFileObject autoValueClass = JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "",
+ "@AutoValue",
+ "abstract class Baz implements Parent<String> {",
+ "}");
+ ContextChecker checker =
+ context -> {
+ assertThat(context.builder()).isEmpty();
+ Map<String, TypeMirror> propertyTypes = context.propertyTypes();
+ assertThat(propertyTypes.keySet()).containsExactly("thing", "list");
+ TypeMirror thingType = propertyTypes.get("thing");
+ assertThat(thingType).isNotNull();
+ assertThat(thingType.getKind()).isEqualTo(TypeKind.DECLARED);
+ assertThat(MoreTypes.asTypeElement(thingType).getQualifiedName().toString())
+ .isEqualTo("java.lang.String");
+ TypeMirror listType = propertyTypes.get("list");
+ assertThat(listType).isNotNull();
+ assertThat(listType.toString()).isEqualTo("java.util.List<java.lang.String>");
+ };
+ ContextCheckingExtension extension = new ContextCheckingExtension(checker);
+ assertThat(autoValueClass, parent)
+ .processedWith(new AutoValueProcessor(ImmutableList.of(extension)))
+ .compilesWithoutError();
+ }
+
+ @Test
+ public void builderContext() {
+ JavaFileObject parent = JavaFileObjects.forSourceLines(
+ "foo.bar.Parent",
+ "package foo.bar;",
+ "",
+ "import com.google.common.collect.ImmutableList;",
+ "",
+ "interface Parent<T> {",
+ " T thing();",
+ " ImmutableList<T> list();",
+ "}");
+ JavaFileObject autoValueClass = JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "import com.google.common.collect.ImmutableList;",
+ "",
+ "@AutoValue",
+ "abstract class Baz implements Parent<String> {",
+ " static Builder builder() {",
+ " return new AutoValue_Baz.Builder();",
+ " }",
+ "",
+ " abstract Builder toBuilder();",
+ "",
+ " @AutoValue.Builder",
+ " abstract static class Builder {",
+ " abstract Builder setThing(String x);",
+ " abstract Builder setList(Iterable<String> x);",
+ " abstract Builder setList(ImmutableList<String> x);",
+ " abstract ImmutableList.Builder<String> listBuilder();",
+ " abstract Baz autoBuild();",
+ " Baz build() {",
+ " return autoBuild();",
+ " }",
+ " }",
+ "}");
+ ContextChecker checker =
+ context -> {
+ assertThat(context.builder()).isPresent();
+ BuilderContext builderContext = context.builder().get();
+
+ assertThat(builderContext.builderType().getQualifiedName().toString())
+ .isEqualTo("foo.bar.Baz.Builder");
+
+ Set<ExecutableElement> builderMethods = builderContext.builderMethods();
+ assertThat(builderMethods).hasSize(1);
+ ExecutableElement builderMethod = Iterables.getOnlyElement(builderMethods);
+ assertThat(builderMethod.getSimpleName().toString()).isEqualTo("builder");
+
+ Set<ExecutableElement> toBuilderMethods = builderContext.toBuilderMethods();
+ assertThat(toBuilderMethods).hasSize(1);
+ ExecutableElement toBuilderMethod = Iterables.getOnlyElement(toBuilderMethods);
+ assertThat(toBuilderMethod.getSimpleName().toString()).isEqualTo("toBuilder");
+
+ Optional<ExecutableElement> buildMethod = builderContext.buildMethod();
+ assertThat(buildMethod).isPresent();
+ assertThat(buildMethod.get().getSimpleName().toString()).isEqualTo("build");
+ assertThat(buildMethod.get().getParameters()).isEmpty();
+ assertThat(buildMethod.get().getReturnType().toString()).isEqualTo("foo.bar.Baz");
+
+ ExecutableElement autoBuildMethod = builderContext.autoBuildMethod();
+ assertThat(autoBuildMethod.getSimpleName().toString()).isEqualTo("autoBuild");
+ assertThat(autoBuildMethod.getModifiers()).contains(Modifier.ABSTRACT);
+ assertThat(autoBuildMethod.getParameters()).isEmpty();
+ assertThat(autoBuildMethod.getReturnType().toString()).isEqualTo("foo.bar.Baz");
+
+ Map<String, Set<ExecutableElement>> setters = builderContext.setters();
+ assertThat(setters.keySet()).containsExactly("thing", "list");
+ Set<ExecutableElement> thingSetters = setters.get("thing");
+ assertThat(thingSetters).hasSize(1);
+ ExecutableElement thingSetter = Iterables.getOnlyElement(thingSetters);
+ assertThat(thingSetter.getSimpleName().toString()).isEqualTo("setThing");
+ Set<ExecutableElement> listSetters = setters.get("list");
+ assertThat(listSetters).hasSize(2);
+ for (ExecutableElement listSetter : listSetters) {
+ assertThat(listSetter.getSimpleName().toString()).isEqualTo("setList");
+ }
+
+ Map<String, ExecutableElement> propertyBuilders = builderContext.propertyBuilders();
+ assertThat(propertyBuilders.keySet()).containsExactly("list");
+ assertThat(propertyBuilders.get("list").getSimpleName().toString())
+ .isEqualTo("listBuilder");
+ };
+ ContextCheckingExtension extension = new ContextCheckingExtension(checker);
+ assertThat(autoValueClass, parent)
+ .processedWith(new AutoValueProcessor(ImmutableList.of(extension)))
+ .compilesWithoutError();
+ }
+
+ @Test
+ public void oddBuilderContext() {
+ JavaFileObject autoValueClass = JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "import com.google.common.collect.ImmutableList;",
+ "",
+ "@AutoValue",
+ "abstract class Baz {",
+ " abstract String string();",
+ "",
+ " @AutoValue.Builder",
+ " abstract static class Builder {",
+ " abstract Builder setString(String x);",
+ " abstract Baz oddBuild();",
+ " Baz build(int butNotReallyBecauseOfThisParameter) {",
+ " return null;",
+ " }",
+ " }",
+ "}");
+ ContextChecker checker =
+ context -> {
+ assertThat(context.builder()).isPresent();
+ BuilderContext builderContext = context.builder().get();
+ assertThat(builderContext.builderMethods()).isEmpty();
+ assertThat(builderContext.toBuilderMethods()).isEmpty();
+ assertThat(builderContext.buildMethod()).isEmpty();
+ assertThat(builderContext.autoBuildMethod().getSimpleName().toString())
+ .isEqualTo("oddBuild");
+
+ Map<String, Set<ExecutableElement>> setters = builderContext.setters();
+ assertThat(setters.keySet()).containsExactly("string");
+ Set<ExecutableElement> thingSetters = setters.get("string");
+ assertThat(thingSetters).hasSize(1);
+ ExecutableElement thingSetter = Iterables.getOnlyElement(thingSetters);
+ assertThat(thingSetter.getSimpleName().toString()).isEqualTo("setString");
+
+ assertThat(builderContext.propertyBuilders()).isEmpty();
+ };
+ ContextCheckingExtension extension = new ContextCheckingExtension(checker);
+ assertThat(autoValueClass)
+ .processedWith(new AutoValueProcessor(ImmutableList.of(extension)))
+ .compilesWithoutError();
+ }
+
+ private interface ContextChecker extends Consumer<AutoValueExtension.Context> {}
+
+ private static class ContextCheckingExtension extends AutoValueExtension {
+ private final Consumer<Context> checker;
+
+ ContextCheckingExtension(Consumer<Context> checker) {
+ this.checker = checker;
+ }
+
+ @Override
+ public boolean applicable(Context context) {
+ return true;
+ }
+
+ @Override
+ public String generateClass(
+ Context context, String className, String classToExtend, boolean isFinal) {
+ checker.accept(context);
+ return null;
+ }
+ }
}