aboutsummaryrefslogtreecommitdiff
path: root/value
diff options
context:
space:
mode:
Diffstat (limited to 'value')
-rw-r--r--value/annotations/pom.xml7
-rw-r--r--value/pom.xml92
-rw-r--r--value/processor/pom.xml15
-rw-r--r--value/src/it/functional/pom.xml17
-rw-r--r--value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java35
-rw-r--r--value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java44
-rw-r--r--value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java3
-rw-r--r--value/src/it/gwtserializer/pom.xml6
-rw-r--r--value/src/main/java/com/google/auto/value/AutoAnnotation.java33
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java64
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java2
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java85
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java9
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java3
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java31
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java142
-rw-r--r--value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java7
-rw-r--r--value/src/main/java/com/google/auto/value/processor/BuilderSpec.java34
-rw-r--r--value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java30
-rw-r--r--value/src/main/java/com/google/auto/value/processor/GwtSerialization.java16
-rw-r--r--value/src/main/java/com/google/auto/value/processor/autovalue.vm6
-rw-r--r--value/src/main/java/com/google/auto/value/processor/builder.vm10
-rw-r--r--value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java86
-rw-r--r--value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java85
-rw-r--r--value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java261
-rw-r--r--value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java15
-rw-r--r--value/userguide/autobuilder.md3
-rw-r--r--value/userguide/builders-howto.md86
-rw-r--r--value/userguide/howto.md2
29 files changed, 998 insertions, 231 deletions
diff --git a/value/annotations/pom.xml b/value/annotations/pom.xml
index 8bc63ac9..4fc183b6 100644
--- a/value/annotations/pom.xml
+++ b/value/annotations/pom.xml
@@ -21,15 +21,14 @@
<parent>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value-parent</artifactId>
- <version>1.7.4</version>
+ <version>HEAD-SNAPSHOT</version>
</parent>
- <groupId>com.google.auto.value</groupId>
<artifactId>auto-value-annotations</artifactId>
- <version>1.7.4</version>
+ <version>HEAD-SNAPSHOT</version>
<name>AutoValue Annotations</name>
<description>
- Immutable value-type code generation for Java 1.6+.
+ Immutable value-type code generation for Java 1.7+.
</description>
<url>https://github.com/google/auto/tree/master/value</url>
diff --git a/value/pom.xml b/value/pom.xml
index 5405c7cd..8b1e238d 100644
--- a/value/pom.xml
+++ b/value/pom.xml
@@ -18,15 +18,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.sonatype.oss</groupId>
- <artifactId>oss-parent</artifactId>
- <version>7</version>
- </parent>
-
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value-parent</artifactId>
- <version>1.7.4</version>
+ <version>HEAD-SNAPSHOT</version>
<name>AutoValue Parent</name>
<description>
Immutable value-type code generation for Java 7+.
@@ -37,7 +31,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
- <guava.version>30.1.1-jre</guava.version>
+ <guava.version>31.0.1-jre</guava.version>
<truth.version>1.1.3</truth.version>
</properties>
@@ -65,6 +59,21 @@
<url>http://www.google.com</url>
</organization>
+ <developers>
+ <developer>
+ <id>eamonnmcmanus</id>
+ <name>Éamonn McManus</name>
+ <email>emcmanus@google.com</email>
+ <organization>Google</organization>
+ <organizationUrl>http://www.google.com</organizationUrl>
+ <roles>
+ <role>owner</role>
+ <role>developer</role>
+ </roles>
+ <timezone>-8</timezone>
+ </developer>
+ </developers>
+
<modules>
<module>annotations</module>
<module>processor</module>
@@ -72,6 +81,19 @@
<module>src/it/gwtserializer</module>
</modules>
+ <distributionManagement>
+ <snapshotRepository>
+ <id>sonatype-nexus-snapshots</id>
+ <name>Sonatype Nexus Snapshots</name>
+ <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
+ </snapshotRepository>
+ <repository>
+ <id>sonatype-nexus-staging</id>
+ <name>Nexus Release Repository</name>
+ <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+ </repository>
+ </distributionManagement>
+
<dependencyManagement>
<dependencies>
<!-- main dependencies -->
@@ -150,7 +172,7 @@
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-java</artifactId>
- <version>1.0.7</version>
+ <version>1.1.0</version>
</dependency>
</dependencies>
</plugin>
@@ -189,4 +211,56 @@
</plugins>
</pluginManagement>
</build>
+ <profiles>
+ <profile>
+ <id>sonatype-oss-release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>3.2.1</version>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <goals>
+ <goal>jar-no-fork</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>3.3.1</version>
+ <configuration>
+ <failOnError>false</failOnError>
+ </configuration>
+ <executions>
+ <execution>
+ <id>attach-javadocs</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <version>3.0.1</version>
+ <executions>
+ <execution>
+ <id>sign-artifacts</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
</project>
diff --git a/value/processor/pom.xml b/value/processor/pom.xml
index 8892c4f6..b00457d5 100644
--- a/value/processor/pom.xml
+++ b/value/processor/pom.xml
@@ -21,15 +21,14 @@
<parent>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value-parent</artifactId>
- <version>1.7.4</version>
+ <version>HEAD-SNAPSHOT</version>
</parent>
- <groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
- <version>1.7.4</version>
+ <version>HEAD-SNAPSHOT</version>
<name>AutoValue Processor</name>
<description>
- Immutable value-type code generation for Java 1.6+.
+ Immutable value-type code generation for Java 1.7+.
</description>
<url>https://github.com/google/auto/tree/master/value</url>
@@ -41,15 +40,15 @@
</scm>
<properties>
- <auto-service.version>1.0</auto-service.version>
- <errorprone.version>2.7.1</errorprone.version>
+ <auto-service.version>1.0.1</auto-service.version>
+ <errorprone.version>2.10.0</errorprone.version>
</properties>
<dependencies>
<dependency>
<groupId>com.google.auto</groupId>
<artifactId>auto-common</artifactId>
- <version>1.1</version>
+ <version>1.2.1</version>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
@@ -125,7 +124,7 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
- <version>3.11.2</version>
+ <version>4.1.0</version>
<scope>test</scope>
</dependency>
</dependencies>
diff --git a/value/src/it/functional/pom.xml b/value/src/it/functional/pom.xml
index d4ae1386..18f3718b 100644
--- a/value/src/it/functional/pom.xml
+++ b/value/src/it/functional/pom.xml
@@ -22,17 +22,17 @@
<parent>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value-parent</artifactId>
- <version>1.7.4</version>
+ <version>HEAD-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<url>https://github.com/google/auto/tree/master/value</url>
<groupId>com.google.auto.value.it.functional</groupId>
<artifactId>functional</artifactId>
- <version>1.7.4</version>
+ <version>HEAD-SNAPSHOT</version>
<name>Auto-Value Functional Integration Test</name>
<properties>
- <kotlin.version>1.5.21</kotlin.version>
+ <kotlin.version>1.6.0</kotlin.version>
<exclude.tests>this-matches-nothing</exclude.tests>
</properties>
<dependencies>
@@ -49,7 +49,7 @@
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
- <version>1.0</version>
+ <version>1.0.1</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
@@ -93,13 +93,13 @@
<dependency>
<groupId>dev.gradleplugins</groupId>
<artifactId>gradle-test-kit</artifactId>
- <version>6.8.3</version>
+ <version>7.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
- <version>3.11.2</version>
+ <version>4.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -166,7 +166,7 @@
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-java</artifactId>
- <version>1.0.7</version>
+ <version>1.1.0</version>
</dependency>
</dependencies>
<configuration>
@@ -176,7 +176,6 @@
<arg>-Xlint:all</arg>
<arg>-encoding</arg>
<arg>utf8</arg>
- <arg>-Acom.google.auto.value.AutoBuilderIsUnstable</arg>
</compilerArgs>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
@@ -221,7 +220,7 @@
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-java</artifactId>
- <version>1.0.7</version>
+ <version>1.1.0</version>
</dependency>
</dependencies>
<configuration>
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java
index 952edaac..dba81992 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java
@@ -149,6 +149,12 @@ public final class AutoBuilderTest {
return new AutoAnnotation_AutoBuilderTest_myAnnotation(value, truthiness);
}
+ // This method has parameters for all the annotation elements.
+ @AutoAnnotation
+ static MyAnnotation myAnnotationAll(String value, int id, Truthiness truthiness) {
+ return new AutoAnnotation_AutoBuilderTest_myAnnotationAll(value, id, truthiness);
+ }
+
@AutoBuilder(callMethod = "myAnnotation")
interface MyAnnotationBuilder {
MyAnnotationBuilder value(String x);
@@ -159,12 +165,28 @@ public final class AutoBuilderTest {
}
static MyAnnotationBuilder myAnnotationBuilder() {
- return new AutoBuilder_AutoBuilderTest_MyAnnotationBuilder()
- .truthiness(MyAnnotation.DEFAULT_TRUTHINESS);
+ return new AutoBuilder_AutoBuilderTest_MyAnnotationBuilder();
+ }
+
+ @AutoBuilder(callMethod = "myAnnotationAll")
+ interface MyAnnotationAllBuilder {
+ MyAnnotationAllBuilder value(String x);
+
+ MyAnnotationAllBuilder id(int x);
+
+ MyAnnotationAllBuilder truthiness(Truthiness x);
+
+ MyAnnotation build();
+ }
+
+ static MyAnnotationAllBuilder myAnnotationAllBuilder() {
+ return new AutoBuilder_AutoBuilderTest_MyAnnotationAllBuilder();
}
@Test
public void simpleAutoAnnotation() {
+ // We haven't supplied a value for `truthiness`, so AutoBuilder should use the default one in
+ // the annotation.
MyAnnotation annotation1 = myAnnotationBuilder().value("foo").build();
assertThat(annotation1.value()).isEqualTo("foo");
assertThat(annotation1.id()).isEqualTo(MyAnnotation.DEFAULT_ID);
@@ -174,6 +196,15 @@ public final class AutoBuilderTest {
assertThat(annotation2.value()).isEqualTo("bar");
assertThat(annotation2.id()).isEqualTo(MyAnnotation.DEFAULT_ID);
assertThat(annotation2.truthiness()).isEqualTo(Truthiness.TRUTHY);
+
+ MyAnnotation annotation3 = myAnnotationAllBuilder().value("foo").build();
+ MyAnnotation annotation4 =
+ myAnnotationAllBuilder()
+ .value("foo")
+ .id(MyAnnotation.DEFAULT_ID)
+ .truthiness(MyAnnotation.DEFAULT_TRUTHINESS)
+ .build();
+ assertThat(annotation3).isEqualTo(annotation4);
}
static class Overload {
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java
index 3a7e7bc4..fd87b3e5 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java
@@ -3623,4 +3623,48 @@ public class AutoValueTest {
} catch (IllegalStateException expected) {
}
}
+
+ @AutoValue
+ public abstract static class Stepped {
+ public abstract String one();
+
+ public abstract int two();
+
+ public abstract double three();
+
+ public interface StepOne<T> {
+ StepTwo setOne(T x);
+ }
+
+ public interface StepTwo {
+ StepThree setTwo(int x);
+ }
+
+ public interface StepThree {
+ Stepped setThreeAndBuild(double x);
+ }
+
+ public static StepOne<String> builder() {
+ return new AutoValue_AutoValueTest_Stepped.Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder implements StepOne<String>, StepTwo, StepThree {
+ abstract Builder setThree(double x);
+ abstract Stepped build();
+
+ @Override
+ public Stepped setThreeAndBuild(double x) {
+ return setThree(x).build();
+ }
+ }
+ }
+
+ @Test
+ public void stepBuilder() {
+ Stepped stepped = Stepped.builder().setOne("one").setTwo(2).setThreeAndBuild(3.0);
+ assertThat(stepped.one()).isEqualTo("one");
+ assertThat(stepped.two()).isEqualTo(2);
+ assertThat(stepped.three()).isEqualTo(3.0);
+ }
}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java
index ca10fb45..5f08a725 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java
@@ -127,8 +127,7 @@ public class CompileWithEclipseTest {
version,
"-target",
version,
- "-warn:-warningToken,-intfAnnotation",
- "-Acom.google.auto.value.AutoBuilderIsUnstable");
+ "-warn:-warningToken,-intfAnnotation");
JavaCompiler.CompilationTask task =
compiler.getTask(null, fileManager, null, options, null, sourceFileObjects);
// Explicitly supply an empty list of extensions for AutoValueProcessor, because otherwise this
diff --git a/value/src/it/gwtserializer/pom.xml b/value/src/it/gwtserializer/pom.xml
index 42cc2fe2..88bf677a 100644
--- a/value/src/it/gwtserializer/pom.xml
+++ b/value/src/it/gwtserializer/pom.xml
@@ -22,14 +22,14 @@
<parent>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value-parent</artifactId>
- <version>1.7.4</version>
+ <version>HEAD-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<url>https://github.com/google/auto/tree/master/value</url>
<groupId>com.google.auto.value.it.gwtserializer</groupId>
<artifactId>gwtserializer</artifactId>
- <version>1.7.4</version>
+ <version>HEAD-SNAPSHOT</version>
<name>Auto-Value GWT-RPC Serialization Integration Test</name>
<dependencyManagement>
<dependencies>
@@ -99,7 +99,7 @@
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-java</artifactId>
- <version>1.0.7</version>
+ <version>1.1.0</version>
</dependency>
</dependencies>
<configuration>
diff --git a/value/src/main/java/com/google/auto/value/AutoAnnotation.java b/value/src/main/java/com/google/auto/value/AutoAnnotation.java
index d36d8e28..c6fab240 100644
--- a/value/src/main/java/com/google/auto/value/AutoAnnotation.java
+++ b/value/src/main/java/com/google/auto/value/AutoAnnotation.java
@@ -71,6 +71,39 @@ import java.lang.reflect.AnnotatedElement;
* parameter corresponding to an array-valued annotation member, and the implementation of each such
* member will also return a clone of the array.
*
+ * <p>If your annotation has many elements, you may consider using {@code @AutoBuilder} to make it
+ * easier to construct instances. In that case, {@code default} values from the annotation will
+ * become default values for the parameters of the {@code @AutoAnnotation} method. For example:
+ *
+ * <pre>
+ * class Example {
+ * {@code @interface} MyAnnotation {
+ * String name() default "foo";
+ * int number() default 23;
+ * }
+ *
+ * {@code @AutoAnnotation}
+ * static MyAnnotation myAnnotation(String value) {
+ * return new AutoAnnotation_Example_myAnnotation(value);
+ * }
+ *
+ * {@code @AutoBuilder(callMethod = "myAnnotation")}
+ * interface MyAnnotationBuilder {
+ * MyAnnotationBuilder name(String name);
+ * MyAnnotationBuilder number(int number);
+ * MyAnnotation build();
+ * }
+ *
+ * static MyAnnotationBuilder myAnnotationBuilder() {
+ * return new AutoBuilder_Example_MyAnnotationBuilder();
+ * }
+ * }
+ * </pre>
+ *
+ * Here, {@code myAnnotationBuilder().build()} is the same as {@code
+ * myAnnotationBuilder().name("foo").number(23).build()} because those are the defaults in the
+ * annotation definition.
+ *
* @author emcmanus@google.com (Éamonn McManus)
*/
@Target(ElementType.METHOD)
diff --git a/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java b/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java
index ed986ab7..ed6abaa6 100644
--- a/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java
+++ b/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java
@@ -15,6 +15,8 @@
*/
package com.google.auto.value.processor;
+import com.google.auto.common.MoreTypes;
+import com.google.auto.value.processor.MissingTypes.MissingTypeException;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import java.util.List;
@@ -24,8 +26,10 @@ import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleAnnotationValueVisitor8;
import javax.tools.Diagnostic;
@@ -130,13 +134,13 @@ final class AnnotationOutput {
private static class InitializerSourceFormVisitor extends SourceFormVisitor {
private final ProcessingEnvironment processingEnv;
private final String memberName;
- private final Element context;
+ private final Element errorContext;
InitializerSourceFormVisitor(
- ProcessingEnvironment processingEnv, String memberName, Element context) {
+ ProcessingEnvironment processingEnv, String memberName, Element errorContext) {
this.processingEnv = processingEnv;
this.memberName = memberName;
- this.context = context;
+ this.errorContext = errorContext;
}
@Override
@@ -148,7 +152,7 @@ final class AnnotationOutput {
"@AutoAnnotation cannot yet supply a default value for annotation-valued member '"
+ memberName
+ "'",
- context);
+ errorContext);
sb.append("null");
return null;
}
@@ -209,9 +213,9 @@ final class AnnotationOutput {
AnnotationValue annotationValue,
ProcessingEnvironment processingEnv,
String memberName,
- Element context) {
+ Element errorContext) {
SourceFormVisitor visitor =
- new InitializerSourceFormVisitor(processingEnv, memberName, context);
+ new InitializerSourceFormVisitor(processingEnv, memberName, errorContext);
StringBuilder sb = new StringBuilder();
visitor.visit(annotationValue, sb);
return sb.toString();
@@ -222,11 +226,59 @@ final class AnnotationOutput {
* Java source file to reproduce the annotation in source form.
*/
static String sourceFormForAnnotation(AnnotationMirror annotationMirror) {
+ // If a value in the annotation is a reference to a class constant and that class constant is
+ // undefined, javac unhelpfully converts it into a string "<error>" and visits that instead. We
+ // want to catch this case and defer processing to allow the class to be defined by another
+ // annotation processor. So we look for annotation elements whose type is Class but whose
+ // reported value is a string. Unfortunately we can't extract the ErrorType corresponding to the
+ // missing class portably. With javac, the AttributeValue is a
+ // com.sun.tools.javac.code.Attribute.UnresolvedClass, which has a public field classType that
+ // is the ErrorType we need, but obviously that's nonportable and fragile.
+ validateClassValues(annotationMirror);
StringBuilder sb = new StringBuilder();
new AnnotationSourceFormVisitor().visitAnnotation(annotationMirror, sb);
return sb.toString();
}
+ /**
+ * Throws an exception if this annotation contains a value for a Class element that is not
+ * actually a type. The assumption is that the value is the string {@code "<error>"} which javac
+ * presents when a Class value is an undefined type.
+ */
+ private static void validateClassValues(AnnotationMirror annotationMirror) {
+ // A class literal can appear in three places:
+ // * for an element of type Class, for example @SomeAnnotation(Foo.class);
+ // * for an element of type Class[], for example @SomeAnnotation({Foo.class, Bar.class});
+ // * inside a nested annotation, for example @SomeAnnotation(@Nested(Foo.class)).
+ // These three possibilities are the three branches of the if/else chain below.
+ annotationMirror
+ .getElementValues()
+ .forEach(
+ (method, value) -> {
+ TypeMirror type = method.getReturnType();
+ if (isJavaLangClass(type) && !(value.getValue() instanceof TypeMirror)) {
+ throw new MissingTypeException(null);
+ } else if (type.getKind().equals(TypeKind.ARRAY)
+ && isJavaLangClass(MoreTypes.asArray(type).getComponentType())
+ && value.getValue() instanceof List<?>) {
+ @SuppressWarnings("unchecked") // a List can only be a List<AnnotationValue> here
+ List<AnnotationValue> values = (List<AnnotationValue>) value.getValue();
+ if (values.stream().anyMatch(av -> !(av.getValue() instanceof TypeMirror))) {
+ throw new MissingTypeException(null);
+ }
+ } else if (type.getKind().equals(TypeKind.DECLARED)
+ && MoreTypes.asElement(type).getKind().equals(ElementKind.ANNOTATION_TYPE)
+ && value.getValue() instanceof AnnotationMirror) {
+ validateClassValues((AnnotationMirror) value.getValue());
+ }
+ });
+ }
+
+ private static boolean isJavaLangClass(TypeMirror type) {
+ return type.getKind().equals(TypeKind.DECLARED)
+ && MoreTypes.asTypeElement(type).getQualifiedName().contentEquals("java.lang.Class");
+ }
+
private static StringBuilder appendQuoted(StringBuilder sb, String s) {
sb.append('"');
for (int i = 0; i < s.length(); i++) {
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java
index 3acf9332..cc0e62ec 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java
@@ -287,7 +287,7 @@ public class AutoAnnotationProcessor extends AbstractProcessor {
private String generatedClassName(ExecutableElement method) {
TypeElement type = MoreElements.asType(method.getEnclosingElement());
String name = type.getSimpleName().toString();
- while (type.getEnclosingElement() instanceof TypeElement) {
+ while (MoreElements.isType(type.getEnclosingElement())) {
type = MoreElements.asType(type.getEnclosingElement());
name = type.getSimpleName() + "_" + name;
}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java
index b6a578fc..fc0d8b3e 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java
@@ -20,6 +20,7 @@ import static com.google.auto.common.MoreElements.getPackage;
import static com.google.auto.common.MoreStreams.toImmutableList;
import static com.google.auto.common.MoreStreams.toImmutableSet;
import static com.google.auto.value.processor.AutoValueProcessor.OMIT_IDENTIFIERS_OPTION;
+import static com.google.auto.value.processor.ClassNames.AUTO_ANNOTATION_NAME;
import static com.google.auto.value.processor.ClassNames.AUTO_BUILDER_NAME;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toCollection;
@@ -38,6 +39,7 @@ import com.google.auto.value.processor.MissingTypes.MissingTypeException;
import com.google.common.base.Ascii;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import java.lang.reflect.Field;
@@ -60,6 +62,7 @@ import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
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 net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
@@ -77,7 +80,7 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
private static final String ALLOW_OPTION = "com.google.auto.value.AutoBuilderIsUnstable";
public AutoBuilderProcessor() {
- super(AUTO_BUILDER_NAME);
+ super(AUTO_BUILDER_NAME, /* appliesToInterfaces= */ true);
}
@Override
@@ -95,21 +98,9 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
@Override
void processType(TypeElement autoBuilderType) {
- if (!processingEnv.getOptions().containsKey(ALLOW_OPTION)) {
- errorReporter()
- .abortWithError(
- autoBuilderType,
- "Compile with -A%s to enable this UNSUPPORTED AND UNSTABLE prototype",
- ALLOW_OPTION);
- }
- if (autoBuilderType.getKind() != ElementKind.CLASS
- && autoBuilderType.getKind() != ElementKind.INTERFACE) {
- errorReporter()
- .abortWithError(
- autoBuilderType,
- "[AutoBuilderWrongType] @AutoBuilder only applies to classes and interfaces");
+ if (processingEnv.getOptions().containsKey(ALLOW_OPTION)) {
+ errorReporter().reportWarning(autoBuilderType, "The -A%s option is obsolete", ALLOW_OPTION);
}
- checkModifiersIfNested(autoBuilderType);
// The annotation is guaranteed to be present by the contract of Processor#process
AnnotationMirror autoBuilderAnnotation =
getAnnotationMirror(autoBuilderType, AUTO_BUILDER_NAME).get();
@@ -126,7 +117,7 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
Optional<BuilderMethodClassifier<VariableElement>> maybeClassifier =
BuilderMethodClassifierForAutoBuilder.classify(
methods, errorReporter(), processingEnv, executable, builtType, autoBuilderType);
- if (!maybeClassifier.isPresent()) {
+ if (!maybeClassifier.isPresent() || errorReporter().errorCount() > 0) {
// We've already output one or more error messages.
return;
}
@@ -134,7 +125,7 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
Map<String, String> propertyToGetterName =
Maps.transformValues(classifier.builderGetters(), PropertyGetter::getName);
AutoBuilderTemplateVars vars = new AutoBuilderTemplateVars();
- vars.props = propertySet(executable, propertyToGetterName);
+ vars.props = propertySet(autoBuilderType, executable, propertyToGetterName);
builder.defineVars(vars, classifier);
vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION);
String generatedClassName = generatedClassName(autoBuilderType, "AutoBuilder_");
@@ -152,7 +143,15 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
}
private ImmutableSet<Property> propertySet(
- ExecutableElement executable, Map<String, String> propertyToGetterName) {
+ TypeElement autoBuilderType,
+ ExecutableElement executable,
+ Map<String, String> propertyToGetterName) {
+ boolean autoAnnotation =
+ MoreElements.getAnnotationMirror(executable, AUTO_ANNOTATION_NAME).isPresent();
+ ImmutableMap<String, String> builderInitializers =
+ autoAnnotation
+ ? autoAnnotationInitializers(autoBuilderType, executable)
+ : ImmutableMap.of();
// Fix any parameter names that are reserved words in Java. Java source code can't have
// such parameter names, but Kotlin code might, for example.
Map<VariableElement, String> identifiers =
@@ -161,18 +160,58 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
fixReservedIdentifiers(identifiers);
return executable.getParameters().stream()
.map(
- v ->
- newProperty(
- v, identifiers.get(v), propertyToGetterName.get(v.getSimpleName().toString())))
+ v -> {
+ String name = v.getSimpleName().toString();
+ return newProperty(
+ v,
+ identifiers.get(v),
+ propertyToGetterName.get(name),
+ Optional.ofNullable(builderInitializers.get(name)));
+ })
.collect(toImmutableSet());
}
- private Property newProperty(VariableElement var, String identifier, String getterName) {
+ private Property newProperty(
+ VariableElement var,
+ String identifier,
+ String getterName,
+ Optional<String> builderInitializer) {
String name = var.getSimpleName().toString();
TypeMirror type = var.asType();
Optional<String> nullableAnnotation = nullableAnnotationFor(var, var.asType());
return new Property(
- name, identifier, TypeEncoder.encode(type), type, nullableAnnotation, getterName);
+ name,
+ identifier,
+ TypeEncoder.encode(type),
+ type,
+ nullableAnnotation,
+ getterName,
+ builderInitializer);
+ }
+
+ private ImmutableMap<String, String> autoAnnotationInitializers(
+ TypeElement autoBuilderType, ExecutableElement autoAnnotationMethod) {
+ // We expect the return type of an @AutoAnnotation method to be an annotation type. If it isn't,
+ // AutoAnnotation will presumably complain, so we don't need to complain further.
+ TypeMirror returnType = autoAnnotationMethod.getReturnType();
+ if (!returnType.getKind().equals(TypeKind.DECLARED)) {
+ return ImmutableMap.of();
+ }
+ // This might not actually be an annotation (if the code is wrong), but if that's the case we
+ // just won't see any contained ExecutableElement where getDefaultValue() returns something.
+ TypeElement annotation = MoreTypes.asTypeElement(returnType);
+ ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
+ for (ExecutableElement method : methodsIn(annotation.getEnclosedElements())) {
+ AnnotationValue defaultValue = method.getDefaultValue();
+ if (defaultValue != null) {
+ String memberName = method.getSimpleName().toString();
+ builder.put(
+ memberName,
+ AnnotationOutput.sourceFormForInitializer(
+ defaultValue, processingEnv, memberName, autoBuilderType));
+ }
+ }
+ return builder.build();
}
private ExecutableElement findExecutable(
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java
index 711b138c..4d19d216 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java
@@ -60,7 +60,7 @@ import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
public class AutoOneOfProcessor extends AutoValueishProcessor {
public AutoOneOfProcessor() {
- super(AUTO_ONE_OF_NAME);
+ super(AUTO_ONE_OF_NAME, /* appliesToInterfaces= */ false);
}
@Override
@@ -75,13 +75,6 @@ public class AutoOneOfProcessor extends AutoValueishProcessor {
@Override
void processType(TypeElement autoOneOfType) {
- if (autoOneOfType.getKind() != ElementKind.CLASS) {
- errorReporter()
- .abortWithError(
- autoOneOfType,
- "[AutoOneOfNotClass] @" + AUTO_ONE_OF_NAME + " only applies to classes");
- }
- checkModifiersIfNested(autoOneOfType);
DeclaredType kindMirror = mirrorForKindType(autoOneOfType);
// We are going to classify the methods of the @AutoOneOf class into several categories.
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java
index 86cf4974..9fbc1652 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java
@@ -109,7 +109,8 @@ abstract class AutoValueOrBuilderTemplateVars extends AutoValueishTemplateVars {
*
* <ul>
* <li>it is {@code @Nullable} (in which case it defaults to null);
- * <li>it is {@code Optional} (in which case it defaults to empty);
+ * <li>it has a builder initializer (for example it is {@code Optional}, which will have an
+ * initializer of {@code Optional.empty()});
* <li>it has a property-builder method (in which case it defaults to empty).
* </ul>
*/
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 ab7da924..4479a056 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
@@ -18,6 +18,7 @@ package com.google.auto.value.processor;
import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
import static com.google.auto.common.MoreStreams.toImmutableList;
import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_NAME;
+import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.intersection;
import static java.util.Comparator.naturalOrder;
@@ -45,7 +46,6 @@ import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
@@ -79,21 +79,24 @@ public class AutoValueProcessor extends AutoValueishProcessor {
@VisibleForTesting
AutoValueProcessor(ClassLoader loaderForExtensions) {
- super(AUTO_VALUE_NAME);
- this.extensions = null;
- this.loaderForExtensions = loaderForExtensions;
+ this(ImmutableList.of(), loaderForExtensions);
}
@VisibleForTesting
- public AutoValueProcessor(Iterable<? extends AutoValueExtension> extensions) {
- super(AUTO_VALUE_NAME);
- this.extensions = ImmutableList.copyOf(extensions);
- this.loaderForExtensions = null;
+ public AutoValueProcessor(Iterable<? extends AutoValueExtension> testExtensions) {
+ this(testExtensions, null);
+ }
+
+ private AutoValueProcessor(
+ Iterable<? extends AutoValueExtension> testExtensions, ClassLoader loaderForExtensions) {
+ super(AUTO_VALUE_NAME, /* appliesToInterfaces= */ false);
+ this.extensions = ImmutableList.copyOf(testExtensions);
+ this.loaderForExtensions = loaderForExtensions;
}
// Depending on how this AutoValueProcessor was constructed, we might already have a list of
- // extensions when init() is run, or, if `extensions` is null, we have a ClassLoader that will be
- // used to get the list using the ServiceLoader API.
+ // extensions when init() is run, or, if `loaderForExtensions` is not null, it is a ClassLoader
+ // that will be used to get the list using the ServiceLoader API.
private ImmutableList<AutoValueExtension> extensions;
private final ClassLoader loaderForExtensions;
@@ -108,7 +111,8 @@ public class AutoValueProcessor extends AutoValueishProcessor {
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
- if (extensions == null) {
+ if (loaderForExtensions != null) {
+ checkState(extensions.isEmpty());
try {
extensions = extensionsFromLoader(loaderForExtensions);
} catch (RuntimeException | Error e) {
@@ -165,10 +169,6 @@ public class AutoValueProcessor extends AutoValueishProcessor {
@Override
void processType(TypeElement type) {
- if (type.getKind() != ElementKind.CLASS) {
- errorReporter()
- .abortWithError(type, "[AutoValueNotClass] @AutoValue only applies to classes");
- }
if (ancestorIsAutoValue(type)) {
errorReporter()
.abortWithError(type, "[AutoValueExtend] One @AutoValue class may not extend another");
@@ -180,7 +180,6 @@ public class AutoValueProcessor extends AutoValueishProcessor {
"[AutoValueImplAnnotation] @AutoValue may not be used to implement an annotation"
+ " interface; try using @AutoAnnotation instead");
}
- checkModifiersIfNested(type);
// We are going to classify the methods of the @AutoValue class into several categories.
// This covers the methods in the class itself and the ones it inherits from supertypes.
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java
index 93f2f79e..31f1ec1c 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java
@@ -29,6 +29,7 @@ import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
+import static javax.lang.model.util.ElementFilter.constructorsIn;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
@@ -92,19 +93,22 @@ import javax.tools.JavaFileObject;
*/
abstract class AutoValueishProcessor extends AbstractProcessor {
private final String annotationClassName;
+ private final boolean appliesToInterfaces;
/**
- * Qualified names of {@code @AutoValue} or {@code AutoOneOf} classes that we attempted to process
- * but had to abandon because we needed other types that they referenced and those other types
- * were missing.
+ * Qualified names of {@code @AutoValue} (etc) classes that we attempted to process but had to
+ * abandon because we needed other types that they referenced and those other types were missing.
*/
private final List<String> deferredTypeNames = new ArrayList<>();
- AutoValueishProcessor(String annotationClassName) {
+ AutoValueishProcessor(String annotationClassName, boolean appliesToInterfaces) {
this.annotationClassName = annotationClassName;
+ this.appliesToInterfaces = appliesToInterfaces;
}
- /** The annotation we are processing, {@code AutoValue} or {@code AutoOneOf}. */
+ /**
+ * The annotation we are processing, for example {@code AutoValue} or {@code AutoBuilder}.
+ */
private TypeElement annotationType;
/** The simple name of {@link #annotationType}. */
private String simpleAnnotationName;
@@ -117,6 +121,10 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
super.init(processingEnv);
errorReporter = new ErrorReporter(processingEnv);
nullables = new Nullables(processingEnv);
+ annotationType = elementUtils().getTypeElement(annotationClassName);
+ if (annotationType != null) {
+ simpleAnnotationName = annotationType.getSimpleName().toString();
+ }
}
final ErrorReporter errorReporter() {
@@ -132,9 +140,9 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
}
/**
- * Qualified names of {@code @AutoValue} or {@code AutoOneOf} classes that we attempted to process
- * but had to abandon because we needed other types that they referenced and those other types
- * were missing. This is used by tests.
+ * Qualified names of {@code @AutoValue} (etc) classes that we attempted to process but had to
+ * abandon because we needed other types that they referenced and those other types were missing.
+ * This is used by tests.
*/
final ImmutableList<String> deferredTypeNames() {
return ImmutableList.copyOf(deferredTypeNames);
@@ -160,6 +168,7 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
private final Optional<String> nullableAnnotation;
private final Optionalish optional;
private final String getter;
+ private final String builderInitializer; // empty, or with initial ` = `.
Property(
String name,
@@ -167,17 +176,41 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
String type,
TypeMirror typeMirror,
Optional<String> nullableAnnotation,
- String getter) {
+ String getter,
+ Optional<String> maybeBuilderInitializer) {
this.name = name;
this.identifier = identifier;
this.type = type;
this.typeMirror = typeMirror;
this.nullableAnnotation = nullableAnnotation;
this.optional = Optionalish.createIfOptional(typeMirror);
+ this.builderInitializer =
+ maybeBuilderInitializer.isPresent()
+ ? " = " + maybeBuilderInitializer.get()
+ : builderInitializer();
this.getter = getter;
}
/**
+ * Returns the appropriate initializer for a builder property. Builder properties are never
+ * primitive; if the built property is an {@code int} the builder property will be an {@code
+ * Integer}. So the default value for a builder property will be null unless there is an
+ * initializer. The caller of the constructor may have supplied an initializer, but otherwise we
+ * supply one only if this property is an {@code Optional} and is not {@code @Nullable}. In that
+ * case the initializer sets it to {@code Optional.empty()}.
+ */
+ private String builderInitializer() {
+ if (nullableAnnotation.isPresent()) {
+ return "";
+ }
+ Optionalish optional = Optionalish.createIfOptional(typeMirror);
+ if (optional == null) {
+ return "";
+ }
+ return " = " + optional.getEmpty();
+ }
+
+ /**
* Returns the name of the property as it should be used when declaring identifiers (fields and
* parameters). If the original getter method was {@code foo()} then this will be {@code foo}.
* If it was {@code getFoo()} then it will be {@code foo}. If it was {@code getPackage()} then
@@ -219,6 +252,14 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
}
/**
+ * Returns a string to be used as an initializer for a builder field for this property,
+ * including the leading {@code =}, or an empty string if there is no explicit initializer.
+ */
+ public String getBuilderInitializer() {
+ return builderInitializer;
+ }
+
+ /**
* Returns the string to use as a method annotation to indicate the nullability of this
* property. It is either the empty string, if the property is not nullable, or an annotation
* string with a trailing space, such as {@code "@`javax.annotation.Nullable` "}, where the
@@ -266,7 +307,8 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
type,
method.getReturnType(),
nullableAnnotation,
- method.getSimpleName().toString());
+ method.getSimpleName().toString(),
+ Optional.empty());
this.method = method;
this.fieldAnnotations = fieldAnnotations;
this.methodAnnotations = methodAnnotations;
@@ -305,7 +347,6 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
@Override
public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
- annotationType = elementUtils().getTypeElement(annotationClassName);
if (annotationType == null) {
// This should not happen. If the annotation type is not found, how did the processor get
// triggered?
@@ -318,7 +359,6 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
+ " because the annotation class was not found");
return false;
}
- simpleAnnotationName = annotationType.getSimpleName().toString();
List<TypeElement> deferredTypes =
deferredTypeNames.stream()
.map(name -> elementUtils().getTypeElement(name))
@@ -330,9 +370,10 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
for (TypeElement type : deferredTypes) {
errorReporter.reportError(
type,
- "[AutoValueUndefined] Did not generate @%s class for %s because it references"
+ "[%sUndefined] Did not generate @%s class for %s because it references"
+ " undefined types",
simpleAnnotationName,
+ simpleAnnotationName,
type.getQualifiedName());
}
return false;
@@ -347,6 +388,7 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
deferredTypeNames.clear();
for (TypeElement type : types) {
try {
+ validateType(type);
processType(type);
} catch (AbortProcessingException e) {
// We abandoned this type; continue with the next.
@@ -362,7 +404,8 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
String trace = Throwables.getStackTraceAsString(e);
errorReporter.reportError(
type,
- "[AutoValueException] @%s processor threw an exception: %s",
+ "[%sException] @%s processor threw an exception: %s",
+ simpleAnnotationName,
simpleAnnotationName,
trace);
throw e;
@@ -372,8 +415,44 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
}
/**
- * Analyzes a single {@code @AutoValue} or {@code @AutoOneOf} class, and outputs the corresponding
- * implementation class or classes.
+ * Validations common to all the subclasses. An {@code @AutoFoo} type must be a class, or possibly
+ * an interface for {@code @AutoBuilder}. If it is a class then it must have a non-private no-arg
+ * constructor. And, since we'll be generating a subclass, it can't be final.
+ */
+ private void validateType(TypeElement type) {
+ ElementKind kind = type.getKind();
+ boolean kindOk =
+ kind.equals(ElementKind.CLASS)
+ || (appliesToInterfaces && kind.equals(ElementKind.INTERFACE));
+ if (!kindOk) {
+ String appliesTo = appliesToInterfaces ? "classes and interfaces" : "classes";
+ errorReporter.abortWithError(
+ type,
+ "[%sWrongType] @%s only applies to %s",
+ simpleAnnotationName,
+ simpleAnnotationName,
+ appliesTo);
+ }
+ checkModifiersIfNested(type);
+ if (!hasVisibleNoArgConstructor(type)) {
+ errorReporter.reportError(
+ type,
+ "[%sConstructor] @%s class must have a non-private no-arg constructor",
+ simpleAnnotationName,
+ simpleAnnotationName);
+ }
+ if (type.getModifiers().contains(Modifier.FINAL)) {
+ errorReporter.abortWithError(
+ type,
+ "[%sFinal] @%s class must not be final",
+ simpleAnnotationName,
+ simpleAnnotationName);
+ }
+ }
+
+ /**
+ * Analyzes a single {@code @AutoValue} (etc) class, and outputs the corresponding implementation
+ * class or classes.
*
* @param type the class with the {@code @AutoValue} or {@code @AutoOneOf} annotation.
*/
@@ -435,7 +514,9 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
if (p.isNullable() && returnType.getKind().isPrimitive()) {
errorReporter()
.reportError(
- propertyMethod, "[AutoValueNullPrimitive] Primitive types cannot be @Nullable");
+ propertyMethod,
+ "[%sNullPrimitive] Primitive types cannot be @Nullable",
+ simpleAnnotationName);
}
});
return props.build();
@@ -467,24 +548,23 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
/** Returns the spelling to be used in the generated code for the given list of annotations. */
static ImmutableList<String> annotationStrings(List<? extends AnnotationMirror> annotations) {
- // TODO(b/68008628): use ImmutableList.toImmutableList() when that works.
return annotations.stream()
.map(AnnotationOutput::sourceFormForAnnotation)
+ .sorted() // ensures deterministic order
.collect(toImmutableList());
}
/**
- * Returns the name of the generated {@code @AutoValue} or {@code @AutoOneOf} class, for example
- * {@code AutoOneOf_TaskResult} or {@code $$AutoValue_SimpleMethod}.
+ * Returns the name of the generated {@code @AutoValue} (etc) class, for example {@code
+ * AutoOneOf_TaskResult} or {@code $$AutoValue_SimpleMethod}.
*
- * @param type the name of the type bearing the {@code @AutoValue} or {@code @AutoOneOf}
- * annotation.
+ * @param type the name of the type bearing the {@code @AutoValue} (etc) annotation.
* @param prefix the prefix to use in the generated class. This may start with one or more dollar
* signs, for an {@code @AutoValue} implementation where there are AutoValue extensions.
*/
static String generatedClassName(TypeElement type, String prefix) {
String name = type.getSimpleName().toString();
- while (type.getEnclosingElement() instanceof TypeElement) {
+ while (MoreElements.isType(type.getEnclosingElement())) {
type = MoreElements.asType(type.getEnclosingElement());
name = type.getSimpleName() + "_" + name;
}
@@ -555,7 +635,8 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
for (ExecutableElement context : contexts) {
errorReporter.reportError(
context,
- "[AutoValueDupProperty] More than one @%s property called %s",
+ "[%sDupProperty] More than one @%s property called %s",
+ simpleAnnotationName,
simpleAnnotationName,
name);
}
@@ -589,8 +670,9 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
List<? extends AnnotationMirror> elementAnnotations = element.getAnnotationMirrors();
OptionalInt nullableAnnotationIndex = nullableAnnotationIndex(elementAnnotations);
if (nullableAnnotationIndex.isPresent()) {
- ImmutableList<String> annotations = annotationStrings(elementAnnotations);
- return Optional.of(annotations.get(nullableAnnotationIndex.getAsInt()) + " ");
+ AnnotationMirror annotation = elementAnnotations.get(nullableAnnotationIndex.getAsInt());
+ String annotationString = AnnotationOutput.sourceFormForAnnotation(annotation);
+ return Optional.of(annotationString + " ");
} else {
return Optional.empty();
}
@@ -1152,6 +1234,14 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
return getAnnotationMirror(element, annotationName).isPresent();
}
+ /** True if the type is a class with a non-private no-arg constructor, or is an interface. */
+ static boolean hasVisibleNoArgConstructor(TypeElement type) {
+ return type.getKind().isInterface()
+ || constructorsIn(type.getEnclosedElements()).stream()
+ .anyMatch(
+ c -> c.getParameters().isEmpty() && !c.getModifiers().contains(Modifier.PRIVATE));
+ }
+
final void writeSourceFile(String className, String text, TypeElement originatingType) {
try {
JavaFileObject sourceFile =
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java
index 51773e6f..a4336f5e 100644
--- a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java
@@ -386,14 +386,17 @@ abstract class BuilderMethodClassifier<E extends Element> {
DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType());
ExecutableType methodMirror =
MoreTypes.asExecutable(typeUtils.asMemberOf(builderTypeMirror, method));
- if (TYPE_EQUIVALENCE.equivalent(methodMirror.getReturnType(), builderType.asType())) {
+ TypeMirror returnType = methodMirror.getReturnType();
+ if (typeUtils.isSubtype(builderType.asType(), returnType)
+ && !MoreTypes.isTypeOf(Object.class, returnType)) {
+ // We allow the return type to be a supertype (other than Object), to support step builders.
TypeMirror parameterType = Iterables.getOnlyElement(methodMirror.getParameterTypes());
propertyNameToSetters.put(
propertyName, new PropertySetter(method, parameterType, function.get()));
} else {
errorReporter.reportError(
method,
- "[%sBuilderRet] Setter methods must return %s",
+ "[%sBuilderRet] Setter methods must return %s or a supertype",
autoWhat(),
builderType.asType());
}
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 9f45d172..b612c104 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
@@ -18,6 +18,7 @@ package com.google.auto.value.processor;
import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
import static com.google.auto.common.MoreStreams.toImmutableSet;
import static com.google.auto.value.processor.AutoValueishProcessor.hasAnnotationMirror;
+import static com.google.auto.value.processor.AutoValueishProcessor.hasVisibleNoArgConstructor;
import static com.google.auto.value.processor.AutoValueishProcessor.nullableAnnotationFor;
import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_BUILDER_NAME;
import static com.google.common.collect.Sets.immutableEnumSet;
@@ -86,16 +87,9 @@ class BuilderSpec {
Optional<TypeElement> builderTypeElement = Optional.empty();
for (TypeElement containedClass : typesIn(autoValueClass.getEnclosedElements())) {
if (hasAnnotationMirror(containedClass, AUTO_VALUE_BUILDER_NAME)) {
- if (!CLASS_OR_INTERFACE.contains(containedClass.getKind())) {
- errorReporter.reportError(
- containedClass,
- "[AutoValueBuilderClass] @AutoValue.Builder can only apply to a class or an"
- + " interface");
- } else if (!containedClass.getModifiers().contains(Modifier.STATIC)) {
- errorReporter.reportError(
- containedClass,
- "[AutoValueInnerBuilder] @AutoValue.Builder cannot be applied to a non-static class");
- } else if (builderTypeElement.isPresent()) {
+ findBuilderError(containedClass)
+ .ifPresent(error -> errorReporter.reportError(containedClass, "%s", error));
+ if (builderTypeElement.isPresent()) {
errorReporter.reportError(
containedClass,
"[AutoValueTwoBuilders] %s already has a Builder: %s",
@@ -114,6 +108,24 @@ class BuilderSpec {
}
}
+ /** Finds why this {@code @AutoValue.Builder} class is bad, if it is bad. */
+ private Optional<String> findBuilderError(TypeElement builderTypeElement) {
+ if (!CLASS_OR_INTERFACE.contains(builderTypeElement.getKind())) {
+ return Optional.of(
+ "[AutoValueBuilderClass] @AutoValue.Builder can only apply to a class or an"
+ + " interface");
+ } else if (!builderTypeElement.getModifiers().contains(Modifier.STATIC)) {
+ return Optional.of(
+ "[AutoValueInnerBuilder] @AutoValue.Builder cannot be applied to a non-static class");
+ } else if (builderTypeElement.getKind().equals(ElementKind.CLASS)
+ && !hasVisibleNoArgConstructor(builderTypeElement)) {
+ return Optional.of(
+ "[AutoValueBuilderConstructor] @AutoValue.Builder class must have a non-private no-arg"
+ + " constructor");
+ }
+ return Optional.empty();
+ }
+
/** Representation of an {@code AutoValue.Builder} class or interface. */
class Builder implements AutoValueExtension.BuilderContext {
private final TypeElement builderTypeElement;
@@ -333,7 +345,7 @@ class BuilderSpec {
vars.builderRequiredProperties =
vars.props.stream()
.filter(p -> !p.isNullable())
- .filter(p -> p.getOptional() == null)
+ .filter(p -> p.getBuilderInitializer().isEmpty())
.filter(p -> !vars.builderPropertyBuilders.containsKey(p.getName()))
.collect(toImmutableSet());
}
diff --git a/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java b/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java
index fae4e092..35fcbbf0 100644
--- a/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java
+++ b/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java
@@ -15,15 +15,9 @@
*/
package com.google.auto.value.processor;
-import static java.util.stream.Collectors.joining;
-
-import java.util.Collections;
import java.util.List;
-import java.util.Map;
import java.util.Optional;
import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
@@ -46,29 +40,7 @@ class GwtCompatibility {
return gwtCompatibleAnnotation;
}
- // Get rid of the misconceived <? extends ExecutableElement, ? extends AnnotationValue>
- // in the return type of getElementValues().
- static Map<ExecutableElement, AnnotationValue> getElementValues(AnnotationMirror annotation) {
- return Collections.<ExecutableElement, AnnotationValue>unmodifiableMap(
- annotation.getElementValues());
- }
-
String gwtCompatibleAnnotationString() {
- if (gwtCompatibleAnnotation.isPresent()) {
- AnnotationMirror annotation = gwtCompatibleAnnotation.get();
- TypeElement annotationElement = (TypeElement) annotation.getAnnotationType().asElement();
- String annotationArguments;
- if (annotation.getElementValues().isEmpty()) {
- annotationArguments = "";
- } else {
- annotationArguments =
- getElementValues(annotation).entrySet().stream()
- .map(e -> e.getKey().getSimpleName() + " = " + e.getValue())
- .collect(joining(", ", "(", ")"));
- }
- return "@" + annotationElement.getQualifiedName() + annotationArguments;
- } else {
- return "";
- }
+ return gwtCompatibleAnnotation.map(AnnotationOutput::sourceFormForAnnotation).orElse("");
}
}
diff --git a/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java b/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java
index 30ad0926..8673d3db 100644
--- a/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java
+++ b/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java
@@ -18,6 +18,7 @@ package com.google.auto.value.processor;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
+import com.google.auto.common.AnnotationMirrors;
import com.google.auto.value.processor.AutoValueishProcessor.GetterProperty;
import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder;
import com.google.common.collect.ImmutableMap;
@@ -26,13 +27,10 @@ import com.google.escapevelocity.Template;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
-import java.util.Map;
import java.util.Optional;
import java.util.zip.CRC32;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
@@ -60,13 +58,11 @@ class GwtSerialization {
Optional<AnnotationMirror> optionalGwtCompatible = gwtCompatibility.gwtCompatibleAnnotation();
if (optionalGwtCompatible.isPresent()) {
AnnotationMirror gwtCompatible = optionalGwtCompatible.get();
- for (Map.Entry<ExecutableElement, AnnotationValue> entry :
- GwtCompatibility.getElementValues(gwtCompatible).entrySet()) {
- if (entry.getKey().getSimpleName().contentEquals("serializable")
- && entry.getValue().getValue().equals(true)) {
- return true;
- }
- }
+ return AnnotationMirrors.getAnnotationValuesWithDefaults(gwtCompatible).entrySet().stream()
+ .anyMatch(
+ e ->
+ e.getKey().getSimpleName().contentEquals("serializable")
+ && e.getValue().getValue().equals(true));
}
return false;
}
diff --git a/value/src/main/java/com/google/auto/value/processor/autovalue.vm b/value/src/main/java/com/google/auto/value/processor/autovalue.vm
index 86cfe493..18ca827a 100644
--- a/value/src/main/java/com/google/auto/value/processor/autovalue.vm
+++ b/value/src/main/java/com/google/auto/value/processor/autovalue.vm
@@ -75,15 +75,11 @@ ${modifiers}class $subclass$formalTypes extends $origClass$actualTypes {
## the constructor is called from the extension code.
#if ($identifiers)
-
if ($p == null) {
throw new NullPointerException("Null $p.name");
}
#else
- ## Just throw NullPointerException with no message if it's null.
- ## The Object cast has no effect on the code but silences an ErrorProne warning.
-
- ((`java.lang.Object`) ${p}).getClass();
+ `java.util.Objects`.requireNonNull($p);
#end
#end
diff --git a/value/src/main/java/com/google/auto/value/processor/builder.vm b/value/src/main/java/com/google/auto/value/processor/builder.vm
index 630330ca..b1787f25 100644
--- a/value/src/main/java/com/google/auto/value/processor/builder.vm
+++ b/value/src/main/java/com/google/auto/value/processor/builder.vm
@@ -40,7 +40,7 @@ class ${builderName}${builderFormalTypes} ##
#if ($p.kind.primitive)
- private $types.boxedClass($p.typeMirror).simpleName $p;
+ private $types.boxedClass($p.typeMirror).simpleName $p $p.builderInitializer;
#else
@@ -54,7 +54,7 @@ class ${builderName}${builderFormalTypes} ##
#end
- private $p.type $p #if ($p.optional && !$p.nullable) = $p.optional.empty #end ;
+ private $p.type $p $p.builderInitializer;
#end
#end
@@ -94,15 +94,11 @@ class ${builderName}${builderFormalTypes} ##
#if (!$setter.primitiveParameter && !$p.nullable && ${setter.copy($p)} == $p)
#if ($identifiers)
-
if ($p == null) {
throw new NullPointerException("Null $p.name");
}
#else
- ## Just throw NullPointerException with no message if it's null.
- ## The Object cast has no effect on the code but silences an ErrorProne warning.
-
- ((`java.lang.Object`) ${p}).getClass();
+ `java.util.Objects`.requireNonNull($p);
#end
#end
diff --git a/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java
index 50b6b271..96649bd1 100644
--- a/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java
@@ -115,7 +115,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation)
.generatedSourceFile("foo.bar.AutoBuilder_Baz_Builder")
@@ -148,7 +147,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation)
.generatedSourceFile("foo.bar.AutoBuilder_Baz_Builder")
@@ -192,7 +190,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(built, builder);
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation).generatedSourceFile("foo.bar.AutoBuilder_Builder");
@@ -214,7 +211,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -242,7 +238,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -252,6 +247,66 @@ public final class AutoBuilderCompilationTest {
}
@Test
+ public void autoBuilderClassMustHaveNoArgConstructor() {
+ JavaFileObject javaFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoBuilder;",
+ "",
+ "public class Baz {",
+ " @AutoBuilder",
+ " abstract static class Builder {",
+ " Builder(int bogus) {}",
+ " Baz build();",
+ " }",
+ "}");
+ Compilation compilation =
+ javac()
+ .withProcessors(new AutoBuilderProcessor())
+ .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+ .compile(javaFileObject);
+ assertThat(compilation).failed();
+ assertThat(compilation)
+ .hadErrorContaining(
+ "[AutoBuilderConstructor] @AutoBuilder class must have a non-private no-arg"
+ + " constructor")
+ .inFile(javaFileObject)
+ .onLineContaining("class Builder");
+ }
+
+ @Test
+ public void autoBuilderClassMustHaveVisibleNoArgConstructor() {
+ JavaFileObject javaFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoBuilder;",
+ "",
+ "public class Baz {",
+ " @AutoBuilder",
+ " abstract static class Builder {",
+ " private Builder() {}",
+ " Baz build();",
+ " }",
+ "}");
+ Compilation compilation =
+ javac()
+ .withProcessors(new AutoBuilderProcessor())
+ .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+ .compile(javaFileObject);
+ assertThat(compilation).failed();
+ assertThat(compilation)
+ .hadErrorContaining(
+ "[AutoBuilderConstructor] @AutoBuilder class must have a non-private no-arg"
+ + " constructor")
+ .inFile(javaFileObject)
+ .onLineContaining("class Builder");
+ }
+
+ @Test
public void autoBuilderNestedInPrivate() {
JavaFileObject javaFileObject =
JavaFileObjects.forSourceLines(
@@ -271,7 +326,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -299,7 +353,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -328,7 +381,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -355,7 +407,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -386,7 +437,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -418,7 +468,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -451,7 +500,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -489,7 +537,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -519,7 +566,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -553,7 +599,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -584,7 +629,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -618,7 +662,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -650,7 +693,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -686,7 +728,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -719,12 +760,11 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
- "[AutoBuilderBuilderRet] Setter methods must return foo.bar.Baz.Builder")
+ "[AutoBuilderBuilderRet] Setter methods must return foo.bar.Baz.Builder or a supertype")
.inFile(javaFileObject)
.onLineContaining("two(int x)");
}
@@ -761,7 +801,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject, nullableFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -794,7 +833,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -827,7 +865,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
@@ -862,7 +899,6 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
- .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
.compile(javaFileObject);
assertThat(compilation).failed();
assertThat(compilation)
diff --git a/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java
index 788b543a..a55b74d0 100644
--- a/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java
@@ -463,6 +463,33 @@ public class AutoOneOfCompilationTest {
}
@Test
+ public void mustBeClass() {
+ JavaFileObject javaFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Pet",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoOneOf;",
+ "",
+ "@AutoOneOf(Pet.Kind.class)",
+ "public interface Pet {",
+ " public enum Kind {",
+ " DOG,",
+ " CAT,",
+ " }",
+ " Kind getKind();",
+ " String dog();",
+ " String cat();",
+ "}");
+ Compilation compilation =
+ javac().withProcessors(new AutoOneOfProcessor()).compile(javaFileObject);
+ assertThat(compilation)
+ .hadErrorContaining("@AutoOneOf only applies to classes")
+ .inFile(javaFileObject)
+ .onLineContaining("interface Pet");
+ }
+
+ @Test
public void cantBeNullable() {
JavaFileObject javaFileObject =
JavaFileObjects.forSourceLines(
@@ -490,4 +517,62 @@ public class AutoOneOfCompilationTest {
.inFile(javaFileObject)
.onLineContaining("@Nullable String dog()");
}
+
+ @Test
+ public void mustHaveNoArgConstructor() {
+ JavaFileObject javaFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Pet",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoOneOf;",
+ "",
+ "@AutoOneOf(Pet.Kind.class)",
+ "public abstract class Pet {",
+ " Pet(boolean cuddly) {}",
+ "",
+ " public enum Kind {",
+ " DOG,",
+ " CAT,",
+ " }",
+ " public abstract Kind getKind();",
+ " public abstract String dog();",
+ " public abstract String cat();",
+ "}");
+ Compilation compilation =
+ javac().withProcessors(new AutoOneOfProcessor()).compile(javaFileObject);
+ assertThat(compilation)
+ .hadErrorContaining("@AutoOneOf class must have a non-private no-arg constructor")
+ .inFile(javaFileObject)
+ .onLineContaining("class Pet");
+ }
+
+ @Test
+ public void mustHaveVisibleNoArgConstructor() {
+ JavaFileObject javaFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Pet",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoOneOf;",
+ "",
+ "@AutoOneOf(Pet.Kind.class)",
+ "public abstract class Pet {",
+ " private Pet() {}",
+ "",
+ " public enum Kind {",
+ " DOG,",
+ " CAT,",
+ " }",
+ " public abstract Kind getKind();",
+ " public abstract String dog();",
+ " public abstract String cat();",
+ "}");
+ Compilation compilation =
+ javac().withProcessors(new AutoOneOfProcessor()).compile(javaFileObject);
+ assertThat(compilation)
+ .hadErrorContaining("@AutoOneOf class must have a non-private no-arg constructor")
+ .inFile(javaFileObject)
+ .onLineContaining("class Pet");
+ }
}
diff --git a/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java
index ab6690fd..09d4faf9 100644
--- a/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java
@@ -21,6 +21,7 @@ import static com.google.testing.compile.CompilationSubject.compilations;
import static com.google.testing.compile.Compiler.javac;
import static java.util.stream.Collectors.joining;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.truth.Expect;
@@ -555,6 +556,50 @@ public class AutoValueCompilationTest {
}
@Test
+ public void autoValueMustBeClass() {
+ JavaFileObject javaFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "",
+ "@AutoValue",
+ "public interface Baz {",
+ " String buh();",
+ "}");
+ Compilation compilation =
+ javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+ assertThat(compilation)
+ .hadErrorContaining("@AutoValue only applies to classes")
+ .inFile(javaFileObject)
+ .onLineContaining("interface Baz");
+ }
+
+ @Test
+ public void autoValueMustNotBeFinal() {
+ JavaFileObject javaFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "",
+ "@AutoValue",
+ "public final class Baz {",
+ " public Baz create() {",
+ " return new AutoValue_Baz();",
+ " }",
+ "}");
+ Compilation compilation =
+ javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+ assertThat(compilation)
+ .hadErrorContaining("@AutoValue class must not be final")
+ .inFile(javaFileObject)
+ .onLineContaining("class Baz");
+ }
+
+ @Test
public void autoValueMustBeStatic() {
JavaFileObject javaFileObject =
JavaFileObjects.forSourceLines(
@@ -581,7 +626,7 @@ public class AutoValueCompilationTest {
}
@Test
- public void autoValueMustBeNotBePrivate() {
+ public void autoValueMustNotBePrivate() {
JavaFileObject javaFileObject =
JavaFileObjects.forSourceLines(
"foo.bar.Baz",
@@ -635,6 +680,52 @@ public class AutoValueCompilationTest {
}
@Test
+ public void autoValueMustHaveNoArgConstructor() {
+ JavaFileObject javaFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "",
+ "@AutoValue",
+ "public abstract class Baz {",
+ " Baz(int buh) {}",
+ "",
+ " public abstract int buh();",
+ "}");
+ Compilation compilation =
+ javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+ assertThat(compilation)
+ .hadErrorContaining("@AutoValue class must have a non-private no-arg constructor")
+ .inFile(javaFileObject)
+ .onLineContaining("class Baz");
+ }
+
+ @Test
+ public void autoValueMustHaveVisibleNoArgConstructor() {
+ JavaFileObject javaFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "",
+ "@AutoValue",
+ "public abstract class Baz {",
+ " private Baz() {}",
+ "",
+ " public abstract int buh();",
+ "}");
+ Compilation compilation =
+ javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+ assertThat(compilation)
+ .hadErrorContaining("@AutoValue class must have a non-private no-arg constructor")
+ .inFile(javaFileObject)
+ .onLineContaining("class Baz");
+ }
+
+ @Test
public void noMultidimensionalPrimitiveArrays() {
JavaFileObject javaFileObject =
JavaFileObjects.forSourceLines(
@@ -1448,6 +1539,42 @@ public class AutoValueCompilationTest {
}
@Test
+ public void autoValueBuilderMustHaveNoArgConstructor() {
+ JavaFileObject javaFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Example",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "",
+ "class Example {",
+ " @AutoValue",
+ " abstract static class Baz {",
+ " abstract int foo();",
+ "",
+ " static Builder builder() {",
+ " return new AutoValue_Example_Baz.Builder();",
+ " }",
+ "",
+ " @AutoValue.Builder",
+ " abstract static class Builder {",
+ " Builder(int defaultFoo) {}",
+ " abstract Builder foo(int x);",
+ " abstract Baz build();",
+ " }",
+ " }",
+ "}");
+ Compilation compilation =
+ javac()
+ .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+ .compile(javaFileObject);
+ assertThat(compilation)
+ .hadErrorContaining("@AutoValue.Builder class must have a non-private no-arg constructor")
+ .inFile(javaFileObject)
+ .onLineContaining("class Builder");
+ }
+
+ @Test
public void autoValueBuilderOnEnum() {
JavaFileObject javaFileObject =
JavaFileObjects.forSourceLines(
@@ -1850,6 +1977,8 @@ public class AutoValueCompilationTest {
@Test
public void autoValueBuilderSetterReturnType() {
+ // We do allow the return type of a setter to be a supertype of the builder type, to support
+ // step builders. But we don't allow it to be Object.
JavaFileObject javaFileObject =
JavaFileObjects.forSourceLines(
"foo.bar.Baz",
@@ -1863,7 +1992,7 @@ public class AutoValueCompilationTest {
"",
" @AutoValue.Builder",
" public interface Builder {",
- " void blim(int x);",
+ " Object blim(int x);",
" Baz build();",
" }",
"}");
@@ -1874,7 +2003,7 @@ public class AutoValueCompilationTest {
assertThat(compilation)
.hadErrorContaining("Setter methods must return foo.bar.Baz.Builder")
.inFile(javaFileObject)
- .onLineContaining("void blim(int x)");
+ .onLineContaining("Object blim(int x)");
}
@Test
@@ -2913,8 +3042,6 @@ public class AutoValueCompilationTest {
"foo.bar.Bar",
"package foo.bar;",
"",
- "import com.google.auto.value.AutoValue;",
- "",
"@" + Foo.class.getCanonicalName(),
"public abstract class Bar {",
" public abstract BarFoo barFoo();",
@@ -2928,6 +3055,73 @@ public class AutoValueCompilationTest {
}
@Test
+ public void referencingGeneratedClassInAnnotation() {
+ // Test that ensures that a type that does not exist can be referenced by a copied annotation
+ // as long as it later does come into existence. The BarFoo type referenced here does not exist
+ // when the AutoValueProcessor runs on the first round, but the FooProcessor then generates it.
+ // That generation provokes a further round of annotation processing and AutoValueProcessor
+ // should succeed then.
+ // We test the three places that a class reference could appear: as the value of a Class
+ // element, as the value of a Class[] element, in a nested annotation.
+ JavaFileObject barFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Bar",
+ "package foo.bar;",
+ "",
+ "@" + Foo.class.getCanonicalName(),
+ "public abstract class Bar {",
+ "}");
+ JavaFileObject referenceClassFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.ReferenceClass",
+ "package foo.bar;",
+ "",
+ "@interface ReferenceClass {",
+ " Class<?> value() default Void.class;",
+ " Class<?>[] values() default {};",
+ " Nested nested() default @Nested;",
+ " @interface Nested {",
+ " Class<?>[] values() default {};",
+ " }",
+ "}");
+ ImmutableList<String> annotations = ImmutableList.of(
+ "@ReferenceClass(BarFoo.class)",
+ "@ReferenceClass(values = {Void.class, BarFoo.class})",
+ "@ReferenceClass(nested = @ReferenceClass.Nested(values = {Void.class, BarFoo.class}))");
+ for (String annotation : annotations) {
+ JavaFileObject bazFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "",
+ "@AutoValue",
+ "@AutoValue.CopyAnnotations",
+ annotation,
+ "public abstract class Baz {",
+ " public abstract int foo();",
+ "",
+ " public static Baz create(int foo) {",
+ " return new AutoValue_Baz(foo);",
+ " }",
+ "}");
+ Compilation compilation =
+ javac()
+ .withProcessors(new AutoValueProcessor(), new FooProcessor())
+ .withOptions("-Xlint:-processing", "-implicit:none")
+ .compile(bazFileObject, barFileObject, referenceClassFileObject);
+ expect.about(compilations()).that(compilation).succeededWithoutWarnings();
+ if (compilation.status().equals(Compilation.Status.SUCCESS)) {
+ expect.about(compilations()).that(compilation)
+ .generatedSourceFile("foo.bar.AutoValue_Baz")
+ .contentsAsUtf8String()
+ .contains(annotation);
+ }
+ }
+ }
+
+ @Test
public void annotationReferencesUndefined() {
// Test that we don't throw an exception if asked to compile @SuppressWarnings(UNDEFINED)
// where UNDEFINED is an undefined symbol.
@@ -3052,6 +3246,63 @@ public class AutoValueCompilationTest {
}
@Test
+ public void methodAnnotationsCopiedInLexicographicalOrder() {
+ JavaFileObject bazFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "import com.package1.Annotation1;",
+ "import com.package2.Annotation0;",
+ "",
+ "@AutoValue",
+ "public abstract class Baz extends Parent {",
+ " @Annotation0",
+ " @Annotation1",
+ " @Override",
+ " public abstract String foo();",
+ "}");
+ JavaFileObject parentFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Parent",
+ "package foo.bar;",
+ "",
+ "public abstract class Parent {",
+ " public abstract String foo();",
+ "}");
+ JavaFileObject annotation1FileObject =
+ JavaFileObjects.forSourceLines(
+ "com.package1.Annotation1",
+ "package com.package1;",
+ "",
+ "import java.lang.annotation.ElementType;",
+ "import java.lang.annotation.Target;",
+ "",
+ "@Target({ElementType.FIELD, ElementType.METHOD})",
+ "public @interface Annotation1 {}");
+ JavaFileObject annotation0FileObject =
+ JavaFileObjects.forSourceLines(
+ "com.package2.Annotation0",
+ "package com.package2;",
+ "",
+ "public @interface Annotation0 {}");
+ Compilation compilation =
+ javac()
+ .withProcessors(new AutoValueProcessor())
+ .withOptions("-Xlint:-processing", "-implicit:none")
+ .compile(bazFileObject, parentFileObject, annotation1FileObject, annotation0FileObject);
+ assertThat(compilation).succeededWithoutWarnings();
+ assertThat(compilation)
+ .generatedSourceFile("foo.bar.AutoValue_Baz")
+ .contentsAsUtf8String()
+ .containsMatch(
+ "(?s:@Annotation1\\s+@Annotation0\\s+@Override\\s+public String foo\\(\\))");
+ // @Annotation1 precedes @Annotation 0 because
+ // @com.package2.Annotation1 precedes @com.package1.Annotation0
+ }
+
+ @Test
public void nonVisibleProtectedAnnotationFromOtherPackage() {
JavaFileObject bazFileObject =
JavaFileObjects.forSourceLines(
diff --git a/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java b/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java
index 48d8cd6e..1d7e89f5 100644
--- a/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java
@@ -510,11 +510,14 @@ public class PropertyAnnotationsTest {
"@PropertyAnnotationsTest.InheritedAnnotation")
.build();
+ // Annotations are in lexicographical order of FQN:
+ // @com.google.auto.value.processor.PropertyAnnotationsTest.InheritedAnnotation precedes
+ // @java.lang.Deprecated
JavaFileObject outputFile =
new OutputFileBuilder()
.setImports(imports)
- .addMethodAnnotations("@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation")
- .addFieldAnnotations("@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation")
+ .addMethodAnnotations("@PropertyAnnotationsTest.InheritedAnnotation", "@Deprecated")
+ .addFieldAnnotations("@PropertyAnnotationsTest.InheritedAnnotation", "@Deprecated")
.build();
Compilation compilation =
@@ -548,12 +551,16 @@ public class PropertyAnnotationsTest {
.addInnerTypes("@Target(ElementType.METHOD) @interface MethodsOnly {}")
.build();
+ // Annotations are in lexicographical order of FQN:
+ // @com.google.auto.value.processor.PropertyAnnotationsTest.InheritedAnnotation precedes
+ // @foo.bar.Baz.MethodsOnly precedes
+ // @java.lang.Deprecated
JavaFileObject outputFile =
new OutputFileBuilder()
.setImports(getImports(PropertyAnnotationsTest.class))
- .addFieldAnnotations("@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation")
+ .addFieldAnnotations("@PropertyAnnotationsTest.InheritedAnnotation", "@Deprecated")
.addMethodAnnotations(
- "@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation", "@Baz.MethodsOnly")
+ "@PropertyAnnotationsTest.InheritedAnnotation", "@Baz.MethodsOnly", "@Deprecated")
.build();
Compilation compilation =
diff --git a/value/userguide/autobuilder.md b/value/userguide/autobuilder.md
index ccd191ca..af9058bd 100644
--- a/value/userguide/autobuilder.md
+++ b/value/userguide/autobuilder.md
@@ -13,9 +13,6 @@ corresponding to the getter methods in the `@AutoValue` class, an `@AutoBuilder`
has setter methods corresponding to the parameters of a constructor or static
method. Apart from that, the two are very similar.
-AutoBuilder is **unstable** and it is possible that its API
-may change. We do not recommend depending on it for production code yet.
-
## Example: calling a constructor
Here is a simple example:
diff --git a/value/userguide/builders-howto.md b/value/userguide/builders-howto.md
index e7cf5373..3ff89468 100644
--- a/value/userguide/builders-howto.md
+++ b/value/userguide/builders-howto.md
@@ -154,7 +154,7 @@ public abstract class Animal {
abstract Builder toBuilder();
- public Animal withName(String name) {
+ public final Animal withName(String name) {
return toBuilder().setName(name).build();
}
@@ -201,7 +201,7 @@ public abstract class Animal {
abstract Animal autoBuild(); // not public
- public Animal build() {
+ public final Animal build() {
Animal animal = autoBuild();
Preconditions.checkState(animal.numberOfLegs() >= 0, "Negative legs");
return animal;
@@ -235,7 +235,7 @@ public abstract class Animal {
abstract Animal autoBuild(); // not public
- public Animal build() {
+ public final Animal build() {
setName(name().toLowerCase());
return autoBuild();
}
@@ -279,8 +279,8 @@ public abstract class Animal {
abstract Animal autoBuild(); // not public
- public Animal build() {
- if (!name().isPresent()) {
+ public final Animal build() {
+ if (name().isEmpty()) {
setName(numberOfLegs() + "-legged creature");
}
return autoBuild();
@@ -491,7 +491,7 @@ public abstract class Animal {
public abstract Builder setNumberOfLegs(int value);
abstract ImmutableSet.Builder<String> countriesBuilder();
- public Builder addCountry(String value) {
+ public final Builder addCountry(String value) {
countriesBuilder().add(value);
return this;
}
@@ -623,11 +623,75 @@ in an exception because the required properties of `Species` have not been set.
A [_step builder_](http://rdafbn.blogspot.com/2012/07/step-builder-pattern_28.html)
is a collection of builder interfaces that take you step by step through the
-setting of each of a list of required properties. We think that these are a nice
-idea in principle but not necessarily in practice. Regardless, if you want to
-use AutoValue to implement a step builder,
-[this example](https://github.com/google/auto/issues/1000#issuecomment-792875738)
-shows you how.
+setting of each of a list of required properties. This means you can be sure at
+compile time that all the properties are set before you build, at the expense of
+some extra code and a bit less flexibility.
+
+Here is an example:
+
+```java
+@AutoValue
+public abstract class Stepped {
+ public abstract String foo();
+ public abstract String bar();
+ public abstract int baz();
+
+ public static FooStep builder() {
+ return new AutoValue_Stepped.Builder();
+ }
+
+ public interface FooStep {
+ BarStep setFoo(String foo);
+ }
+
+ public interface BarStep {
+ BazStep setBar(String bar);
+ }
+
+ public interface BazStep {
+ Build setBaz(int baz);
+ }
+
+ public interface Build {
+ Stepped build();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder implements FooStep, BarStep, BazStep, Build {}
+}
+```
+
+It might be used like this:
+
+```java
+Stepped stepped = Stepped.builder().setFoo("foo").setBar("bar").setBaz(3).build();
+```
+
+The idea is that the only way to build an instance of `Stepped`
+is to go through the steps imposed by the `FooStep`, `BarStep`, and
+`BazStep` interfaces to set the properties in order, with a final build step.
+
+Once you have set the `baz` property there is nothing else to do except build,
+so you could also combine the `setBaz` and `build` methods like this:
+
+```java
+ ...
+
+ public interface BazStep {
+ Stepped setBazAndBuild(int baz);
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder implements FooStep, BarStep, BazStep {
+ abstract Builder setBaz(int baz);
+ abstract Stepped build();
+
+ @Override
+ public Stepped setBazAndBuild(int baz) {
+ return setBaz(baz).build();
+ }
+ }
+```
## <a name="autobuilder"></a> ... create a builder for something other than an `@AutoValue`?
diff --git a/value/userguide/howto.md b/value/userguide/howto.md
index c4511854..0a7607b5 100644
--- a/value/userguide/howto.md
+++ b/value/userguide/howto.md
@@ -608,7 +608,7 @@ variant as just described.
### Copying to the generated class
If you want to copy annotations from your `@AutoValue`-annotated class to the
-generated `AutoValue_...` implemention, annotate your class with
+generated `AutoValue_...` implementation, annotate your class with
[`@AutoValue.CopyAnnotations`].
For example, if `Example.java` is: