diff options
Diffstat (limited to 'value')
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: |