aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Cross <ccross@android.com>2023-08-07 19:49:11 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-08-07 19:49:11 +0000
commit0a1ade69ee54ecfd03f761eca3f2436e14367c94 (patch)
treee169463665583d3d3ce6be01017b06deb802b2e2
parent941c7a94ba9ccee7b4f2b2bfe49d2c87ec7d2b3e (diff)
parente51b5c5889bd8579432c9693660eb3eddc42cb35 (diff)
downloadauto-0a1ade69ee54ecfd03f761eca3f2436e14367c94.tar.gz
Merge commit 'auto-value-1.10.2^' into update-auto am: 898bff0150 am: f5698d756a am: e51b5c5889
Original change: https://android-review.googlesource.com/c/platform/external/auto/+/2690586 Change-Id: I57fcf80a5a90f002fb9a1df2b9c67322f471d30d Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.github/workflows/ci.yml26
-rw-r--r--METADATA16
-rw-r--r--README.md10
-rw-r--r--common/README.md24
-rw-r--r--common/pom.xml54
-rw-r--r--common/src/main/java/com/google/auto/common/AnnotationMirrors.java6
-rw-r--r--common/src/main/java/com/google/auto/common/AnnotationValues.java7
-rw-r--r--common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java33
-rw-r--r--common/src/main/java/com/google/auto/common/MoreTypes.java48
-rw-r--r--common/src/main/java/com/google/auto/common/Overrides.java41
-rw-r--r--common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java10
-rw-r--r--common/src/test/java/com/google/auto/common/MoreElementsTest.java59
-rw-r--r--common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java254
-rw-r--r--common/src/test/java/com/google/auto/common/OverridesTest.java10
-rw-r--r--common/src/test/java/com/google/auto/common/SuperficialValidationTest.java187
-rw-r--r--factory/README.md18
-rw-r--r--factory/pom.xml39
-rw-r--r--factory/src/it/functional/pom.xml10
-rw-r--r--factory/src/main/java/com/google/auto/factory/AutoFactory.java43
-rw-r--r--factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java30
-rw-r--r--factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java64
-rw-r--r--factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java8
-rw-r--r--factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java12
-rw-r--r--factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java101
-rw-r--r--factory/src/main/java/com/google/auto/factory/processor/Mirrors.java2
-rw-r--r--factory/src/main/java/com/google/auto/factory/processor/Parameter.java31
-rw-r--r--factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorNegativeTest.java181
-rw-r--r--factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java650
-rw-r--r--factory/src/test/resources/bad/AnnotationsToApplyMultiple.java33
-rw-r--r--factory/src/test/resources/bad/AnnotationsToApplyNotAnnotations.java31
-rw-r--r--factory/src/test/resources/bad/AnnotationsToApplyRepeated.java28
-rw-r--r--factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java15
-rw-r--r--factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java14
-rw-r--r--factory/src/test/resources/expected/ConstructorAnnotatedFactory.java18
-rw-r--r--factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java18
-rw-r--r--factory/src/test/resources/expected/ConstructorAnnotatedThrowsFactory.java18
-rw-r--r--factory/src/test/resources/expected/CustomAnnotationsFactory.java35
-rw-r--r--factory/src/test/resources/expected/CustomNamedFactory.java2
-rw-r--r--factory/src/test/resources/expected/CustomNullableFactory.java12
-rw-r--r--factory/src/test/resources/expected/DefaultPackageFactory.java2
-rw-r--r--factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java2
-rw-r--r--factory/src/test/resources/expected/FactoryExtendingAbstractClassThrowsFactory.java2
-rw-r--r--factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java12
-rw-r--r--factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java14
-rw-r--r--factory/src/test/resources/expected/Generics_ExplicitFooImplFactory.java14
-rw-r--r--factory/src/test/resources/expected/Generics_FooImplFactory.java5
-rw-r--r--factory/src/test/resources/expected/Generics_FooImplWithClassFactory.java8
-rw-r--r--factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java16
-rw-r--r--factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java28
-rw-r--r--factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java2
-rw-r--r--factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java2
-rw-r--r--factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java16
-rw-r--r--factory/src/test/resources/expected/NestedClassCustomNamedFactory.java2
-rw-r--r--factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java2
-rw-r--r--factory/src/test/resources/expected/OnlyPrimitivesFactory.java2
-rw-r--r--factory/src/test/resources/expected/ParameterAnnotationsFactory.java57
-rw-r--r--factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java12
-rw-r--r--factory/src/test/resources/expected/PublicClassFactory.java2
-rw-r--r--factory/src/test/resources/expected/SimpleClassFactory.java2
-rw-r--r--factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java2
-rw-r--r--factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java2
-rw-r--r--factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java14
-rw-r--r--factory/src/test/resources/expected/SimpleClassNonFinalFactory.java2
-rw-r--r--factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java14
-rw-r--r--factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java12
-rw-r--r--factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java26
-rw-r--r--factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java14
-rw-r--r--factory/src/test/resources/expected/SimpleClassThrowsFactory.java2
-rw-r--r--factory/src/test/resources/expected/SimpleClassVarargsFactory.java12
-rw-r--r--factory/src/test/resources/good/CustomAnnotations.java29
-rw-r--r--factory/src/test/resources/good/ParameterAnnotations.java52
-rw-r--r--service/annotations/pom.xml2
-rw-r--r--service/pom.xml10
-rw-r--r--service/processor/pom.xml4
-rw-r--r--service/processor/src/main/java/com/google/auto/service/processor/AutoServiceProcessor.java43
-rw-r--r--service/processor/src/main/java/com/google/auto/service/processor/ServicesFiles.java14
-rw-r--r--service/processor/src/test/java/com/google/auto/service/processor/AutoServiceProcessorTest.java138
-rw-r--r--service/processor/src/test/resources/test/AutoServiceOnAbstractClass.java21
-rw-r--r--service/processor/src/test/resources/test/AutoServiceOnInterface.java21
-rw-r--r--service/processor/src/test/resources/test/DoesNotImplement.java22
-rw-r--r--service/processor/src/test/resources/test/DoesNotImplementSuppressed.java23
-rwxr-xr-xutil/mvn-deploy.sh20
-rw-r--r--value/Android.bp3
-rw-r--r--value/CHANGES.md2
-rw-r--r--value/README.md11
-rw-r--r--value/annotations/pom.xml2
-rw-r--r--value/pom.xml28
-rw-r--r--value/processor/pom.xml98
-rw-r--r--value/src/it/functional/pom.xml145
-rw-r--r--value/src/it/functional/src/main/java/PackagelessNestedValueType.java1
-rw-r--r--value/src/it/functional/src/main/java/PackagelessValueType.java1
-rw-r--r--value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationTest.java44
-rw-r--r--value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderKotlinTest.java206
-rw-r--r--value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java183
-rw-r--r--value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java200
-rw-r--r--value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java271
-rw-r--r--value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java22
-rw-r--r--value/src/it/functional/src/test/java/com/google/auto/value/GradleIT.java (renamed from value/src/it/functional/src/test/java/com/google/auto/value/GradleTest.java)2
-rw-r--r--value/src/it/functional/src/test/java/com/google/auto/value/KotlinData.kt32
-rw-r--r--value/src/it/functional/src/test/java/com/google/auto/value/annotations/TestAnnotation.java23
-rw-r--r--value/src/it/functional/src/test/java/com/google/auto/value/gwt/CustomFieldSerializerTest.java134
-rw-r--r--value/src/it/gwtserializer/pom.xml38
-rw-r--r--value/src/main/java/com/google/auto/value/AutoAnnotation.java15
-rw-r--r--value/src/main/java/com/google/auto/value/AutoBuilder.java3
-rw-r--r--value/src/main/java/com/google/auto/value/AutoOneOf.java2
-rw-r--r--value/src/main/java/com/google/auto/value/AutoValue.java4
-rw-r--r--value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java29
-rw-r--r--value/src/main/java/com/google/auto/value/extension/memoized/Memoized.java2
-rw-r--r--value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java198
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/g3doc/index.md8
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/g3doc/serializer-extension.md8
-rw-r--r--value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java4
-rw-r--r--value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java183
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java18
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoBuilderAnnotationTemplateVars.java57
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java513
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java15
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java12
-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.java233
-rw-r--r--value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java27
-rw-r--r--value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoBuilder.java70
-rw-r--r--value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java16
-rw-r--r--value/src/main/java/com/google/auto/value/processor/BuilderRequiredProperties.java401
-rw-r--r--value/src/main/java/com/google/auto/value/processor/BuilderSpec.java13
-rw-r--r--value/src/main/java/com/google/auto/value/processor/ClassNames.java2
-rw-r--r--value/src/main/java/com/google/auto/value/processor/Executable.java144
-rw-r--r--value/src/main/java/com/google/auto/value/processor/ExtensionContext.java43
-rw-r--r--value/src/main/java/com/google/auto/value/processor/ForwardingClassGenerator.java229
-rw-r--r--value/src/main/java/com/google/auto/value/processor/MissingTypes.java2
-rw-r--r--value/src/main/java/com/google/auto/value/processor/Nullables.java89
-rw-r--r--value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java59
-rw-r--r--value/src/main/java/com/google/auto/value/processor/TypeEncoder.java12
-rw-r--r--value/src/main/java/com/google/auto/value/processor/autobuilder.vm2
-rw-r--r--value/src/main/java/com/google/auto/value/processor/autobuilderannotation.vm54
-rw-r--r--value/src/main/java/com/google/auto/value/processor/builder.vm90
-rw-r--r--value/src/main/java/com/google/auto/value/processor/equalshashcode.vm8
-rw-r--r--value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java106
-rw-r--r--value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java45
-rw-r--r--value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/CompilationAbstractTest.java42
-rw-r--r--value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java23
-rw-r--r--value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java114
-rw-r--r--value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java417
-rw-r--r--value/src/test/java/com/google/auto/value/processor/AutoValueModuleCompilationTest.java150
-rw-r--r--value/src/test/java/com/google/auto/value/processor/BuilderRequiredPropertiesTest.java351
-rw-r--r--value/src/test/java/com/google/auto/value/processor/ForwardingClassGeneratorTest.java189
-rw-r--r--value/src/test/java/com/google/auto/value/processor/GeneratedDoesNotExistTest.java33
-rw-r--r--value/src/test/java/com/google/auto/value/processor/NullablesTest.java15
-rw-r--r--value/userguide/autobuilder.md139
-rw-r--r--value/userguide/builders-howto.md77
-rw-r--r--value/userguide/builders.md4
-rw-r--r--value/userguide/extensions.md4
-rw-r--r--value/userguide/howto.md43
-rw-r--r--value/userguide/index.md25
-rw-r--r--value/userguide/practices.md8
-rw-r--r--value/userguide/records.md560
-rw-r--r--value/userguide/why.md6
157 files changed, 7308 insertions, 1984 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 44718421..9880de9b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,35 +3,35 @@ name: CI
on:
push:
branches:
- - master
+ - main
pull_request:
branches:
- - master
+ - main
jobs:
test:
name: "JDK ${{ matrix.java }}"
strategy:
matrix:
- java: [ 8, 11 ]
+ java: [ 8, 11, 17 ]
runs-on: ubuntu-latest
steps:
# Cancel any previous runs for the same branch that are still running.
- name: 'Cancel previous runs'
- uses: styfle/cancel-workflow-action@0.9.1
+ uses: styfle/cancel-workflow-action@b173b6ec0100793626c2d9e6b90435061f4fc3e5
with:
access_token: ${{ github.token }}
- name: 'Check out repository'
- uses: actions/checkout@v2.4.0
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
- name: 'Cache local Maven repository'
- uses: actions/cache@v2.1.7
+ uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8
with:
path: ~/.m2/repository
key: maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
maven-
- name: 'Set up JDK ${{ matrix.java }}'
- uses: actions/setup-java@v2
+ uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2
with:
java-version: ${{ matrix.java }}
distribution: 'zulu'
@@ -49,16 +49,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Check out repository'
- uses: actions/checkout@v2.4.0
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
- name: 'Cache local Maven repository'
- uses: actions/cache@v2.1.7
+ uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8
with:
path: ~/.m2/repository
key: maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
maven-
- name: 'Set up JDK 11'
- uses: actions/setup-java@v2
+ uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2
with:
java-version: 11
distribution: 'zulu'
@@ -78,16 +78,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Check out repository'
- uses: actions/checkout@v2.4.0
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
- name: 'Cache local Maven repository'
- uses: actions/cache@v2.1.7
+ uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8
with:
path: ~/.m2/repository
key: maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
maven-
- name: 'Set up JDK 11'
- uses: actions/setup-java@v2
+ uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2
with:
java-version: 11
distribution: 'zulu'
diff --git a/METADATA b/METADATA
index 560bb920..3fabcf89 100644
--- a/METADATA
+++ b/METADATA
@@ -1,7 +1,9 @@
-name: "auto"
-description:
- "A collection of source code generators for Java."
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update auto
+# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+name: "auto"
+description: "A collection of source code generators for Java."
third_party {
url {
type: HOMEPAGE
@@ -11,7 +13,11 @@ third_party {
type: GIT
value: "https://github.com/google/auto"
}
- version: "auto-value-1.9"
- last_upgrade_date { year: 2022 month: 04 day: 11 }
+ version: "auto-value-1.10.2"
license_type: NOTICE
+ last_upgrade_date {
+ year: 2023
+ month: 8
+ day: 2
+ }
}
diff --git a/README.md b/README.md
index bbefd275..204c059a 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
A collection of source code generators for [Java][java].
-## Auto‽
+## Overview
[Java][java] is full of code that is mechanical, repetitive, typically untested
and sometimes the source of subtle bugs. _Sounds like a job for robots!_
@@ -49,10 +49,10 @@ Save time. Save code. Save sanity.
See the License for the specific language governing permissions and
limitations under the License.
-[AutoFactory]: https://github.com/google/auto/tree/master/factory
-[AutoService]: https://github.com/google/auto/tree/master/service
-[AutoValue]: https://github.com/google/auto/tree/master/value
-[Common]: https://github.com/google/auto/tree/master/common
+[AutoFactory]: https://github.com/google/auto/tree/main/factory
+[AutoService]: https://github.com/google/auto/tree/main/service
+[AutoValue]: https://github.com/google/auto/tree/main/value
+[Common]: https://github.com/google/auto/tree/main/common
[java]: https://en.wikipedia.org/wiki/Java_(programming_language)
[value-type]: http://en.wikipedia.org/wiki/Value_object
diff --git a/common/README.md b/common/README.md
index 9f8eb79e..f85bca04 100644
--- a/common/README.md
+++ b/common/README.md
@@ -7,14 +7,22 @@ annotation processing environment.
## Utility classes of note
-* MoreTypes - utilities and Equivalence wrappers for TypeMirror and related
- subtypes
-* MoreElements - utilities for Element and related subtypes
-* SuperficialValidation - very simple scanner to ensure an Element is valid
- and free from distortion from upstream compilation errors
-* Visibility - utilities for working with Elements' visibility levels (public,
- protected, etc.)
-* BasicAnnotationProcessor/ProcessingStep - simple types that
+`MoreTypes`
+: Utilities and `Equivalence` wrappers for `TypeMirror` and related subtypes
+
+`MoreElements`
+: Utilities for `Element` and related subtypes
+
+`SuperficialValidation`
+: Very simple scanner to ensure an `Element` is valid and free from distortion
+ from upstream compilation errors
+
+`Visibility`
+: Utilities for working with `Element`s' visibility levels (public, protected,
+ etc.)
+
+`BasicAnnotationProcessor`/`Step`
+: Simple types that
- implement a validating annotation processor
- defer invalid elements until later
- break processor actions into multiple steps (which may each handle
diff --git a/common/pom.xml b/common/pom.xml
index a9c1e3f1..63cf9427 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -31,12 +31,12 @@
<description>
Common utilities for creating annotation processors.
</description>
- <url>https://github.com/google/auto/tree/master/common</url>
+ <url>https://github.com/google/auto/tree/main/common</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
- <guava.version>31.0.1-jre</guava.version>
+ <guava.version>32.0.1-jre</guava.version>
<truth.version>1.1.3</truth.version>
</properties>
@@ -104,39 +104,71 @@
<version>${truth.version}</version>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>org.eclipse.jdt</groupId>
- <artifactId>ecj</artifactId>
- <version>3.25.0</version>
- <scope>test</scope>
- </dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
- <version>3.8.1</version>
+ <version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
+ <testExcludes combine.children="append" />
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-java</artifactId>
- <version>1.1.0</version>
+ <version>1.1.2</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
- <version>3.2.0</version>
+ <version>3.3.0</version>
</plugin>
</plugins>
</build>
+
+ <profiles>
+ <profile>
+ <id>test-with-ecj</id>
+ <activation>
+ <jdk>[17,)</jdk>
+ </activation>
+ <dependencies>
+ <!-- test dependencies -->
+ <dependency>
+ <groupId>org.eclipse.jdt</groupId>
+ <artifactId>ecj</artifactId>
+ <version>3.34.0</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ </profile>
+
+ <profile>
+ <id>test-without-ecj</id>
+ <activation>
+ <jdk>(,17)</jdk>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <testExcludes>
+ <exclude>**/OverridesTest.java</exclude>
+ </testExcludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
</project>
diff --git a/common/src/main/java/com/google/auto/common/AnnotationMirrors.java b/common/src/main/java/com/google/auto/common/AnnotationMirrors.java
index 2f59dd39..47169fb5 100644
--- a/common/src/main/java/com/google/auto/common/AnnotationMirrors.java
+++ b/common/src/main/java/com/google/auto/common/AnnotationMirrors.java
@@ -159,7 +159,7 @@ public final class AnnotationMirrors {
* Returns all {@linkplain AnnotationMirror annotations} that are present on the given {@link
* Element} which are themselves annotated with {@code annotationClass}.
*/
- public static ImmutableSet<? extends AnnotationMirror> getAnnotatedAnnotations(
+ public static ImmutableSet<AnnotationMirror> getAnnotatedAnnotations(
Element element, Class<? extends Annotation> annotationClass) {
String name = annotationClass.getCanonicalName();
if (name == null) {
@@ -172,7 +172,7 @@ public final class AnnotationMirrors {
* Returns all {@linkplain AnnotationMirror annotations} that are present on the given {@link
* Element} which are themselves annotated with {@code annotation}.
*/
- public static ImmutableSet<? extends AnnotationMirror> getAnnotatedAnnotations(
+ public static ImmutableSet<AnnotationMirror> getAnnotatedAnnotations(
Element element, TypeElement annotation) {
return element.getAnnotationMirrors().stream()
.filter(input -> isAnnotationPresent(input.getAnnotationType().asElement(), annotation))
@@ -184,7 +184,7 @@ public final class AnnotationMirrors {
* Element} which are themselves annotated with an annotation whose type's canonical name is
* {@code annotationName}.
*/
- public static ImmutableSet<? extends AnnotationMirror> getAnnotatedAnnotations(
+ public static ImmutableSet<AnnotationMirror> getAnnotatedAnnotations(
Element element, String annotationName) {
return element.getAnnotationMirrors().stream()
.filter(input -> isAnnotationPresent(input.getAnnotationType().asElement(), annotationName))
diff --git a/common/src/main/java/com/google/auto/common/AnnotationValues.java b/common/src/main/java/com/google/auto/common/AnnotationValues.java
index a3cc9495..f3012382 100644
--- a/common/src/main/java/com/google/auto/common/AnnotationValues.java
+++ b/common/src/main/java/com/google/auto/common/AnnotationValues.java
@@ -152,6 +152,11 @@ public final class AnnotationValues {
},
null);
}
+
+ @Override
+ public String toString() {
+ return "AnnotationValues.equivalence()";
+ }
};
/**
@@ -348,7 +353,7 @@ public final class AnnotationValues {
@Override
public ImmutableList<T> defaultAction(Object o, Void unused) {
- throw new IllegalStateException("Expected an array, got instead: " + o);
+ throw new IllegalArgumentException("Expected an array, got instead: " + o);
}
@Override
diff --git a/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java b/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java
index d951aaf4..7b69d332 100644
--- a/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java
+++ b/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java
@@ -17,6 +17,7 @@ package com.google.auto.common;
import static com.google.auto.common.MoreElements.asExecutable;
import static com.google.auto.common.MoreElements.asPackage;
+import static com.google.auto.common.MoreElements.isAnnotationPresent;
import static com.google.auto.common.MoreStreams.toImmutableMap;
import static com.google.auto.common.MoreStreams.toImmutableSet;
import static com.google.auto.common.SuperficialValidation.validateElement;
@@ -29,7 +30,6 @@ import static javax.lang.model.element.ElementKind.PACKAGE;
import static javax.tools.Diagnostic.Kind.ERROR;
import com.google.common.base.Ascii;
-import com.google.common.base.Optional;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -42,6 +42,7 @@ import com.google.common.collect.Sets;
import java.lang.annotation.Annotation;
import java.util.LinkedHashSet;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
@@ -49,7 +50,6 @@ import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
-import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
@@ -180,9 +180,7 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor {
@Override
public final ImmutableSet<String> getSupportedAnnotationTypes() {
checkState(steps != null);
- return steps.stream()
- .flatMap(step -> step.annotations().stream())
- .collect(toImmutableSet());
+ return steps.stream().flatMap(step -> step.annotations().stream()).collect(toImmutableSet());
}
@Override
@@ -351,10 +349,14 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor {
}
// element.getEnclosedElements() does NOT return parameter elements
- if (element instanceof ExecutableElement) {
- for (Element parameterElement : asExecutable(element).getParameters()) {
- findAnnotatedElements(parameterElement, annotationTypes, annotatedElements);
- }
+ switch (element.getKind()) {
+ case METHOD:
+ case CONSTRUCTOR:
+ for (Element parameterElement : asExecutable(element).getParameters()) {
+ findAnnotatedElements(parameterElement, annotationTypes, annotatedElements);
+ }
+ break;
+ default: // do nothing
}
for (TypeElement annotationType : annotationTypes) {
if (isAnnotationPresent(element, annotationType)) {
@@ -363,12 +365,6 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor {
}
}
- private static boolean isAnnotationPresent(Element element, TypeElement annotationType) {
- return element.getAnnotationMirrors().stream()
- .anyMatch(
- mirror -> MoreTypes.asTypeElement(mirror.getAnnotationType()).equals(annotationType));
- }
-
/**
* Returns the nearest enclosing {@link TypeElement} to the current element, throwing an {@link
* IllegalArgumentException} if the provided {@link Element} is a {@link PackageElement} or is
@@ -538,6 +534,9 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor {
* An {@link ElementName} for an annotated element. If {@code element} is a package, uses the
* fully qualified name of the package. If it's a type, uses its fully qualified name.
* Otherwise, uses the fully-qualified name of the nearest enclosing type.
+ *
+ * <p>A package can be annotated if it has a {@code package-info.java} with annotations on the
+ * package declaration.
*/
static ElementName forAnnotatedElement(Element element) {
return element.getKind() == PACKAGE
@@ -551,11 +550,11 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor {
}
/**
- * The {@link Element} whose fully-qualified name is {@link #name()}. Absent if the relevant
+ * The {@link Element} whose fully-qualified name is {@link #name()}. Empty if the relevant
* method on {@link Elements} returns {@code null}.
*/
Optional<? extends Element> getElement(Elements elements) {
- return Optional.fromNullable(
+ return Optional.ofNullable(
kind == Kind.PACKAGE_NAME
? elements.getPackageElement(name)
: elements.getTypeElement(name));
diff --git a/common/src/main/java/com/google/auto/common/MoreTypes.java b/common/src/main/java/com/google/auto/common/MoreTypes.java
index 1a490626..c8be6255 100644
--- a/common/src/main/java/com/google/auto/common/MoreTypes.java
+++ b/common/src/main/java/com/google/auto/common/MoreTypes.java
@@ -141,22 +141,44 @@ public final class MoreTypes {
@Override
public boolean equals(@Nullable Object o) {
- if (o instanceof ComparedElements) {
- ComparedElements that = (ComparedElements) o;
- int nArguments = aArguments.size();
- if (!this.a.equals(that.a) || !this.b.equals(that.b) || nArguments != bArguments.size()) {
- // The arguments must be the same size, but we check anyway.
+ if (!(o instanceof ComparedElements)) {
+ return false;
+ }
+ ComparedElements that = (ComparedElements) o;
+
+ int nArguments = this.aArguments.size();
+ if (nArguments != that.aArguments.size()) {
+ return false;
+ }
+ // The arguments must be the same size, but we check anyway.
+ if (nArguments != this.bArguments.size() || nArguments != that.bArguments.size()) {
+ return false;
+ }
+
+ if (!this.a.equals(that.a) || !this.b.equals(that.b)) {
+ return false;
+ }
+
+ /*
+ * The purpose here is just to avoid the infinite recursion that we would otherwise have
+ * if Enum<E extends Enum<E>> is compared against itself, for example. If we are able to
+ * see that the inner Enum<E> is the same object as the outer one then we don't need a
+ * recursive call to compare the "a" Enum<E> against the "b" Enum<E>. The same-object check
+ * may not be completely justified, but it relies on the practical assumption that the
+ * compiler is not going to conjure up an infinite regress of objects to represent this
+ * recursive type. Other comparison methods like comparing their toString() are expensive
+ * and not warranted.
+ */
+ for (int i = 0; i < nArguments; i++) {
+ if (this.aArguments.get(i) != that.aArguments.get(i)) {
return false;
}
- for (int i = 0; i < nArguments; i++) {
- if (aArguments.get(i) != bArguments.get(i)) {
- return false;
- }
+ if (this.bArguments.get(i) != that.bArguments.get(i)) {
+ return false;
}
- return true;
- } else {
- return false;
}
+
+ return true;
}
@Override
@@ -314,7 +336,7 @@ public final class MoreTypes {
// ExecutableType.
@SuppressWarnings("TypesEquals")
boolean equal = a.equals(b);
- if (equal && !(a instanceof ExecutableType)) {
+ if (equal && a.getKind() != TypeKind.EXECUTABLE) {
return true;
}
EqualVisitorParam p = new EqualVisitorParam();
diff --git a/common/src/main/java/com/google/auto/common/Overrides.java b/common/src/main/java/com/google/auto/common/Overrides.java
index cdcd741d..6f4c34fc 100644
--- a/common/src/main/java/com/google/auto/common/Overrides.java
+++ b/common/src/main/java/com/google/auto/common/Overrides.java
@@ -22,9 +22,12 @@ import com.google.common.base.Verify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
@@ -43,12 +46,12 @@ import javax.lang.model.util.Types;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
- * Determines if one method overrides another. This class defines two ways of doing that:
- * {@code NativeOverrides} uses the method
- * {@link Elements#overrides(ExecutableElement, ExecutableElement, TypeElement)} while
- * {@code ExplicitOverrides} reimplements that method in a way that is more consistent between
- * compilers, in particular between javac and ecj (the Eclipse compiler).
+ * Determines if one method overrides another. This class defines two ways of doing that: {@code
+ * NativeOverrides} uses the method {@link Elements#overrides(ExecutableElement, ExecutableElement,
+ * TypeElement)} while {@code ExplicitOverrides} reimplements that method in a way that is more
+ * consistent between compilers, in particular between javac and ecj (the Eclipse compiler).
*
+ * @see <a href="https://github.com/google/auto/issues/372">AutoValue issue about Eclipse</a>
* @author emcmanus@google.com (Éamonn McManus)
*/
abstract class Overrides {
@@ -100,6 +103,14 @@ abstract class Overrides {
// Static methods can't be overridden (though they can be hidden by other static methods).
return false;
}
+ if (overrider.getParameters().size() != overridden.getParameters().size()) {
+ // One method can't override another if they have a different number of parameters.
+ // Varargs `Foo...` appears as `Foo[]` here; there is a separate
+ // ExecutableElement.isVarArgs() method to tell whether a method is varargs, but that has no
+ // effect on override logic.
+ // The check here isn't strictly needed but avoids unnecessary work.
+ return false;
+ }
Visibility overriddenVisibility = Visibility.ofElement(overridden);
Visibility overriderVisibility = Visibility.ofElement(overrider);
if (overriddenVisibility.equals(Visibility.PRIVATE)
@@ -251,6 +262,13 @@ abstract class Overrides {
*/
private final Map<TypeParameterElement, TypeMirror> typeBindings = Maps.newLinkedHashMap();
+ /**
+ * Type elements that we are currently visiting. This helps us stay out of trouble when
+ * looking at something like {@code Enum<E extends Enum<E>>}. At the second {@code Enum} we
+ * will just return raw {@code Enum}.
+ */
+ private final Set<TypeElement> visitingTypes = new LinkedHashSet<>();
+
@Nullable
ImmutableList<TypeMirror> erasedParameterTypes(ExecutableElement method, TypeElement in) {
if (method.getEnclosingElement().equals(in)) {
@@ -295,8 +313,8 @@ abstract class Overrides {
@Override
public TypeMirror visitTypeVariable(TypeVariable t, Void p) {
- Element element = typeUtils.asElement(t);
- if (element instanceof TypeParameterElement) {
+ Element element = t.asElement();
+ if (element.getKind() == ElementKind.TYPE_PARAMETER) {
TypeParameterElement e = (TypeParameterElement) element;
if (typeBindings.containsKey(e)) {
return visit(typeBindings.get(e));
@@ -312,11 +330,18 @@ abstract class Overrides {
if (t.getTypeArguments().isEmpty()) {
return t;
}
+ TypeElement typeElement = asTypeElement(t);
+ if (!visitingTypes.add(typeElement)) {
+ return typeUtils.erasure(t);
+ }
List<TypeMirror> newArgs = Lists.newArrayList();
for (TypeMirror arg : t.getTypeArguments()) {
newArgs.add(visit(arg));
}
- return typeUtils.getDeclaredType(asTypeElement(t), newArgs.toArray(new TypeMirror[0]));
+ TypeMirror result =
+ typeUtils.getDeclaredType(asTypeElement(t), newArgs.toArray(new TypeMirror[0]));
+ visitingTypes.remove(typeElement);
+ return result;
}
@Override
diff --git a/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java b/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java
index f7534503..03944e5d 100644
--- a/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java
+++ b/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java
@@ -79,7 +79,7 @@ public class BasicAnnotationProcessorTest {
@Override
protected Iterable<? extends Step> steps() {
- return ImmutableSet.of(
+ return ImmutableList.of(
new Step() {
@Override
public ImmutableSet<? extends Element> process(
@@ -126,7 +126,7 @@ public class BasicAnnotationProcessorTest {
public static class GeneratesCodeProcessor extends BaseAnnotationProcessor {
@Override
protected Iterable<? extends Step> steps() {
- return ImmutableSet.of(
+ return ImmutableList.of(
new Step() {
@Override
public ImmutableSet<? extends Element> process(
@@ -150,7 +150,7 @@ public class BasicAnnotationProcessorTest {
@Override
protected Iterable<? extends Step> steps() {
- return ImmutableSet.of(
+ return ImmutableList.of(
new Step() {
@Override
public ImmutableSet<Element> process(
@@ -177,7 +177,7 @@ public class BasicAnnotationProcessorTest {
@Override
protected Iterable<? extends Step> steps() {
- return ImmutableSet.of(
+ return ImmutableList.of(
new Step() {
@Override
public ImmutableSet<Element> process(
@@ -202,7 +202,7 @@ public class BasicAnnotationProcessorTest {
@Override
protected Iterable<? extends Step> steps() {
- return ImmutableSet.of(
+ return ImmutableList.of(
new Step() {
@Override
public ImmutableSet<Element> process(
diff --git a/common/src/test/java/com/google/auto/common/MoreElementsTest.java b/common/src/test/java/com/google/auto/common/MoreElementsTest.java
index eaa504a1..d074d221 100644
--- a/common/src/test/java/com/google/auto/common/MoreElementsTest.java
+++ b/common/src/test/java/com/google/auto/common/MoreElementsTest.java
@@ -233,8 +233,7 @@ public class MoreElementsTest {
@Test
public void getAnnotationMirror() {
- TypeElement element =
- elements.getTypeElement(AnnotatedAnnotation.class.getCanonicalName());
+ TypeElement element = elements.getTypeElement(AnnotatedAnnotation.class.getCanonicalName());
// Test Class API
getAnnotationMirrorAsserts(
@@ -362,6 +361,59 @@ public class MoreElementsTest {
}
@Test
+ public void getLocalAndInheritedMethods_recursiveTypeVariableBound() {
+ Types types = compilation.getTypes();
+ TypeElement builderElement =
+ elements.getTypeElement(FakeProto.Builder.class.getCanonicalName());
+ TypeMirror abstractMessageLiteMirror =
+ elements.getTypeElement(AbstractMessageLite.class.getCanonicalName()).asType();
+ ExecutableElement internalMergeFromMethod =
+ getMethod(FakeProto.Builder.class, "internalMergeFrom", abstractMessageLiteMirror);
+
+ ImmutableSet<ExecutableElement> methods =
+ MoreElements.getLocalAndInheritedMethods(builderElement, types, elements);
+
+ assertThat(methods).contains(internalMergeFromMethod);
+ }
+
+ // The classes that follow mimic the proto classes that triggered the bug that
+ // getLocalAndInheritedMethods_recursiveTypeVariableBound is testing for. They include raw type
+ // usages, because the corresponding real proto API classes do.
+
+ static class FakeProto extends AbstractMessage {
+ static class Builder
+ extends AbstractMessage.Builder<Builder> {
+ @Override
+ @SuppressWarnings("rawtypes")
+ Builder internalMergeFrom(AbstractMessageLite other) {
+ return this;
+ }
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ static class AbstractMessage extends AbstractMessageLite {
+ static class Builder<B extends Builder<B>> extends AbstractMessageLite.Builder {
+ @Override
+ @SuppressWarnings("unchecked")
+ B internalMergeFrom(AbstractMessageLite other) {
+ return (B) this;
+ }
+ }
+
+ }
+
+ static class AbstractMessageLite<
+ M extends AbstractMessageLite<M, B>, B extends AbstractMessageLite.Builder<M, B>> {
+ static class Builder<M extends AbstractMessageLite<M, B>, B extends Builder<M, B>> {
+ @SuppressWarnings("unchecked")
+ B internalMergeFrom(M other) {
+ return (B) this;
+ }
+ }
+ }
+
+ @Test
public void getAllMethods() {
Types types = compilation.getTypes();
TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT);
@@ -418,7 +470,6 @@ public class MoreElementsTest {
assertThat(methods)
.containsAtLeast(
getMethod(Object.class, "clone"),
- getMethod(Object.class, "registerNatives"),
getMethod(Object.class, "finalize"),
getMethod(Object.class, "wait"),
getMethod(Object.class, "wait", longMirror),
@@ -437,7 +488,7 @@ public class MoreElementsTest {
for (int i = 0; i < parameterTypes.length; i++) {
TypeMirror expectedType = parameterTypes[i];
TypeMirror actualType = method.getParameters().get(i).asType();
- match &= types.isSameType(expectedType, actualType);
+ match &= types.isSameType(types.erasure(expectedType), types.erasure(actualType));
}
if (match) {
assertThat(found).isNull();
diff --git a/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java b/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java
index 7cd7865d..ba8fcceb 100644
--- a/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java
+++ b/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java
@@ -15,198 +15,172 @@
*/
package com.google.auto.common;
-import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.testing.compile.CompilationRule;
-import javax.lang.model.element.Element;
+import java.util.List;
+import java.util.SortedMap;
import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-/**
- * Tests {@link MoreTypes#isTypeOf}.
- */
+/** Tests {@link MoreTypes#isTypeOf(Class, TypeMirror)}. */
@RunWith(JUnit4.class)
public class MoreTypesIsTypeOfTest {
@Rule public CompilationRule compilationRule = new CompilationRule();
- private Elements elements;
+ private Elements elementUtils;
+ private Types typeUtils;
@Before
public void setUp() {
- this.elements = compilationRule.getElements();
+ this.elementUtils = compilationRule.getElements();
+ this.typeUtils = compilationRule.getTypes();
}
- private interface TestType {}
+ @Test
+ public void isTypeOf_primitiveAndBoxedPrimitiveTypes() {
+ class PrimitiveTypeInfo {
+ final Class<?> CLASS_TYPE;
+ final Class<?> BOXED_CLASS_TYPE;
+ final TypeKind TYPE_KIND;
+
+ PrimitiveTypeInfo(Class<?> classType, Class<?> boxedClassType, TypeKind typeKind) {
+ this.CLASS_TYPE = classType;
+ this.BOXED_CLASS_TYPE = boxedClassType;
+ this.TYPE_KIND = typeKind;
+ }
+ }
+ final List<PrimitiveTypeInfo> primitivesTypeInfo =
+ ImmutableList.of(
+ new PrimitiveTypeInfo(Byte.TYPE, Byte.class, TypeKind.BYTE),
+ new PrimitiveTypeInfo(Short.TYPE, Short.class, TypeKind.SHORT),
+ new PrimitiveTypeInfo(Integer.TYPE, Integer.class, TypeKind.INT),
+ new PrimitiveTypeInfo(Long.TYPE, Long.class, TypeKind.LONG),
+ new PrimitiveTypeInfo(Float.TYPE, Float.class, TypeKind.FLOAT),
+ new PrimitiveTypeInfo(Double.TYPE, Double.class, TypeKind.DOUBLE),
+ new PrimitiveTypeInfo(Boolean.TYPE, Boolean.class, TypeKind.BOOLEAN),
+ new PrimitiveTypeInfo(Character.TYPE, Character.class, TypeKind.CHAR));
+
+ for (boolean isBoxedI : new boolean[] {false, true}) {
+ for (int i = 0; i < primitivesTypeInfo.size(); i++) { // For the Class<?> arg
+ Class<?> clazz =
+ isBoxedI
+ ? primitivesTypeInfo.get(i).BOXED_CLASS_TYPE
+ : primitivesTypeInfo.get(i).CLASS_TYPE;
+
+ for (boolean isBoxedJ : new boolean[] {false, true}) {
+ for (int j = 0; j < primitivesTypeInfo.size(); j++) { // For the TypeMirror arg
+ TypeKind typeKind = primitivesTypeInfo.get(j).TYPE_KIND;
+ TypeMirror typeMirror =
+ isBoxedJ
+ ? typeUtils.boxedClass(typeUtils.getPrimitiveType(typeKind)).asType()
+ : typeUtils.getPrimitiveType(typeKind);
+
+ String message =
+ "Mirror:\t" + typeMirror.toString() + "\nClass:\t" + clazz.getCanonicalName();
+ if (isBoxedI == isBoxedJ && i == j) {
+ assertWithMessage(message).that(MoreTypes.isTypeOf(clazz, typeMirror)).isTrue();
+ } else {
+ assertWithMessage(message).that(MoreTypes.isTypeOf(clazz, typeMirror)).isFalse();
+ }
+ }
+ }
+ }
+ }
+ }
@Test
- public void isTypeOf_declaredType() {
- assertTrue(MoreTypes.isType(typeElementFor(TestType.class).asType()));
- assertWithMessage("mirror represents the TestType")
- .that(MoreTypes.isTypeOf(TestType.class, typeElementFor(TestType.class).asType()))
+ public void isTypeOf_voidAndPseudoVoidTypes() {
+ TypeMirror voidType = typeUtils.getNoType(TypeKind.VOID);
+ TypeMirror pseudoVoidType = getTypeElementFor(Void.class).asType();
+
+ assertWithMessage("Mirror:\t" + voidType + "\nClass:\t" + Void.TYPE.getCanonicalName())
+ .that(MoreTypes.isTypeOf(Void.TYPE, voidType))
.isTrue();
- assertWithMessage("mirror does not represent a String")
- .that(MoreTypes.isTypeOf(String.class, typeElementFor(TestType.class).asType()))
+ assertWithMessage("Mirror:\t" + pseudoVoidType + "\nClass:\t" + Void.TYPE.getCanonicalName())
+ .that(MoreTypes.isTypeOf(Void.TYPE, pseudoVoidType))
.isFalse();
- }
- private interface ArrayType {
- String[] array();
+ assertWithMessage("Mirror:\t" + voidType + "\nClass:\t" + Void.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(Void.class, voidType))
+ .isFalse();
+ assertWithMessage("Mirror:\t" + pseudoVoidType + "\nClass:\t" + Void.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(Void.class, pseudoVoidType))
+ .isTrue();
}
@Test
public void isTypeOf_arrayType() {
- assertTrue(MoreTypes.isType(typeElementFor(ArrayType.class).asType()));
- TypeMirror type = extractReturnTypeFromHolder(typeElementFor(ArrayType.class));
- assertWithMessage("array mirror represents an array Class object")
+ TypeMirror type = typeUtils.getArrayType(getTypeElementFor(String.class).asType());
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + String[].class.getCanonicalName())
.that(MoreTypes.isTypeOf(String[].class, type))
.isTrue();
- }
-
- private interface PrimitiveBoolean {
- boolean method();
- }
-
- @Test
- public void isTypeOf_primitiveBoolean() {
- assertTrue(MoreTypes.isType(typeElementFor(PrimitiveBoolean.class).asType()));
- TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveBoolean.class));
- assertWithMessage("mirror of a boolean").that(MoreTypes.isTypeOf(Boolean.TYPE, type)).isTrue();
- }
-
- private interface PrimitiveByte {
- byte method();
- }
-
- @Test
- public void isTypeOf_primitiveByte() {
- assertTrue(MoreTypes.isType(typeElementFor(PrimitiveByte.class).asType()));
- TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveByte.class));
- assertWithMessage("mirror of a byte").that(MoreTypes.isTypeOf(Byte.TYPE, type)).isTrue();
- }
-
- private interface PrimitiveChar {
- char method();
- }
-
- @Test
- public void isTypeOf_primitiveChar() {
- assertTrue(MoreTypes.isType(typeElementFor(PrimitiveChar.class).asType()));
- TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveChar.class));
- assertWithMessage("mirror of a char").that(MoreTypes.isTypeOf(Character.TYPE, type)).isTrue();
- }
-
- private interface PrimitiveDouble {
- double method();
- }
-
- @Test
- public void isTypeOf_primitiveDouble() {
- assertTrue(MoreTypes.isType(typeElementFor(PrimitiveDouble.class).asType()));
- TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveDouble.class));
- assertWithMessage("mirror of a double").that(MoreTypes.isTypeOf(Double.TYPE, type)).isTrue();
- }
-
- private interface PrimitiveFloat {
- float method();
- }
-
- @Test
- public void isTypeOf_primitiveFloat() {
- assertTrue(MoreTypes.isType(typeElementFor(PrimitiveFloat.class).asType()));
- TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveFloat.class));
- assertWithMessage("mirror of a float").that(MoreTypes.isTypeOf(Float.TYPE, type)).isTrue();
- }
-
- private interface PrimitiveInt {
- int method();
- }
-
- @Test
- public void isTypeOf_primitiveInt() {
- assertTrue(MoreTypes.isType(typeElementFor(PrimitiveInt.class).asType()));
- TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveInt.class));
- assertWithMessage("mirror of a int").that(MoreTypes.isTypeOf(Integer.TYPE, type)).isTrue();
- }
-
- private interface PrimitiveLong {
- long method();
- }
-
- @Test
- public void isTypeOf_primitiveLong() {
- assertTrue(MoreTypes.isType(typeElementFor(PrimitiveLong.class).asType()));
- TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveLong.class));
- assertWithMessage("mirror of a long").that(MoreTypes.isTypeOf(Long.TYPE, type)).isTrue();
- }
-
- private interface PrimitiveShort {
- short method();
- }
-
- @Test
- public void isTypeOf_primitiveShort() {
- assertTrue(MoreTypes.isType(typeElementFor(PrimitiveShort.class).asType()));
- TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveShort.class));
- assertWithMessage("mirror of a short").that(MoreTypes.isTypeOf(Short.TYPE, type)).isTrue();
- }
-
- private interface PrimitiveVoid {
- void method();
- }
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + Integer[].class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(Integer[].class, type))
+ .isFalse();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + int[].class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(int[].class, type))
+ .isFalse();
- @Test
- public void isTypeOf_primitiveVoid() {
- assertTrue(MoreTypes.isType(typeElementFor(PrimitiveVoid.class).asType()));
- TypeMirror primitive = extractReturnTypeFromHolder(typeElementFor(PrimitiveVoid.class));
- assertWithMessage("mirror of a void").that(MoreTypes.isTypeOf(Void.TYPE, primitive)).isTrue();
+ type = typeUtils.getArrayType(typeUtils.getPrimitiveType(TypeKind.INT));
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + String[].class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(String[].class, type))
+ .isFalse();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + Integer[].class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(Integer[].class, type))
+ .isFalse();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + int[].class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(int[].class, type))
+ .isTrue();
}
- private interface DeclaredVoid {
- Void method();
+ private interface TestType {
+ @SuppressWarnings("unused")
+ <T extends SortedMap<Number, String>> T method0();
}
@Test
- public void isTypeOf_declaredVoid() {
- assertTrue(MoreTypes.isType(typeElementFor(DeclaredVoid.class).asType()));
- TypeMirror declared = extractReturnTypeFromHolder(typeElementFor(DeclaredVoid.class));
- assertWithMessage("mirror of a void").that(MoreTypes.isTypeOf(Void.class, declared)).isTrue();
+ public void isTypeOf_declaredType() {
+ TypeMirror TestTypeTypeMirror = getTypeElementFor(TestType.class).asType();
+ assertTrue(MoreTypes.isType(TestTypeTypeMirror));
+ assertWithMessage(
+ "Mirror:\t" + TestTypeTypeMirror + "\nClass:\t" + TestType.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(TestType.class, TestTypeTypeMirror))
+ .isTrue();
+ assertWithMessage(
+ "Mirror:\t" + TestTypeTypeMirror + "\nClass:\t" + String.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(String.class, TestTypeTypeMirror))
+ .isFalse();
}
@Test
public void isTypeOf_fail() {
- assertFalse(
- MoreTypes.isType(
- getOnlyElement(typeElementFor(DeclaredVoid.class).getEnclosedElements()).asType()));
- TypeMirror method =
- getOnlyElement(typeElementFor(DeclaredVoid.class).getEnclosedElements()).asType();
+ TypeMirror methodType =
+ Iterables.getOnlyElement(getTypeElementFor(TestType.class).getEnclosedElements()).asType();
+ assertFalse(MoreTypes.isType(methodType));
try {
- MoreTypes.isTypeOf(String.class, method);
+ MoreTypes.isTypeOf(List.class, methodType);
fail();
} catch (IllegalArgumentException expected) {
}
}
- // Utility methods for this test.
-
- private TypeMirror extractReturnTypeFromHolder(TypeElement typeElement) {
- Element element = Iterables.getOnlyElement(typeElement.getEnclosedElements());
- TypeMirror arrayType = MoreElements.asExecutable(element).getReturnType();
- return arrayType;
- }
-
- private TypeElement typeElementFor(Class<?> clazz) {
- return elements.getTypeElement(clazz.getCanonicalName());
+ /* Utility method(s) */
+ private TypeElement getTypeElementFor(Class<?> clazz) {
+ return elementUtils.getTypeElement(clazz.getCanonicalName());
}
}
diff --git a/common/src/test/java/com/google/auto/common/OverridesTest.java b/common/src/test/java/com/google/auto/common/OverridesTest.java
index 8d77fc76..a69f0083 100644
--- a/common/src/test/java/com/google/auto/common/OverridesTest.java
+++ b/common/src/test/java/com/google/auto/common/OverridesTest.java
@@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Range;
import com.google.common.io.Files;
+import com.google.common.truth.Correspondence;
import com.google.common.truth.Expect;
import com.google.testing.compile.CompilationRule;
import java.io.File;
@@ -574,11 +575,10 @@ public class OverridesTest {
}
private void assertTypeListsEqual(@Nullable List<TypeMirror> actual, List<TypeMirror> expected) {
- requireNonNull(actual);
- assertThat(actual).hasSize(expected.size());
- for (int i = 0; i < actual.size(); i++) {
- assertThat(typeUtils.isSameType(actual.get(i), expected.get(i))).isTrue();
- }
+ assertThat(actual)
+ .comparingElementsUsing(Correspondence.from(typeUtils::isSameType, "is same type as"))
+ .containsExactlyElementsIn(expected)
+ .inOrder();
}
// TODO(emcmanus): replace this with something from compile-testing when that's available.
diff --git a/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java b/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java
index c9bcf778..586ab14f 100644
--- a/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java
+++ b/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java
@@ -23,8 +23,11 @@ import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
import com.google.common.collect.ImmutableSet;
import com.google.testing.compile.JavaFileObjects;
import java.util.Set;
+import java.util.function.Consumer;
import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
import org.junit.Test;
@@ -46,14 +49,12 @@ public class SuperficialValidationTest {
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(
- new AssertingProcessor() {
- @Override
- void runAssertions() {
- TypeElement testClassElement =
- processingEnv.getElementUtils().getTypeElement("test.TestClass");
- assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
- }
- })
+ new AssertingProcessor(
+ processingEnv -> {
+ TypeElement testClassElement =
+ processingEnv.getElementUtils().getTypeElement("test.TestClass");
+ assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+ }))
.failsToCompile();
}
@@ -70,14 +71,12 @@ public class SuperficialValidationTest {
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(
- new AssertingProcessor() {
- @Override
- void runAssertions() {
- TypeElement testClassElement =
- processingEnv.getElementUtils().getTypeElement("test.TestClass");
- assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
- }
- })
+ new AssertingProcessor(
+ processingEnv -> {
+ TypeElement testClassElement =
+ processingEnv.getElementUtils().getTypeElement("test.TestClass");
+ assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+ }))
.failsToCompile();
}
@@ -97,14 +96,12 @@ public class SuperficialValidationTest {
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(
- new AssertingProcessor() {
- @Override
- void runAssertions() {
- TypeElement testClassElement =
- processingEnv.getElementUtils().getTypeElement("test.TestClass");
- assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
- }
- })
+ new AssertingProcessor(
+ processingEnv -> {
+ TypeElement testClassElement =
+ processingEnv.getElementUtils().getTypeElement("test.TestClass");
+ assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+ }))
.failsToCompile();
}
@@ -119,14 +116,12 @@ public class SuperficialValidationTest {
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(
- new AssertingProcessor() {
- @Override
- void runAssertions() {
- TypeElement testClassElement =
- processingEnv.getElementUtils().getTypeElement("test.TestClass");
- assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
- }
- })
+ new AssertingProcessor(
+ processingEnv -> {
+ TypeElement testClassElement =
+ processingEnv.getElementUtils().getTypeElement("test.TestClass");
+ assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+ }))
.failsToCompile();
}
@@ -143,14 +138,12 @@ public class SuperficialValidationTest {
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(
- new AssertingProcessor() {
- @Override
- void runAssertions() {
- TypeElement testClassElement =
- processingEnv.getElementUtils().getTypeElement("test.TestClass");
- assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
- }
- })
+ new AssertingProcessor(
+ processingEnv -> {
+ TypeElement testClassElement =
+ processingEnv.getElementUtils().getTypeElement("test.TestClass");
+ assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+ }))
.failsToCompile();
}
@@ -166,14 +159,16 @@ public class SuperficialValidationTest {
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(
- new AssertingProcessor() {
- @Override
- void runAssertions() {
- TypeElement testClassElement =
- processingEnv.getElementUtils().getTypeElement("test.TestClass");
- assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
- }
- })
+ new AssertingProcessor(
+ processingEnv -> {
+ TypeElement testClassElement =
+ processingEnv.getElementUtils().getTypeElement("test.TestClass");
+ // It appears that in some JDK versions, getAnnotationMirrors() doesn't return
+ // erroneous annotations such as we have here.
+ if (!testClassElement.getAnnotationMirrors().isEmpty()) {
+ assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+ }
+ }))
.failsToCompile();
}
@@ -188,14 +183,12 @@ public class SuperficialValidationTest {
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(
- new AssertingProcessor() {
- @Override
- void runAssertions() {
- TypeElement testClassElement =
- processingEnv.getElementUtils().getTypeElement("test.TestClass");
- assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue();
- }
- })
+ new AssertingProcessor(
+ processingEnv -> {
+ TypeElement testClassElement =
+ processingEnv.getElementUtils().getTypeElement("test.TestClass");
+ assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue();
+ }))
.compilesWithoutError();
}
@@ -212,14 +205,12 @@ public class SuperficialValidationTest {
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(
- new AssertingProcessor() {
- @Override
- void runAssertions() {
- TypeElement testClassElement =
- processingEnv.getElementUtils().getTypeElement("test.TestClass");
- assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue();
- }
- })
+ new AssertingProcessor(
+ processingEnv -> {
+ TypeElement testClassElement =
+ processingEnv.getElementUtils().getTypeElement("test.TestClass");
+ assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue();
+ }))
.compilesWithoutError();
}
@@ -244,14 +235,12 @@ public class SuperficialValidationTest {
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(
- new AssertingProcessor() {
- @Override
- void runAssertions() {
- TypeElement testClassElement =
- processingEnv.getElementUtils().getTypeElement("test.TestClass");
- assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
- }
- })
+ new AssertingProcessor(
+ processingEnv -> {
+ TypeElement testClassElement =
+ processingEnv.getElementUtils().getTypeElement("test.TestClass");
+ assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+ }))
.failsToCompile();
}
@@ -266,14 +255,12 @@ public class SuperficialValidationTest {
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(
- new AssertingProcessor() {
- @Override
- void runAssertions() {
- TypeElement testClassElement =
- processingEnv.getElementUtils().getTypeElement("test.TestClass");
- assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
- }
- })
+ new AssertingProcessor(
+ processingEnv -> {
+ TypeElement testClassElement =
+ processingEnv.getElementUtils().getTypeElement("test.TestClass");
+ assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+ }))
.failsToCompile();
}
@@ -295,20 +282,30 @@ public class SuperficialValidationTest {
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(
- new AssertingProcessor() {
- @Override
- void runAssertions() {
- TypeElement testClassElement =
- processingEnv.getElementUtils().getTypeElement("test.Outer.TestClass");
- assertWithMessage("testClassElement is valid")
- .that(SuperficialValidation.validateElement(testClassElement))
- .isFalse();
- }
- })
+ new AssertingProcessor(
+ processingEnv -> {
+ TypeElement testClassElement =
+ processingEnv.getElementUtils().getTypeElement("test.Outer.TestClass");
+ assertWithMessage("testClassElement is valid")
+ .that(SuperficialValidation.validateElement(testClassElement))
+ .isFalse();
+ }))
.failsToCompile();
}
- private abstract static class AssertingProcessor extends AbstractProcessor {
+ private static class AssertingProcessor extends AbstractProcessor {
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ private final Consumer<ProcessingEnvironment> assertions;
+
+ AssertingProcessor(Consumer<ProcessingEnvironment> assertions) {
+ this.assertions = assertions;
+ }
+
@Override
public Set<String> getSupportedAnnotationTypes() {
return ImmutableSet.of("*");
@@ -316,14 +313,8 @@ public class SuperficialValidationTest {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
- try {
- runAssertions();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ assertions.accept(processingEnv);
return false;
}
-
- abstract void runAssertions() throws Exception;
}
}
diff --git a/factory/README.md b/factory/README.md
index f5874fe9..5eccd907 100644
--- a/factory/README.md
+++ b/factory/README.md
@@ -10,7 +10,9 @@ AutoWhat‽
AutoFactory generates factories that can be used on their own or with [JSR-330](http://jcp.org/en/jsr/detail?id=330)-compatible [dependency injectors](http://en.wikipedia.org/wiki/Dependency_injection) from a simple annotation. Any combination of parameters can either be passed through factory methods or provided to the factory at construction time. They can implement interfaces or extend abstract classes. They're what you would have written, but without the bugs.
-Save time. Save code. Save sanity.
+[Dagger](https://dagger.dev/) users: Dagger's own
+[assisted injection](https://dagger.dev/dev-guide/assisted-injection.html) is
+now usually preferred to AutoFactory.
Example
-------
@@ -58,6 +60,20 @@ final class SomeClassFactory {
> framework-specific annotations from Guice, Spring, etc are not
> supported (though these all support JSR-330)
+Mocking
+-------
+
+By default, the factory class generated by AutoFactory is final, and thus cannot
+be mocked. The generated factory class can be made mockable by setting
+`allowSubclasses = true`, as follows:
+
+```java
+@AutoFactory(allowSubclasses = true)
+final class SomeClass {
+ // …
+}
+```
+
Download
--------
diff --git a/factory/pom.xml b/factory/pom.xml
index 922cdbac..0e1bdd22 100644
--- a/factory/pom.xml
+++ b/factory/pom.xml
@@ -26,15 +26,15 @@
<description>
JSR-330-compatible factories.
</description>
- <url>https://github.com/google/auto/tree/master/factory</url>
+ <url>https://github.com/google/auto/tree/main/factory</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <auto-service.version>1.0.1</auto-service.version>
- <auto-value.version>1.8.2</auto-value.version>
+ <auto-service.version>1.1.1</auto-service.version>
+ <auto-value.version>1.10.1</auto-value.version>
<java.version>1.8</java.version>
- <guava.version>31.0.1-jre</guava.version>
- <truth.version>1.1.3</truth.version>
+ <guava.version>32.0.1-jre</guava.version>
+ <truth.version>1.1.5</truth.version>
</properties>
<scm>
@@ -78,7 +78,7 @@
<dependency>
<groupId>com.google.auto</groupId>
<artifactId>auto-common</artifactId>
- <version>1.2.1</version>
+ <version>1.2.2</version>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
@@ -93,7 +93,7 @@
<dependency>
<groupId>net.ltgt.gradle.incap</groupId>
<artifactId>incap</artifactId>
- <version>0.3</version>
+ <version>1.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -115,7 +115,13 @@
<dependency>
<groupId>com.google.testing.compile</groupId>
<artifactId>compile-testing</artifactId>
- <version>0.19</version>
+ <version>0.21.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.testparameterinjector</groupId>
+ <artifactId>test-parameter-injector</artifactId>
+ <version>1.12</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -148,7 +154,7 @@
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
- <version>3.8.1</version>
+ <version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
@@ -169,7 +175,7 @@
<path>
<groupId>net.ltgt.gradle.incap</groupId>
<artifactId>incap-processor</artifactId>
- <version>0.3</version>
+ <version>1.0.0</version>
</path>
</annotationProcessorPaths>
</configuration>
@@ -177,28 +183,29 @@
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-java</artifactId>
- <version>1.1.0</version>
+ <version>1.1.2</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
- <version>2.22.2</version>
+ <version>3.1.2</version>
<configuration>
<argLine>${test.jvm.flags}</argLine>
</configuration>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
- <version>3.2.0</version>
+ <version>3.3.0</version>
</plugin>
<plugin>
<artifactId>maven-invoker-plugin</artifactId>
- <version>3.2.2</version>
+ <version>3.6.0</version>
<configuration>
<addTestClassPath>true</addTestClassPath>
<cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
+ <localRepositoryPath>${project.build.directory}/it-repo</localRepositoryPath>
<pomIncludes>
<pomInclude>*/pom.xml</pomInclude>
</pomIncludes>
@@ -228,7 +235,9 @@
<jdk>[9,)</jdk>
</activation>
<properties>
- <test.jvm.flags>--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</test.jvm.flags>
+ <test.jvm.flags>--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+ --add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+ --add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</test.jvm.flags>
</properties>
</profile>
</profiles>
diff --git a/factory/src/it/functional/pom.xml b/factory/src/it/functional/pom.xml
index a7a1853e..8a87a10f 100644
--- a/factory/src/it/functional/pom.xml
+++ b/factory/src/it/functional/pom.xml
@@ -45,22 +45,22 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
- <version>29.0-jre</version>
+ <version>31.1-jre</version>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
- <version>4.1.0</version>
+ <version>5.1.0</version>
</dependency>
<dependency>
<groupId>com.google.dagger</groupId>
<artifactId>dagger</artifactId>
- <version>2.13</version>
+ <version>2.42</version>
</dependency>
<dependency>
<groupId>com.google.dagger</groupId>
<artifactId>dagger-compiler</artifactId>
- <version>2.13</version>
+ <version>2.42</version>
<optional>true</optional>
</dependency>
<dependency>
@@ -72,7 +72,7 @@
<dependency>
<groupId>com.google.truth</groupId>
<artifactId>truth</artifactId>
- <version>0.44</version>
+ <version>1.1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
diff --git a/factory/src/main/java/com/google/auto/factory/AutoFactory.java b/factory/src/main/java/com/google/auto/factory/AutoFactory.java
index 3b5d90ea..3feecd4c 100644
--- a/factory/src/main/java/com/google/auto/factory/AutoFactory.java
+++ b/factory/src/main/java/com/google/auto/factory/AutoFactory.java
@@ -15,6 +15,7 @@
*/
package com.google.auto.factory;
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.TYPE;
@@ -53,7 +54,7 @@ public @interface AutoFactory {
Class<?>[] implementing() default {};
/**
- * The type that the generated factory is require to extend.
+ * The type that the generated factory is required to extend.
*/
Class<?> extending() default Object.class;
@@ -62,4 +63,44 @@ public @interface AutoFactory {
* Defaults to disallowing subclasses (generating the factory as final).
*/
boolean allowSubclasses() default false;
+
+ /**
+ * Specifies that an annotation should be used to determine how to annotate generated AutoFactory
+ * classes. For example, suppose you have this annotation:
+ * <pre>
+ * {@code @AutoFactory.AnnotationsToApply}
+ * {@code @interface} ApplyImmutableAndSuppressWarnings {
+ * Immutable immutable() default @Immutable;
+ * SuppressWarnings suppressWarnings() default @SuppressWarnings("Immutable");
+ * }
+ * </pre>
+ *
+ * And suppose you use it like this:
+ * <pre>
+ * {@code @ApplyImmutableAndSuppressWarnings}
+ * {@code @AutoFactory}
+ * public class Foo {...}
+ * </pre>
+ *
+ * Then the generated {@code FooFactory} would look like this:
+ * <pre>
+ * {@code @Immutable}
+ * {@code @SuppressWarnings("Immutable")}
+ * public class FooFactory {...}
+ * </pre>
+ *
+ * The same would be true if you used it like this:
+ * <pre>
+ * {@code @ApplyImmutableAndSuppressWarnings}(
+ * immutable = @Immutable,
+ * suppressWarnings = @SuppressWarnings("Immutable"))
+ * {@code @AutoFactory}
+ * public class Foo {...}
+ * </pre>
+ *
+ * You could also have {@code suppressWarnings = @SuppressWarnings({"Immutable", "unchecked"})},
+ * etc, to specify a value different from the default.
+ */
+ @Target(ANNOTATION_TYPE)
+ @interface AnnotationsToApply {}
}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java
index 889d8e41..2313907e 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java
@@ -16,17 +16,19 @@
package com.google.auto.factory.processor;
import static com.google.auto.common.MoreElements.getPackage;
+import static com.google.auto.common.MoreStreams.toImmutableSet;
import static com.google.auto.factory.processor.Elements2.isValidSupertypeForClass;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.getOnlyElement;
import static java.util.Objects.requireNonNull;
import static javax.lang.model.element.ElementKind.PACKAGE;
import static javax.lang.model.util.ElementFilter.typesIn;
import static javax.tools.Diagnostic.Kind.ERROR;
+import com.google.auto.common.AnnotationMirrors;
import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.AutoFactory.AnnotationsToApply;
import com.google.auto.value.AutoValue;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
@@ -34,6 +36,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -60,6 +63,8 @@ abstract class AutoFactoryDeclaration {
abstract Optional<String> className();
+ abstract ImmutableSet<AnnotationMirror> annotations();
+
abstract TypeElement extendingType();
abstract ImmutableSet<TypeElement> implementingTypes();
@@ -110,7 +115,6 @@ abstract class AutoFactoryDeclaration {
.contentEquals(AutoFactory.class.getName()));
Map<String, AnnotationValue> values =
Mirrors.simplifyAnnotationValueMap(elements.getElementValuesWithDefaults(mirror));
- checkState(values.size() == 4);
// className value is a string, so we can just call toString. We know values.get("className")
// is non-null because @AutoFactory has an annotation element of that name.
@@ -126,6 +130,7 @@ abstract class AutoFactoryDeclaration {
return Optional.empty();
}
+ ImmutableSet<AnnotationMirror> annotations = annotationsToAdd(element);
AnnotationValue extendingValue = checkNotNull(values.get("extending"));
TypeElement extendingType = AnnotationValues.asType(extendingValue);
if (extendingType == null) {
@@ -188,6 +193,7 @@ abstract class AutoFactoryDeclaration {
getAnnotatedType(element),
element,
className.isEmpty() ? Optional.empty() : Optional.of(className),
+ annotations,
extendingType,
implementingTypes,
allowSubclasses,
@@ -207,5 +213,25 @@ abstract class AutoFactoryDeclaration {
static boolean isValidIdentifier(String identifier) {
return SourceVersion.isIdentifier(identifier) && !SourceVersion.isKeyword(identifier);
}
+
+ private ImmutableSet<AnnotationMirror> annotationsToAdd(Element element) {
+ ImmutableSet<? extends AnnotationMirror> containers =
+ AnnotationMirrors.getAnnotatedAnnotations(element, AnnotationsToApply.class);
+ if (containers.size() > 1) {
+ messager.printMessage(
+ ERROR, "Multiple @AnnotationsToApply annotations are not supported", element);
+ }
+ return containers.stream()
+ .limit(1)
+ .map(elements::getElementValuesWithDefaults)
+ .map(Map::values)
+ .flatMap(Collection::stream)
+ .map(AnnotationValue::getValue)
+ .filter(AnnotationMirror.class::isInstance)
+ // Any non-annotation element should already have been flagged when processing
+ // @AnnotationsToApply
+ .map(AnnotationMirror.class::cast)
+ .collect(toImmutableSet());
+ }
}
}
diff --git a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java
index 82349f21..0654eed1 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java
@@ -15,8 +15,16 @@
*/
package com.google.auto.factory.processor;
+import static com.google.auto.common.MoreTypes.asElement;
+import static com.google.auto.common.MoreTypes.asTypeElement;
+import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE;
+import static javax.lang.model.type.TypeKind.DECLARED;
+import static javax.lang.model.type.TypeKind.ERROR;
+import static javax.lang.model.util.ElementFilter.methodsIn;
+
import com.google.auto.common.MoreTypes;
import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.AutoFactory.AnnotationsToApply;
import com.google.auto.factory.Provided;
import com.google.auto.service.AutoService;
import com.google.common.base.Throwables;
@@ -39,6 +47,7 @@ import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
@@ -96,11 +105,16 @@ public final class AutoFactoryProcessor extends AbstractProcessor {
providedChecker.checkProvidedParameter(element);
}
+ for (Element element :
+ roundEnv.getElementsAnnotatedWith(AnnotationsToApply.class)) {
+ checkAnnotationsToApply(element);
+ }
+
ImmutableListMultimap.Builder<PackageAndClass, FactoryMethodDescriptor> indexedMethodsBuilder =
ImmutableListMultimap.builder();
ImmutableSetMultimap.Builder<PackageAndClass, ImplementationMethodDescriptor>
implementationMethodDescriptorsBuilder = ImmutableSetMultimap.builder();
- // Iterate over the classes and methods that are annotated with @AutoFactory.
+ // Iterate over the classes and constructors that are annotated with @AutoFactory.
for (Element element : roundEnv.getElementsAnnotatedWith(AutoFactory.class)) {
Optional<AutoFactoryDeclaration> declaration = declarationFactory.createIfValid(element);
if (declaration.isPresent()) {
@@ -138,6 +152,9 @@ public final class AutoFactoryProcessor extends AbstractProcessor {
// methodDescriptors.iterator().next() below.
return;
}
+ // Sort to ensure output is deterministic.
+ ImmutableSortedSet.Builder<AnnotationMirror> annotationsBuilder =
+ ImmutableSortedSet.orderedBy(ANNOTATION_COMPARATOR);
// The sets of classes that are mentioned in the `extending` and `implementing`
// elements, respectively, of the @AutoFactory annotations for this factory.
ImmutableSet.Builder<TypeMirror> extending = newTypeSetBuilder();
@@ -146,6 +163,7 @@ public final class AutoFactoryProcessor extends AbstractProcessor {
Set<Boolean> allowSubclassesSet = new HashSet<>();
boolean skipCreation = false;
for (FactoryMethodDescriptor methodDescriptor : methodDescriptors) {
+ annotationsBuilder.addAll(methodDescriptor.declaration().annotations());
extending.add(methodDescriptor.declaration().extendingType().asType());
for (TypeElement implementingType :
methodDescriptor.declaration().implementingTypes()) {
@@ -170,6 +188,7 @@ public final class AutoFactoryProcessor extends AbstractProcessor {
factoryWriter.writeFactory(
FactoryDescriptor.create(
factoryName,
+ annotationsBuilder.build(),
Iterables.getOnlyElement(extending.build()),
implementing.build(),
publicType,
@@ -183,6 +202,9 @@ public final class AutoFactoryProcessor extends AbstractProcessor {
});
}
+ private static final Comparator<AnnotationMirror> ANNOTATION_COMPARATOR =
+ Comparator.comparing(mirror -> mirror.getAnnotationType().toString());
+
private ImmutableSet<ImplementationMethodDescriptor> implementationMethods(
TypeElement supertype, Element autoFactoryElement) {
ImmutableSet.Builder<ImplementationMethodDescriptor> implementationMethodsBuilder =
@@ -234,9 +256,47 @@ public final class AutoFactoryProcessor extends AbstractProcessor {
Comparator.comparing(t -> MoreTypes.asTypeElement(t).getQualifiedName().toString()));
}
+ /** Checks that {@link AnnotationsToApply} is used correctly. */
+ private void checkAnnotationsToApply(Element annotation) {
+ if (!annotation.getKind().equals(ANNOTATION_TYPE)) {
+ // Should not be possible because of @Target.
+ messager.printMessage(
+ Kind.ERROR,
+ "@"
+ + AnnotationsToApply.class.getSimpleName()
+ + " must be applied to an annotation type declaration.",
+ annotation);
+ }
+ Set<TypeElement> seenAnnotations = new HashSet<>();
+ for (ExecutableElement annotationMember : methodsIn(annotation.getEnclosedElements())) {
+ TypeMirror memberType = annotationMember.getReturnType();
+ boolean isAnnotation = memberType.getKind().equals(DECLARED) && asElement(memberType).getKind().equals(ANNOTATION_TYPE);
+ if (!isAnnotation && !memberType.getKind().equals(ERROR)) {
+ messager.printMessage(
+ Kind.ERROR,
+ "Members of an @"
+ + AnnotationsToApply.class.getSimpleName()
+ + " annotation must themselves be annotations; "
+ + annotationMember.getSimpleName()
+ + " has type "
+ + memberType,
+ annotationMember);
+ } else {
+ TypeElement annotationElement = asTypeElement(memberType);
+ if (!seenAnnotations.add(annotationElement)) {
+ messager.printMessage(
+ Kind.ERROR, "More than one @" + annotationElement + " in " + annotation, annotation);
+ }
+ }
+ }
+ }
+
@Override
public ImmutableSet<String> getSupportedAnnotationTypes() {
- return ImmutableSet.of(AutoFactory.class.getName(), Provided.class.getName());
+ return ImmutableSet.of(
+ AutoFactory.class.getCanonicalName(),
+ Provided.class.getCanonicalName(),
+ AnnotationsToApply.class.getCanonicalName());
}
@Override
diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java
index 019295f6..03eeecf3 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java
@@ -37,7 +37,7 @@ import javax.lang.model.type.TypeMirror;
*/
@AutoValue
abstract class FactoryDescriptor {
- private static final CharMatcher invalidIdentifierCharacters =
+ private static final CharMatcher INVALID_IDENTIFIER_CHARACTERS =
new CharMatcher() {
@Override
public boolean matches(char c) {
@@ -47,6 +47,8 @@ abstract class FactoryDescriptor {
abstract PackageAndClass name();
+ abstract ImmutableSet<AnnotationMirror> annotations();
+
abstract TypeMirror extendingType();
abstract ImmutableSet<TypeMirror> implementingTypes();
@@ -84,6 +86,7 @@ abstract class FactoryDescriptor {
static FactoryDescriptor create(
PackageAndClass name,
+ ImmutableSet<AnnotationMirror> annotations,
TypeMirror extendingType,
ImmutableSet<TypeMirror> implementingTypes,
boolean publicType,
@@ -119,7 +122,7 @@ abstract class FactoryDescriptor {
default:
String providerName =
uniqueNames.getUniqueName(
- invalidIdentifierCharacters.replaceFrom(key.toString(), '_')
+ INVALID_IDENTIFIER_CHARACTERS.replaceFrom(key.toString(), '_')
+ "Provider");
Optional<AnnotationMirror> nullable =
parameters.stream()
@@ -145,6 +148,7 @@ abstract class FactoryDescriptor {
return new AutoValue_FactoryDescriptor(
name,
+ annotations,
extendingType,
implementingTypes,
publicType,
diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java
index 45259573..79cce538 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java
@@ -40,10 +40,22 @@ abstract class FactoryMethodDescriptor {
abstract boolean overridingMethod();
+ /** The parameters that are passed to the {@code create} method. */
abstract ImmutableSet<Parameter> passedParameters();
+ /**
+ * The factory constructor parameters that this factory method requires. When there is more than
+ * one AutoFactory constructor, each one can have its own {@code @Provided} parameters, or
+ * constructors can have {@code @Provided} parameters in common. The generated factory has a
+ * single constructor, which has one {@code @Injected} constructor parameter for each unique
+ * {@code @Provided} parameter in any constructor.
+ */
abstract ImmutableSet<Parameter> providedParameters();
+ /**
+ * The parameters of the constructor that this {@code create} method calls. This is the union of
+ * {@link #passedParameters()} and {@link #providedParameters()}.
+ */
abstract ImmutableSet<Parameter> creationParameters();
abstract boolean isVarArgs();
diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java
index 8d6027dd..a887c577 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java
@@ -16,6 +16,7 @@
package com.google.auto.factory.processor;
import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec;
+import static com.google.auto.common.MoreStreams.toImmutableList;
import static com.squareup.javapoet.MethodSpec.constructorBuilder;
import static com.squareup.javapoet.MethodSpec.methodBuilder;
import static com.squareup.javapoet.TypeSpec.classBuilder;
@@ -27,15 +28,13 @@ import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
-import com.google.auto.common.AnnotationMirrors;
-import com.google.auto.common.AnnotationValues;
import com.google.auto.common.MoreTypes;
+import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
-import com.google.common.collect.Streams;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
@@ -47,19 +46,12 @@ import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import java.io.IOException;
-import java.lang.annotation.Target;
-import java.util.Iterator;
import java.util.List;
-import java.util.Optional;
-import java.util.stream.Stream;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.lang.model.SourceVersion;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
@@ -90,8 +82,9 @@ final class FactoryWriter {
elements,
sourceVersion,
AutoFactoryProcessor.class,
- "https://github.com/google/auto/tree/master/factory")
+ "https://github.com/google/auto/tree/main/factory")
.ifPresent(factory::addAnnotation);
+ descriptor.annotations().forEach(a -> factory.addAnnotation(AnnotationSpec.get(a)));
if (!descriptor.allowSubclasses()) {
factory.addModifiers(FINAL);
}
@@ -129,9 +122,10 @@ final class FactoryWriter {
if (descriptor.publicType()) {
constructor.addModifiers(PUBLIC);
}
- Iterator<ProviderField> providerFields = descriptor.providers().values().iterator();
- for (int argumentIndex = 1; providerFields.hasNext(); argumentIndex++) {
- ProviderField provider = providerFields.next();
+ ImmutableCollection<ProviderField> providerFields = descriptor.providers().values();
+ int argumentNumber = 0;
+ for (ProviderField provider : providerFields) {
+ ++argumentNumber;
TypeName typeName = resolveTypeName(provider.key().type().get()).box();
TypeName providerType = ParameterizedTypeName.get(ClassName.get(Provider.class), typeName);
factory.addField(providerType, provider.name(), PRIVATE, FINAL);
@@ -140,7 +134,11 @@ final class FactoryWriter {
providerType = providerType.annotated(AnnotationSpec.get(provider.key().qualifier().get()));
}
constructor.addParameter(providerType, provider.name());
- constructor.addStatement("this.$1L = checkNotNull($1L, $2L)", provider.name(), argumentIndex);
+ constructor.addStatement(
+ "this.$1L = checkNotNull($1L, $2L, $3L)",
+ provider.name(),
+ argumentNumber,
+ providerFields.size());
}
factory.addMethod(constructor.build());
@@ -166,9 +164,13 @@ final class FactoryWriter {
methodDescriptor.exceptions().stream().map(TypeName::get).collect(toList()));
CodeBlock.Builder args = CodeBlock.builder();
method.addParameters(parameters(methodDescriptor.passedParameters()));
- Iterator<Parameter> parameters = methodDescriptor.creationParameters().iterator();
- for (int argumentIndex = 1; parameters.hasNext(); argumentIndex++) {
- Parameter parameter = parameters.next();
+ ImmutableSet<Parameter> parameters = methodDescriptor.creationParameters();
+ int argumentNumber = 0;
+ String sep = "";
+ for (Parameter parameter : parameters) {
+ ++argumentNumber;
+ args.add(sep);
+ sep = ", ";
boolean checkNotNull = !parameter.nullable().isPresent();
CodeBlock argument;
if (methodDescriptor.passedParameters().contains(parameter)) {
@@ -187,12 +189,10 @@ final class FactoryWriter {
}
}
if (checkNotNull) {
- argument = CodeBlock.of("checkNotNull($L, $L)", argument, argumentIndex);
+ argument =
+ CodeBlock.of("checkNotNull($L, $L, $L)", argument, argumentNumber, parameters.size());
}
args.add(argument);
- if (parameters.hasNext()) {
- args.add(", ");
- }
}
method.addStatement("return new $T($L)", methodDescriptor.returnType(), args.build());
factory.addMethod(method.build());
@@ -228,14 +228,8 @@ final class FactoryWriter {
ImmutableList.Builder<ParameterSpec> builder = ImmutableList.builder();
for (Parameter parameter : parameters) {
TypeName type = resolveTypeName(parameter.type().get());
- // Remove TYPE_USE annotations, since resolveTypeName will already have included those in
- // the TypeName it returns.
- List<AnnotationSpec> annotations =
- Stream.of(parameter.nullable(), parameter.key().qualifier())
- .flatMap(Streams::stream)
- .filter(a -> !isTypeUseAnnotation(a))
- .map(AnnotationSpec::get)
- .collect(toList());
+ ImmutableList<AnnotationSpec> annotations =
+ parameter.annotations().stream().map(AnnotationSpec::get).collect(toImmutableList());
ParameterSpec parameterSpec =
ParameterSpec.builder(type, parameter.name()).addAnnotations(annotations).build();
builder.add(parameterSpec);
@@ -243,27 +237,6 @@ final class FactoryWriter {
return builder.build();
}
- private static boolean isTypeUseAnnotation(AnnotationMirror mirror) {
- Element annotationElement = mirror.getAnnotationType().asElement();
- // This is basically equivalent to:
- // Target target = annotationElement.getAnnotation(Target.class);
- // return target != null
- // && Arrays.asList(annotationElement.getAnnotation(Target.class)).contains(TYPE_USE);
- // but that might blow up if the annotation is being compiled at the same time and has an
- // undefined identifier in its @Target values. The rigmarole below avoids that problem.
- Optional<AnnotationMirror> maybeTargetMirror =
- Mirrors.getAnnotationMirror(annotationElement, Target.class);
- return maybeTargetMirror
- .map(
- targetMirror ->
- AnnotationValues.getEnums(
- AnnotationMirrors.getAnnotationValue(targetMirror, "value"))
- .stream()
- .map(VariableElement::getSimpleName)
- .anyMatch(name -> name.contentEquals("TYPE_USE")))
- .orElse(false);
- }
-
private static void addCheckNotNullMethod(
TypeSpec.Builder factory, FactoryDescriptor descriptor) {
if (shouldGenerateCheckNotNull(descriptor)) {
@@ -274,13 +247,14 @@ final class FactoryWriter {
.addTypeVariable(typeVariable)
.returns(typeVariable)
.addParameter(typeVariable, "reference")
- .addParameter(TypeName.INT, "argumentIndex")
+ .addParameter(TypeName.INT, "argumentNumber")
+ .addParameter(TypeName.INT, "argumentCount")
.beginControlFlow("if (reference == null)")
.addStatement(
- "throw new $T($S + argumentIndex)",
+ "throw new $T($S + argumentNumber + $S + argumentCount)",
NullPointerException.class,
- "@AutoFactory method argument is null but is not marked @Nullable. Argument "
- + "index: ")
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument ",
+ " of ")
.endControlFlow()
.addStatement("return reference")
.build());
@@ -302,15 +276,14 @@ final class FactoryWriter {
}
/**
- * Returns an appropriate {@code TypeName} for the given type. If the type is an
- * {@code ErrorType}, and if it is a simple-name reference to one of the {@code *Factory}
- * classes that we are going to generate, then we return its fully-qualified name. In every other
- * case we just return {@code TypeName.get(type)}. Specifically, if it is an {@code ErrorType}
- * referencing some other type, or referencing one of the classes we are going to generate but
- * using its fully-qualified name, then we leave it as-is. JavaPoet treats {@code TypeName.get(t)}
- * the same for {@code ErrorType} as for {@code DeclaredType}, which means that if this is a name
- * that will eventually be generated then the code we write that references the type will in fact
- * compile.
+ * Returns an appropriate {@code TypeName} for the given type. If the type is an {@code
+ * ErrorType}, and if it is a simple-name reference to one of the {@code *Factory} classes that we
+ * are going to generate, then we return its fully-qualified name. In every other case we just
+ * return {@code TypeName.get(type)}. Specifically, if it is an {@code ErrorType} referencing some
+ * other type, or referencing one of the classes we are going to generate but using its
+ * fully-qualified name, then we leave it as-is. JavaPoet treats {@code TypeName.get(t)} the same
+ * for {@code ErrorType} as for {@code DeclaredType}, which means that if this is a name that will
+ * eventually be generated then the code we write that references the type will in fact compile.
*
* <p>A simpler alternative would be to defer processing to a later round if we find an
* {@code @AutoFactory} class that references undefined types, under the assumption that something
diff --git a/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java b/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java
index 313fc9ee..5739511e 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java
@@ -59,7 +59,7 @@ final class Mirrors {
}
/**
- * Returns an annotation value map with {@link String} keys instead of {@link ExecutableElement}
+ * Returns an annotation value map with {@link String} keys instead of {@link ExecutableElement}
* instances.
*/
static ImmutableMap<String, AnnotationValue> simplifyAnnotationValueMap(
diff --git a/factory/src/main/java/com/google/auto/factory/processor/Parameter.java b/factory/src/main/java/com/google/auto/factory/processor/Parameter.java
index c5059ece..bb586a1c 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/Parameter.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/Parameter.java
@@ -15,16 +15,18 @@
*/
package com.google.auto.factory.processor;
+import static com.google.auto.common.MoreStreams.toImmutableList;
+import static com.google.auto.common.MoreStreams.toImmutableSet;
import static com.google.auto.factory.processor.Mirrors.unwrapOptionalEquivalence;
import static com.google.auto.factory.processor.Mirrors.wrapOptionalInEquivalence;
import static com.google.common.base.Preconditions.checkArgument;
-import static java.util.stream.Collectors.toList;
import com.google.auto.common.AnnotationMirrors;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.value.AutoValue;
import com.google.common.base.Equivalence;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@@ -65,26 +67,43 @@ abstract class Parameter {
abstract Key key();
+ /** Annotations on the parameter (not its type). */
+ abstract ImmutableList<Equivalence.Wrapper<AnnotationMirror>> annotationWrappers();
+
+ ImmutableList<AnnotationMirror> annotations() {
+ return annotationWrappers().stream().map(Equivalence.Wrapper::get).collect(toImmutableList());
+ }
+
abstract Optional<Equivalence.Wrapper<AnnotationMirror>> nullableWrapper();
Optional<AnnotationMirror> nullable() {
return unwrapOptionalEquivalence(nullableWrapper());
}
-
private static Parameter forVariableElement(
VariableElement variable, TypeMirror type, Types types) {
- List<AnnotationMirror> annotations =
+ ImmutableList<AnnotationMirror> allAnnotations =
Stream.of(variable.getAnnotationMirrors(), type.getAnnotationMirrors())
.flatMap(List::stream)
- .collect(toList());
+ .collect(toImmutableList());
Optional<AnnotationMirror> nullable =
- annotations.stream().filter(Parameter::isNullable).findFirst();
+ allAnnotations.stream().filter(Parameter::isNullable).findFirst();
+ Key key = Key.create(type, allAnnotations, types);
+
+ ImmutableSet<Equivalence.Wrapper<AnnotationMirror>> typeAnnotationWrappers =
+ type.getAnnotationMirrors().stream()
+ .map(AnnotationMirrors.equivalence()::wrap)
+ .collect(toImmutableSet());
+ ImmutableList<Equivalence.Wrapper<AnnotationMirror>> parameterAnnotationWrappers =
+ variable.getAnnotationMirrors().stream()
+ .map(AnnotationMirrors.equivalence()::wrap)
+ .filter(annotation -> !typeAnnotationWrappers.contains(annotation))
+ .collect(toImmutableList());
- Key key = Key.create(type, annotations, types);
return new AutoValue_Parameter(
MoreTypes.equivalence().wrap(type),
variable.getSimpleName().toString(),
key,
+ parameterAnnotationWrappers,
wrapOptionalInEquivalence(AnnotationMirrors.equivalence(), nullable));
}
diff --git a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorNegativeTest.java b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorNegativeTest.java
new file mode 100644
index 00000000..4998aa57
--- /dev/null
+++ b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorNegativeTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2013 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.factory.processor;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.Compiler;
+import com.google.testing.compile.JavaFileObjects;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Testing compilation errors from {@link AutoFactoryProcessor}. */
+@RunWith(JUnit4.class)
+public class AutoFactoryProcessorNegativeTest {
+ private final Compiler javac = Compiler.javac().withProcessors(new AutoFactoryProcessor());
+
+ @Test
+ public void failsWithMixedFinals() {
+ JavaFileObject file = JavaFileObjects.forResource("bad/MixedFinals.java");
+ Compilation compilation = javac.compile(file);
+ assertThat(compilation).failed();
+ assertThat(compilation)
+ .hadErrorContaining(
+ "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.")
+ .inFile(file)
+ .onLine(24);
+ assertThat(compilation)
+ .hadErrorContaining(
+ "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.")
+ .inFile(file)
+ .onLine(27);
+ }
+
+ @Test
+ public void providedButNoAutoFactory() {
+ JavaFileObject file = JavaFileObjects.forResource("bad/ProvidedButNoAutoFactory.java");
+ Compilation compilation = javac.compile(file);
+ assertThat(compilation).failed();
+ assertThat(compilation)
+ .hadErrorContaining(
+ "@Provided may only be applied to constructors requesting an auto-factory")
+ .inFile(file)
+ .onLineContaining("@Provided");
+ }
+
+ @Test
+ public void providedOnMethodParameter() {
+ JavaFileObject file = JavaFileObjects.forResource("bad/ProvidedOnMethodParameter.java");
+ Compilation compilation = javac.compile(file);
+ assertThat(compilation).failed();
+ assertThat(compilation)
+ .hadErrorContaining("@Provided may only be applied to constructor parameters")
+ .inFile(file)
+ .onLineContaining("@Provided");
+ }
+
+ @Test
+ public void invalidCustomName() {
+ JavaFileObject file = JavaFileObjects.forResource("bad/InvalidCustomName.java");
+ Compilation compilation = javac.compile(file);
+ assertThat(compilation).failed();
+ assertThat(compilation)
+ .hadErrorContaining("\"SillyFactory!\" is not a valid Java identifier")
+ .inFile(file)
+ .onLineContaining("SillyFactory!");
+ }
+
+ @Test
+ public void factoryExtendingAbstractClass_withConstructorParams() {
+ JavaFileObject file =
+ JavaFileObjects.forResource("bad/FactoryExtendingAbstractClassWithConstructorParams.java");
+ Compilation compilation = javac.compile(file);
+ assertThat(compilation).failed();
+ assertThat(compilation)
+ .hadErrorContaining(
+ "tests.FactoryExtendingAbstractClassWithConstructorParams.AbstractFactory is not a"
+ + " valid supertype for a factory. Factory supertypes must have a no-arg"
+ + " constructor.")
+ .inFile(file)
+ .onLineContaining("@AutoFactory");
+ }
+
+ @Test
+ public void factoryExtendingInterface() {
+ JavaFileObject file = JavaFileObjects.forResource("bad/InterfaceSupertype.java");
+ Compilation compilation = javac.compile(file);
+ assertThat(compilation).failed();
+ assertThat(compilation)
+ .hadErrorContaining(
+ "java.lang.Runnable is not a valid supertype for a factory. Supertypes must be"
+ + " non-final classes.")
+ .inFile(file)
+ .onLineContaining("@AutoFactory");
+ }
+
+ @Test
+ public void factoryExtendingEnum() {
+ JavaFileObject file = JavaFileObjects.forResource("bad/EnumSupertype.java");
+ Compilation compilation = javac.compile(file);
+ assertThat(compilation).failed();
+ assertThat(compilation)
+ .hadErrorContaining(
+ "java.util.concurrent.TimeUnit is not a valid supertype for a factory. Supertypes must"
+ + " be non-final classes.")
+ .inFile(file)
+ .onLineContaining("@AutoFactory");
+ }
+
+ @Test
+ public void factoryExtendingFinalClass() {
+ JavaFileObject file = JavaFileObjects.forResource("bad/FinalSupertype.java");
+ Compilation compilation = javac.compile(file);
+ assertThat(compilation).failed();
+ assertThat(compilation)
+ .hadErrorContaining(
+ "java.lang.Boolean is not a valid supertype for a factory. Supertypes must be"
+ + " non-final classes.")
+ .inFile(file)
+ .onLineContaining("@AutoFactory");
+ }
+
+ /**
+ * We don't currently allow you to have more than one {@code @AnnotationsToApply} annotation for
+ * any given AutoFactory class.
+ */
+ @Test
+ public void annotationsToApplyMultiple() {
+ JavaFileObject file = JavaFileObjects.forResource("bad/AnnotationsToApplyMultiple.java");
+ Compilation compilation = javac.compile(file);
+ assertThat(compilation).failed();
+ assertThat(compilation)
+ .hadErrorContaining("Multiple @AnnotationsToApply annotations are not supported");
+ }
+
+ /**
+ * We also don't allow you to have the same annotation appear more than once inside a given
+ * {@code @AnnotationsToApply}, even with the same values.
+ */
+ @Test
+ public void annotationsToApplyRepeated() {
+ JavaFileObject file = JavaFileObjects.forResource("bad/AnnotationsToApplyRepeated.java");
+ Compilation compilation = javac.compile(file);
+ assertThat(compilation).failed();
+ assertThat(compilation).hadErrorContaining("More than one @java.lang.SuppressWarnings");
+ }
+
+ @Test
+ public void annotationsToApplyNotAnnotations() {
+ JavaFileObject file = JavaFileObjects.forResource("bad/AnnotationsToApplyNotAnnotations.java");
+ Compilation compilation = javac.compile(file);
+ assertThat(compilation).failed();
+ assertThat(compilation)
+ .hadErrorContaining(
+ "Members of an @AnnotationsToApply annotation must themselves be annotations;"
+ + " whatIsThis has type int")
+ .inFile(file)
+ .onLineContaining("whatIsThis");
+ assertThat(compilation)
+ .hadErrorContaining(
+ "Members of an @AnnotationsToApply annotation must themselves be annotations;"
+ + " andWhatIsThis has type com.google.errorprone.annotations.Immutable[]")
+ .inFile(file)
+ .onLineContaining("andWhatIsThis");
+ }
+}
diff --git a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java
index 2ab0fe95..845ea58a 100644
--- a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java
+++ b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java
@@ -15,211 +15,364 @@
*/
package com.google.auto.factory.processor;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
import static com.google.testing.compile.CompilationSubject.assertThat;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.regex.Pattern.MULTILINE;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.io.Resources;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.Compiler;
import com.google.testing.compile.JavaFileObjects;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.lang.model.SourceVersion;
import javax.tools.JavaFileObject;
+import org.junit.AfterClass;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
/** Functional tests for the {@link AutoFactoryProcessor}. */
-@RunWith(JUnit4.class)
+@RunWith(TestParameterInjector.class)
public class AutoFactoryProcessorTest {
- private final Compiler javac = Compiler.javac().withProcessors(new AutoFactoryProcessor());
+ private final Config config;
+
+ public AutoFactoryProcessorTest(@TestParameter Config config) {
+ this.config = config;
+ }
+
+ private enum InjectPackage {
+ JAVAX,
+ }
+
+ private enum Config {
+ JAVAX_ONLY_ON_CLASSPATH(ImmutableList.of(InjectPackage.JAVAX), InjectPackage.JAVAX);
+
+ /** Config that is used for negative tests, and to update the golden files. */
+ static final Config DEFAULT = JAVAX_ONLY_ON_CLASSPATH;
+
+ final ImmutableList<InjectPackage> packagesOnClasspath;
+ final InjectPackage unusedExpectedPackage;
+ final ImmutableList<String> options;
+
+ Config(ImmutableList<InjectPackage> packagesOnClasspath, InjectPackage expectedPackage) {
+ this(packagesOnClasspath, expectedPackage, ImmutableList.of());
+ }
+
+ Config(
+ ImmutableList<InjectPackage> packagesOnClasspath,
+ InjectPackage expectedPackage,
+ ImmutableList<String> options) {
+ this.packagesOnClasspath = packagesOnClasspath;
+ this.unusedExpectedPackage = expectedPackage;
+ this.options = options;
+ }
+
+ static final ImmutableList<File> COMMON_CLASSPATH =
+ ImmutableList.of(
+ fileForClass("com.google.auto.factory.AutoFactory"),
+ fileForClass("com.google.errorprone.annotations.Immutable"),
+ fileForClass("javax.annotation.Nullable"),
+ fileForClass("org.checkerframework.checker.nullness.compatqual.NullableType"));
+ static final File JAVAX_CLASSPATH = fileForClass("javax.inject.Provider");
+
+ static File fileForClass(String className) {
+ Class<?> c;
+ try {
+ c = Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException(e);
+ }
+ URL url = c.getProtectionDomain().getCodeSource().getLocation();
+ assertThat(url.getProtocol()).isEqualTo("file");
+ return new File(url.getPath());
+ }
+
+ ImmutableList<File> classpath() {
+ ImmutableList.Builder<File> classpathBuilder =
+ ImmutableList.<File>builder().addAll(COMMON_CLASSPATH);
+ if (packagesOnClasspath.contains(InjectPackage.JAVAX)) {
+ classpathBuilder.add(JAVAX_CLASSPATH);
+ }
+ return classpathBuilder.build();
+ }
+
+ Compiler javac() {
+ return Compiler.javac()
+ .withClasspath(classpath())
+ .withProcessors(new AutoFactoryProcessor())
+ .withOptions(options);
+ }
+ }
+
+ private static volatile boolean goldenFileFailures;
+
+ private static final String GOLDEN_FILE_ROOT_ENV = "GOLDEN_FILE_ROOT";
+ private static final String GOLDEN_FILE_ROOT = System.getenv(GOLDEN_FILE_ROOT_ENV);
+ private static final Pattern CLASS_START =
+ Pattern.compile("^(public )?(final )?class ", MULTILINE);
+
+ @AfterClass
+ public static void explainGoldenFileFailures() {
+ if (goldenFileFailures) {
+ System.err.println();
+ System.err.println("Some golden-file tests failed.");
+ }
+ }
+
+ /**
+ * Runs a golden-file test, and optionally updates the golden file if the test fails.
+ *
+ * <p>If the golden file does not match current generated output, and the environment variable
+ * {@value #GOLDEN_FILE_ROOT_ENV} is set to the root directory for resources, then the golden file
+ * will be rewritten to match the generated output.
+ *
+ * @param inputResources resource names for the sources that the test will compile
+ * @param expectedOutput map where each key is the name of an expected generated file, and each
+ * corresponding value is the name of the resource with the text that should have been
+ * generated
+ */
+ private void goldenTest(
+ ImmutableList<String> inputResources, ImmutableMap<String, String> expectedOutput) {
+ ImmutableList<JavaFileObject> javaFileObjects =
+ inputResources.stream().map(this::goldenFile).collect(toImmutableList());
+ Compilation compilation = config.javac().compile(javaFileObjects);
+ assertThat(compilation).succeededWithoutWarnings();
+ expectedOutput.forEach(
+ (className, expectedSourceResource) -> {
+ try {
+ assertThat(compilation)
+ .generatedSourceFile(className)
+ .hasSourceEquivalentTo(loadExpectedFile(expectedSourceResource));
+ } catch (AssertionError e) {
+ if (GOLDEN_FILE_ROOT == null) {
+ goldenFileFailures = true;
+ throw e;
+ }
+ if (config.equals(Config.DEFAULT)) {
+ try {
+ updateGoldenFile(compilation, className, expectedSourceResource);
+ } catch (IOException e2) {
+ throw new UncheckedIOException(e2);
+ }
+ }
+ }
+ });
+ }
+
+ private JavaFileObject goldenFile(String resourceName) {
+ try {
+ URL resourceUrl = Resources.getResource(resourceName);
+ String source = Resources.toString(resourceUrl, UTF_8);
+ String className = resourceName.replaceFirst("\\.java$", "").replace('/', '.');
+ return JavaFileObjects.forSourceString(className, source);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private void updateGoldenFile(Compilation compilation, String className, String relativePath)
+ throws IOException {
+ Path goldenFileRootPath = Paths.get(GOLDEN_FILE_ROOT);
+ Path goldenFilePath = goldenFileRootPath.resolve(relativePath);
+ checkState(
+ Files.isRegularFile(goldenFilePath) && Files.isWritable(goldenFilePath),
+ "%s does not exist or can't be written",
+ goldenFilePath);
+
+ JavaFileObject newJavaFileObject =
+ compilation
+ .generatedSourceFile(className)
+ .orElseThrow(() -> new IllegalStateException("No generated file for " + className));
+ // We can't use Files.readString here because this test must run on Java 8.
+ String oldContent = new String(Files.readAllBytes(goldenFilePath), UTF_8);
+ String newContent =
+ newJavaFileObject.getCharContent(/* ignoreEncodingErrors= */ false).toString();
+
+ // We want to preserve the copyright notice and some minor Google-internal things that are
+ // stripped from the open-source version. So keep text from the old golden file before the
+ // class declaration.
+ int oldPosition = indexOfClassStartIn(oldContent, "original " + relativePath);
+ int newPosition = indexOfClassStartIn(newContent, "generated " + relativePath);
+ String updatedContent =
+ oldContent.substring(0, oldPosition) + newContent.substring(newPosition);
+ // We can't use Files.writeString here because this test must run on Java 8.
+ Files.write(goldenFilePath, updatedContent.getBytes(UTF_8));
+ System.err.println("Updated " + goldenFilePath);
+ }
+
+ private int indexOfClassStartIn(String content, String where) {
+ Matcher matcher = CLASS_START.matcher(content);
+ boolean found = matcher.find();
+ checkArgument(found, "Pattern /%s/ not found in %s:\n%s", CLASS_START, where, content);
+ return matcher.start();
+ }
@Test
public void simpleClass() {
- Compilation compilation = javac.compile(JavaFileObjects.forResource("good/SimpleClass.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.SimpleClassFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/SimpleClass.java"),
+ ImmutableMap.of("tests.SimpleClassFactory", "expected/SimpleClassFactory.java"));
}
@Test
public void simpleClassWithConstructorThrowsClause() {
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/SimpleClassThrows.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.SimpleClassThrowsFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassThrowsFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/SimpleClassThrows.java"),
+ ImmutableMap.of(
+ "tests.SimpleClassThrowsFactory", "expected/SimpleClassThrowsFactory.java"));
}
@Test
public void nestedClasses() {
- Compilation compilation = javac.compile(JavaFileObjects.forResource("good/NestedClasses.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.NestedClasses_SimpleNestedClassFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/NestedClasses_SimpleNestedClassFactory.java"));
- assertThat(compilation)
- .generatedSourceFile("tests.NestedClassCustomNamedFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/NestedClassCustomNamedFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/NestedClasses.java"),
+ ImmutableMap.of(
+ "tests.NestedClasses_SimpleNestedClassFactory",
+ "expected/NestedClasses_SimpleNestedClassFactory.java",
+ "tests.NestedClassCustomNamedFactory",
+ "expected/NestedClassCustomNamedFactory.java"));
}
@Test
public void simpleClassNonFinal() {
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/SimpleClassNonFinal.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.SimpleClassNonFinalFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassNonFinalFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/SimpleClassNonFinal.java"),
+ ImmutableMap.of(
+ "tests.SimpleClassNonFinalFactory", "expected/SimpleClassNonFinalFactory.java"));
}
@Test
public void publicClass() {
- Compilation compilation = javac.compile(JavaFileObjects.forResource("good/PublicClass.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.PublicClassFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/PublicClassFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/PublicClass.java"),
+ ImmutableMap.of("tests.PublicClassFactory", "expected/PublicClassFactory.java"));
}
@Test
public void simpleClassCustomName() {
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/SimpleClassCustomName.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.CustomNamedFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/CustomNamedFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/SimpleClassCustomName.java"),
+ ImmutableMap.of("tests.CustomNamedFactory", "expected/CustomNamedFactory.java"));
}
@Test
public void simpleClassMixedDeps() {
- Compilation compilation =
- javac.compile(
- JavaFileObjects.forResource("good/SimpleClassMixedDeps.java"),
- JavaFileObjects.forResource("support/AQualifier.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.SimpleClassMixedDepsFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassMixedDepsFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/SimpleClassMixedDeps.java", "support/AQualifier.java"),
+ ImmutableMap.of(
+ "tests.SimpleClassMixedDepsFactory", "expected/SimpleClassMixedDepsFactory.java"));
}
@Test
public void simpleClassPassedDeps() {
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/SimpleClassPassedDeps.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.SimpleClassPassedDepsFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassPassedDepsFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/SimpleClassPassedDeps.java"),
+ ImmutableMap.of(
+ "tests.SimpleClassPassedDepsFactory", "expected/SimpleClassPassedDepsFactory.java"));
}
@Test
public void simpleClassProvidedDeps() {
- Compilation compilation =
- javac.compile(
- JavaFileObjects.forResource("support/AQualifier.java"),
- JavaFileObjects.forResource("support/BQualifier.java"),
- JavaFileObjects.forResource("good/SimpleClassProvidedDeps.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.SimpleClassProvidedDepsFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassProvidedDepsFactory.java"));
+ goldenTest(
+ ImmutableList.of(
+ "good/SimpleClassProvidedDeps.java",
+ "support/AQualifier.java",
+ "support/BQualifier.java"),
+ ImmutableMap.of(
+ "tests.SimpleClassProvidedDepsFactory",
+ "expected/SimpleClassProvidedDepsFactory.java"));
}
@Test
public void simpleClassProvidedProviderDeps() {
- Compilation compilation =
- javac.compile(
- JavaFileObjects.forResource("support/AQualifier.java"),
- JavaFileObjects.forResource("support/BQualifier.java"),
- JavaFileObjects.forResource("good/SimpleClassProvidedProviderDeps.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.SimpleClassProvidedProviderDepsFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/SimpleClassProvidedProviderDepsFactory.java"));
+ goldenTest(
+ ImmutableList.of(
+ "good/SimpleClassProvidedProviderDeps.java",
+ "support/AQualifier.java",
+ "support/BQualifier.java"),
+ ImmutableMap.of(
+ "tests.SimpleClassProvidedProviderDepsFactory",
+ "expected/SimpleClassProvidedProviderDepsFactory.java"));
}
@Test
public void constructorAnnotated() {
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/ConstructorAnnotated.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.ConstructorAnnotatedFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/ConstructorAnnotatedFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/ConstructorAnnotated.java"),
+ ImmutableMap.of(
+ "tests.ConstructorAnnotatedFactory", "expected/ConstructorAnnotatedFactory.java"));
}
@Test
public void constructorWithThrowsClauseAnnotated() {
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/ConstructorAnnotatedThrows.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.ConstructorAnnotatedThrowsFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/ConstructorAnnotatedThrowsFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/ConstructorAnnotatedThrows.java"),
+ ImmutableMap.of(
+ "tests.ConstructorAnnotatedThrowsFactory",
+ "expected/ConstructorAnnotatedThrowsFactory.java"));
}
@Test
public void constructorAnnotatedNonFinal() {
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/ConstructorAnnotatedNonFinal.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.ConstructorAnnotatedNonFinalFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/ConstructorAnnotatedNonFinalFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/ConstructorAnnotatedNonFinal.java"),
+ ImmutableMap.of(
+ "tests.ConstructorAnnotatedNonFinalFactory",
+ "expected/ConstructorAnnotatedNonFinalFactory.java"));
}
@Test
public void simpleClassImplementingMarker() {
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/SimpleClassImplementingMarker.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.SimpleClassImplementingMarkerFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/SimpleClassImplementingMarkerFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/SimpleClassImplementingMarker.java"),
+ ImmutableMap.of(
+ "tests.SimpleClassImplementingMarkerFactory",
+ "expected/SimpleClassImplementingMarkerFactory.java"));
}
@Test
public void simpleClassImplementingSimpleInterface() {
- Compilation compilation =
- javac.compile(
- JavaFileObjects.forResource("good/SimpleClassImplementingSimpleInterface.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.SimpleClassImplementingSimpleInterfaceFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/SimpleClassImplementingSimpleInterfaceFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/SimpleClassImplementingSimpleInterface.java"),
+ ImmutableMap.of(
+ "tests.SimpleClassImplementingSimpleInterfaceFactory",
+ "expected/SimpleClassImplementingSimpleInterfaceFactory.java"));
}
@Test
public void mixedDepsImplementingInterfaces() {
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/MixedDepsImplementingInterfaces.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.MixedDepsImplementingInterfacesFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/MixedDepsImplementingInterfacesFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/MixedDepsImplementingInterfaces.java"),
+ ImmutableMap.of(
+ "tests.MixedDepsImplementingInterfacesFactory",
+ "expected/MixedDepsImplementingInterfacesFactory.java"));
}
@Test
public void failsWithMixedFinals() {
JavaFileObject file = JavaFileObjects.forResource("bad/MixedFinals.java");
- Compilation compilation = javac.compile(file);
+ Compilation compilation = config.javac().compile(file);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
@@ -236,7 +389,7 @@ public class AutoFactoryProcessorTest {
@Test
public void providedButNoAutoFactory() {
JavaFileObject file = JavaFileObjects.forResource("bad/ProvidedButNoAutoFactory.java");
- Compilation compilation = javac.compile(file);
+ Compilation compilation = config.javac().compile(file);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
@@ -248,7 +401,7 @@ public class AutoFactoryProcessorTest {
@Test
public void providedOnMethodParameter() {
JavaFileObject file = JavaFileObjects.forResource("bad/ProvidedOnMethodParameter.java");
- Compilation compilation = javac.compile(file);
+ Compilation compilation = config.javac().compile(file);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining("@Provided may only be applied to constructor parameters")
@@ -259,7 +412,7 @@ public class AutoFactoryProcessorTest {
@Test
public void invalidCustomName() {
JavaFileObject file = JavaFileObjects.forResource("bad/InvalidCustomName.java");
- Compilation compilation = javac.compile(file);
+ Compilation compilation = config.javac().compile(file);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining("\"SillyFactory!\" is not a valid Java identifier")
@@ -269,31 +422,27 @@ public class AutoFactoryProcessorTest {
@Test
public void factoryExtendingAbstractClass() {
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/FactoryExtendingAbstractClass.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.FactoryExtendingAbstractClassFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/FactoryExtendingAbstractClassFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/FactoryExtendingAbstractClass.java"),
+ ImmutableMap.of(
+ "tests.FactoryExtendingAbstractClassFactory",
+ "expected/FactoryExtendingAbstractClassFactory.java"));
}
@Test
public void factoryWithConstructorThrowsClauseExtendingAbstractClass() {
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/FactoryExtendingAbstractClassThrows.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.FactoryExtendingAbstractClassThrowsFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/FactoryExtendingAbstractClassThrowsFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/FactoryExtendingAbstractClassThrows.java"),
+ ImmutableMap.of(
+ "tests.FactoryExtendingAbstractClassThrowsFactory",
+ "expected/FactoryExtendingAbstractClassThrowsFactory.java"));
}
@Test
public void factoryExtendingAbstractClass_withConstructorParams() {
JavaFileObject file =
JavaFileObjects.forResource("bad/FactoryExtendingAbstractClassWithConstructorParams.java");
- Compilation compilation = javac.compile(file);
+ Compilation compilation = config.javac().compile(file);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
@@ -306,17 +455,15 @@ public class AutoFactoryProcessorTest {
@Test
public void factoryExtendingAbstractClass_multipleConstructors() {
- JavaFileObject file =
- JavaFileObjects.forResource(
- "good/FactoryExtendingAbstractClassWithMultipleConstructors.java");
- Compilation compilation = javac.compile(file);
- assertThat(compilation).succeededWithoutWarnings();
+ goldenTest(
+ ImmutableList.of("good/FactoryExtendingAbstractClassWithMultipleConstructors.java"),
+ ImmutableMap.of());
}
@Test
public void factoryExtendingInterface() {
JavaFileObject file = JavaFileObjects.forResource("bad/InterfaceSupertype.java");
- Compilation compilation = javac.compile(file);
+ Compilation compilation = config.javac().compile(file);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
@@ -329,7 +476,7 @@ public class AutoFactoryProcessorTest {
@Test
public void factoryExtendingEnum() {
JavaFileObject file = JavaFileObjects.forResource("bad/EnumSupertype.java");
- Compilation compilation = javac.compile(file);
+ Compilation compilation = config.javac().compile(file);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
@@ -342,7 +489,7 @@ public class AutoFactoryProcessorTest {
@Test
public void factoryExtendingFinalClass() {
JavaFileObject file = JavaFileObjects.forResource("bad/FinalSupertype.java");
- Compilation compilation = javac.compile(file);
+ Compilation compilation = config.javac().compile(file);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
@@ -354,78 +501,59 @@ public class AutoFactoryProcessorTest {
@Test
public void factoryImplementingGenericInterfaceExtension() {
- JavaFileObject file =
- JavaFileObjects.forResource("good/FactoryImplementingGenericInterfaceExtension.java");
- Compilation compilation = javac.compile(file);
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.FactoryImplementingGenericInterfaceExtensionFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/FactoryImplementingGenericInterfaceExtensionFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/FactoryImplementingGenericInterfaceExtension.java"),
+ ImmutableMap.of(
+ "tests.FactoryImplementingGenericInterfaceExtensionFactory",
+ "expected/FactoryImplementingGenericInterfaceExtensionFactory.java"));
}
@Test
public void multipleFactoriesImpementingInterface() {
- JavaFileObject file =
- JavaFileObjects.forResource("good/MultipleFactoriesImplementingInterface.java");
- Compilation compilation = javac.compile(file);
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.MultipleFactoriesImplementingInterface_ClassAFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/MultipleFactoriesImplementingInterface_ClassAFactory.java"));
- assertThat(compilation)
- .generatedSourceFile("tests.MultipleFactoriesImplementingInterface_ClassBFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/MultipleFactoriesImplementingInterface_ClassBFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/MultipleFactoriesImplementingInterface.java"),
+ ImmutableMap.of(
+ "tests.MultipleFactoriesImplementingInterface_ClassAFactory",
+ "expected/MultipleFactoriesImplementingInterface_ClassAFactory.java",
+ "tests.MultipleFactoriesImplementingInterface_ClassBFactory",
+ "expected/MultipleFactoriesImplementingInterface_ClassBFactory.java"));
}
@Test
public void classUsingQualifierWithArgs() {
- Compilation compilation =
- javac.compile(
- JavaFileObjects.forResource("support/QualifierWithArgs.java"),
- JavaFileObjects.forResource("good/ClassUsingQualifierWithArgs.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.ClassUsingQualifierWithArgsFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/ClassUsingQualifierWithArgsFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/ClassUsingQualifierWithArgs.java", "support/QualifierWithArgs.java"),
+ ImmutableMap.of(
+ "tests.ClassUsingQualifierWithArgsFactory",
+ "expected/ClassUsingQualifierWithArgsFactory.java"));
}
@Test
public void factoryImplementingInterfaceWhichRedeclaresCreateMethods() {
- JavaFileObject file = JavaFileObjects.forResource("good/FactoryImplementingCreateMethod.java");
- Compilation compilation = javac.compile(file);
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.FactoryImplementingCreateMethod_ConcreteClassFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/FactoryImplementingCreateMethod.java"),
+ ImmutableMap.of(
+ "tests.FactoryImplementingCreateMethod_ConcreteClassFactory",
+ "expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java"));
}
@Test
public void nullableParams() {
- Compilation compilation =
- javac.compile(
- JavaFileObjects.forResource("good/SimpleClassNullableParameters.java"),
- JavaFileObjects.forResource("support/AQualifier.java"),
- JavaFileObjects.forResource("support/BQualifier.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.SimpleClassNullableParametersFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/SimpleClassNullableParametersFactory.java"));
+ goldenTest(
+ ImmutableList.of(
+ "good/SimpleClassNullableParameters.java",
+ "support/AQualifier.java",
+ "support/BQualifier.java"),
+ ImmutableMap.of(
+ "tests.SimpleClassNullableParametersFactory",
+ "expected/SimpleClassNullableParametersFactory.java"));
}
@Test
public void customNullableType() {
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/CustomNullable.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.CustomNullableFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/CustomNullableFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/CustomNullable.java"),
+ ImmutableMap.of("tests.CustomNullableFactory", "expected/CustomNullableFactory.java"));
}
@Test
@@ -435,102 +563,96 @@ public class AutoFactoryProcessorTest {
// it. Checking for a java.specification.version that does not start with "1." eliminates 8 and
// any earlier version.
assume().that(JAVA_SPECIFICATION_VERSION.value()).doesNotMatch("1\\..*");
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/CheckerFrameworkNullable.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.CheckerFrameworkNullableFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/CheckerFrameworkNullableFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/CheckerFrameworkNullable.java"),
+ ImmutableMap.of(
+ "tests.CheckerFrameworkNullableFactory",
+ "expected/CheckerFrameworkNullableFactory.java"));
}
@Test
public void multipleProvidedParamsWithSameKey() {
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/MultipleProvidedParamsSameKey.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.MultipleProvidedParamsSameKeyFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/MultipleProvidedParamsSameKeyFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/MultipleProvidedParamsSameKey.java"),
+ ImmutableMap.of(
+ "tests.MultipleProvidedParamsSameKeyFactory",
+ "expected/MultipleProvidedParamsSameKeyFactory.java"));
}
@Test
public void providerArgumentToCreateMethod() {
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/ProviderArgumentToCreateMethod.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.ProviderArgumentToCreateMethodFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/ProviderArgumentToCreateMethodFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/ProviderArgumentToCreateMethod.java"),
+ ImmutableMap.of(
+ "tests.ProviderArgumentToCreateMethodFactory",
+ "expected/ProviderArgumentToCreateMethodFactory.java"));
}
@Test
public void multipleFactoriesConflictingParameterNames() {
- Compilation compilation =
- javac.compile(
- JavaFileObjects.forResource("good/MultipleFactoriesConflictingParameterNames.java"),
- JavaFileObjects.forResource("support/AQualifier.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.MultipleFactoriesConflictingParameterNamesFactory")
- .hasSourceEquivalentTo(
- loadExpectedFile("expected/MultipleFactoriesConflictingParameterNamesFactory.java"));
+ goldenTest(
+ ImmutableList.of(
+ "good/MultipleFactoriesConflictingParameterNames.java", "support/AQualifier.java"),
+ ImmutableMap.of(
+ "tests.MultipleFactoriesConflictingParameterNamesFactory",
+ "expected/MultipleFactoriesConflictingParameterNamesFactory.java"));
}
@Test
public void factoryVarargs() {
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/SimpleClassVarargs.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.SimpleClassVarargsFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassVarargsFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/SimpleClassVarargs.java"),
+ ImmutableMap.of(
+ "tests.SimpleClassVarargsFactory", "expected/SimpleClassVarargsFactory.java"));
}
@Test
public void onlyPrimitives() {
- Compilation compilation =
- javac.compile(JavaFileObjects.forResource("good/OnlyPrimitives.java"));
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.OnlyPrimitivesFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/OnlyPrimitivesFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/OnlyPrimitives.java"),
+ ImmutableMap.of("tests.OnlyPrimitivesFactory", "expected/OnlyPrimitivesFactory.java"));
}
@Test
public void defaultPackage() {
- JavaFileObject file = JavaFileObjects.forResource("good/DefaultPackage.java");
- Compilation compilation = javac.compile(file);
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("DefaultPackageFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/DefaultPackageFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/DefaultPackage.java"),
+ ImmutableMap.of("DefaultPackageFactory", "expected/DefaultPackageFactory.java"));
}
@Test
public void generics() {
- JavaFileObject file = JavaFileObjects.forResource("good/Generics.java");
- Compilation compilation = javac.compile(file);
- assertThat(compilation).succeededWithoutWarnings();
- assertThat(compilation)
- .generatedSourceFile("tests.Generics_FooImplFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/Generics_FooImplFactory.java"));
- assertThat(compilation)
- .generatedSourceFile("tests.Generics_ExplicitFooImplFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/Generics_ExplicitFooImplFactory.java"));
- assertThat(compilation)
- .generatedSourceFile("tests.Generics_FooImplWithClassFactory")
- .hasSourceEquivalentTo(loadExpectedFile("expected/Generics_FooImplWithClassFactory.java"));
+ goldenTest(
+ ImmutableList.of("good/Generics.java"),
+ ImmutableMap.of(
+ "tests.Generics_FooImplFactory",
+ "expected/Generics_FooImplFactory.java",
+ "tests.Generics_ExplicitFooImplFactory",
+ "expected/Generics_ExplicitFooImplFactory.java",
+ "tests.Generics_FooImplWithClassFactory",
+ "expected/Generics_FooImplWithClassFactory.java"));
+ }
+
+ @Test
+ public void parameterAnnotations() {
+ goldenTest(
+ ImmutableList.of("good/ParameterAnnotations.java"),
+ ImmutableMap.of(
+ "tests.ParameterAnnotationsFactory", "expected/ParameterAnnotationsFactory.java"));
+ }
+
+ @Test
+ public void customAnnotations() {
+ goldenTest(
+ ImmutableList.of("good/CustomAnnotations.java"),
+ ImmutableMap.of(
+ "tests.CustomAnnotationsFactory", "expected/CustomAnnotationsFactory.java"));
}
private JavaFileObject loadExpectedFile(String resourceName) {
- if (isJavaxAnnotationProcessingGeneratedAvailable()) {
- return JavaFileObjects.forResource(resourceName);
- }
try {
List<String> sourceLines = Resources.readLines(Resources.getResource(resourceName), UTF_8);
- replaceGeneratedImport(sourceLines);
+ rewriteImports(sourceLines);
return JavaFileObjects.forSourceLines(
resourceName.replace('/', '.').replace(".java", ""), sourceLines);
} catch (IOException e) {
@@ -538,11 +660,11 @@ public class AutoFactoryProcessorTest {
}
}
- private boolean isJavaxAnnotationProcessingGeneratedAvailable() {
+ private static boolean isJavaxAnnotationProcessingGeneratedAvailable() {
return SourceVersion.latestSupported().compareTo(SourceVersion.RELEASE_8) > 0;
}
- private static void replaceGeneratedImport(List<String> sourceLines) {
+ private void rewriteImports(List<String> sourceLines) {
int i = 0;
int firstImport = Integer.MAX_VALUE;
int lastImport = -1;
@@ -555,11 +677,13 @@ public class AutoFactoryProcessorTest {
}
if (lastImport >= 0) {
List<String> importLines = sourceLines.subList(firstImport, lastImport + 1);
- importLines.replaceAll(
- line ->
- line.startsWith("import javax.annotation.processing.Generated;")
- ? "import javax.annotation.Generated;"
- : line);
+ if (!isJavaxAnnotationProcessingGeneratedAvailable()) {
+ importLines.replaceAll(
+ line ->
+ line.startsWith("import javax.annotation.processing.Generated;")
+ ? "import javax.annotation.Generated;"
+ : line);
+ }
Collections.sort(importLines);
}
}
diff --git a/factory/src/test/resources/bad/AnnotationsToApplyMultiple.java b/factory/src/test/resources/bad/AnnotationsToApplyMultiple.java
new file mode 100644
index 00000000..ef4af1ae
--- /dev/null
+++ b/factory/src/test/resources/bad/AnnotationsToApplyMultiple.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package tests;
+
+import com.google.auto.factory.AutoFactory;
+
+@AutoFactory.AnnotationsToApply
+@interface This {
+ SuppressWarnings suppressWarnings() default @SuppressWarnings("Immutable");
+}
+
+@AutoFactory.AnnotationsToApply
+@interface That {
+ SuppressWarnings suppressWarnings() default @SuppressWarnings("Immutable");
+}
+
+@This
+@That
+@AutoFactory
+final class AnnotationsToApplyMultiple {}
diff --git a/factory/src/test/resources/bad/AnnotationsToApplyNotAnnotations.java b/factory/src/test/resources/bad/AnnotationsToApplyNotAnnotations.java
new file mode 100644
index 00000000..865cbfbe
--- /dev/null
+++ b/factory/src/test/resources/bad/AnnotationsToApplyNotAnnotations.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package tests;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.errorprone.annotations.Immutable;
+
+@AutoFactory.AnnotationsToApply
+@interface ImmutableAndSuppressWarnings {
+ Immutable immutable() default @Immutable;
+ SuppressWarnings suppressWarnings() default @SuppressWarnings("Immutable");
+ int whatIsThis() default 23;
+ Immutable[] andWhatIsThis() default {};
+}
+
+@ImmutableAndSuppressWarnings(suppressWarnings = @SuppressWarnings({"unchecked", "Immutable"}))
+@AutoFactory
+final class AnnotationsToApplyNotAnnotations {}
diff --git a/factory/src/test/resources/bad/AnnotationsToApplyRepeated.java b/factory/src/test/resources/bad/AnnotationsToApplyRepeated.java
new file mode 100644
index 00000000..5a77ed7c
--- /dev/null
+++ b/factory/src/test/resources/bad/AnnotationsToApplyRepeated.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package tests;
+
+import com.google.auto.factory.AutoFactory;
+
+@AutoFactory.AnnotationsToApply
+@interface ReallySuppressWarnings {
+ SuppressWarnings suppressWarnings() default @SuppressWarnings("Immutable");
+ SuppressWarnings suppressWarningsSomeMore() default @SuppressWarnings("Immutable");
+}
+
+@ReallySuppressWarnings
+@AutoFactory
+final class AnnotationsToApplyMultiple {}
diff --git a/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java b/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java
index faa83971..775bda07 100644
--- a/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java
+++ b/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java
@@ -24,7 +24,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class CheckerFrameworkNullableFactory {
@@ -36,8 +36,9 @@ final class CheckerFrameworkNullableFactory {
CheckerFrameworkNullableFactory(
Provider<String> java_lang_StringProvider,
Provider<Map.@NullableType Entry<?, ?>> providedNestedNullableTypeProvider) {
- this.java_lang_StringProvider = checkNotNull(java_lang_StringProvider, 1);
- this.providedNestedNullableTypeProvider = checkNotNull(providedNestedNullableTypeProvider, 2);
+ this.java_lang_StringProvider = checkNotNull(java_lang_StringProvider, 1, 2);
+ this.providedNestedNullableTypeProvider =
+ checkNotNull(providedNestedNullableTypeProvider, 2, 2);
}
CheckerFrameworkNullable create(
@@ -53,11 +54,13 @@ final class CheckerFrameworkNullableFactory {
providedNestedNullableTypeProvider.get());
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java b/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java
index 8d889eed..4d9cc805 100644
--- a/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java
+++ b/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java
@@ -21,7 +21,7 @@ import javax.inject.Provider;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class ClassUsingQualifierWithArgsFactory {
private final Provider<String> providedDepAProvider;
@@ -29,18 +29,20 @@ final class ClassUsingQualifierWithArgsFactory {
@Inject
ClassUsingQualifierWithArgsFactory(
@QualifierWithArgs(name = "Fred", count = 3) Provider<String> providedDepAProvider) {
- this.providedDepAProvider = checkNotNull(providedDepAProvider, 1);
+ this.providedDepAProvider = checkNotNull(providedDepAProvider, 1, 1);
}
ClassUsingQualifierWithArgs create() {
- return new ClassUsingQualifierWithArgs(checkNotNull(providedDepAProvider.get(), 1));
+ return new ClassUsingQualifierWithArgs(checkNotNull(providedDepAProvider.get(), 1, 1));
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java b/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java
index 22349851..aa8730c5 100644
--- a/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java
+++ b/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java
@@ -21,14 +21,14 @@ import javax.inject.Provider;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class ConstructorAnnotatedFactory {
private final Provider<Object> objProvider;
@Inject
ConstructorAnnotatedFactory(Provider<Object> objProvider) {
- this.objProvider = checkNotNull(objProvider, 1);
+ this.objProvider = checkNotNull(objProvider, 1, 1);
}
ConstructorAnnotated create() {
@@ -36,22 +36,24 @@ final class ConstructorAnnotatedFactory {
}
ConstructorAnnotated create(String s) {
- return new ConstructorAnnotated(checkNotNull(s, 1));
+ return new ConstructorAnnotated(checkNotNull(s, 1, 1));
}
ConstructorAnnotated create(int i) {
- return new ConstructorAnnotated(checkNotNull(objProvider.get(), 1), i);
+ return new ConstructorAnnotated(checkNotNull(objProvider.get(), 1, 2), i);
}
ConstructorAnnotated create(char c) {
- return new ConstructorAnnotated(checkNotNull(objProvider.get(), 1), c);
+ return new ConstructorAnnotated(checkNotNull(objProvider.get(), 1, 2), c);
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java b/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java
index 25ec894f..6866ab35 100644
--- a/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java
+++ b/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java
@@ -21,14 +21,14 @@ import javax.inject.Provider;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
class ConstructorAnnotatedNonFinalFactory {
private final Provider<Object> objProvider;
@Inject
ConstructorAnnotatedNonFinalFactory(Provider<Object> objProvider) {
- this.objProvider = checkNotNull(objProvider, 1);
+ this.objProvider = checkNotNull(objProvider, 1, 1);
}
ConstructorAnnotatedNonFinal create() {
@@ -36,22 +36,24 @@ class ConstructorAnnotatedNonFinalFactory {
}
ConstructorAnnotatedNonFinal create(String s) {
- return new ConstructorAnnotatedNonFinal(checkNotNull(s, 1));
+ return new ConstructorAnnotatedNonFinal(checkNotNull(s, 1, 1));
}
ConstructorAnnotatedNonFinal create(int i) {
- return new ConstructorAnnotatedNonFinal(checkNotNull(objProvider.get(), 1), i);
+ return new ConstructorAnnotatedNonFinal(checkNotNull(objProvider.get(), 1, 2), i);
}
ConstructorAnnotatedNonFinal create(char c) {
- return new ConstructorAnnotatedNonFinal(checkNotNull(objProvider.get(), 1), c);
+ return new ConstructorAnnotatedNonFinal(checkNotNull(objProvider.get(), 1, 2), c);
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/ConstructorAnnotatedThrowsFactory.java b/factory/src/test/resources/expected/ConstructorAnnotatedThrowsFactory.java
index 05b30fdf..e84ff2fb 100644
--- a/factory/src/test/resources/expected/ConstructorAnnotatedThrowsFactory.java
+++ b/factory/src/test/resources/expected/ConstructorAnnotatedThrowsFactory.java
@@ -22,14 +22,14 @@ import javax.inject.Provider;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class ConstructorAnnotatedThrowsFactory {
private final Provider<Object> objProvider;
@Inject
ConstructorAnnotatedThrowsFactory(Provider<Object> objProvider) {
- this.objProvider = checkNotNull(objProvider, 1);
+ this.objProvider = checkNotNull(objProvider, 1, 1);
}
ConstructorAnnotatedThrows create() throws IOException, InterruptedException {
@@ -37,22 +37,24 @@ final class ConstructorAnnotatedThrowsFactory {
}
ConstructorAnnotatedThrows create(String s) {
- return new ConstructorAnnotatedThrows(checkNotNull(s, 1));
+ return new ConstructorAnnotatedThrows(checkNotNull(s, 1, 1));
}
ConstructorAnnotatedThrows create(int i) throws IOException {
- return new ConstructorAnnotatedThrows(checkNotNull(objProvider.get(), 1), i);
+ return new ConstructorAnnotatedThrows(checkNotNull(objProvider.get(), 1, 2), i);
}
ConstructorAnnotatedThrows create(char c) throws InterruptedException {
- return new ConstructorAnnotatedThrows(checkNotNull(objProvider.get(), 1), c);
+ return new ConstructorAnnotatedThrows(checkNotNull(objProvider.get(), 1, 2), c);
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/CustomAnnotationsFactory.java b/factory/src/test/resources/expected/CustomAnnotationsFactory.java
new file mode 100644
index 00000000..5caa1c25
--- /dev/null
+++ b/factory/src/test/resources/expected/CustomAnnotationsFactory.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package tests;
+
+import com.google.errorprone.annotations.Immutable;
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+ value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+ comments = "https://github.com/google/auto/tree/main/factory"
+ )
+@Immutable
+@SuppressWarnings({"unchecked", "Immutable"})
+final class CustomAnnotationsFactory {
+ @Inject
+ CustomAnnotationsFactory() {}
+
+ CustomAnnotations create() {
+ return new CustomAnnotations();
+ }
+}
diff --git a/factory/src/test/resources/expected/CustomNamedFactory.java b/factory/src/test/resources/expected/CustomNamedFactory.java
index 512c244c..25aa9b06 100644
--- a/factory/src/test/resources/expected/CustomNamedFactory.java
+++ b/factory/src/test/resources/expected/CustomNamedFactory.java
@@ -20,7 +20,7 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class CustomNamedFactory {
@Inject
diff --git a/factory/src/test/resources/expected/CustomNullableFactory.java b/factory/src/test/resources/expected/CustomNullableFactory.java
index c8f2f286..964e9861 100644
--- a/factory/src/test/resources/expected/CustomNullableFactory.java
+++ b/factory/src/test/resources/expected/CustomNullableFactory.java
@@ -21,7 +21,7 @@ import javax.inject.Provider;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class CustomNullableFactory {
@@ -29,18 +29,20 @@ final class CustomNullableFactory {
@Inject
CustomNullableFactory(Provider<Object> objectProvider) {
- this.objectProvider = checkNotNull(objectProvider, 1);
+ this.objectProvider = checkNotNull(objectProvider, 1, 1);
}
CustomNullable create(@CustomNullable.Nullable String string) {
return new CustomNullable(string, objectProvider.get());
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/DefaultPackageFactory.java b/factory/src/test/resources/expected/DefaultPackageFactory.java
index d1d4cc85..7146e679 100644
--- a/factory/src/test/resources/expected/DefaultPackageFactory.java
+++ b/factory/src/test/resources/expected/DefaultPackageFactory.java
@@ -19,7 +19,7 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
public final class DefaultPackageFactory {
@Inject
diff --git a/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java b/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java
index f35b414e..9361008d 100644
--- a/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java
+++ b/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java
@@ -20,7 +20,7 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class FactoryExtendingAbstractClassFactory
extends FactoryExtendingAbstractClass.AbstractFactory {
diff --git a/factory/src/test/resources/expected/FactoryExtendingAbstractClassThrowsFactory.java b/factory/src/test/resources/expected/FactoryExtendingAbstractClassThrowsFactory.java
index 402f8946..acc417ba 100644
--- a/factory/src/test/resources/expected/FactoryExtendingAbstractClassThrowsFactory.java
+++ b/factory/src/test/resources/expected/FactoryExtendingAbstractClassThrowsFactory.java
@@ -21,7 +21,7 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class FactoryExtendingAbstractClassThrowsFactory
extends FactoryExtendingAbstractClassThrows.AbstractFactory {
diff --git a/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java b/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java
index 2d8a392d..ecd41ab1 100644
--- a/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java
+++ b/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java
@@ -21,7 +21,7 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class FactoryImplementingCreateMethod_ConcreteClassFactory
implements FactoryImplementingCreateMethod.FactoryInterfaceWithCreateMethod {
@@ -43,18 +43,20 @@ final class FactoryImplementingCreateMethod_ConcreteClassFactory
public FactoryImplementingCreateMethod.ConcreteClass create(
List<Integer> genericWithDifferentArgumentName) {
return new FactoryImplementingCreateMethod.ConcreteClass(
- checkNotNull(genericWithDifferentArgumentName, 1));
+ checkNotNull(genericWithDifferentArgumentName, 1, 1));
}
FactoryImplementingCreateMethod.ConcreteClass create(int a, boolean b) {
return new FactoryImplementingCreateMethod.ConcreteClass(a, b);
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java b/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java
index 9f2bf0ae..5379bbdb 100644
--- a/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java
+++ b/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java
@@ -21,7 +21,7 @@ import javax.inject.Provider;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class FactoryImplementingGenericInterfaceExtensionFactory
implements FactoryImplementingGenericInterfaceExtension.MyFactory {
@@ -29,12 +29,12 @@ final class FactoryImplementingGenericInterfaceExtensionFactory
@Inject
FactoryImplementingGenericInterfaceExtensionFactory(Provider<String> sProvider) {
- this.sProvider = checkNotNull(sProvider, 1);
+ this.sProvider = checkNotNull(sProvider, 1, 1);
}
FactoryImplementingGenericInterfaceExtension create(Integer i) {
return new FactoryImplementingGenericInterfaceExtension(
- checkNotNull(sProvider.get(), 1), checkNotNull(i, 2));
+ checkNotNull(sProvider.get(), 1, 2), checkNotNull(i, 2, 2));
}
@Override
@@ -42,11 +42,13 @@ final class FactoryImplementingGenericInterfaceExtensionFactory
return create(arg);
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/Generics_ExplicitFooImplFactory.java b/factory/src/test/resources/expected/Generics_ExplicitFooImplFactory.java
index 00c6d92c..69b0df34 100644
--- a/factory/src/test/resources/expected/Generics_ExplicitFooImplFactory.java
+++ b/factory/src/test/resources/expected/Generics_ExplicitFooImplFactory.java
@@ -21,7 +21,7 @@ import javax.inject.Provider;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class Generics_ExplicitFooImplFactory<M extends Generics.Bar>
implements Generics.FooFactory<M> {
@@ -29,19 +29,21 @@ final class Generics_ExplicitFooImplFactory<M extends Generics.Bar>
@Inject
Generics_ExplicitFooImplFactory(Provider<M> unusedProvider) {
- this.unusedProvider = checkNotNull(unusedProvider, 1);
+ this.unusedProvider = checkNotNull(unusedProvider, 1, 1);
}
@Override
public Generics.ExplicitFooImpl<M> create() {
- return new Generics.ExplicitFooImpl<M>(checkNotNull(unusedProvider.get(), 1));
+ return new Generics.ExplicitFooImpl<M>(checkNotNull(unusedProvider.get(), 1, 1));
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/Generics_FooImplFactory.java b/factory/src/test/resources/expected/Generics_FooImplFactory.java
index 2fb560a7..f63e68bf 100644
--- a/factory/src/test/resources/expected/Generics_FooImplFactory.java
+++ b/factory/src/test/resources/expected/Generics_FooImplFactory.java
@@ -20,12 +20,11 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class Generics_FooImplFactory<M extends Generics.Bar> implements Generics.FooFactory<M> {
@Inject
- Generics_FooImplFactory() {
- }
+ Generics_FooImplFactory() {}
@Override
public Generics.FooImpl<M> create() {
diff --git a/factory/src/test/resources/expected/Generics_FooImplWithClassFactory.java b/factory/src/test/resources/expected/Generics_FooImplWithClassFactory.java
index b338454f..7230492e 100644
--- a/factory/src/test/resources/expected/Generics_FooImplWithClassFactory.java
+++ b/factory/src/test/resources/expected/Generics_FooImplWithClassFactory.java
@@ -20,12 +20,12 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
-final class Generics_FooImplWithClassFactory<M extends Generics.Bar> extends Generics.FooFactoryClass<M> {
+final class Generics_FooImplWithClassFactory<M extends Generics.Bar>
+ extends Generics.FooFactoryClass<M> {
@Inject
- Generics_FooImplWithClassFactory() {
- }
+ Generics_FooImplWithClassFactory() {}
@Override
public Generics.FooImplWithClass<M> create() {
diff --git a/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java b/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java
index ec4089b7..12102bb1 100644
--- a/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java
+++ b/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java
@@ -24,7 +24,7 @@ import javax.inject.Provider;
*/
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class MixedDepsImplementingInterfacesFactory
implements MixedDepsImplementingInterfaces.FromInt,
@@ -35,15 +35,15 @@ final class MixedDepsImplementingInterfacesFactory
@Inject
MixedDepsImplementingInterfacesFactory(Provider<String> sProvider) {
- this.sProvider = checkNotNull(sProvider, 1);
+ this.sProvider = checkNotNull(sProvider, 1, 1);
}
MixedDepsImplementingInterfaces create(int i) {
- return new MixedDepsImplementingInterfaces(checkNotNull(sProvider.get(), 1), i);
+ return new MixedDepsImplementingInterfaces(checkNotNull(sProvider.get(), 1, 2), i);
}
MixedDepsImplementingInterfaces create(Object o) {
- return new MixedDepsImplementingInterfaces(checkNotNull(o, 1));
+ return new MixedDepsImplementingInterfaces(checkNotNull(o, 1, 1));
}
@Override
@@ -56,11 +56,13 @@ final class MixedDepsImplementingInterfacesFactory
return create(o);
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java b/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java
index 3eaf3afa..529684ed 100644
--- a/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java
+++ b/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java
@@ -21,7 +21,7 @@ import javax.inject.Provider;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class MultipleFactoriesConflictingParameterNamesFactory {
@@ -36,33 +36,35 @@ final class MultipleFactoriesConflictingParameterNamesFactory {
Provider<Object> java_lang_ObjectProvider,
@AQualifier Provider<String> stringProvider2,
@AQualifier Provider<Object> _tests_AQualifier_java_lang_ObjectProvider) {
- this.stringProvider = checkNotNull(stringProvider, 1);
- this.java_lang_ObjectProvider = checkNotNull(java_lang_ObjectProvider, 2);
- this.stringProvider2 = checkNotNull(stringProvider2, 3);
+ this.stringProvider = checkNotNull(stringProvider, 1, 4);
+ this.java_lang_ObjectProvider = checkNotNull(java_lang_ObjectProvider, 2, 4);
+ this.stringProvider2 = checkNotNull(stringProvider2, 3, 4);
this._tests_AQualifier_java_lang_ObjectProvider =
- checkNotNull(_tests_AQualifier_java_lang_ObjectProvider, 4);
+ checkNotNull(_tests_AQualifier_java_lang_ObjectProvider, 4, 4);
}
MultipleFactoriesConflictingParameterNames create(Object unused) {
return new MultipleFactoriesConflictingParameterNames(
- checkNotNull(stringProvider.get(), 1),
- checkNotNull(java_lang_ObjectProvider.get(), 2),
+ checkNotNull(stringProvider.get(), 1, 4),
+ checkNotNull(java_lang_ObjectProvider.get(), 2, 4),
java_lang_ObjectProvider,
- checkNotNull(unused, 4));
+ checkNotNull(unused, 4, 4));
}
MultipleFactoriesConflictingParameterNames create() {
return new MultipleFactoriesConflictingParameterNames(
- checkNotNull(stringProvider2.get(), 1),
- checkNotNull(_tests_AQualifier_java_lang_ObjectProvider.get(), 2),
+ checkNotNull(stringProvider2.get(), 1, 3),
+ checkNotNull(_tests_AQualifier_java_lang_ObjectProvider.get(), 2, 3),
_tests_AQualifier_java_lang_ObjectProvider);
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java
index 6fcfb036..63c98865 100644
--- a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java
+++ b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java
@@ -20,7 +20,7 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class MultipleFactoriesImplementingInterface_ClassAFactory
implements MultipleFactoriesImplementingInterface.Base.Factory {
diff --git a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java
index 56646891..cca84d6c 100644
--- a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java
+++ b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java
@@ -20,7 +20,7 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class MultipleFactoriesImplementingInterface_ClassBFactory
implements MultipleFactoriesImplementingInterface.Base.Factory {
diff --git a/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java b/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java
index 97cc8ac2..7a8eb97c 100644
--- a/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java
+++ b/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java
@@ -21,30 +21,32 @@ import javax.inject.Provider;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class MultipleProvidedParamsSameKeyFactory {
private final Provider<String> java_lang_StringProvider;
@Inject
MultipleProvidedParamsSameKeyFactory(Provider<String> java_lang_StringProvider) {
- this.java_lang_StringProvider = checkNotNull(java_lang_StringProvider, 1);
+ this.java_lang_StringProvider = checkNotNull(java_lang_StringProvider, 1, 1);
}
MultipleProvidedParamsSameKey create() {
return new MultipleProvidedParamsSameKey(
- checkNotNull(java_lang_StringProvider.get(), 1),
- checkNotNull(java_lang_StringProvider.get(), 2),
+ checkNotNull(java_lang_StringProvider.get(), 1, 5),
+ checkNotNull(java_lang_StringProvider.get(), 2, 5),
java_lang_StringProvider.get(),
java_lang_StringProvider,
java_lang_StringProvider);
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java b/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java
index fe7aa1a9..ff5b94ca 100644
--- a/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java
+++ b/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java
@@ -20,7 +20,7 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class NestedClassCustomNamedFactory {
@Inject
diff --git a/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java b/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java
index 41ecc52e..38f67cbb 100644
--- a/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java
+++ b/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java
@@ -20,7 +20,7 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class NestedClasses_SimpleNestedClassFactory {
@Inject
diff --git a/factory/src/test/resources/expected/OnlyPrimitivesFactory.java b/factory/src/test/resources/expected/OnlyPrimitivesFactory.java
index b931a222..95546956 100644
--- a/factory/src/test/resources/expected/OnlyPrimitivesFactory.java
+++ b/factory/src/test/resources/expected/OnlyPrimitivesFactory.java
@@ -20,7 +20,7 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class OnlyPrimitivesFactory {
@Inject
diff --git a/factory/src/test/resources/expected/ParameterAnnotationsFactory.java b/factory/src/test/resources/expected/ParameterAnnotationsFactory.java
new file mode 100644
index 00000000..a238a9d2
--- /dev/null
+++ b/factory/src/test/resources/expected/ParameterAnnotationsFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package tests;
+
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+@Generated(
+ value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+ comments = "https://github.com/google/auto/tree/main/factory"
+ )
+final class ParameterAnnotationsFactory {
+ private final Provider<@ParameterAnnotations.NullableType String> fooProvider;
+
+ @Inject
+ ParameterAnnotationsFactory(Provider<@ParameterAnnotations.NullableType String> fooProvider) {
+ this.fooProvider = checkNotNull(fooProvider, 1, 1);
+ }
+
+ ParameterAnnotations create(
+ @ParameterAnnotations.NullableParameter Integer bar,
+ @ParameterAnnotations.Nullable Long baz,
+ @ParameterAnnotations.NullableType Thread buh,
+ @ParameterAnnotations.NullableParameterAndType String quux) {
+ return new ParameterAnnotations(
+ checkNotNull(fooProvider.get(), 1, 5),
+ checkNotNull(bar, 2, 5),
+ baz,
+ checkNotNull(buh, 4, 5),
+ checkNotNull(quux, 5, 5));
+ }
+
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
+ if (reference == null) {
+ throw new NullPointerException(
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
+ }
+ return reference;
+ }
+}
diff --git a/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java b/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java
index 75a6291c..224e38d0 100644
--- a/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java
+++ b/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java
@@ -21,7 +21,7 @@ import javax.inject.Provider;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class ProviderArgumentToCreateMethodFactory
implements ProviderArgumentToCreateMethod.CustomCreator {
@@ -29,7 +29,7 @@ final class ProviderArgumentToCreateMethodFactory
ProviderArgumentToCreateMethodFactory() {}
ProviderArgumentToCreateMethod create(Provider<String> stringProvider) {
- return new ProviderArgumentToCreateMethod(checkNotNull(stringProvider, 1));
+ return new ProviderArgumentToCreateMethod(checkNotNull(stringProvider, 1, 1));
}
@Override
@@ -37,11 +37,13 @@ final class ProviderArgumentToCreateMethodFactory
return create(stringProvider);
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/PublicClassFactory.java b/factory/src/test/resources/expected/PublicClassFactory.java
index 9e5c113d..b176f3f8 100644
--- a/factory/src/test/resources/expected/PublicClassFactory.java
+++ b/factory/src/test/resources/expected/PublicClassFactory.java
@@ -20,7 +20,7 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
public final class PublicClassFactory {
@Inject
diff --git a/factory/src/test/resources/expected/SimpleClassFactory.java b/factory/src/test/resources/expected/SimpleClassFactory.java
index 4741b752..14020726 100644
--- a/factory/src/test/resources/expected/SimpleClassFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassFactory.java
@@ -20,7 +20,7 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class SimpleClassFactory {
@Inject
diff --git a/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java b/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java
index 17701387..2bf88ce3 100644
--- a/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java
@@ -21,7 +21,7 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class SimpleClassImplementingMarkerFactory implements RandomAccess {
@Inject
diff --git a/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java b/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java
index 7dd91bea..803e6365 100644
--- a/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java
@@ -20,7 +20,7 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class SimpleClassImplementingSimpleInterfaceFactory
implements SimpleClassImplementingSimpleInterface.SimpleInterface {
diff --git a/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java b/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java
index b69ea326..3e00e550 100644
--- a/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java
@@ -21,26 +21,28 @@ import javax.inject.Provider;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class SimpleClassMixedDepsFactory {
private final Provider<String> providedDepAProvider;
@Inject
SimpleClassMixedDepsFactory(@AQualifier Provider<String> providedDepAProvider) {
- this.providedDepAProvider = checkNotNull(providedDepAProvider, 1);
+ this.providedDepAProvider = checkNotNull(providedDepAProvider, 1, 1);
}
SimpleClassMixedDeps create(String depB) {
return new SimpleClassMixedDeps(
- checkNotNull(providedDepAProvider.get(), 1), checkNotNull(depB, 2));
+ checkNotNull(providedDepAProvider.get(), 1, 2), checkNotNull(depB, 2, 2));
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java b/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java
index 5ab90306..55f38e40 100644
--- a/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java
@@ -20,7 +20,7 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
class SimpleClassNonFinalFactory {
@Inject
diff --git a/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java b/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java
index 5b955964..b200daa1 100644
--- a/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java
@@ -22,7 +22,7 @@ import javax.inject.Provider;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class SimpleClassNullableParametersFactory {
private final Provider<String> providedNullableProvider;
@@ -33,8 +33,8 @@ final class SimpleClassNullableParametersFactory {
SimpleClassNullableParametersFactory(
Provider<String> providedNullableProvider,
@BQualifier Provider<String> providedQualifiedNullableProvider) {
- this.providedNullableProvider = checkNotNull(providedNullableProvider, 1);
- this.providedQualifiedNullableProvider = checkNotNull(providedQualifiedNullableProvider, 2);
+ this.providedNullableProvider = checkNotNull(providedNullableProvider, 1, 2);
+ this.providedQualifiedNullableProvider = checkNotNull(providedQualifiedNullableProvider, 2, 2);
}
SimpleClassNullableParameters create(
@@ -46,11 +46,13 @@ final class SimpleClassNullableParametersFactory {
providedQualifiedNullableProvider.get());
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java b/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java
index 9cc8a166..abc92230 100644
--- a/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java
@@ -20,21 +20,23 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class SimpleClassPassedDepsFactory {
@Inject
SimpleClassPassedDepsFactory() {}
SimpleClassPassedDeps create(String depA, String depB) {
- return new SimpleClassPassedDeps(checkNotNull(depA, 1), checkNotNull(depB, 2));
+ return new SimpleClassPassedDeps(checkNotNull(depA, 1, 2), checkNotNull(depB, 2, 2));
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java b/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java
index 52448aad..31a21aad 100644
--- a/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java
@@ -21,7 +21,7 @@ import javax.inject.Provider;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class SimpleClassProvidedDepsFactory {
private final Provider<Integer> providedPrimitiveAProvider;
@@ -35,25 +35,27 @@ final class SimpleClassProvidedDepsFactory {
@BQualifier Provider<Integer> providedPrimitiveBProvider,
@AQualifier Provider<String> providedDepAProvider,
@BQualifier Provider<String> providedDepBProvider) {
- this.providedPrimitiveAProvider = checkNotNull(providedPrimitiveAProvider, 1);
- this.providedPrimitiveBProvider = checkNotNull(providedPrimitiveBProvider, 2);
- this.providedDepAProvider = checkNotNull(providedDepAProvider, 3);
- this.providedDepBProvider = checkNotNull(providedDepBProvider, 4);
+ this.providedPrimitiveAProvider = checkNotNull(providedPrimitiveAProvider, 1, 4);
+ this.providedPrimitiveBProvider = checkNotNull(providedPrimitiveBProvider, 2, 4);
+ this.providedDepAProvider = checkNotNull(providedDepAProvider, 3, 4);
+ this.providedDepBProvider = checkNotNull(providedDepBProvider, 4, 4);
}
SimpleClassProvidedDeps create() {
return new SimpleClassProvidedDeps(
- checkNotNull(providedPrimitiveAProvider.get(), 1),
- checkNotNull(providedPrimitiveBProvider.get(), 2),
- checkNotNull(providedDepAProvider.get(), 3),
- checkNotNull(providedDepBProvider.get(), 4));
+ checkNotNull(providedPrimitiveAProvider.get(), 1, 4),
+ checkNotNull(providedPrimitiveBProvider.get(), 2, 4),
+ checkNotNull(providedDepAProvider.get(), 3, 4),
+ checkNotNull(providedDepBProvider.get(), 4, 4));
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java b/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java
index 7bf2372c..aec94301 100644
--- a/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java
@@ -21,7 +21,7 @@ import javax.inject.Provider;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class SimpleClassProvidedProviderDepsFactory {
private final Provider<String> providedDepAProvider;
@@ -31,19 +31,21 @@ final class SimpleClassProvidedProviderDepsFactory {
SimpleClassProvidedProviderDepsFactory(
@AQualifier Provider<String> providedDepAProvider,
@BQualifier Provider<String> providedDepBProvider) {
- this.providedDepAProvider = checkNotNull(providedDepAProvider, 1);
- this.providedDepBProvider = checkNotNull(providedDepBProvider, 2);
+ this.providedDepAProvider = checkNotNull(providedDepAProvider, 1, 2);
+ this.providedDepBProvider = checkNotNull(providedDepBProvider, 2, 2);
}
SimpleClassProvidedProviderDeps create() {
return new SimpleClassProvidedProviderDeps(providedDepAProvider, providedDepBProvider);
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/expected/SimpleClassThrowsFactory.java b/factory/src/test/resources/expected/SimpleClassThrowsFactory.java
index eda503a4..760febf9 100644
--- a/factory/src/test/resources/expected/SimpleClassThrowsFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassThrowsFactory.java
@@ -21,7 +21,7 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class SimpleClassThrowsFactory {
@Inject
diff --git a/factory/src/test/resources/expected/SimpleClassVarargsFactory.java b/factory/src/test/resources/expected/SimpleClassVarargsFactory.java
index ac7c4bdc..99deab31 100644
--- a/factory/src/test/resources/expected/SimpleClassVarargsFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassVarargsFactory.java
@@ -20,14 +20,14 @@ import javax.inject.Inject;
@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
- comments = "https://github.com/google/auto/tree/master/factory"
+ comments = "https://github.com/google/auto/tree/main/factory"
)
final class SimpleClassVarargsFactory implements SimpleClassVarargs.InterfaceWithVarargs {
@Inject
SimpleClassVarargsFactory() {}
SimpleClassVarargs create(String... args) {
- return new SimpleClassVarargs(checkNotNull(args, 1));
+ return new SimpleClassVarargs(checkNotNull(args, 1, 1));
}
@Override
@@ -35,11 +35,13 @@ final class SimpleClassVarargsFactory implements SimpleClassVarargs.InterfaceWit
return create(args);
}
- private static <T> T checkNotNull(T reference, int argumentIndex) {
+ private static <T> T checkNotNull(T reference, int argumentNumber, int argumentCount) {
if (reference == null) {
throw new NullPointerException(
- "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
- + argumentIndex);
+ "@AutoFactory method argument is null but is not marked @Nullable. Argument "
+ + argumentNumber
+ + " of "
+ + argumentCount);
}
return reference;
}
diff --git a/factory/src/test/resources/good/CustomAnnotations.java b/factory/src/test/resources/good/CustomAnnotations.java
new file mode 100644
index 00000000..e3636d72
--- /dev/null
+++ b/factory/src/test/resources/good/CustomAnnotations.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package tests;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.errorprone.annotations.Immutable;
+
+@AutoFactory.AnnotationsToApply
+@interface ImmutableAndSuppressWarnings {
+ Immutable immutable() default @Immutable;
+ SuppressWarnings suppressWarnings() default @SuppressWarnings("Immutable");
+}
+
+@ImmutableAndSuppressWarnings(suppressWarnings = @SuppressWarnings({"unchecked", "Immutable"}))
+@AutoFactory
+final class CustomAnnotations {}
diff --git a/factory/src/test/resources/good/ParameterAnnotations.java b/factory/src/test/resources/good/ParameterAnnotations.java
new file mode 100644
index 00000000..07a32ae4
--- /dev/null
+++ b/factory/src/test/resources/good/ParameterAnnotations.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package tests;
+
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE_USE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@AutoFactory
+final class ParameterAnnotations {
+ @Retention(RUNTIME)
+ @Target(PARAMETER)
+ @interface NullableParameter {}
+
+ // We have special treatment of @Nullable; make sure it doesn't get copied twice.
+ @Retention(RUNTIME)
+ @Target(PARAMETER)
+ @interface Nullable {}
+
+ @Retention(RUNTIME)
+ @Target(TYPE_USE)
+ @interface NullableType {}
+
+ @Retention(RUNTIME)
+ @Target({PARAMETER, TYPE_USE})
+ @interface NullableParameterAndType {}
+
+ ParameterAnnotations(
+ @Provided @NullableParameter @NullableType String foo,
+ @NullableParameter Integer bar,
+ @Nullable Long baz,
+ @NullableType Thread buh,
+ @NullableParameterAndType String quux) {}
+}
diff --git a/service/annotations/pom.xml b/service/annotations/pom.xml
index e84147d2..f3c45baf 100644
--- a/service/annotations/pom.xml
+++ b/service/annotations/pom.xml
@@ -31,7 +31,7 @@
<description>
Provider-configuration files for ServiceLoader.
</description>
- <url>https://github.com/google/auto/tree/master/service</url>
+ <url>https://github.com/google/auto/tree/main/service</url>
<scm>
<url>http://github.com/google/auto</url>
diff --git a/service/pom.xml b/service/pom.xml
index f3068854..e29cc06c 100644
--- a/service/pom.xml
+++ b/service/pom.xml
@@ -32,12 +32,12 @@
Aggregator POM for @AutoService
</description>
<packaging>pom</packaging>
- <url>https://github.com/google/auto/tree/master/service</url>
+ <url>https://github.com/google/auto/tree/main/service</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
- <guava.version>31.0.1-jre</guava.version>
+ <guava.version>32.0.1-jre</guava.version>
<truth.version>1.1.3</truth.version>
</properties>
@@ -111,7 +111,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
- <version>3.8.1</version>
+ <version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
@@ -123,14 +123,14 @@
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-java</artifactId>
- <version>1.1.0</version>
+ <version>1.1.2</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
- <version>3.2.0</version>
+ <version>3.3.0</version>
</plugin>
</plugins>
</pluginManagement>
diff --git a/service/processor/pom.xml b/service/processor/pom.xml
index cef19ade..e8045096 100644
--- a/service/processor/pom.xml
+++ b/service/processor/pom.xml
@@ -31,7 +31,7 @@
<description>
Provider-configuration files for ServiceLoader.
</description>
- <url>https://github.com/google/auto/tree/master/service</url>
+ <url>https://github.com/google/auto/tree/main/service</url>
<scm>
<url>http://github.com/google/auto</url>
@@ -49,7 +49,7 @@
<dependency>
<groupId>com.google.auto</groupId>
<artifactId>auto-common</artifactId>
- <version>1.2.1</version>
+ <version>1.2.2</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
diff --git a/service/processor/src/main/java/com/google/auto/service/processor/AutoServiceProcessor.java b/service/processor/src/main/java/com/google/auto/service/processor/AutoServiceProcessor.java
index 85a24cb4..26c1435d 100644
--- a/service/processor/src/main/java/com/google/auto/service/processor/AutoServiceProcessor.java
+++ b/service/processor/src/main/java/com/google/auto/service/processor/AutoServiceProcessor.java
@@ -46,6 +46,7 @@ import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
+import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
@@ -228,25 +229,30 @@ public class AutoServiceProcessor extends AbstractProcessor {
TypeElement providerType,
AnnotationMirror annotationMirror) {
- String verify = processingEnv.getOptions().get("verify");
- if (verify == null || !Boolean.parseBoolean(verify)) {
+ if (!Boolean.parseBoolean(processingEnv.getOptions().getOrDefault("verify", "true"))
+ || suppresses(providerImplementer, "AutoService")) {
return true;
}
- // TODO: We're currently only enforcing the subtype relationship
- // constraint. It would be nice to enforce them all.
+ // We check that providerImplementer does indeed inherit from providerType, and that it is not
+ // abstract (an abstract class or interface). For ServiceLoader, we could also check that it has
+ // a public no-arg constructor. But it turns out that people also use AutoService in contexts
+ // where the META-INF/services entries are read by things other than ServiceLoader. Those things
+ // still require the class to exist and inherit from providerType, but they don't necessarily
+ // require a public no-arg constructor.
+ // More background: https://github.com/google/auto/issues/1505.
Types types = processingEnv.getTypeUtils();
if (types.isSubtype(providerImplementer.asType(), providerType.asType())) {
- return true;
+ return checkNotAbstract(providerImplementer, annotationMirror);
}
// Maybe the provider has generic type, but the argument to @AutoService can't be generic.
// So we allow that with a warning, which can be suppressed with @SuppressWarnings("rawtypes").
// See https://github.com/google/auto/issues/870.
if (types.isSubtype(providerImplementer.asType(), types.erasure(providerType.asType()))) {
- if (!rawTypesSuppressed(providerImplementer)) {
+ if (!suppresses(providerImplementer, "rawtypes")) {
warning(
"Service provider "
+ providerType
@@ -255,16 +261,35 @@ public class AutoServiceProcessor extends AbstractProcessor {
providerImplementer,
annotationMirror);
}
- return true;
+ return checkNotAbstract(providerImplementer, annotationMirror);
}
+ String message =
+ "ServiceProviders must implement their service provider interface. "
+ + providerImplementer.getQualifiedName()
+ + " does not implement "
+ + providerType.getQualifiedName();
+ error(message, providerImplementer, annotationMirror);
+
return false;
}
- private static boolean rawTypesSuppressed(Element element) {
+ private boolean checkNotAbstract(
+ TypeElement providerImplementer, AnnotationMirror annotationMirror) {
+ if (providerImplementer.getModifiers().contains(Modifier.ABSTRACT)) {
+ error(
+ "@AutoService cannot be applied to an abstract class or an interface",
+ providerImplementer,
+ annotationMirror);
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean suppresses(Element element, String warning) {
for (; element != null; element = element.getEnclosingElement()) {
SuppressWarnings suppress = element.getAnnotation(SuppressWarnings.class);
- if (suppress != null && Arrays.asList(suppress.value()).contains("rawtypes")) {
+ if (suppress != null && Arrays.asList(suppress.value()).contains(warning)) {
return true;
}
}
diff --git a/service/processor/src/main/java/com/google/auto/service/processor/ServicesFiles.java b/service/processor/src/main/java/com/google/auto/service/processor/ServicesFiles.java
index 75d6cca7..b08431e8 100644
--- a/service/processor/src/main/java/com/google/auto/service/processor/ServicesFiles.java
+++ b/service/processor/src/main/java/com/google/auto/service/processor/ServicesFiles.java
@@ -15,9 +15,8 @@
*/
package com.google.auto.service.processor;
-import static com.google.common.base.Charsets.UTF_8;
+import static java.nio.charset.StandardCharsets.UTF_8;
-import com.google.common.io.Closer;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
@@ -57,12 +56,9 @@ final class ServicesFiles {
*/
static Set<String> readServiceFile(InputStream input) throws IOException {
HashSet<String> serviceClasses = new HashSet<String>();
- Closer closer = Closer.create();
- try {
- // TODO(gak): use CharStreams
- BufferedReader r = closer.register(new BufferedReader(new InputStreamReader(input, UTF_8)));
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, UTF_8))) {
String line;
- while ((line = r.readLine()) != null) {
+ while ((line = reader.readLine()) != null) {
int commentStart = line.indexOf('#');
if (commentStart >= 0) {
line = line.substring(0, commentStart);
@@ -73,10 +69,6 @@ final class ServicesFiles {
}
}
return serviceClasses;
- } catch (Throwable t) {
- throw closer.rethrow(t);
- } finally {
- closer.close();
}
}
diff --git a/service/processor/src/test/java/com/google/auto/service/processor/AutoServiceProcessorTest.java b/service/processor/src/test/java/com/google/auto/service/processor/AutoServiceProcessorTest.java
index 7a176dd9..69a42785 100644
--- a/service/processor/src/test/java/com/google/auto/service/processor/AutoServiceProcessorTest.java
+++ b/service/processor/src/test/java/com/google/auto/service/processor/AutoServiceProcessorTest.java
@@ -16,13 +16,14 @@
package com.google.auto.service.processor;
import static com.google.auto.service.processor.AutoServiceProcessor.MISSING_SERVICES_ERROR;
-import static com.google.testing.compile.CompilationSubject.assertThat;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.testing.compile.CompilationSubject.assertThat;
import com.google.common.io.Resources;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.Compiler;
import com.google.testing.compile.JavaFileObjects;
+import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,18 +32,18 @@ import org.junit.runners.JUnit4;
/** Tests the {@link AutoServiceProcessor}. */
@RunWith(JUnit4.class)
public class AutoServiceProcessorTest {
+ private final Compiler compiler = Compiler.javac().withProcessors(new AutoServiceProcessor());
+
@Test
public void autoService() {
Compilation compilation =
- Compiler.javac()
- .withProcessors(new AutoServiceProcessor())
- .compile(
- JavaFileObjects.forResource("test/SomeService.java"),
- JavaFileObjects.forResource("test/SomeServiceProvider1.java"),
- JavaFileObjects.forResource("test/SomeServiceProvider2.java"),
- JavaFileObjects.forResource("test/Enclosing.java"),
- JavaFileObjects.forResource("test/AnotherService.java"),
- JavaFileObjects.forResource("test/AnotherServiceProvider.java"));
+ compiler.compile(
+ JavaFileObjects.forResource("test/SomeService.java"),
+ JavaFileObjects.forResource("test/SomeServiceProvider1.java"),
+ JavaFileObjects.forResource("test/SomeServiceProvider2.java"),
+ JavaFileObjects.forResource("test/Enclosing.java"),
+ JavaFileObjects.forResource("test/AnotherService.java"),
+ JavaFileObjects.forResource("test/AnotherServiceProvider.java"));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/services/test.SomeService")
@@ -57,12 +58,10 @@ public class AutoServiceProcessorTest {
@Test
public void multiService() {
Compilation compilation =
- Compiler.javac()
- .withProcessors(new AutoServiceProcessor())
- .compile(
- JavaFileObjects.forResource("test/SomeService.java"),
- JavaFileObjects.forResource("test/AnotherService.java"),
- JavaFileObjects.forResource("test/MultiServiceProvider.java"));
+ compiler.compile(
+ JavaFileObjects.forResource("test/SomeService.java"),
+ JavaFileObjects.forResource("test/AnotherService.java"),
+ JavaFileObjects.forResource("test/MultiServiceProvider.java"));
assertThat(compilation).succeededWithoutWarnings();
// We have @AutoService({SomeService.class, AnotherService.class}) class MultiServiceProvider.
// So we expect META-INF/services/test.SomeService with contents that name MultiServiceProvider
@@ -79,19 +78,53 @@ public class AutoServiceProcessorTest {
@Test
public void badMultiService() {
- Compilation compilation =
- Compiler.javac()
- .withProcessors(new AutoServiceProcessor())
- .compile(JavaFileObjects.forResource("test/NoServices.java"));
+ Compilation compilation = compiler.compile(JavaFileObjects.forResource("test/NoServices.java"));
assertThat(compilation).failed();
assertThat(compilation).hadErrorContaining(MISSING_SERVICES_ERROR);
}
@Test
+ public void doesNotImplement_failsByDefault() {
+ Compilation compilation =
+ compiler.compile(JavaFileObjects.forResource("test/DoesNotImplement.java"));
+ assertThat(compilation).failed();
+ assertThat(compilation)
+ .hadErrorContaining("test.DoesNotImplement does not implement test.SomeService");
+ }
+
+ @Test
+ public void doesNotImplement_succeedsWithVerifyFalse() {
+ Compilation compilation =
+ compiler
+ .withOptions("-Averify=false")
+ .compile(
+ JavaFileObjects.forResource("test/DoesNotImplement.java"),
+ JavaFileObjects.forResource("test/SomeService.java"));
+ assertThat(compilation).succeededWithoutWarnings();
+ assertThat(compilation)
+ .generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/services/test.SomeService")
+ .contentsAsUtf8String()
+ .isEqualTo("test.DoesNotImplement\n");
+ }
+
+ @Test
+ public void doesNotImplement_suppressed() {
+ Compilation compilation =
+ compiler.compile(
+ JavaFileObjects.forResource("test/DoesNotImplementSuppressed.java"),
+ JavaFileObjects.forResource("test/SomeService.java"));
+ assertThat(compilation).succeededWithoutWarnings();
+ assertThat(compilation)
+ .generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/services/test.SomeService")
+ .contentsAsUtf8String()
+ .isEqualTo("test.DoesNotImplementSuppressed\n");
+ }
+
+ @Test
public void generic() {
Compilation compilation =
- Compiler.javac()
- .withProcessors(new AutoServiceProcessor())
+ compiler
+ .withOptions("-Averify=false")
.compile(
JavaFileObjects.forResource("test/GenericService.java"),
JavaFileObjects.forResource("test/GenericServiceProvider.java"));
@@ -103,10 +136,22 @@ public class AutoServiceProcessorTest {
}
@Test
- public void genericWithVerifyOption() {
+ public void genericWithNoVerifyOption() {
Compilation compilation =
- Compiler.javac()
- .withProcessors(new AutoServiceProcessor())
+ compiler.compile(
+ JavaFileObjects.forResource("test/GenericService.java"),
+ JavaFileObjects.forResource("test/GenericServiceProvider.java"));
+ assertThat(compilation).succeeded();
+ assertThat(compilation)
+ .hadWarningContaining(
+ "Service provider test.GenericService is generic, so it can't be named exactly by"
+ + " @AutoService. If this is OK, add @SuppressWarnings(\"rawtypes\").");
+ }
+
+ @Test
+ public void genericWithExplicitVerify() {
+ Compilation compilation =
+ compiler
.withOptions("-Averify=true")
.compile(
JavaFileObjects.forResource("test/GenericService.java"),
@@ -121,8 +166,7 @@ public class AutoServiceProcessorTest {
@Test
public void genericWithVerifyOptionAndSuppressWarings() {
Compilation compilation =
- Compiler.javac()
- .withProcessors(new AutoServiceProcessor())
+ compiler
.withOptions("-Averify=true")
.compile(
JavaFileObjects.forResource("test/GenericService.java"),
@@ -133,8 +177,7 @@ public class AutoServiceProcessorTest {
@Test
public void nestedGenericWithVerifyOptionAndSuppressWarnings() {
Compilation compilation =
- Compiler.javac()
- .withProcessors(new AutoServiceProcessor())
+ compiler
.withOptions("-Averify=true")
.compile(
JavaFileObjects.forResource("test/GenericService.java"),
@@ -150,8 +193,7 @@ public class AutoServiceProcessorTest {
public void missing() {
AutoServiceProcessor processor = new AutoServiceProcessor();
Compilation compilation =
- Compiler.javac()
- .withProcessors(processor)
+ compiler
.withOptions("-Averify=true")
.compile(
JavaFileObjects.forResource(
@@ -159,4 +201,38 @@ public class AutoServiceProcessorTest {
assertThat(compilation).failed();
assertThat(processor.exceptionStacks()).isEmpty();
}
+
+ @Test
+ public void autoServiceOnInterface() {
+ AutoServiceProcessor processor = new AutoServiceProcessor();
+ JavaFileObject autoServiceOnInterface =
+ JavaFileObjects.forResource("test/AutoServiceOnInterface.java");
+ Compilation compilation =
+ Compiler.javac()
+ .withProcessors(processor)
+ .withOptions("-Averify=true")
+ .compile(autoServiceOnInterface);
+ assertThat(compilation)
+ .hadErrorContaining("@AutoService cannot be applied to an abstract class or an interface")
+ .inFile(autoServiceOnInterface)
+ .onLineContaining("@AutoService");
+ assertThat(processor.exceptionStacks()).isEmpty();
+ }
+
+ @Test
+ public void autoServiceOnAbstractClass() {
+ AutoServiceProcessor processor = new AutoServiceProcessor();
+ JavaFileObject autoServiceOnAbstractClass =
+ JavaFileObjects.forResource("test/AutoServiceOnAbstractClass.java");
+ Compilation compilation =
+ Compiler.javac()
+ .withProcessors(processor)
+ .withOptions("-Averify=true")
+ .compile(autoServiceOnAbstractClass);
+ assertThat(compilation)
+ .hadErrorContaining("@AutoService cannot be applied to an abstract class or an interface")
+ .inFile(autoServiceOnAbstractClass)
+ .onLineContaining("@AutoService");
+ assertThat(processor.exceptionStacks()).isEmpty();
+ }
}
diff --git a/service/processor/src/test/resources/test/AutoServiceOnAbstractClass.java b/service/processor/src/test/resources/test/AutoServiceOnAbstractClass.java
new file mode 100644
index 00000000..1669ca29
--- /dev/null
+++ b/service/processor/src/test/resources/test/AutoServiceOnAbstractClass.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package test;
+
+import com.google.auto.service.AutoService;
+
+@AutoService(SomeService.class)
+public abstract class AutoServiceOnAbstractClass implements SomeService {}
diff --git a/service/processor/src/test/resources/test/AutoServiceOnInterface.java b/service/processor/src/test/resources/test/AutoServiceOnInterface.java
new file mode 100644
index 00000000..84fcf126
--- /dev/null
+++ b/service/processor/src/test/resources/test/AutoServiceOnInterface.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package test;
+
+import com.google.auto.service.AutoService;
+
+@AutoService(SomeService.class)
+public interface AutoServiceOnInterface extends SomeService {}
diff --git a/service/processor/src/test/resources/test/DoesNotImplement.java b/service/processor/src/test/resources/test/DoesNotImplement.java
new file mode 100644
index 00000000..1ecfd03b
--- /dev/null
+++ b/service/processor/src/test/resources/test/DoesNotImplement.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package test;
+
+import com.google.auto.service.AutoService;
+
+@AutoService(SomeService.class)
+public class DoesNotImplement {}
+
diff --git a/service/processor/src/test/resources/test/DoesNotImplementSuppressed.java b/service/processor/src/test/resources/test/DoesNotImplementSuppressed.java
new file mode 100644
index 00000000..2aca9c5b
--- /dev/null
+++ b/service/processor/src/test/resources/test/DoesNotImplementSuppressed.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package test;
+
+import com.google.auto.service.AutoService;
+
+@AutoService(SomeService.class)
+@SuppressWarnings("AutoService")
+public class DoesNotImplementSuppressed {}
+
diff --git a/util/mvn-deploy.sh b/util/mvn-deploy.sh
deleted file mode 100755
index e7160ebe..00000000
--- a/util/mvn-deploy.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-if [ $# -lt 1 ]; then
- echo "usage $0 <ssl-key> [<param> ...]"
- exit 1;
-fi
-key=${1}
-shift
-params=${@}
-
-#validate key
-keystatus=$(gpg --list-keys | grep ${key} | awk '{print $1}')
-if [ "${keystatus}" != "pub" ]; then
- echo "Could not find public key with label ${key}"
- echo -n "Available keys from: "
- gpg --list-keys | grep --invert-match '^sub'
-
- exit 1
-fi
-
-mvn ${params} clean site:jar -P sonatype-oss-release -Dgpg.keyname=${key} deploy
diff --git a/value/Android.bp b/value/Android.bp
index a723474d..0e78bc6f 100644
--- a/value/Android.bp
+++ b/value/Android.bp
@@ -22,11 +22,14 @@ java_library_host {
"auto_service_annotations",
"error_prone_annotations",
+ "asm-9.2",
"auto_common",
"auto_value_extension",
"escapevelocity",
"guava",
"javapoet",
+ "kotlin-stdlib",
+ "kotlinx_metadata_jvm",
],
visibility: ["//visibility:public"],
}
diff --git a/value/CHANGES.md b/value/CHANGES.md
index 32b6107c..c6fb29bd 100644
--- a/value/CHANGES.md
+++ b/value/CHANGES.md
@@ -81,7 +81,7 @@ later.
* The Extension API is now a committed API, meaning we no longer warn that it is
likely to change incompatibly. A
- [guide](https://github.com/google/auto/blob/master/value/userguide/extensions.md)
+ [guide](https://github.com/google/auto/blob/main/value/userguide/extensions.md)
gives tips on writing extensions.
* Extensions can now return null rather than generated code. In that case the
diff --git a/value/README.md b/value/README.md
index f6c00c30..bdf936c4 100644
--- a/value/README.md
+++ b/value/README.md
@@ -21,5 +21,12 @@ less code and less room for error, while **not restricting your freedom** to
code almost any aspect of your class exactly the way you want it.
For more information, consult the
-[detailed
-documentation](userguide/index.md)
+[detailed documentation](userguide/index.md).
+
+**Note:** If you are using Kotlin then its
+[data classes](https://kotlinlang.org/docs/data-classes.html) are usually more
+appropriate than AutoValue. Likewise, if you are using a version of Java that
+has [records](https://docs.oracle.com/en/java/javase/16/language/records.html),
+then those are usually more appropriate. You can still use
+[AutoBuilder](userguide/autobuilder.md)
+to make builders for data classes or records.
diff --git a/value/annotations/pom.xml b/value/annotations/pom.xml
index 4fc183b6..48f33dac 100644
--- a/value/annotations/pom.xml
+++ b/value/annotations/pom.xml
@@ -30,7 +30,7 @@
<description>
Immutable value-type code generation for Java 1.7+.
</description>
- <url>https://github.com/google/auto/tree/master/value</url>
+ <url>https://github.com/google/auto/tree/main/value</url>
<properties>
<java.version>1.7</java.version>
diff --git a/value/pom.xml b/value/pom.xml
index 8b1e238d..a1fb0ffd 100644
--- a/value/pom.xml
+++ b/value/pom.xml
@@ -26,13 +26,13 @@
Immutable value-type code generation for Java 7+.
</description>
<packaging>pom</packaging>
- <url>https://github.com/google/auto/tree/master/value</url>
+ <url>https://github.com/google/auto/tree/main/value</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
- <guava.version>31.0.1-jre</guava.version>
- <truth.version>1.1.3</truth.version>
+ <guava.version>32.0.1-jre</guava.version>
+ <truth.version>1.1.5</truth.version>
</properties>
<scm>
@@ -129,7 +129,7 @@
<dependency>
<groupId>com.google.testing.compile</groupId>
<artifactId>compile-testing</artifactId>
- <version>0.19</version>
+ <version>0.21.0</version>
</dependency>
<dependency>
<groupId>com.google.truth</groupId>
@@ -146,11 +146,6 @@
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
- <dependency>
- <groupId>org.apache.velocity</groupId>
- <artifactId>velocity</artifactId>
- <version>1.7</version>
- </dependency>
</dependencies>
</dependencyManagement>
@@ -160,7 +155,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
- <version>3.8.1</version>
+ <version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
@@ -172,22 +167,23 @@
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-java</artifactId>
- <version>1.1.0</version>
+ <version>1.1.2</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
- <version>3.2.0</version>
+ <version>3.3.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-invoker-plugin</artifactId>
- <version>3.2.2</version>
+ <version>3.6.0</version>
<configuration>
<addTestClassPath>true</addTestClassPath>
<cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
+ <localRepositoryPath>${project.build.directory}/it-repo</localRepositoryPath>
<pomIncludes>
<pomInclude>*/pom.xml</pomInclude>
</pomIncludes>
@@ -219,7 +215,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
- <version>3.2.1</version>
+ <version>3.3.0</version>
<executions>
<execution>
<id>attach-sources</id>
@@ -232,7 +228,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
- <version>3.3.1</version>
+ <version>3.5.0</version>
<configuration>
<failOnError>false</failOnError>
</configuration>
@@ -248,7 +244,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
- <version>3.0.1</version>
+ <version>3.1.0</version>
<executions>
<execution>
<id>sign-artifacts</id>
diff --git a/value/processor/pom.xml b/value/processor/pom.xml
index b00457d5..6999c477 100644
--- a/value/processor/pom.xml
+++ b/value/processor/pom.xml
@@ -30,7 +30,7 @@
<description>
Immutable value-type code generation for Java 1.7+.
</description>
- <url>https://github.com/google/auto/tree/master/value</url>
+ <url>https://github.com/google/auto/tree/main/value</url>
<scm>
<url>http://github.com/google/auto</url>
@@ -40,15 +40,15 @@
</scm>
<properties>
- <auto-service.version>1.0.1</auto-service.version>
- <errorprone.version>2.10.0</errorprone.version>
+ <auto-service.version>1.1.1</auto-service.version>
+ <errorprone.version>2.20.0</errorprone.version>
</properties>
<dependencies>
<dependency>
<groupId>com.google.auto</groupId>
<artifactId>auto-common</artifactId>
- <version>1.2.1</version>
+ <version>1.2.2</version>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
@@ -63,12 +63,12 @@
<dependency>
<groupId>com.google.escapevelocity</groupId>
<artifactId>escapevelocity</artifactId>
- <version>0.9.1</version>
+ <version>1.1</version>
</dependency>
<dependency>
<groupId>net.ltgt.gradle.incap</groupId>
<artifactId>incap</artifactId>
- <version>0.3</version>
+ <version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
@@ -78,6 +78,16 @@
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.jetbrains.kotlinx</groupId>
+ <artifactId>kotlinx-metadata-jvm</artifactId>
+ <version>0.6.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm</artifactId>
+ <version>9.5</version>
+ </dependency>
<!-- test dependencies -->
<dependency>
<groupId>com.google.auto.value</groupId>
@@ -92,11 +102,6 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.apache.velocity</groupId>
- <artifactId>velocity</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-testlib</artifactId>
<scope>test</scope>
@@ -121,12 +126,6 @@
<artifactId>compile-testing</artifactId>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-core</artifactId>
- <version>4.1.0</version>
- <scope>test</scope>
- </dependency>
</dependencies>
<build>
@@ -170,7 +169,7 @@
<path>
<groupId>net.ltgt.gradle.incap</groupId>
<artifactId>incap-processor</artifactId>
- <version>0.3</version>
+ <version>1.0.0</version>
</path>
</annotationProcessorPaths>
</configuration>
@@ -178,6 +177,10 @@
<execution>
<id>default-testCompile</id>
<configuration>
+ <compilerArgs>
+ <!-- For MemoizeTest. -->
+ <arg>-parameters</arg>
+ </compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>com.google.auto.value</groupId>
@@ -197,7 +200,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
- <version>2.22.2</version>
+ <version>3.1.2</version>
<configuration>
<argLine>${test.jvm.flags}</argLine>
</configuration>
@@ -211,8 +214,9 @@
<artifactId>maven-invoker-plugin</artifactId>
</plugin>
<plugin>
- <groupId>org.immutables.tools</groupId>
+ <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
+ <version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
@@ -226,29 +230,45 @@
<exclude>com.google.code.findbugs:jsr305</exclude>
</excludes>
</artifactSet>
+ <transformers>
+ <!-- Needed to avoid "No MetadataExtensions instances found in the classpath" from Kotlin reflection. -->
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
+ </transformers>
<relocations>
<relocation>
- <pattern>org.objectweb</pattern>
- <shadedPattern>autovalue.shaded.org.objectweb$</shadedPattern>
- </relocation>
- <relocation>
<pattern>com.google</pattern>
- <shadedPattern>autovalue.shaded.com.google$</shadedPattern>
+ <shadedPattern>autovalue.shaded.com.google</shadedPattern>
<excludes>
<exclude>com.google.auto.value.**</exclude>
</excludes>
</relocation>
<relocation>
<pattern>com.squareup.javapoet</pattern>
- <shadedPattern>autovalue.shaded.com.squareup.javapoet$</shadedPattern>
+ <shadedPattern>autovalue.shaded.com.squareup.javapoet</shadedPattern>
+ </relocation>
+ <relocation>
+ <pattern>kotlin</pattern>
+ <shadedPattern>autovalue.shaded.kotlin</shadedPattern>
+ </relocation>
+ <relocation>
+ <pattern>kotlinx</pattern>
+ <shadedPattern>autovalue.shaded.kotlinx</shadedPattern>
</relocation>
<relocation>
<pattern>net.ltgt.gradle.incap</pattern>
- <shadedPattern>autovalue.shaded.net.ltgt.gradle.incap$</shadedPattern>
+ <shadedPattern>autovalue.shaded.net.ltgt.gradle.incap</shadedPattern>
</relocation>
<relocation>
<pattern>org.checkerframework</pattern>
- <shadedPattern>autovalue.shaded.org.checkerframework$</shadedPattern>
+ <shadedPattern>autovalue.shaded.org.checkerframework</shadedPattern>
+ </relocation>
+ <relocation>
+ <pattern>org.jetbrains</pattern>
+ <shadedPattern>autovalue.shaded.org.jetbrains</shadedPattern>
+ </relocation>
+ <relocation>
+ <pattern>org.objectweb</pattern>
+ <shadedPattern>autovalue.shaded.org.objectweb</shadedPattern>
</relocation>
</relocations>
</configuration>
@@ -273,8 +293,28 @@
<jdk>[9,)</jdk>
</activation>
<properties>
- <test.jvm.flags>--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</test.jvm.flags>
+ <test.jvm.flags>--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+ --add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+ --add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</test.jvm.flags>
</properties>
</profile>
+ <profile>
+ <id>exclude-module-tests</id>
+ <activation>
+ <jdk>(,9)</jdk>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <testExcludes>
+ <exclude>**/AutoValueModuleCompilationTest.java</exclude>
+ </testExcludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
</profiles>
</project>
diff --git a/value/src/it/functional/pom.xml b/value/src/it/functional/pom.xml
index 18f3718b..414d6c59 100644
--- a/value/src/it/functional/pom.xml
+++ b/value/src/it/functional/pom.xml
@@ -25,15 +25,14 @@
<version>HEAD-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
- <url>https://github.com/google/auto/tree/master/value</url>
+ <url>https://github.com/google/auto/tree/main/value</url>
<groupId>com.google.auto.value.it.functional</groupId>
<artifactId>functional</artifactId>
<version>HEAD-SNAPSHOT</version>
<name>Auto-Value Functional Integration Test</name>
<properties>
- <kotlin.version>1.6.0</kotlin.version>
- <exclude.tests>this-matches-nothing</exclude.tests>
+ <kotlin.version>1.8.22</kotlin.version>
</properties>
<dependencies>
<dependency>
@@ -49,7 +48,7 @@
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
- <version>1.0.1</version>
+ <version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
@@ -61,9 +60,9 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>com.google.gwt</groupId>
+ <groupId>org.gwtproject</groupId>
<artifactId>gwt-user</artifactId>
- <version>2.9.0</version>
+ <version>2.10.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
@@ -93,24 +92,13 @@
<dependency>
<groupId>dev.gradleplugins</groupId>
<artifactId>gradle-test-kit</artifactId>
- <version>7.3</version>
+ <version>8.1</version>
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-core</artifactId>
- <version>4.1.0</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jdt</groupId>
- <artifactId>ecj</artifactId>
- <version>3.25.0</version>
- </dependency>
- <dependency>
<groupId>com.google.escapevelocity</groupId>
<artifactId>escapevelocity</artifactId>
- <version>0.9.1</version>
+ <version>1.1</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
@@ -124,7 +112,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
- <version>3.2.0</version>
+ <version>3.3.0</version>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
@@ -161,12 +149,12 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
- <version>3.8.1</version>
+ <version>3.11.0</version>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-java</artifactId>
- <version>1.1.0</version>
+ <version>1.1.2</version>
</dependency>
</dependencies>
<configuration>
@@ -179,15 +167,13 @@
</compilerArgs>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
- <testExcludes>
- <exclude>${exclude.tests}</exclude>
- </testExcludes>
+ <testExcludes combine.children="append" />
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
- <version>2.8.2</version>
+ <version>3.1.1</version>
<configuration>
<!-- Build/test, but don't deploy -->
<skip>true</skip>
@@ -196,14 +182,27 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
- <version>2.22.2</version>
+ <version>3.1.2</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <version>3.1.2</version>
<configuration>
- <argLine>${test.jvm.flags}</argLine>
<systemPropertyVariables>
<autoValueVersion>${project.version}</autoValueVersion>
</systemPropertyVariables>
</configuration>
- </plugin>
+ <executions>
+ <execution>
+ <phase>install</phase>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
@@ -215,12 +214,12 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
- <version>3.8.1</version>
+ <version>3.11.0</version>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-java</artifactId>
- <version>1.1.0</version>
+ <version>1.1.2</version>
</dependency>
</dependencies>
<configuration>
@@ -233,32 +232,87 @@
</compilerArgs>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
+ <testExcludes combine.children="append" />
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ <profile>
+ <id>test-with-ecj</id>
+ <activation>
+ <jdk>[11,)</jdk>
+ </activation>
+ <dependencies>
+ <!-- test dependencies -->
+ <dependency>
+ <groupId>org.eclipse.jdt</groupId>
+ <artifactId>ecj</artifactId>
+ <version>3.34.0</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ </profile>
+
+ <profile>
+ <id>test-without-ecj</id>
+ <activation>
+ <jdk>(,17)</jdk>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
<testExcludes>
- <exclude>${exclude.tests}</exclude>
+ <exclude>**/CompileWithEclipseTest.java</exclude>
</testExcludes>
</configuration>
</plugin>
</plugins>
</build>
</profile>
+
<profile>
<id>exclude-java8-tests</id>
<activation>
<jdk>(,1.7]</jdk>
</activation>
- <properties>
- <exclude.tests>**/AutoValueJava8Test.java</exclude.tests>
- </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <testExcludes>
+ <exclude>**/AutoValueJava8Test.java</exclude>
+ </testExcludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
</profile>
+
<profile>
<id>open-modules</id>
<activation>
<jdk>[9,)</jdk>
</activation>
- <properties>
- <test.jvm.flags>--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</test.jvm.flags>
- </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <argLine>--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+ --add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+ --add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+ </argLine>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
</profile>
+
<profile>
<!-- Before JDK 11, parameter names from already-compiled classes are not reliably available
to the compiler even when they are present in class files. Since our Kotlin test file
@@ -268,9 +322,18 @@
<activation>
<jdk>(,11)</jdk>
</activation>
- <properties>
- <exclude.tests>**/AutoBuilderKotlinTest.java</exclude.tests>
- </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <testExcludes>
+ <exclude>**/AutoBuilderKotlinTest.java</exclude>
+ </testExcludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
</profile>
</profiles>
</project>
diff --git a/value/src/it/functional/src/main/java/PackagelessNestedValueType.java b/value/src/it/functional/src/main/java/PackagelessNestedValueType.java
index 77b19396..e16b1549 100644
--- a/value/src/it/functional/src/main/java/PackagelessNestedValueType.java
+++ b/value/src/it/functional/src/main/java/PackagelessNestedValueType.java
@@ -18,6 +18,7 @@ import com.google.auto.value.AutoValue;
import java.util.Map;
/** @author emcmanus@google.com (Éamonn McManus) */
+@SuppressWarnings("DefaultPackage")
public class PackagelessNestedValueType {
@AutoValue
public abstract static class Nested {
diff --git a/value/src/it/functional/src/main/java/PackagelessValueType.java b/value/src/it/functional/src/main/java/PackagelessValueType.java
index 62b172e7..5a8296c9 100644
--- a/value/src/it/functional/src/main/java/PackagelessValueType.java
+++ b/value/src/it/functional/src/main/java/PackagelessValueType.java
@@ -23,6 +23,7 @@ import javax.annotation.Nullable;
*
* @author emcmanus@google.com (Éamonn McManus)
*/
+@SuppressWarnings("DefaultPackage")
@AutoValue
public abstract class PackagelessValueType {
// The getters here are formatted as an illustration of what getters typically look in real
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationTest.java
index a04d41f3..de6885cf 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationTest.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationTest.java
@@ -178,6 +178,8 @@ public class AutoAnnotationTest {
StringValues anAnnotation();
+ Class<? extends CharSequence> aClass();
+
byte[] bytes();
short[] shorts();
@@ -199,6 +201,8 @@ public class AutoAnnotationTest {
RetentionPolicy[] enums();
StringValues[] annotations();
+
+ Class<? extends CharSequence>[] classes();
}
@AutoAnnotation
@@ -214,6 +218,7 @@ public class AutoAnnotationTest {
String aString,
RetentionPolicy anEnum,
StringValues anAnnotation,
+ Class<? extends CharSequence> aClass,
byte[] bytes,
short[] shorts,
int[] ints,
@@ -224,7 +229,8 @@ public class AutoAnnotationTest {
boolean[] booleans,
String[] strings,
RetentionPolicy[] enums,
- StringValues[] annotations) {
+ StringValues[] annotations,
+ Class<? extends CharSequence>... classes) {
return new AutoAnnotation_AutoAnnotationTest_newEverything(
aByte,
aShort,
@@ -237,6 +243,7 @@ public class AutoAnnotationTest {
aString,
anEnum,
anAnnotation,
+ aClass,
bytes,
shorts,
ints,
@@ -247,7 +254,8 @@ public class AutoAnnotationTest {
booleans,
strings,
enums,
- annotations);
+ annotations,
+ classes);
}
@AutoAnnotation
@@ -263,6 +271,7 @@ public class AutoAnnotationTest {
String aString,
RetentionPolicy anEnum,
StringValues anAnnotation,
+ Class<? extends CharSequence> aClass,
Collection<Byte> bytes,
List<Short> shorts,
ArrayList<Integer> ints,
@@ -273,7 +282,8 @@ public class AutoAnnotationTest {
ImmutableCollection<Boolean> booleans,
ImmutableList<String> strings,
ImmutableSet<RetentionPolicy> enums,
- Set<StringValues> annotations) {
+ Set<StringValues> annotations,
+ List<Class<? extends CharSequence>> classes) {
return new AutoAnnotation_AutoAnnotationTest_newEverythingCollections(
aByte,
aShort,
@@ -286,6 +296,7 @@ public class AutoAnnotationTest {
aString,
anEnum,
anAnnotation,
+ aClass,
bytes,
shorts,
ints,
@@ -296,7 +307,8 @@ public class AutoAnnotationTest {
booleans,
strings,
enums,
- annotations);
+ annotations,
+ classes);
}
@Everything(
@@ -311,6 +323,7 @@ public class AutoAnnotationTest {
aString = "maybe\nmaybe not\n",
anEnum = RetentionPolicy.RUNTIME,
anAnnotation = @StringValues("whatever"),
+ aClass = String.class,
bytes = {5, 6},
shorts = {},
ints = {7},
@@ -321,7 +334,8 @@ public class AutoAnnotationTest {
booleans = {false, true, false},
strings = {"ver", "vers", "vert", "verre", "vair"},
enums = {RetentionPolicy.CLASS, RetentionPolicy.RUNTIME},
- annotations = {@StringValues({}), @StringValues({"foo", "bar"})})
+ annotations = {@StringValues({}), @StringValues({"foo", "bar"})},
+ classes = {String.class, StringBuilder.class})
private static class AnnotatedWithEverything {}
// Get an instance of @Everything via reflection on the class AnnotatedWithEverything,
@@ -342,6 +356,7 @@ public class AutoAnnotationTest {
"maybe\nmaybe not\n",
RetentionPolicy.RUNTIME,
newStringValues(new String[] {"whatever"}),
+ String.class,
new byte[] {5, 6},
new short[] {},
new int[] {7},
@@ -354,7 +369,9 @@ public class AutoAnnotationTest {
new RetentionPolicy[] {RetentionPolicy.CLASS, RetentionPolicy.RUNTIME},
new StringValues[] {
newStringValues(new String[] {}), newStringValues(new String[] {"foo", "bar"}),
- });
+ },
+ String.class,
+ StringBuilder.class);
private static final Everything EVERYTHING_FROM_AUTO_COLLECTIONS =
newEverythingCollections(
(byte) 1,
@@ -368,6 +385,7 @@ public class AutoAnnotationTest {
"maybe\nmaybe not\n",
RetentionPolicy.RUNTIME,
newStringValues(new String[] {"whatever"}),
+ String.class,
Arrays.asList((byte) 5, (byte) 6),
Collections.<Short>emptyList(),
new ArrayList<Integer>(Collections.singleton(7)),
@@ -380,7 +398,10 @@ public class AutoAnnotationTest {
ImmutableList.of("ver", "vers", "vert", "verre", "vair"),
ImmutableSet.of(RetentionPolicy.CLASS, RetentionPolicy.RUNTIME),
ImmutableSet.of(
- newStringValues(new String[] {}), newStringValues(new String[] {"foo", "bar"})));
+ newStringValues(new String[] {}), newStringValues(new String[] {"foo", "bar"})),
+ ImmutableList.of(String.class.asSubclass(CharSequence.class), StringBuilder.class));
+ // .asSubclass because of pre-Java8, where otherwise we get a compilation error because
+ // the inferred type is <Class<? extends CharSequence & Serializable>>.
private static final Everything EVERYTHING_ELSE_FROM_AUTO =
newEverything(
(byte) 0,
@@ -394,6 +415,7 @@ public class AutoAnnotationTest {
"",
RetentionPolicy.SOURCE,
newStringValues(new String[] {""}),
+ String.class,
new byte[0],
new short[0],
new int[0],
@@ -418,6 +440,7 @@ public class AutoAnnotationTest {
"",
RetentionPolicy.SOURCE,
newStringValues(new String[] {""}),
+ String.class,
ImmutableList.<Byte>of(),
Collections.<Short>emptyList(),
new ArrayList<Integer>(),
@@ -428,7 +451,8 @@ public class AutoAnnotationTest {
ImmutableSet.<Boolean>of(),
ImmutableList.<String>of(),
ImmutableSet.<RetentionPolicy>of(),
- Collections.<StringValues>emptySet());
+ Collections.<StringValues>emptySet(),
+ Collections.<Class<? extends CharSequence>>emptyList());
@Test
public void testEqualsAndHashCode() {
@@ -515,6 +539,7 @@ public class AutoAnnotationTest {
+ "aByte=1, aShort=2, anInt=3, aLong=-4, aFloat=NaN, aDouble=NaN, aChar='#', "
+ "aBoolean=true, aString=\"maybe\\nmaybe not\\n\", anEnum=RUNTIME, "
+ "anAnnotation=@com.google.auto.value.annotations.StringValues([\"whatever\"]), "
+ + "aClass=class java.lang.String, "
+ "bytes=[5, 6], shorts=[], ints=[7], longs=[8, 9], floats=[10.0, 11.0], "
+ "doubles=[-Infinity, -12.0, Infinity], "
+ "chars=['?', '!', '\\n'], "
@@ -524,7 +549,8 @@ public class AutoAnnotationTest {
+ "annotations=["
+ "@com.google.auto.value.annotations.StringValues([]), "
+ "@com.google.auto.value.annotations.StringValues([\"foo\", \"bar\"])"
- + "]"
+ + "], "
+ + "classes=[class java.lang.String, class java.lang.StringBuilder]"
+ ")";
assertThat(EVERYTHING_FROM_AUTO.toString()).isEqualTo(expected);
assertThat(EVERYTHING_FROM_AUTO_COLLECTIONS.toString()).isEqualTo(expected);
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderKotlinTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderKotlinTest.java
index 1dc346c9..3e046220 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderKotlinTest.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderKotlinTest.java
@@ -16,7 +16,10 @@
package com.google.auto.value;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -29,6 +32,10 @@ public final class AutoBuilderKotlinTest {
return new AutoBuilder_AutoBuilderKotlinTest_KotlinDataBuilder();
}
+ static KotlinDataBuilder builder(KotlinData kotlinData) {
+ return new AutoBuilder_AutoBuilderKotlinTest_KotlinDataBuilder(kotlinData);
+ }
+
abstract KotlinDataBuilder setInt(int x);
abstract KotlinDataBuilder setString(String x);
@@ -41,6 +48,12 @@ public final class AutoBuilderKotlinTest {
KotlinData x = KotlinDataBuilder.builder().setInt(23).setString("skidoo").build();
assertThat(x.getInt()).isEqualTo(23);
assertThat(x.getString()).isEqualTo("skidoo");
+
+ KotlinData y = KotlinDataBuilder.builder(x).setString("chromosomes").build();
+ assertThat(y.getInt()).isEqualTo(23);
+ assertThat(y.getString()).isEqualTo("chromosomes");
+
+ assertThrows(IllegalStateException.class, () -> KotlinDataBuilder.builder().build());
}
@AutoBuilder(ofClass = KotlinDataWithNullable.class)
@@ -76,24 +89,195 @@ public final class AutoBuilderKotlinTest {
abstract KotlinDataWithDefaultsBuilder setAnInt(int x);
+ abstract int getAnInt();
+
+ abstract ImmutableList.Builder<String> anImmutableListBuilder();
+
+ abstract KotlinDataWithDefaultsBuilder setNotDefaulted(long x);
+
+ abstract long getNotDefaulted();
+
abstract KotlinDataWithDefaultsBuilder setAString(String x);
+ abstract String getAString();
+
abstract KotlinDataWithDefaults build();
}
@Test
- public void kotlinWithDefaults() {
- // AutoBuilder doesn't currently try to give the builder the same defaults as the Kotlin class,
- // but we do at least check that the presence of defaults doesn't throw AutoBuilder off.
- // When a constructor has default parameters, the Kotlin compiler generates an extra constructor
- // with two extra parameters: an int bitmask saying which parameters were defaulted, and a
- // DefaultConstructorMarker parameter to avoid clashing with another constructor that might have
- // an extra int parameter for some other reason. If AutoBuilder found this constructor it might
- // be confused, but fortunately the constructor is marked synthetic, and javax.lang.model
- // doesn't show synthetic elements.
- KotlinDataWithDefaults x =
- KotlinDataWithDefaultsBuilder.builder().setAString("answer").setAnInt(42).build();
+ public void kotlinWithDefaults_explicit() {
+ KotlinDataWithDefaultsBuilder builder =
+ KotlinDataWithDefaultsBuilder.builder()
+ .setAString("answer")
+ .setNotDefaulted(100L)
+ .setAnInt(42);
+ builder.anImmutableListBuilder().add("bar");
+ KotlinDataWithDefaults x = builder.build();
assertThat(x.getAString()).isEqualTo("answer");
+ assertThat(x.getAnImmutableList()).containsExactly("bar");
+ assertThat(x.getNotDefaulted()).isEqualTo(100L);
assertThat(x.getAnInt()).isEqualTo(42);
}
+
+ @Test
+ public void kotlinWithDefaults_defaulted() {
+ KotlinDataWithDefaults x =
+ KotlinDataWithDefaultsBuilder.builder().setNotDefaulted(100L).build();
+ assertThat(x.getAnInt()).isEqualTo(23);
+ assertThat(x.getAnImmutableList()).containsExactly("foo");
+ assertThat(x.getAString()).isEqualTo("skidoo");
+ assertThat(x.getNotDefaulted()).isEqualTo(100L);
+ KotlinDataWithDefaults copy =
+ new AutoBuilder_AutoBuilderKotlinTest_KotlinDataWithDefaultsBuilder(x).build();
+ assertThat(copy).isEqualTo(x);
+ assertThat(copy).isNotSameInstanceAs(x);
+ KotlinDataWithDefaults modified =
+ new AutoBuilder_AutoBuilderKotlinTest_KotlinDataWithDefaultsBuilder(x).setAnInt(17).build();
+ assertThat(modified.getAnInt()).isEqualTo(17);
+ }
+
+ @Test
+ public void kotlinWithDefaults_getter() {
+ KotlinDataWithDefaultsBuilder builder = KotlinDataWithDefaultsBuilder.builder();
+ assertThrows(IllegalStateException.class, builder::getAnInt);
+ builder.setAnInt(42);
+ assertThat(builder.getAnInt()).isEqualTo(42);
+ assertThrows(IllegalStateException.class, builder::getNotDefaulted);
+ builder.setNotDefaulted(100L);
+ assertThat(builder.getNotDefaulted()).isEqualTo(100L);
+ assertThrows(IllegalStateException.class, builder::getAString);
+ builder.setAString("answer");
+ assertThat(builder.getAString()).isEqualTo("answer");
+ }
+
+ @AutoBuilder(ofClass = KotlinDataEightDefaults.class)
+ interface KotlinDataEightDefaultsBuilder {
+ static KotlinDataEightDefaultsBuilder builder() {
+ return new AutoBuilder_AutoBuilderKotlinTest_KotlinDataEightDefaultsBuilder();
+ }
+
+ KotlinDataEightDefaultsBuilder a1(int x);
+
+ KotlinDataEightDefaultsBuilder a2(int x);
+
+ KotlinDataEightDefaultsBuilder a3(int x);
+
+ KotlinDataEightDefaultsBuilder a4(int x);
+
+ KotlinDataEightDefaultsBuilder a5(int x);
+
+ KotlinDataEightDefaultsBuilder a6(int x);
+
+ KotlinDataEightDefaultsBuilder a7(int x);
+
+ KotlinDataEightDefaultsBuilder a8(int x);
+
+ KotlinDataEightDefaults build();
+ }
+
+ // We test a class that has exactly 8 default parameters because we will use a byte for the
+ // bitmask in that case and it is possible that we might have an issue with sign extension when
+ // bit 7 of that bitmask is set.
+ @Test
+ public void kotlinEightDefaults() {
+ KotlinDataEightDefaults allDefaulted = KotlinDataEightDefaultsBuilder.builder().build();
+ assertThat(allDefaulted.getA1()).isEqualTo(1);
+ assertThat(allDefaulted.getA8()).isEqualTo(8);
+ KotlinDataEightDefaults noneDefaulted =
+ KotlinDataEightDefaultsBuilder.builder()
+ .a1(-1)
+ .a2(-2)
+ .a3(-3)
+ .a4(-4)
+ .a5(-5)
+ .a6(-6)
+ .a7(-7)
+ .a8(-8)
+ .build();
+ assertThat(noneDefaulted.getA1()).isEqualTo(-1);
+ assertThat(noneDefaulted.getA8()).isEqualTo(-8);
+ }
+
+ @AutoBuilder(ofClass = KotlinDataSomeDefaults.class)
+ interface KotlinDataSomeDefaultsBuilder {
+ static KotlinDataSomeDefaultsBuilder builder() {
+ return new AutoBuilder_AutoBuilderKotlinTest_KotlinDataSomeDefaultsBuilder();
+ }
+
+ static KotlinDataSomeDefaultsBuilder fromInstance(KotlinDataSomeDefaults instance) {
+ return new AutoBuilder_AutoBuilderKotlinTest_KotlinDataSomeDefaultsBuilder(instance);
+ }
+
+ KotlinDataSomeDefaultsBuilder requiredInt(int x);
+
+ KotlinDataSomeDefaultsBuilder requiredString(String x);
+
+ KotlinDataSomeDefaultsBuilder optionalInt(int x);
+
+ KotlinDataSomeDefaultsBuilder optionalString(String x);
+
+ KotlinDataSomeDefaults build();
+ }
+
+ @Test
+ public void kotlinSomeDefaults_someDefaulted() {
+ KotlinDataSomeDefaults someDefaulted =
+ KotlinDataSomeDefaultsBuilder.builder().requiredInt(12).requiredString("Monkeys").build();
+ assertThat(someDefaulted.getOptionalInt()).isEqualTo(23);
+ assertThat(someDefaulted.getOptionalString()).isEqualTo("Skidoo");
+ assertThat(KotlinDataSomeDefaultsBuilder.fromInstance(someDefaulted).build())
+ .isEqualTo(someDefaulted);
+ }
+
+ @Test
+ public void kotlinSomeDefaults_noneDefaulted() {
+ KotlinDataSomeDefaults noneDefaulted =
+ KotlinDataSomeDefaultsBuilder.builder()
+ .requiredInt(12)
+ .requiredString("Monkeys")
+ .optionalInt(3)
+ .optionalString("Oranges")
+ .build();
+ KotlinDataSomeDefaults copy = KotlinDataSomeDefaultsBuilder.fromInstance(noneDefaulted).build();
+ assertThat(copy).isEqualTo(noneDefaulted);
+ }
+
+ @Test
+ public void kotlinSomeDefaults_missingRequired() {
+ IllegalStateException e =
+ assertThrows(
+ IllegalStateException.class, () -> KotlinDataSomeDefaultsBuilder.builder().build());
+ assertThat(e).hasMessageThat().contains("requiredInt");
+ assertThat(e).hasMessageThat().contains("requiredString");
+ }
+
+ @AutoBuilder(ofClass = KotlinDataWithList.class)
+ interface KotlinDataWithListBuilder {
+ static KotlinDataWithListBuilder builder() {
+ return new AutoBuilder_AutoBuilderKotlinTest_KotlinDataWithListBuilder();
+ }
+
+ static KotlinDataWithListBuilder builder(KotlinDataWithList kotlinData) {
+ return new AutoBuilder_AutoBuilderKotlinTest_KotlinDataWithListBuilder(kotlinData);
+ }
+
+ KotlinDataWithListBuilder list(List<? extends CharSequence> list);
+
+ KotlinDataWithListBuilder number(int number);
+
+ KotlinDataWithList build();
+ }
+
+ // The `getList()` method returns `List<CharSequence>` as seen from Java, but the `list` parameter
+ // to the constructor has type `List<? extends CharSequence>`.
+ @Test
+ public void kotlinWildcards() {
+ List<String> strings = ImmutableList.of("foo");
+ KotlinDataWithList x = KotlinDataWithListBuilder.builder().list(strings).number(17).build();
+ assertThat(x.getList()).isEqualTo(strings);
+ assertThat(x.getNumber()).isEqualTo(17);
+ KotlinDataWithList y = KotlinDataWithListBuilder.builder(x).number(23).build();
+ assertThat(y.getList()).isEqualTo(strings);
+ assertThat(y.getNumber()).isEqualTo(23);
+ }
}
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 dba81992..4581f6e1 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
@@ -18,13 +18,19 @@ package com.google.auto.value;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
+import static java.lang.annotation.ElementType.TYPE_USE;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.math.BigInteger;
import java.time.LocalTime;
import java.util.AbstractSet;
@@ -35,6 +41,7 @@ import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import javax.lang.model.SourceVersion;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -130,6 +137,7 @@ public final class AutoBuilderTest {
TRUTHY
}
+ @Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String value();
@@ -207,6 +215,68 @@ public final class AutoBuilderTest {
assertThat(annotation3).isEqualTo(annotation4);
}
+ @AutoBuilder(ofClass = MyAnnotation.class)
+ public interface MyAnnotationSimpleBuilder {
+ MyAnnotationSimpleBuilder value(String x);
+
+ MyAnnotationSimpleBuilder id(int x);
+
+ MyAnnotationSimpleBuilder truthiness(Truthiness x);
+
+ MyAnnotation build();
+ }
+
+ public static MyAnnotationSimpleBuilder myAnnotationSimpleBuilder() {
+ return new AutoBuilder_AutoBuilderTest_MyAnnotationSimpleBuilder();
+ }
+
+ @Test
+ public void buildWithoutAutoAnnotation() {
+ // We don't set a value for `id` or `truthiness`, so AutoBuilder should use the default ones in
+ // the annotation.
+ MyAnnotation annotation1 = myAnnotationSimpleBuilder().value("foo").build();
+ assertThat(annotation1.value()).isEqualTo("foo");
+ assertThat(annotation1.id()).isEqualTo(MyAnnotation.DEFAULT_ID);
+ assertThat(annotation1.truthiness()).isEqualTo(MyAnnotation.DEFAULT_TRUTHINESS);
+
+ // Now we set `truthiness` but still not `id`.
+ MyAnnotation annotation2 =
+ myAnnotationSimpleBuilder().value("bar").truthiness(Truthiness.TRUTHY).build();
+ assertThat(annotation2.value()).isEqualTo("bar");
+ assertThat(annotation2.id()).isEqualTo(MyAnnotation.DEFAULT_ID);
+ assertThat(annotation2.truthiness()).isEqualTo(Truthiness.TRUTHY);
+
+ // All three elements set explicitly.
+ MyAnnotation annotation3 =
+ myAnnotationSimpleBuilder().value("foo").id(23).truthiness(Truthiness.TRUTHY).build();
+ assertThat(annotation3.value()).isEqualTo("foo");
+ assertThat(annotation3.id()).isEqualTo(23);
+ assertThat(annotation3.truthiness()).isEqualTo(Truthiness.TRUTHY);
+ }
+
+ // This builder doesn't have a setter for the `truthiness` element, so the annotations it builds
+ // should always get the default value.
+ @AutoBuilder(ofClass = MyAnnotation.class)
+ public interface MyAnnotationSimplerBuilder {
+ MyAnnotationSimplerBuilder value(String x);
+
+ MyAnnotationSimplerBuilder id(int x);
+
+ MyAnnotation build();
+ }
+
+ public static MyAnnotationSimplerBuilder myAnnotationSimplerBuilder() {
+ return new AutoBuilder_AutoBuilderTest_MyAnnotationSimplerBuilder();
+ }
+
+ @Test
+ public void buildWithoutAutoAnnotation_noSetterForElement() {
+ MyAnnotation annotation = myAnnotationSimplerBuilder().value("foo").id(23).build();
+ assertThat(annotation.value()).isEqualTo("foo");
+ assertThat(annotation.id()).isEqualTo(23);
+ assertThat(annotation.truthiness()).isEqualTo(MyAnnotation.DEFAULT_TRUTHINESS);
+ }
+
static class Overload {
final int anInt;
final String aString;
@@ -427,7 +497,7 @@ public final class AutoBuilderTest {
assertThat(builder3.build()).isEqualTo(expected);
ListContainer.Builder builder4 = ListContainer.builder();
- builder4.listBuilder();
+ ImmutableList.Builder<String> unused = builder4.listBuilder();
try {
builder4.setList(ImmutableList.of("one", "two", "three"));
fail();
@@ -437,7 +507,7 @@ public final class AutoBuilderTest {
}
static <T> String concatList(ImmutableList<T> list) {
- // We're avoiding streams for now so we compile this in Java 7 mode in CompileWithEclipseTest.
+ // We're avoiding streams for now since we compile this in Java 7 mode in CompileWithEclipseTest
StringBuilder sb = new StringBuilder();
for (T element : list) {
sb.append(element);
@@ -608,4 +678,113 @@ public final class AutoBuilderTest {
assertThat(builder.getFirst()).isEqualTo(1.0);
assertThat(builder.build()).containsExactly(1.0, 2).inOrder();
}
+
+ static class NumberHolder<T extends Number> {
+ private final T number;
+
+ NumberHolder(T number) {
+ this.number = number;
+ }
+
+ T getNumber() {
+ return number;
+ }
+ }
+
+ static <T extends Number> NumberHolder<T> buildNumberHolder(T number) {
+ return new NumberHolder<>(number);
+ }
+
+ @AutoBuilder(callMethod = "buildNumberHolder")
+ interface NumberHolderBuilder<T extends Number> {
+ NumberHolderBuilder<T> setNumber(T number);
+
+ NumberHolder<T> build();
+ }
+
+ static <T extends Number> NumberHolderBuilder<T> numberHolderBuilder() {
+ return new AutoBuilder_AutoBuilderTest_NumberHolderBuilder<>();
+ }
+
+ static <T extends Number> NumberHolderBuilder<T> numberHolderBuilder(
+ NumberHolder<T> numberHolder) {
+ return new AutoBuilder_AutoBuilderTest_NumberHolderBuilder<>(numberHolder);
+ }
+
+ @Test
+ public void builderFromInstance() {
+ NumberHolder<Integer> instance1 =
+ AutoBuilderTest.<Integer>numberHolderBuilder().setNumber(23).build();
+ assertThat(instance1.getNumber()).isEqualTo(23);
+ NumberHolder<Integer> instance2 = numberHolderBuilder(instance1).build();
+ assertThat(instance2.getNumber()).isEqualTo(23);
+ NumberHolder<Integer> instance3 = numberHolderBuilder(instance2).setNumber(17).build();
+ assertThat(instance3.getNumber()).isEqualTo(17);
+ }
+
+ @AutoBuilder(callMethod = "of", ofClass = Simple.class)
+ @MyAnnotation("thing")
+ interface AnnotatedSimpleStaticBuilder1 {
+ AnnotatedSimpleStaticBuilder1 anInt(int x);
+
+ AnnotatedSimpleStaticBuilder1 aString(String x);
+
+ Simple build();
+ }
+
+ @Test
+ public void builderAnnotationsNotCopiedByDefault() {
+ assertThat(AutoBuilder_AutoBuilderTest_AnnotatedSimpleStaticBuilder1.class.getAnnotations())
+ .asList()
+ .isEmpty();
+ }
+
+ @AutoBuilder(callMethod = "of", ofClass = Simple.class)
+ @AutoValue.CopyAnnotations
+ @MyAnnotation("thing")
+ interface AnnotatedSimpleStaticBuilder2 {
+ AnnotatedSimpleStaticBuilder2 anInt(int x);
+
+ AnnotatedSimpleStaticBuilder2 aString(String x);
+
+ Simple build();
+ }
+
+ @Test
+ public void builderAnnotationsCopiedIfRequested() {
+ assertThat(AutoBuilder_AutoBuilderTest_AnnotatedSimpleStaticBuilder2.class.getAnnotations())
+ .asList()
+ .contains(myAnnotationBuilder().value("thing").build());
+ }
+
+ @Target(TYPE_USE)
+ public @interface Nullable {}
+
+ public static <T extends @Nullable Object, U> T frob(T arg, U notNull) {
+ return arg;
+ }
+
+ @AutoBuilder(callMethod = "frob")
+ interface FrobCaller<T extends @Nullable Object, U> {
+ FrobCaller<T, U> arg(T arg);
+
+ FrobCaller<T, U> notNull(U notNull);
+
+ T call();
+
+ static <T extends @Nullable Object, U> FrobCaller<T, U> caller() {
+ return new AutoBuilder_AutoBuilderTest_FrobCaller<>();
+ }
+ }
+
+ @Test
+ public void builderTypeVariableWithNullableBound() {
+ // The Annotation Processing API doesn't see the @Nullable Object bound on Java 8.
+ assumeTrue(SourceVersion.latest().ordinal() > SourceVersion.RELEASE_8.ordinal());
+ assertThat(FrobCaller.<@Nullable String, String>caller().arg(null).notNull("foo").call())
+ .isNull();
+ assertThrows(
+ NullPointerException.class,
+ () -> FrobCaller.<@Nullable String, String>caller().arg(null).notNull(null).call());
+ }
}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java
index 3f4e9bf5..dfd825ca 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java
@@ -30,6 +30,7 @@ import com.google.common.testing.EqualsTester;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.Compiler;
import com.google.testing.compile.JavaFileObjects;
+import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -39,15 +40,16 @@ import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.TypeVariable;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
+import java.util.OptionalDouble;
import java.util.Set;
import java.util.function.Predicate;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
-import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
@@ -107,8 +109,13 @@ public class AutoValueJava8Test {
private static final String JAVAC_HAS_BUG_ERROR = "javac has the type-annotation bug";
@SupportedAnnotationTypes("*")
- @SupportedSourceVersion(SourceVersion.RELEASE_8)
private static class BugTestProcessor extends AbstractProcessor {
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
@@ -411,6 +418,8 @@ public class AutoValueJava8Test {
Builder nullable(@Nullable String s);
+ Optional<String> nullable();
+
NullablePropertyWithBuilder build();
}
}
@@ -437,6 +446,9 @@ public class AutoValueJava8Test {
assertThrows(
IllegalStateException.class, () -> NullablePropertyWithBuilder.builder().build());
assertThat(e).hasMessageThat().contains("notNullable");
+
+ NullablePropertyWithBuilder.Builder builder = NullablePropertyWithBuilder.builder();
+ assertThat(builder.nullable()).isEmpty();
}
@AutoValue
@@ -496,6 +508,8 @@ public class AutoValueJava8Test {
public interface Builder {
Builder optional(@Nullable String s);
+ Optional<String> optional();
+
NullableOptionalPropertyWithNullableBuilder build();
}
}
@@ -513,6 +527,10 @@ public class AutoValueJava8Test {
NullableOptionalPropertyWithNullableBuilder instance3 =
NullableOptionalPropertyWithNullableBuilder.builder().optional("haruspex").build();
assertThat(instance3.optional()).hasValue("haruspex");
+
+ NullableOptionalPropertyWithNullableBuilder.Builder builder =
+ NullableOptionalPropertyWithNullableBuilder.builder();
+ assertThat(builder.optional()).isNull();
}
@AutoValue
@@ -851,4 +869,182 @@ public class AutoValueJava8Test {
OptionalExtends t = OptionalExtends.builder().setPredicate(predicate).build();
assertThat(t.predicate()).hasValue(predicate);
}
+
+ @AutoValue
+ public abstract static class Foo {
+ public abstract Bar bar();
+
+ public abstract double baz();
+
+ public static Foo.Builder builder() {
+ return new AutoValue_AutoValueJava8Test_Foo.Builder();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ // https://github.com/google/auto/blob/main/value/userguide/builders-howto.md#normalize
+ abstract Optional<Bar> bar();
+
+ public abstract Builder bar(Bar bar);
+
+ // https://github.com/google/auto/blob/main/value/userguide/builders-howto.md#nested_builders
+ public abstract Bar.Builder barBuilder();
+
+ abstract OptionalDouble baz();
+
+ public abstract Builder baz(double baz);
+
+ abstract Foo autoBuild();
+
+ public Foo build() {
+ if (!bar().isPresent()) {
+ bar(Bar.builder().build());
+ }
+ if (!baz().isPresent()) {
+ baz(0.0);
+ }
+ return autoBuild();
+ }
+ }
+ }
+
+ @AutoValue
+ public abstract static class Bar {
+ public abstract Bar.Builder toBuilder();
+
+ public static Bar.Builder builder() {
+ return new AutoValue_AutoValueJava8Test_Bar.Builder();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Bar build();
+ }
+ }
+
+ @Test
+ public void nestedOptionalGetter() {
+ Foo foo = Foo.builder().build();
+ assertThat(foo.bar()).isNotNull();
+ assertThat(foo.baz()).isEqualTo(0.0);
+ }
+
+ // Test that we can build a property of type List<? extends Foo> using a property builder whose
+ // build() method returns List<Foo>. The main motivation for this is Kotlin, where you can
+ // easily run into this situation with "in" types.
+ // This is a "Java 8" test because the generated code uses List.of (which is actually Java 9).
+ // If we really are on Java 8 then the generated code will use `new ListBuilder<T>().build()`
+ // instead.
+ @AutoValue
+ public abstract static class PropertyBuilderWildcard<T> {
+ public abstract List<? extends T> list();
+
+ public static <T> PropertyBuilderWildcard.Builder<T> builder() {
+ return new AutoValue_AutoValueJava8Test_PropertyBuilderWildcard.Builder<>();
+ }
+
+ @AutoValue.Builder
+ public interface Builder<T> {
+ ListBuilder<T> listBuilder();
+
+ PropertyBuilderWildcard<T> build();
+ }
+
+ public static class ListBuilder<T> {
+ private final List<T> list = new ArrayList<>();
+
+ public void add(T value) {
+ list.add(value);
+ }
+
+ public List<T> build() {
+ return list;
+ }
+ }
+ }
+
+ @Test
+ public void propertyBuilderWildcard() {
+ PropertyBuilderWildcard.Builder<CharSequence> builder = PropertyBuilderWildcard.builder();
+ builder.listBuilder().add("foo");
+ assertThat(builder.build().list()).containsExactly("foo");
+ }
+
+ @AutoValue
+ public abstract static class NullableBound<T extends @Nullable Object> {
+ public abstract T maybeNullable();
+
+ public static <T extends @Nullable Object> NullableBound<T> create(T maybeNullable) {
+ return new AutoValue_AutoValueJava8Test_NullableBound<>(maybeNullable);
+ }
+ }
+
+ @Test
+ public void propertyCanBeNullIfNullableBound() {
+ assumeTrue(javacHandlesTypeAnnotationsCorrectly);
+ // The generated class doesn't know what the actual type argument is, so it can't know whether
+ // it is @Nullable. Because of the @Nullable bound, it omits an explicit null check, under the
+ // assumption that some static-checking framework is validating type uses.
+ NullableBound<@Nullable String> x = NullableBound.create(null);
+ assertThat(x.maybeNullable()).isNull();
+ }
+
+ @AutoValue
+ public abstract static class NullableIntersectionBound<
+ T extends @Nullable Object & @Nullable Serializable> {
+ public abstract T maybeNullable();
+
+ public static <T extends @Nullable Object & @Nullable Serializable>
+ NullableIntersectionBound<T> create(T maybeNullable) {
+ return new AutoValue_AutoValueJava8Test_NullableIntersectionBound<>(maybeNullable);
+ }
+ }
+
+ @Test
+ public void propertyCanBeNullIfNullableIntersectionBound() {
+ assumeTrue(javacHandlesTypeAnnotationsCorrectly);
+ // The generated class doesn't know what the actual type argument is, so it can't know whether
+ // it is @Nullable. Because of the @Nullable bound, it omits an explicit null check, under the
+ // assumption that some static-checking framework is validating type uses.
+ NullableIntersectionBound<@Nullable String> x = NullableIntersectionBound.create(null);
+ assertThat(x.maybeNullable()).isNull();
+ }
+
+ @AutoValue
+ public abstract static class PartlyNullableIntersectionBound<
+ T extends @Nullable Object & Serializable> {
+ public abstract T notNullable();
+
+ public static <T extends @Nullable Object & Serializable>
+ PartlyNullableIntersectionBound<T> create(T notNullable) {
+ return new AutoValue_AutoValueJava8Test_PartlyNullableIntersectionBound<>(notNullable);
+ }
+ }
+
+ @Test
+ public void propertyCannotBeNullWithPartlyNullableIntersectionBound() {
+ assumeTrue(javacHandlesTypeAnnotationsCorrectly);
+ assertThrows(NullPointerException.class, () -> PartlyNullableIntersectionBound.create(null));
+ }
+
+ @AutoValue
+ public abstract static class NullableVariableBound<T extends @Nullable Object, U extends T> {
+ public abstract T nullOne();
+
+ public abstract U nullTwo();
+
+ public static <T extends @Nullable Object, U extends T> NullableVariableBound<T, U> create(
+ T nullOne, U nullTwo) {
+ return new AutoValue_AutoValueJava8Test_NullableVariableBound<>(nullOne, nullTwo);
+ }
+ }
+
+ @Test
+ public void nullableVariableBound() {
+ assumeTrue(javacHandlesTypeAnnotationsCorrectly);
+ NullableVariableBound<@Nullable CharSequence, @Nullable String> x =
+ NullableVariableBound.create(null, null);
+ assertThat(x.nullOne()).isNull();
+ assertThat(x.nullTwo()).isNull();
+ }
}
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 fd87b3e5..d13c96ae 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
@@ -1414,6 +1414,29 @@ public class AutoValueTest {
abstract int foo();
}
+ // We use Double.doubleToLongBits in equals and hashCode, so that better be qualified correctly if
+ // someone has unwisely declared their own Float or Double class.
+ @SuppressWarnings("JavaLangClash")
+ @AutoValue
+ public abstract static class RedeclareFloatAndDouble {
+ public abstract float aFloat();
+ public abstract double aDouble();
+
+ public static RedeclareFloatAndDouble of(float aFloat, double aDouble) {
+ return new AutoValue_AutoValueTest_RedeclareFloatAndDouble(aFloat, aDouble);
+ }
+
+ static class Float {}
+ static class Double {}
+ }
+
+ @SuppressWarnings("TruthSelfEquals")
+ @Test
+ public void testRedeclareFloatAndDouble() {
+ RedeclareFloatAndDouble iEqualMyself = RedeclareFloatAndDouble.of(Float.NaN, Double.NaN);
+ assertThat(iEqualMyself).isEqualTo(iEqualMyself);
+ }
+
@AutoValue
abstract static class AbstractChild extends AbstractParent {
// The main point of this test is to ensure that we don't try to copy this @Override into the
@@ -2103,7 +2126,7 @@ public class AutoValueTest {
@Nullable
public abstract int[] getInts();
- public abstract String getOAuth();
+ public abstract ImmutableList<String> getOAuths();
public abstract int getNoGetter();
@@ -2121,7 +2144,9 @@ public class AutoValueTest {
public abstract Builder<T> setNoGetter(int x);
- public abstract Builder<T> setOAuth(String x);
+ public abstract Builder<T> setOAuths(List<String> x);
+
+ public abstract ImmutableList.Builder<String> oAuthsBuilder();
abstract ImmutableList<T> getList();
@@ -2156,14 +2181,22 @@ public class AutoValueTest {
assertThat(builder.getList()).isSameInstanceAs(names);
builder.setT(name);
assertThat(builder.getInts()).isNull();
- builder.setOAuth("OAuth");
+ builder.setOAuths(ImmutableList.of("OAuth"));
BuilderWithPrefixedGetters<String> instance = builder.setNoGetter(noGetter).build();
assertThat(instance.getList()).isSameInstanceAs(names);
assertThat(instance.getT()).isEqualTo(name);
assertThat(instance.getInts()).isNull();
assertThat(instance.getNoGetter()).isEqualTo(noGetter);
- assertThat(instance.getOAuth()).isEqualTo("OAuth");
+ assertThat(instance.getOAuths()).containsExactly("OAuth");
+
+ builder =
+ BuilderWithPrefixedGetters.<String>builder()
+ .setList(names)
+ .setT(name)
+ .setNoGetter(noGetter);
+ builder.oAuthsBuilder().add("foo", "bar");
+ assertThat(builder.build().getOAuths()).containsExactly("foo", "bar").inOrder();
}
@AutoValue
@@ -3667,4 +3700,234 @@ public class AutoValueTest {
assertThat(stepped.two()).isEqualTo(2);
assertThat(stepped.three()).isEqualTo(3.0);
}
+
+ // The code for tracking unset properties with bitmasks is fairly tricky. When there are many
+ // properties, we use as many ints as needed to track which properties are still unset, with
+ // 32 properties being tracked per int. So here we test @AutoValue classes with 31, 32, and 33
+ // required properties, to catch problems at these edge values that we wouldn't see in our smaller
+ // test classes.
+ abstract static class Giant {
+ abstract int x1();
+ abstract int x2();
+ abstract int x3();
+ abstract int x4();
+ abstract int x5();
+ abstract int x6();
+ abstract int x7();
+ abstract int x8();
+ abstract int x9();
+ abstract int x10();
+ abstract int x11();
+ abstract int x12();
+ abstract int x13();
+ abstract int x14();
+ abstract int x15();
+ abstract int x16();
+ abstract int x17();
+ abstract int x18();
+ abstract int x19();
+ abstract int x20();
+ abstract int x21();
+ abstract int x22();
+ abstract int x23();
+ abstract int x24();
+ abstract int x25();
+ abstract int x26();
+ abstract int x27();
+ abstract int x28();
+ abstract int x29();
+ abstract int x30();
+ abstract int x31();
+
+ abstract static class Builder {
+ abstract Builder x1(int x);
+ abstract Builder x2(int x);
+ abstract Builder x3(int x);
+ abstract Builder x4(int x);
+ abstract Builder x5(int x);
+ abstract Builder x6(int x);
+ abstract Builder x7(int x);
+ abstract Builder x8(int x);
+ abstract Builder x9(int x);
+ abstract Builder x10(int x);
+ abstract Builder x11(int x);
+ abstract Builder x12(int x);
+ abstract Builder x13(int x);
+ abstract Builder x14(int x);
+ abstract Builder x15(int x);
+ abstract Builder x16(int x);
+ abstract Builder x17(int x);
+ abstract Builder x18(int x);
+ abstract Builder x19(int x);
+ abstract Builder x20(int x);
+ abstract Builder x21(int x);
+ abstract Builder x22(int x);
+ abstract Builder x23(int x);
+ abstract Builder x24(int x);
+ abstract Builder x25(int x);
+ abstract Builder x26(int x);
+ abstract Builder x27(int x);
+ abstract Builder x28(int x);
+ abstract Builder x29(int x);
+ abstract Builder x30(int x);
+ abstract Builder x31(int x);
+
+ Builder setFirst30() {
+ return this.x1(1)
+ .x2(2)
+ .x3(3)
+ .x4(4)
+ .x5(5)
+ .x6(6)
+ .x7(7)
+ .x8(8)
+ .x9(9)
+ .x10(10)
+ .x11(11)
+ .x12(12)
+ .x13(13)
+ .x14(14)
+ .x15(15)
+ .x16(16)
+ .x17(17)
+ .x18(18)
+ .x19(19)
+ .x20(20)
+ .x21(21)
+ .x22(22)
+ .x23(23)
+ .x24(24)
+ .x25(25)
+ .x26(26)
+ .x27(27)
+ .x28(28)
+ .x29(29)
+ .x30(30);
+ }
+ }
+ }
+
+ @AutoValue
+ abstract static class Giant31 extends Giant {
+ static Builder builder() {
+ return new AutoValue_AutoValueTest_Giant31.Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder extends Giant.Builder {
+ abstract Giant31 build();
+ }
+ }
+
+ @AutoValue
+ abstract static class Giant32 extends Giant {
+ abstract int x32();
+
+ static Builder builder() {
+ return new AutoValue_AutoValueTest_Giant32.Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder extends Giant.Builder {
+ abstract Builder x32(int x);
+ abstract Giant32 build();
+ }
+ }
+
+ @AutoValue
+ abstract static class Giant33 extends Giant {
+ abstract int x32();
+ abstract int x33();
+
+ static Builder builder() {
+ return new AutoValue_AutoValueTest_Giant33.Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder extends Giant.Builder {
+ abstract Builder x32(int x);
+ abstract Builder x33(int x);
+ abstract Giant33 build();
+ }
+ }
+
+ @Test
+ public void testGiant31() {
+ Giant31.Builder builder = Giant31.builder();
+ builder.setFirst30();
+ builder.x31(31);
+ Giant31 giant = builder.build();
+ assertThat(giant.x1()).isEqualTo(1);
+ assertThat(giant.x31()).isEqualTo(31);
+
+ builder = Giant31.builder();
+ builder.setFirst30();
+ try {
+ builder.build();
+ fail();
+ } catch (IllegalStateException expected) {
+ if (omitIdentifiers) {
+ assertThat(expected).hasMessageThat().isNull();
+ } else {
+ assertThat(expected).hasMessageThat().contains("x31");
+ assertThat(expected).hasMessageThat().doesNotContain("x30");
+ }
+ }
+ }
+
+ @Test
+ public void testGiant32() {
+ Giant32.Builder builder = Giant32.builder();
+ builder.setFirst30();
+ builder.x31(31);
+ builder.x32(32);
+ Giant32 giant = builder.build();
+ assertThat(giant.x1()).isEqualTo(1);
+ assertThat(giant.x31()).isEqualTo(31);
+
+ builder = Giant32.builder();
+ builder.setFirst30();
+ try {
+ builder.build();
+ fail();
+ } catch (IllegalStateException expected) {
+ if (omitIdentifiers) {
+ assertThat(expected).hasMessageThat().isNull();
+ } else {
+ assertThat(expected).hasMessageThat().contains("x31");
+ assertThat(expected).hasMessageThat().contains("x32");
+ assertThat(expected).hasMessageThat().doesNotContain("x30");
+ }
+ }
+ }
+
+ @Test
+ public void testGiant33() {
+ Giant33.Builder builder = Giant33.builder();
+ builder.setFirst30();
+ builder.x31(31);
+ builder.x32(32);
+ builder.x33(33);
+ Giant33 giant = builder.build();
+ assertThat(giant.x1()).isEqualTo(1);
+ assertThat(giant.x31()).isEqualTo(31);
+ assertThat(giant.x32()).isEqualTo(32);
+ assertThat(giant.x33()).isEqualTo(33);
+
+ builder = Giant33.builder();
+ builder.setFirst30();
+ try {
+ builder.build();
+ fail();
+ } catch (IllegalStateException expected) {
+ if (omitIdentifiers) {
+ assertThat(expected).hasMessageThat().isNull();
+ } else {
+ assertThat(expected).hasMessageThat().contains("x31");
+ assertThat(expected).hasMessageThat().contains("x32");
+ assertThat(expected).hasMessageThat().contains("x33");
+ assertThat(expected).hasMessageThat().doesNotContain("x30");
+ }
+ }
+ }
}
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 5f08a725..a4292907 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
@@ -67,16 +67,26 @@ public class CompileWithEclipseTest {
private static final ImmutableSet<String> IGNORED_TEST_FILES =
ImmutableSet.of(
- "AutoValueNotEclipseTest.java", "CompileWithEclipseTest.java", "GradleTest.java");
+ "AutoValueNotEclipseTest.java",
+ "CompileWithEclipseTest.java",
+ "CustomFieldSerializerTest.java",
+ "GradleIT.java",
+
+ // AutoBuilder sometimes needs to generate a .class file for Kotlin that is used in the
+ // rest of compilation, and Eclipse doesn't seem to handle that well. Presumably not many
+ // Kotlin users use Eclipse since IntelliJ is obviously much more suitable.
+ "AutoBuilderKotlinTest.java");
private static final Predicate<File> JAVA_FILE =
f -> f.getName().endsWith(".java") && !IGNORED_TEST_FILES.contains(f.getName());
- private static final Predicate<File> JAVA8_TEST =
- f ->
- f.getName().equals("AutoValueJava8Test.java")
- || f.getName().equals("AutoOneOfJava8Test.java")
- || f.getName().equals("EmptyExtension.java");
+ private static final ImmutableSet<String> JAVA8_TEST_FILES =
+ ImmutableSet.of(
+ "AutoBuilderTest.java",
+ "AutoOneOfJava8Test.java",
+ "AutoValueJava8Test.java",
+ "EmptyExtension.java");
+ private static final Predicate<File> JAVA8_TEST = f -> JAVA8_TEST_FILES.contains(f.getName());
@Test
public void compileWithEclipseJava7() throws Exception {
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/GradleTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/GradleIT.java
index f4eb5388..1601f3af 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/GradleTest.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/GradleIT.java
@@ -38,7 +38,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
-public class GradleTest {
+public class GradleIT {
@Rule public TemporaryFolder fakeProject = new TemporaryFolder();
private static final String BUILD_GRADLE_TEXT =
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/KotlinData.kt b/value/src/it/functional/src/test/java/com/google/auto/value/KotlinData.kt
index f3318890..c518a4d1 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/KotlinData.kt
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/KotlinData.kt
@@ -15,8 +15,38 @@
*/
package com.google.auto.value
+import com.google.common.collect.ImmutableList
+
data class KotlinData(val int: Int, val string: String)
data class KotlinDataWithNullable(val anInt: Int?, val aString: String?)
-data class KotlinDataWithDefaults(val anInt: Int = 23, val aString: String = "skidoo")
+data class KotlinDataWithDefaults(
+ val anInt: Int = 23,
+ val anImmutableList: ImmutableList<String> = ImmutableList.of("foo"),
+ val notDefaulted: Long,
+ val aString: String = "skidoo"
+)
+
+// Exactly 8 defaulted properties, in case we have a problem with sign-extending byte bitmasks.
+data class KotlinDataEightDefaults(
+ val a1: Int = 1,
+ val a2: Int = 2,
+ val a3: Int = 3,
+ val a4: Int = 4,
+ val a5: Int = 5,
+ val a6: Int = 6,
+ val a7: Int = 7,
+ val a8: Int = 8,
+)
+
+data class KotlinDataSomeDefaults(
+ val requiredInt: Int,
+ val requiredString: String,
+ val optionalInt: Int = 23,
+ val optionalString: String = "Skidoo"
+)
+
+// CharSequence is an interface so the parameter appears from Java as List<? extends CharSequence>,
+// but getList() appears as returning List<CharSequence>.
+data class KotlinDataWithList(val list: List<CharSequence>, val number: Int)
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/annotations/TestAnnotation.java b/value/src/it/functional/src/test/java/com/google/auto/value/annotations/TestAnnotation.java
new file mode 100644
index 00000000..1c5d951c
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/annotations/TestAnnotation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.annotations;
+
+/** Test annotation for AutoAnnotation and Kotlin. */
+public @interface TestAnnotation {
+ String value() default "default";
+ int integer() default 23;
+ String[] values() default {};
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/CustomFieldSerializerTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/CustomFieldSerializerTest.java
index fa1bbfbf..ba1838c2 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/CustomFieldSerializerTest.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/CustomFieldSerializerTest.java
@@ -15,26 +15,28 @@
*/
package com.google.auto.value.gwt;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.GwtCompatible;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.reflect.Reflection;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayDeque;
import java.util.Collections;
+import java.util.Deque;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
-import org.junit.Before;
import org.junit.Test;
+import org.junit.function.ThrowingRunnable;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
/**
* Tests that the generated GWT serializer for GwtValueType serializes fields in the expected way.
@@ -43,11 +45,6 @@ import org.mockito.MockitoAnnotations;
*/
@RunWith(JUnit4.class)
public class CustomFieldSerializerTest {
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- }
-
@AutoValue
@GwtCompatible(serializable = true)
abstract static class ValueType implements Serializable {
@@ -75,7 +72,9 @@ public class CustomFieldSerializerTest {
private static final ValueType WITH_LIST =
ValueType.create("blim", 11881376, SIMPLE, ImmutableList.of(SIMPLE, CONS));
- @Mock SerializationStreamWriter streamWriter;
+ private final MickeyMouseMock<SerializationStreamWriter> mock =
+ new MickeyMouseMock<>(SerializationStreamWriter.class);
+ private final SerializationStreamWriter streamWriter = mock.proxy();
@Test
public void testCustomFieldSerializer() throws SerializationException {
@@ -83,11 +82,13 @@ public class CustomFieldSerializerTest {
(AutoValue_CustomFieldSerializerTest_ValueType) WITH_LIST;
AutoValue_CustomFieldSerializerTest_ValueType_CustomFieldSerializer.serialize(
streamWriter, withList);
- verify(streamWriter).writeString("blim");
- verify(streamWriter).writeInt(11881376);
- verify(streamWriter).writeObject(SIMPLE);
- verify(streamWriter).writeObject(ImmutableList.of(SIMPLE, CONS));
- verifyNoMoreInteractions(streamWriter);
+ mock.verify(
+ () -> {
+ streamWriter.writeString("blim");
+ streamWriter.writeInt(11881376);
+ streamWriter.writeObject(SIMPLE);
+ streamWriter.writeObject(ImmutableList.of(SIMPLE, CONS));
+ });
}
@AutoValue
@@ -109,9 +110,11 @@ public class CustomFieldSerializerTest {
ValueTypeWithGetters.create("package", true);
AutoValue_CustomFieldSerializerTest_ValueTypeWithGetters_CustomFieldSerializer.serialize(
streamWriter, instance);
- verify(streamWriter).writeString("package");
- verify(streamWriter).writeBoolean(true);
- verifyNoMoreInteractions(streamWriter);
+ mock.verify(
+ () -> {
+ streamWriter.writeString("package");
+ streamWriter.writeBoolean(true);
+ });
}
@AutoValue
@@ -133,8 +136,10 @@ public class CustomFieldSerializerTest {
GenericValueType.create(map);
AutoValue_CustomFieldSerializerTest_GenericValueType_CustomFieldSerializer.serialize(
streamWriter, instance);
- verify(streamWriter).writeObject(map);
- verifyNoMoreInteractions(streamWriter);
+ mock.verify(
+ () -> {
+ streamWriter.writeObject(map);
+ });
}
@AutoValue
@@ -165,9 +170,11 @@ public class CustomFieldSerializerTest {
ValueTypeWithBuilder.builder().string("s").strings(ImmutableList.of("a", "b")).build();
AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilder_CustomFieldSerializer.serialize(
streamWriter, instance);
- verify(streamWriter).writeString("s");
- verify(streamWriter).writeObject(ImmutableList.of("a", "b"));
- verifyNoMoreInteractions(streamWriter);
+ mock.verify(
+ () -> {
+ streamWriter.writeString("s");
+ streamWriter.writeObject(ImmutableList.of("a", "b"));
+ });
}
@AutoValue
@@ -198,9 +205,11 @@ public class CustomFieldSerializerTest {
ValueTypeWithBuilderAndGetters.builder().setPackage("s").setDefault(false).build();
AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilderAndGetters_CustomFieldSerializer
.serialize(streamWriter, instance);
- verify(streamWriter).writeString("s");
- verify(streamWriter).writeBoolean(false);
- verifyNoMoreInteractions(streamWriter);
+ mock.verify(
+ () -> {
+ streamWriter.writeString("s");
+ streamWriter.writeBoolean(false);
+ });
}
@AutoValue
@@ -229,7 +238,74 @@ public class CustomFieldSerializerTest {
GenericValueTypeWithBuilder.<Integer, Integer>builder().map(map).build();
AutoValue_CustomFieldSerializerTest_GenericValueTypeWithBuilder_CustomFieldSerializer.serialize(
streamWriter, instance);
- verify(streamWriter).writeObject(map);
- verifyNoMoreInteractions(streamWriter);
+ mock.verify(
+ () -> {
+ streamWriter.writeObject(map);
+ });
+ }
+
+ @AutoValue
+ abstract static class MethodCall {
+ abstract String method();
+
+ abstract ImmutableList<Object> args();
+
+ static MethodCall of(String method, ImmutableList<Object> args) {
+ return new AutoValue_CustomFieldSerializerTest_MethodCall(method, args);
+ }
+ }
+
+ /**
+ * A trivial home-made mocking framework.
+ *
+ * <p>Mockito 5 no longer supports Java 8, and we do, so rather than pinning to an older version
+ * of Mockito we fake it with {@link Reflection}. This is only really possible because the thing
+ * we want to mock ({@link SerializationStreamWriter}) is an interface. Furthermore all its
+ * methods return void so we don't even need a way to specify what to return.
+ *
+ * <p>The idea is that you make an instance of this class, have the code under test call methods
+ * on it, then call {@link #verify} with a lambda that repeats the expected calls. If they match
+ * the actual calls then the test passes.
+ */
+ private static class MickeyMouseMock<T> {
+ private boolean recording = true;
+ private final Deque<MethodCall> methodCalls = new ArrayDeque<>();
+ private final T proxy;
+
+ MickeyMouseMock(Class<T> intf) {
+ this.proxy = Reflection.newProxy(intf, this::invocationHandler);
+ }
+
+ T proxy() {
+ return proxy;
+ }
+
+ void verify(ThrowingRunnable actions) {
+ assertThat(recording).isTrue();
+ recording = false;
+ try {
+ actions.run();
+ } catch (AssertionError e) {
+ throw e;
+ } catch (Throwable t) {
+ throw new AssertionError(t);
+ }
+ assertThat(methodCalls).isEmpty();
+ }
+
+ private Object invocationHandler(Object proxy, Method method, Object[] args) {
+ if (args == null) {
+ args = new Object[0];
+ }
+ MethodCall methodCall = MethodCall.of(method.getName(), ImmutableList.copyOf(args));
+ if (recording) {
+ methodCalls.add(methodCall);
+ } else { // verifying
+ assertWithMessage("Missing call %s", methodCall).that(methodCalls).isNotEmpty();
+ MethodCall recorded = methodCalls.removeFirst();
+ assertThat(methodCall).isEqualTo(recorded);
+ }
+ return null;
+ }
}
}
diff --git a/value/src/it/gwtserializer/pom.xml b/value/src/it/gwtserializer/pom.xml
index 88bf677a..91aade21 100644
--- a/value/src/it/gwtserializer/pom.xml
+++ b/value/src/it/gwtserializer/pom.xml
@@ -25,7 +25,7 @@
<version>HEAD-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
- <url>https://github.com/google/auto/tree/master/value</url>
+ <url>https://github.com/google/auto/tree/main/value</url>
<groupId>com.google.auto.value.it.gwtserializer</groupId>
<artifactId>gwtserializer</artifactId>
@@ -34,9 +34,9 @@
<dependencyManagement>
<dependencies>
<dependency>
- <groupId>com.google.gwt</groupId>
+ <groupId>org.gwtproject</groupId>
<artifactId>gwt</artifactId>
- <version>2.9.0</version>
+ <version>2.10.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -55,12 +55,12 @@
<optional>true</optional>
</dependency>
<dependency>
- <groupId>com.google.gwt</groupId>
+ <groupId>org.gwtproject</groupId>
<artifactId>gwt-user</artifactId>
<scope>test</scope>
</dependency>
<dependency>
- <groupId>com.google.gwt</groupId>
+ <groupId>org.gwtproject</groupId>
<artifactId>gwt-dev</artifactId>
<scope>test</scope>
</dependency>
@@ -94,12 +94,12 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
- <version>3.8.1</version>
+ <version>3.11.0</version>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-java</artifactId>
- <version>1.1.0</version>
+ <version>1.1.2</version>
</dependency>
</dependencies>
<configuration>
@@ -120,7 +120,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
- <version>3.2.0</version>
+ <version>3.3.1</version>
<executions>
<execution>
<!-- postpone resources:testResources until after compiler:testCompile to get generated sources -->
@@ -132,7 +132,7 @@
<plugin>
<groupId>net.ltgt.gwt.maven</groupId>
<artifactId>gwt-maven-plugin</artifactId>
- <version>1.0.0</version>
+ <version>1.0.1</version>
<executions>
<execution>
<goals>
@@ -144,12 +144,30 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
- <version>2.8.2</version>
+ <version>3.1.1</version>
<configuration>
<!-- Build/test, but don't deploy -->
<skip>true</skip>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>3.3.0</version>
+ <executions>
+ <execution>
+ <id>default-jar</id>
+ <phase>install</phase>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
</project>
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 c6fab240..3360d80e 100644
--- a/value/src/main/java/com/google/auto/value/AutoAnnotation.java
+++ b/value/src/main/java/com/google/auto/value/AutoAnnotation.java
@@ -71,9 +71,9 @@ 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:
+ * <p>If your annotation has many elements, you may consider using {@code @AutoBuilder} instead of
+ * {@code @AutoAnnotation} to make it easier to construct instances. In that case, {@code default}
+ * values from the annotation will become default values for the values in the builder. For example:
*
* <pre>
* class Example {
@@ -82,12 +82,7 @@ import java.lang.reflect.AnnotatedElement;
* int number() default 23;
* }
*
- * {@code @AutoAnnotation}
- * static MyAnnotation myAnnotation(String value) {
- * return new AutoAnnotation_Example_myAnnotation(value);
- * }
- *
- * {@code @AutoBuilder(callMethod = "myAnnotation")}
+ * {@code @AutoBuilder(ofClass = MyAnnotation.class)}
* interface MyAnnotationBuilder {
* MyAnnotationBuilder name(String name);
* MyAnnotationBuilder number(int number);
@@ -106,6 +101,6 @@ import java.lang.reflect.AnnotatedElement;
*
* @author emcmanus@google.com (Éamonn McManus)
*/
+@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.SOURCE)
public @interface AutoAnnotation {}
diff --git a/value/src/main/java/com/google/auto/value/AutoBuilder.java b/value/src/main/java/com/google/auto/value/AutoBuilder.java
index b9709005..bfdaf84f 100644
--- a/value/src/main/java/com/google/auto/value/AutoBuilder.java
+++ b/value/src/main/java/com/google/auto/value/AutoBuilder.java
@@ -22,7 +22,6 @@ import java.lang.annotation.Target;
/**
* Specifies that the annotated interface or abstract class should be implemented as a builder.
- * This is still unstable; uses outside Google may break.
*
* <p>A simple example:
*
@@ -40,7 +39,7 @@ import java.lang.annotation.Target;
* }</pre>
*
* @see <a
- * href="https://github.com/google/auto/blob/master/value/userguide/autobuilder.md">AutoBuilder
+ * href="https://github.com/google/auto/blob/main/value/userguide/autobuilder.md">AutoBuilder
* User's Guide</a>
*/
@Retention(RetentionPolicy.CLASS)
diff --git a/value/src/main/java/com/google/auto/value/AutoOneOf.java b/value/src/main/java/com/google/auto/value/AutoOneOf.java
index 064b777d..79f33289 100644
--- a/value/src/main/java/com/google/auto/value/AutoOneOf.java
+++ b/value/src/main/java/com/google/auto/value/AutoOneOf.java
@@ -56,7 +56,7 @@ import java.lang.annotation.Target;
* }}</pre>
*
* <p>{@code @AutoOneOf} is explained in more detail in the <a
- * href="https://github.com/google/auto/blob/master/value/userguide/howto.md#oneof">user guide</a>.
+ * href="https://github.com/google/auto/blob/main/value/userguide/howto.md#oneof">user guide</a>.
*
* @author Chris Nokleberg
* @author Éamonn McManus
diff --git a/value/src/main/java/com/google/auto/value/AutoValue.java b/value/src/main/java/com/google/auto/value/AutoValue.java
index d7541f65..205002a9 100644
--- a/value/src/main/java/com/google/auto/value/AutoValue.java
+++ b/value/src/main/java/com/google/auto/value/AutoValue.java
@@ -20,7 +20,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * Specifies that <a href="https://github.com/google/auto/tree/master/value">AutoValue</a> should
+ * Specifies that <a href="https://github.com/google/auto/tree/main/value">AutoValue</a> should
* generate an implementation class for the annotated abstract class, implementing the standard
* {@link Object} methods like {@link Object#equals equals} to have conventional value semantics. A
* simple example:
@@ -37,7 +37,7 @@ import java.lang.annotation.Target;
* abstract int id();
* }</pre>
*
- * @see <a href="https://github.com/google/auto/tree/master/value">AutoValue User's Guide</a>
+ * @see <a href="https://github.com/google/auto/tree/main/value">AutoValue User's Guide</a>
* @author Éamonn McManus
* @author Kevin Bourrillion
*/
diff --git a/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java b/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java
index 343645ae..375864e3 100644
--- a/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java
+++ b/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java
@@ -15,13 +15,16 @@
*/
package com.google.auto.value.extension;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.SupportedOptions;
+import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
@@ -157,6 +160,32 @@ public abstract class AutoValueExtension {
Set<ExecutableElement> abstractMethods();
/**
+ * Returns the complete list of annotations defined on the {@code classToCopyFrom} that should
+ * be added to any generated subclass. Only annotations visible to the {@code @AutoValue} will
+ * be present. See {@link com.google.auto.value.AutoValue.CopyAnnotations
+ * AutoValue.CopyAnnotations} for more information.
+ *
+ * <p>The default implementation of this method returns an empty list for compatibility with
+ * extensions which may have implemented this interface themselves.
+ */
+ default List<AnnotationMirror> classAnnotationsToCopy(TypeElement classToCopyFrom) {
+ return ImmutableList.of();
+ }
+
+ /**
+ * Returns the complete list of annotations defined on the {@code method} that should be applied
+ * to any override of that method. Only annotations visible to the {@code @AutoValue} will be
+ * present. See {@link com.google.auto.value.AutoValue.CopyAnnotations
+ * AutoValue.CopyAnnotations} for more information.
+ *
+ * <p>The default implementation of this method returns an empty list for compatibility with
+ * extensions which may have implemented this interface themselves.
+ */
+ default List<AnnotationMirror> methodAnnotationsToCopy(ExecutableElement method) {
+ return ImmutableList.of();
+ }
+
+ /**
* Returns a representation of the {@code Builder} associated with the {@code @AutoValue} class,
* if there is one.
*
diff --git a/value/src/main/java/com/google/auto/value/extension/memoized/Memoized.java b/value/src/main/java/com/google/auto/value/extension/memoized/Memoized.java
index ef16135a..cf24cb74 100644
--- a/value/src/main/java/com/google/auto/value/extension/memoized/Memoized.java
+++ b/value/src/main/java/com/google/auto/value/extension/memoized/Memoized.java
@@ -37,7 +37,7 @@ import java.lang.annotation.Target;
* </ul>
*
* <p>If you want to memoize {@link #hashCode()} or {@link #toString()}, you can redeclare them,
- * keeping them {@code abstract}, and annotate them with {@code @Memoize}.
+ * keeping them {@code abstract}, and annotate them with {@code @Memoized}.
*
* <p>If a {@code @Memoized} method is annotated with an annotation whose simple name is {@code
* Nullable}, then {@code null} values will also be memoized. Otherwise, if the method returns
diff --git a/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java
index acbe1c03..b611ae1e 100644
--- a/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java
+++ b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java
@@ -15,11 +15,9 @@
*/
package com.google.auto.value.extension.memoized.processor;
-import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec;
-import static com.google.auto.common.MoreElements.getPackage;
-import static com.google.auto.common.MoreElements.isAnnotationPresent;
import static com.google.auto.common.MoreStreams.toImmutableList;
+import static com.google.auto.common.MoreStreams.toImmutableMap;
import static com.google.auto.common.MoreStreams.toImmutableSet;
import static com.google.auto.value.extension.memoized.processor.ClassNames.MEMOIZED_NAME;
import static com.google.auto.value.extension.memoized.processor.MemoizedValidator.getAnnotationMirror;
@@ -27,13 +25,11 @@ import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.getOnlyElement;
-import static com.google.common.collect.Sets.union;
import static com.squareup.javapoet.MethodSpec.constructorBuilder;
import static com.squareup.javapoet.MethodSpec.methodBuilder;
import static com.squareup.javapoet.TypeSpec.classBuilder;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
-import static java.util.stream.Collectors.toSet;
import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
@@ -46,12 +42,10 @@ import static javax.lang.model.util.ElementFilter.methodsIn;
import static javax.tools.Diagnostic.Kind.ERROR;
import com.google.auto.common.MoreElements;
-import com.google.auto.common.MoreTypes;
-import com.google.auto.common.Visibility;
import com.google.auto.service.AutoService;
import com.google.auto.value.extension.AutoValueExtension;
-import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.FormatMethod;
import com.squareup.javapoet.AnnotationSpec;
@@ -64,7 +58,6 @@ import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
-import java.lang.annotation.Inherited;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -72,11 +65,8 @@ import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
-import javax.lang.model.element.QualifiedNameable;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
@@ -92,11 +82,6 @@ public final class MemoizeExtension extends AutoValueExtension {
private static final ImmutableSet<String> DO_NOT_PULL_DOWN_ANNOTATIONS =
ImmutableSet.of(Override.class.getCanonicalName(), MEMOIZED_NAME);
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private static final String AUTO_VALUE_PACKAGE_NAME = "com.google.auto.value.";
- private static final String AUTO_VALUE_NAME = AUTO_VALUE_PACKAGE_NAME + "AutoValue";
- private static final String COPY_ANNOTATIONS_NAME = AUTO_VALUE_NAME + ".CopyAnnotations";
-
// Maven is configured to shade (rewrite) com.google packages to prevent dependency conflicts.
// Split up the package here with a call to concat to prevent Maven from finding and rewriting it,
// so that this will be able to find the LazyInit annotation if it's on the classpath.
@@ -153,15 +138,20 @@ public final class MemoizeExtension extends AutoValueExtension {
}
String generate() {
+
TypeSpec.Builder generated =
classBuilder(className)
.superclass(superType())
- .addAnnotations(copiedClassAnnotations(context.autoValueClass()))
+ .addAnnotations(
+ context.classAnnotationsToCopy(context.autoValueClass()).stream()
+ .map(AnnotationSpec::get)
+ .collect(toImmutableList()))
.addTypeVariables(annotatedTypeVariableNames())
.addModifiers(isFinal ? FINAL : ABSTRACT)
.addMethod(constructor());
generatedAnnotationSpec(elements, sourceVersion, MemoizeExtension.class)
.ifPresent(generated::addAnnotation);
+
for (ExecutableElement method : memoizedMethods(context)) {
MethodOverrider methodOverrider = new MethodOverrider(method);
generated.addFields(methodOverrider.fields());
@@ -176,7 +166,7 @@ public final class MemoizeExtension extends AutoValueExtension {
return JavaFile.builder(context.packageName(), generated.build()).build().toString();
}
- // LINT.IfChange
+
private TypeName superType() {
ClassName superType = ClassName.get(context.packageName(), classToExtend);
ImmutableList<TypeVariableName> typeVariableNames = typeVariableNames();
@@ -206,15 +196,37 @@ public final class MemoizeExtension extends AutoValueExtension {
private MethodSpec constructor() {
MethodSpec.Builder constructor = constructorBuilder();
+ // TODO(b/35944623): Replace this with a standard way of avoiding keywords.
+ Set<String> propertyNames = context.properties().keySet();
+ ImmutableMap<String, String> parameterNames =
+ propertyNames.stream()
+ .collect(
+ toImmutableMap(name -> name, name -> generateIdentifier(name, propertyNames)));
context
.propertyTypes()
- .forEach((name, type) -> constructor.addParameter(annotatedType(type), name + "$"));
+ .forEach(
+ (name, type) ->
+ constructor.addParameter(annotatedType(type), parameterNames.get(name)));
String superParams =
- context.properties().keySet().stream().map(n -> n + "$").collect(joining(", "));
+ context.properties().keySet().stream().map(parameterNames::get).collect(joining(", "));
constructor.addStatement("super($L)", superParams);
return constructor.build();
}
+ private static String generateIdentifier(String name, Set<String> existingNames) {
+ if (!SourceVersion.isKeyword(name)) {
+ return name;
+ }
+ for (int i = 0;; i++) {
+ String newName = name + i;
+ if (!existingNames.contains(newName)) {
+ return newName;
+ }
+ }
+ }
+
+
+
private boolean isHashCodeMemoized() {
return memoizedMethods(context).stream()
.anyMatch(method -> method.getSimpleName().contentEquals("hashCode"));
@@ -252,146 +264,6 @@ public final class MemoizeExtension extends AutoValueExtension {
.build();
}
- // LINT.IfChange
- /**
- * True if the given class name is in the com.google.auto.value package or a subpackage. False
- * if the class name contains {@code Test}, since many AutoValue tests under
- * com.google.auto.value define their own annotations.
- */
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private boolean isInAutoValuePackage(String className) {
- return className.startsWith(AUTO_VALUE_PACKAGE_NAME) && !className.contains("Test");
- }
-
- /**
- * Returns the fully-qualified name of an annotation-mirror, e.g.
- * "com.google.auto.value.AutoValue".
- */
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private static String getAnnotationFqName(AnnotationMirror annotation) {
- return ((QualifiedNameable) annotation.getAnnotationType().asElement())
- .getQualifiedName()
- .toString();
- }
-
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private boolean annotationVisibleFrom(AnnotationMirror annotation, Element from) {
- Element annotationElement = annotation.getAnnotationType().asElement();
- Visibility visibility = Visibility.effectiveVisibilityOfElement(annotationElement);
- switch (visibility) {
- case PUBLIC:
- return true;
- case PROTECTED:
- // If the annotation is protected, it must be inside another class, call it C. If our
- // @AutoValue class is Foo then, for the annotation to be visible, either Foo must be in
- // the same package as C or Foo must be a subclass of C. If the annotation is visible from
- // Foo then it is also visible from our generated subclass AutoValue_Foo.
- // The protected case only applies to method annotations. An annotation on the
- // AutoValue_Foo class itself can't be protected, even if AutoValue_Foo ultimately
- // inherits from the class that defines the annotation. The JLS says "Access is permitted
- // only within the body of a subclass":
- // https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.6.2.1
- // AutoValue_Foo is a top-level class, so an annotation on it cannot be in the body of a
- // subclass of anything.
- return getPackage(annotationElement).equals(getPackage(from))
- || types.isSubtype(from.asType(), annotationElement.getEnclosingElement().asType());
- case DEFAULT:
- return getPackage(annotationElement).equals(getPackage(from));
- default:
- return false;
- }
- }
-
- /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private ImmutableList<AnnotationMirror> annotationsToCopy(
- Element autoValueType, Element typeOrMethod, Set<String> excludedAnnotations) {
- ImmutableList.Builder<AnnotationMirror> result = ImmutableList.builder();
- for (AnnotationMirror annotation : typeOrMethod.getAnnotationMirrors()) {
- String annotationFqName = getAnnotationFqName(annotation);
- // To be included, the annotation should not be in com.google.auto.value,
- // and it should not be in the excludedAnnotations set.
- if (!isInAutoValuePackage(annotationFqName)
- && !excludedAnnotations.contains(annotationFqName)
- && annotationVisibleFrom(annotation, autoValueType)) {
- result.add(annotation);
- }
- }
-
- return result.build();
- }
-
- /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private ImmutableList<AnnotationSpec> copyAnnotations(
- Element autoValueType, Element typeOrMethod, Set<String> excludedAnnotations) {
- ImmutableList<AnnotationMirror> annotationsToCopy =
- annotationsToCopy(autoValueType, typeOrMethod, excludedAnnotations);
- return annotationsToCopy.stream().map(AnnotationSpec::get).collect(toImmutableList());
- }
-
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private static boolean hasAnnotationMirror(Element element, String annotationName) {
- return getAnnotationMirror(element, annotationName).isPresent();
- }
-
- /**
- * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of
- * {@code TypeMirror} where each type is an annotation type.
- */
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private ImmutableSet<TypeMirror> getExcludedAnnotationTypes(Element element) {
- Optional<AnnotationMirror> maybeAnnotation =
- getAnnotationMirror(element, COPY_ANNOTATIONS_NAME);
- if (!maybeAnnotation.isPresent()) {
- return ImmutableSet.of();
- }
-
- @SuppressWarnings("unchecked")
- List<AnnotationValue> excludedClasses =
- (List<AnnotationValue>) getAnnotationValue(maybeAnnotation.get(), "exclude").getValue();
- return excludedClasses.stream()
- .map(
- annotationValue ->
- MoreTypes.equivalence().wrap((TypeMirror) annotationValue.getValue()))
- // TODO(b/122509249): Move TypeMirrorSet to common package instead of doing this.
- .distinct()
- .map(Wrapper::get)
- .collect(toImmutableSet());
- }
-
- /**
- * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of
- * strings that are fully-qualified class names.
- */
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private Set<String> getExcludedAnnotationClassNames(Element element) {
- return getExcludedAnnotationTypes(element).stream()
- .map(MoreTypes::asTypeElement)
- .map(typeElement -> typeElement.getQualifiedName().toString())
- .collect(toSet());
- }
-
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private static Set<String> getAnnotationsMarkedWithInherited(Element element) {
- return element.getAnnotationMirrors().stream()
- .filter(a -> isAnnotationPresent(a.getAnnotationType().asElement(), Inherited.class))
- .map(Generator::getAnnotationFqName)
- .collect(toSet());
- }
-
- private ImmutableList<AnnotationSpec> copiedClassAnnotations(TypeElement type) {
- // Only copy annotations from a class if it has @AutoValue.CopyAnnotations.
- if (hasAnnotationMirror(type, COPY_ANNOTATIONS_NAME)) {
- Set<String> excludedAnnotations =
- union(getExcludedAnnotationClassNames(type), getAnnotationsMarkedWithInherited(type));
-
- return copyAnnotations(type, type, excludedAnnotations);
- } else {
- return ImmutableList.of();
- }
- }
-
/**
* Determines the required fields and overriding method for a {@link
* com.google.auto.value.extension.memoized.Memoized @Memoized} method.
@@ -416,7 +288,7 @@ public final class MemoizeExtension extends AutoValueExtension {
.addExceptions(
method.getThrownTypes().stream().map(TypeName::get).collect(toList()))
.addModifiers(filter(method.getModifiers(), not(equalTo(ABSTRACT))));
- for (AnnotationMirror annotation : method.getAnnotationMirrors()) {
+ for (AnnotationMirror annotation : context.methodAnnotationsToCopy(method)) {
AnnotationSpec annotationSpec = AnnotationSpec.get(annotation);
if (pullDownMethodAnnotation(annotation)) {
override.addAnnotation(annotationSpec);
@@ -594,10 +466,12 @@ public final class MemoizeExtension extends AutoValueExtension {
.anyMatch(n -> n.contentEquals("Nullable"));
}
+
/** Translate a {@link TypeMirror} into a {@link TypeName}, including type annotations. */
private static TypeName annotatedType(TypeMirror type) {
List<AnnotationSpec> annotations =
type.getAnnotationMirrors().stream().map(AnnotationSpec::get).collect(toList());
return TypeName.get(type).annotated(annotations);
}
+
}
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/index.md b/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/index.md
index 0282404b..896b9735 100644
--- a/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/index.md
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/index.md
@@ -95,7 +95,7 @@ object `Proxy$` where `Foo`'s data is unwrapped.
`SerializableAutoValueExtension` can be extended to support additional
un-serializable types with [SerializerExtensions].
-[`AutoValue`]: https://github.com/google/auto/tree/master/value
-[`SerializableAutoValue`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/SerializableAutoValue.java
-[`SerializableAutoValueExtension`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/extension/SerializableAutoValueExtension.java
-[SerializerExtensions]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/userguide/serializer-extension.md
+[`AutoValue`]: https://github.com/google/auto/tree/main/value
+[`SerializableAutoValue`]: https://github.com/google/auto/blob/main/value/src/main/java/com/google/auto/value/extension/serializable/SerializableAutoValue.java
+[`SerializableAutoValueExtension`]: https://github.com/google/auto/blob/main/value/src/main/java/com/google/auto/value/extension/serializable/extension/SerializableAutoValueExtension.java
+[SerializerExtensions]: serializer-extension
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/serializer-extension.md b/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/serializer-extension.md
index aaec7c02..da026a78 100644
--- a/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/serializer-extension.md
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/serializer-extension.md
@@ -239,7 +239,7 @@ a `Serializer` is available, we use it to map `Baz` to a serializable type. If
no `Serializer` is available, we can do nothing and let `Baz` be serialized
as-is.
-[AutoService]: https://github.com/google/auto/tree/master/service
-[`SerializableAutoValueExtension`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/extension/SerializableAutoValueExtension.java
-[`SerializerExtension`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerExtension.java
-[`Serializer`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/Serializer.java
+[AutoService]: https://github.com/google/auto/tree/main/service
+[`SerializableAutoValueExtension`]: https://github.com/google/auto/blob/main/value/src/main/java/com/google/auto/value/extension/serializable/extension/SerializableAutoValueExtension.java
+[`SerializerExtension`]: https://github.com/google/auto/blob/main/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerExtension.java
+[`Serializer`]: https://github.com/google/auto/blob/main/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/Serializer.java
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java
index 5143d8bf..13752b6b 100644
--- a/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java
@@ -114,7 +114,9 @@ public final class SerializableAutoValueExtension extends AutoValueExtension {
.collect(toImmutableList());
TypeName classTypeName =
- getClassTypeName(ClassName.get(context.packageName(), className), typeVariableNames);
+ getClassTypeName(
+ ClassName.get(context.packageName(), context.finalAutoValueClassName()),
+ typeVariableNames);
this.proxyGenerator =
new ProxyGenerator(
classTypeName, typeVariableNames, propertyMirrors, buildSerializersMap());
diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java
index e2381f7e..a8bcef53 100644
--- a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java
+++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java
@@ -16,29 +16,20 @@
package com.google.auto.value.extension.toprettystring.processor;
-import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec;
-import static com.google.auto.common.MoreElements.getPackage;
-import static com.google.auto.common.MoreElements.isAnnotationPresent;
import static com.google.auto.common.MoreStreams.toImmutableList;
-import static com.google.auto.common.MoreStreams.toImmutableSet;
-import static com.google.auto.value.extension.toprettystring.processor.Annotations.getAnnotationMirror;
-import static com.google.common.collect.Sets.union;
+import static com.google.auto.common.MoreStreams.toImmutableMap;
import static com.squareup.javapoet.MethodSpec.constructorBuilder;
import static com.squareup.javapoet.TypeSpec.classBuilder;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
-import static java.util.stream.Collectors.toSet;
import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.FINAL;
-import com.google.auto.common.MoreTypes;
-import com.google.auto.common.Visibility;
import com.google.auto.value.extension.AutoValueExtension;
import com.google.auto.value.extension.AutoValueExtension.Context;
-import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableMap;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
@@ -46,19 +37,11 @@ import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
-import java.lang.annotation.Inherited;
import java.util.List;
-import java.util.Optional;
import java.util.Set;
import javax.lang.model.SourceVersion;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.QualifiedNameable;
-import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
-import javax.lang.model.util.Types;
/**
* A factory for {@link TypeSpec}s used in {@link AutoValueExtension} implementations.
@@ -68,15 +51,11 @@ import javax.lang.model.util.Types;
* location to consolidate the code.
*/
final class ExtensionClassTypeSpecBuilder {
- private static final String AUTO_VALUE_PACKAGE_NAME = "com.google.auto.value.";
- private static final String AUTO_VALUE_NAME = AUTO_VALUE_PACKAGE_NAME + "AutoValue";
- private static final String COPY_ANNOTATIONS_NAME = AUTO_VALUE_NAME + ".CopyAnnotations";
private final Context context;
private final String className;
private final String classToExtend;
private final boolean isFinal;
- private final Types types;
private final Elements elements;
private final SourceVersion sourceVersion;
@@ -86,7 +65,6 @@ final class ExtensionClassTypeSpecBuilder {
this.className = className;
this.classToExtend = classToExtend;
this.isFinal = isFinal;
- this.types = context.processingEnvironment().getTypeUtils();
this.elements = context.processingEnvironment().getElementUtils();
this.sourceVersion = context.processingEnvironment().getSourceVersion();
}
@@ -101,7 +79,10 @@ final class ExtensionClassTypeSpecBuilder {
TypeSpec.Builder builder =
classBuilder(className)
.superclass(superType())
- .addAnnotations(copiedClassAnnotations(context.autoValueClass()))
+ .addAnnotations(
+ context.classAnnotationsToCopy(context.autoValueClass()).stream()
+ .map(AnnotationSpec::get)
+ .collect(toImmutableList()))
.addTypeVariables(annotatedTypeVariableNames())
.addModifiers(isFinal ? FINAL : ABSTRACT)
.addMethod(constructor());
@@ -139,152 +120,32 @@ final class ExtensionClassTypeSpecBuilder {
private MethodSpec constructor() {
MethodSpec.Builder constructor = constructorBuilder();
+ // TODO(b/35944623): Replace this with a standard way of avoiding keywords.
+ Set<String> propertyNames = context.properties().keySet();
+ ImmutableMap<String, String> parameterNames =
+ propertyNames.stream()
+ .collect(toImmutableMap(name -> name, name -> generateIdentifier(name, propertyNames)));
context
.propertyTypes()
- .forEach((name, type) -> constructor.addParameter(annotatedType(type), name + "$"));
+ .forEach(
+ (name, type) ->
+ constructor.addParameter(annotatedType(type), parameterNames.get(name)));
String superParams =
- context.properties().keySet().stream().map(n -> n + "$").collect(joining(", "));
+ context.properties().keySet().stream().map(parameterNames::get).collect(joining(", "));
constructor.addStatement("super($L)", superParams);
return constructor.build();
}
- /**
- * True if the given class name is in the com.google.auto.value package or a subpackage. False if
- * the class name contains {@code Test}, since many AutoValue tests under com.google.auto.value
- * define their own annotations.
- */
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private boolean isInAutoValuePackage(String className) {
- return className.startsWith(AUTO_VALUE_PACKAGE_NAME) && !className.contains("Test");
- }
-
- /**
- * Returns the fully-qualified name of an annotation-mirror, e.g.
- * "com.google.auto.value.AutoValue".
- */
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private static String getAnnotationFqName(AnnotationMirror annotation) {
- return ((QualifiedNameable) annotation.getAnnotationType().asElement())
- .getQualifiedName()
- .toString();
- }
-
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private boolean annotationVisibleFrom(AnnotationMirror annotation, Element from) {
- Element annotationElement = annotation.getAnnotationType().asElement();
- Visibility visibility = Visibility.effectiveVisibilityOfElement(annotationElement);
- switch (visibility) {
- case PUBLIC:
- return true;
- case PROTECTED:
- // If the annotation is protected, it must be inside another class, call it C. If our
- // @AutoValue class is Foo then, for the annotation to be visible, either Foo must be in
- // the same package as C or Foo must be a subclass of C. If the annotation is visible from
- // Foo then it is also visible from our generated subclass AutoValue_Foo.
- // The protected case only applies to method annotations. An annotation on the
- // AutoValue_Foo class itself can't be protected, even if AutoValue_Foo ultimately
- // inherits from the class that defines the annotation. The JLS says "Access is permitted
- // only within the body of a subclass":
- // https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.6.2.1
- // AutoValue_Foo is a top-level class, so an annotation on it cannot be in the body of a
- // subclass of anything.
- return getPackage(annotationElement).equals(getPackage(from))
- || types.isSubtype(from.asType(), annotationElement.getEnclosingElement().asType());
- case DEFAULT:
- return getPackage(annotationElement).equals(getPackage(from));
- default:
- return false;
+ private static String generateIdentifier(String name, Set<String> existingNames) {
+ if (!SourceVersion.isKeyword(name)) {
+ return name;
}
- }
-
- /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private ImmutableList<AnnotationMirror> annotationsToCopy(
- Element autoValueType, Element typeOrMethod, Set<String> excludedAnnotations) {
- ImmutableList.Builder<AnnotationMirror> result = ImmutableList.builder();
- for (AnnotationMirror annotation : typeOrMethod.getAnnotationMirrors()) {
- String annotationFqName = getAnnotationFqName(annotation);
- // To be included, the annotation should not be in com.google.auto.value,
- // and it should not be in the excludedAnnotations set.
- if (!isInAutoValuePackage(annotationFqName)
- && !excludedAnnotations.contains(annotationFqName)
- && annotationVisibleFrom(annotation, autoValueType)) {
- result.add(annotation);
+ for (int i = 0; ; i++) {
+ String newName = name + i;
+ if (!existingNames.contains(newName)) {
+ return newName;
}
}
-
- return result.build();
- }
-
- /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private ImmutableList<AnnotationSpec> copyAnnotations(
- Element autoValueType, Element typeOrMethod, Set<String> excludedAnnotations) {
- ImmutableList<AnnotationMirror> annotationsToCopy =
- annotationsToCopy(autoValueType, typeOrMethod, excludedAnnotations);
- return annotationsToCopy.stream().map(AnnotationSpec::get).collect(toImmutableList());
- }
-
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private static boolean hasAnnotationMirror(Element element, String annotationName) {
- return getAnnotationMirror(element, annotationName).isPresent();
- }
-
- /**
- * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of
- * {@code TypeMirror} where each type is an annotation type.
- */
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private ImmutableSet<TypeMirror> getExcludedAnnotationTypes(Element element) {
- Optional<AnnotationMirror> maybeAnnotation =
- getAnnotationMirror(element, COPY_ANNOTATIONS_NAME);
- if (!maybeAnnotation.isPresent()) {
- return ImmutableSet.of();
- }
-
- @SuppressWarnings("unchecked")
- List<AnnotationValue> excludedClasses =
- (List<AnnotationValue>) getAnnotationValue(maybeAnnotation.get(), "exclude").getValue();
- return excludedClasses.stream()
- .map(
- annotationValue ->
- MoreTypes.equivalence().wrap((TypeMirror) annotationValue.getValue()))
- // TODO(b/122509249): Move TypeMirrorSet to common package instead of doing this.
- .distinct()
- .map(Wrapper::get)
- .collect(toImmutableSet());
- }
-
- /**
- * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of
- * strings that are fully-qualified class names.
- */
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private Set<String> getExcludedAnnotationClassNames(Element element) {
- return getExcludedAnnotationTypes(element).stream()
- .map(MoreTypes::asTypeElement)
- .map(typeElement -> typeElement.getQualifiedName().toString())
- .collect(toSet());
- }
-
- // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
- private static Set<String> getAnnotationsMarkedWithInherited(Element element) {
- return element.getAnnotationMirrors().stream()
- .filter(a -> isAnnotationPresent(a.getAnnotationType().asElement(), Inherited.class))
- .map(ExtensionClassTypeSpecBuilder::getAnnotationFqName)
- .collect(toSet());
- }
-
- private ImmutableList<AnnotationSpec> copiedClassAnnotations(TypeElement type) {
- // Only copy annotations from a class if it has @AutoValue.CopyAnnotations.
- if (hasAnnotationMirror(type, COPY_ANNOTATIONS_NAME)) {
- Set<String> excludedAnnotations =
- union(getExcludedAnnotationClassNames(type), getAnnotationsMarkedWithInherited(type));
-
- return copyAnnotations(type, type, excludedAnnotations);
- } else {
- return ImmutableList.of();
- }
}
/** Translate a {@link TypeMirror} into a {@link TypeName}, including type annotations. */
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 cc0e62ec..600fbf0e 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
@@ -52,7 +52,6 @@ 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.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
@@ -83,7 +82,6 @@ public class AutoAnnotationProcessor extends AbstractProcessor {
private Elements elementUtils;
private Types typeUtils;
- private Nullables nullables;
private TypeMirror javaLangObject;
@Override
@@ -101,7 +99,6 @@ public class AutoAnnotationProcessor extends AbstractProcessor {
super.init(processingEnv);
this.elementUtils = processingEnv.getElementUtils();
this.typeUtils = processingEnv.getTypeUtils();
- this.nullables = new Nullables(processingEnv);
this.javaLangObject = elementUtils.getTypeElement("java.lang.Object").asType();
}
@@ -153,14 +150,9 @@ public class AutoAnnotationProcessor extends AbstractProcessor {
}
private void processMethod(ExecutableElement method) {
- if (!method.getModifiers().contains(Modifier.STATIC)) {
- throw abortWithError(method, "@AutoAnnotation method must be static");
- }
-
TypeElement annotationElement = getAnnotationReturnType(method);
- Set<Class<?>> wrapperTypesUsedInCollections = wrapperTypesUsedInCollections(method);
-
+ ImmutableSet<Class<?>> wrapperTypesUsedInCollections = wrapperTypesUsedInCollections(method);
ImmutableMap<String, ExecutableElement> memberMethods = getMemberMethods(annotationElement);
TypeElement methodClass = MoreElements.asType(method.getEnclosingElement());
String pkg = TypeSimplifier.packageNameOf(methodClass);
@@ -207,12 +199,8 @@ public class AutoAnnotationProcessor extends AbstractProcessor {
// Unlike AutoValue, we don't currently try to guess a @Nullable based on the methods in your
// class. It's the default one or nothing.
ImmutableList<AnnotationMirror> equalsParameterAnnotations =
- nullables
- .appropriateNullableGivenMethods(ImmutableSet.of())
- .map(ImmutableList::of)
- .orElse(ImmutableList.of());
- return TypeEncoder.encodeWithAnnotations(
- javaLangObject, equalsParameterAnnotations, ImmutableSet.of());
+ Nullables.fromMethods(processingEnv, ImmutableList.of()).nullableTypeAnnotations();
+ return TypeEncoder.encodeWithAnnotations(javaLangObject, equalsParameterAnnotations);
}
/**
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoBuilderAnnotationTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoBuilderAnnotationTemplateVars.java
new file mode 100644
index 00000000..68df14ed
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AutoBuilderAnnotationTemplateVars.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.processor;
+
+import com.google.auto.value.processor.AutoValueishProcessor.Property;
+import com.google.common.collect.ImmutableSet;
+import com.google.escapevelocity.Template;
+
+/** The variables to substitute into the autobuilderannotation.vm template. */
+class AutoBuilderAnnotationTemplateVars extends TemplateVars {
+ private static final Template TEMPLATE = parsedTemplateForResource("autobuilderannotation.vm");
+
+ /** Package of generated class. */
+ String pkg;
+
+ /** The encoding of the {@code Generated} class. Empty if the class is not available. */
+ String generated;
+
+ /** The name of the class to generate. */
+ String className;
+
+ /**
+ * The {@linkplain TypeEncoder#encode encoded} name of the annotation type that the generated code
+ * will build.
+ */
+ String annotationType;
+
+ /**
+ * The {@linkplain TypeEncoder#encode encoded} name of the {@code @AutoBuilder} type that users
+ * will call to build this annotation.
+ */
+ String autoBuilderType;
+
+ /**
+ * The "properties" that the builder will build. These are really just names and types, being the
+ * names and types of the annotation elements.
+ */
+ ImmutableSet<Property> props;
+
+ @Override
+ Template parsedTemplate() {
+ return TEMPLATE;
+ }
+}
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 fc0d8b3e..607e7367 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
@@ -15,15 +15,18 @@
*/
package com.google.auto.value.processor;
+import static com.google.auto.common.GeneratedAnnotations.generatedAnnotation;
import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
import static com.google.auto.common.MoreElements.getPackage;
import static com.google.auto.common.MoreStreams.toImmutableList;
+import static com.google.auto.common.MoreStreams.toImmutableMap;
import static com.google.auto.common.MoreStreams.toImmutableSet;
+import static com.google.auto.common.MoreTypes.asTypeElement;
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 com.google.auto.value.processor.ClassNames.KOTLIN_METADATA_NAME;
import static java.util.stream.Collectors.joining;
-import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toMap;
import static javax.lang.model.util.ElementFilter.constructorsIn;
import static javax.lang.model.util.ElementFilter.methodsIn;
@@ -34,20 +37,24 @@ import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.common.Visibility;
import com.google.auto.service.AutoService;
-import com.google.auto.value.processor.BuilderSpec.PropertyGetter;
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.io.IOException;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
import java.lang.reflect.Field;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.Set;
+import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Stream;
import javax.annotation.processing.ProcessingEnvironment;
@@ -64,13 +71,20 @@ import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
+import javax.tools.JavaFileObject;
+import kotlinx.metadata.Flag;
+import kotlinx.metadata.KmClass;
+import kotlinx.metadata.KmConstructor;
+import kotlinx.metadata.KmValueParameter;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
/**
* Javac annotation processor (compiler plugin) for builders; user code never references this class.
*
- * @see <a href="https://github.com/google/auto/tree/master/value">AutoValue User's Guide</a>
+ * @see <a href="https://github.com/google/auto/tree/main/value">AutoValue User's Guide</a>
* @author Éamonn McManus
*/
@AutoService(Processor.class)
@@ -78,6 +92,7 @@ import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
public class AutoBuilderProcessor extends AutoValueishProcessor {
private static final String ALLOW_OPTION = "com.google.auto.value.AutoBuilderIsUnstable";
+ private static final String AUTO_ANNOTATION_CLASS_PREFIX = "AutoBuilderAnnotation_";
public AutoBuilderProcessor() {
super(AUTO_BUILDER_NAME, /* appliesToInterfaces= */ true);
@@ -96,6 +111,55 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
javaLangVoid = elementUtils().getTypeElement("java.lang.Void").asType();
}
+ // The handling of @AutoBuilder to generate annotation implementations needs some explanation.
+ // Suppose we have this:
+ //
+ // public class Annotations {
+ // @interface MyAnnot {...}
+ //
+ // @AutoBuilder(ofClass = MyAnnot.class)
+ // public interface MyAnnotBuilder {
+ // ...
+ // MyAnnot build();
+ // }
+ //
+ // public static MyAnnotBuilder myAnnotBuilder() {
+ // return new AutoBuilder_Annotations_MyAnnotBuilder();
+ // }
+ // }
+ //
+ // Then we will detect that the ofClass type is an annotation. Since annotations can have neither
+ // constructors nor static methods, we know this isn't a regular @AutoBuilder. We want to
+ // generate an implementation of the MyAnnot annotation, and we know we can do that if we have a
+ // suitable @AutoAnnotation method. So we generate:
+ //
+ // class AutoBuilderAnnotation_Annotations_MyAnnotBuilder {
+ // @AutoAnnotation
+ // static MyAnnot newAnnotation(...) {
+ // return new AutoAnnotation_AutoBuilderAnnotation_Annotations_MyAnnotBuilder_newAnnotation(
+ // ...);
+ // }
+ // }
+ //
+ // We also "defer" MyAnnotBuilder so that it will be considered again on the next round. At that
+ // point the method AutoBuilderAnnotation_Annotations_MyAnnotBuilder.newAnnotation will exist, and
+ // we just need to tweak the handling of MyAnnotBuilder so that it behaves as if it were:
+ //
+ // @AutoBuilder(
+ // callMethod = newAnnotation,
+ // ofClass = AutoBuilderAnnotation_Annotations_MyAnnotBuilder.class)
+ // interface MyAnnotBuilder {...}
+ //
+ // Using AutoAnnotation and AutoBuilder together you'd write
+ //
+ // @AutoAnnotation static MyAnnot newAnnotation(...) { ... }
+ //
+ // @AutoBuilder(callMethod = "newAnnotation", ofClass = Some.class)
+ // interface MyAnnotBuilder { ... }
+ //
+ // If you set ofClass to an annotation class, AutoBuilder generates the @AutoAnnotation method for
+ // you and then acts as if your @AutoBuilder annotation pointed to it.
+
@Override
void processType(TypeElement autoBuilderType) {
if (processingEnv.getOptions().containsKey(ALLOW_OPTION)) {
@@ -107,58 +171,130 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
TypeElement ofClass = getOfClass(autoBuilderType, autoBuilderAnnotation);
checkModifiersIfNested(ofClass, autoBuilderType, "AutoBuilder ofClass");
String callMethod = findCallMethodValue(autoBuilderAnnotation);
+ if (ofClass.getKind() == ElementKind.ANNOTATION_TYPE) {
+ buildAnnotation(autoBuilderType, ofClass, callMethod);
+ } else {
+ processType(autoBuilderType, ofClass, callMethod);
+ }
+ }
+
+ private void processType(TypeElement autoBuilderType, TypeElement ofClass, String callMethod) {
ImmutableSet<ExecutableElement> methods =
abstractMethodsIn(
getLocalAndInheritedMethods(autoBuilderType, typeUtils(), elementUtils()));
- ExecutableElement executable = findExecutable(ofClass, callMethod, autoBuilderType, methods);
+ Executable executable = findExecutable(ofClass, callMethod, autoBuilderType, methods);
BuilderSpec builderSpec = new BuilderSpec(ofClass, processingEnv, errorReporter());
BuilderSpec.Builder builder = builderSpec.new Builder(autoBuilderType);
- TypeMirror builtType = builtType(executable);
+ TypeMirror builtType = executable.builtType();
+ ImmutableMap<String, String> propertyInitializers =
+ propertyInitializers(autoBuilderType, executable);
+ Nullables nullables = Nullables.fromMethods(processingEnv, methods);
Optional<BuilderMethodClassifier<VariableElement>> maybeClassifier =
BuilderMethodClassifierForAutoBuilder.classify(
- methods, errorReporter(), processingEnv, executable, builtType, autoBuilderType);
+ methods,
+ errorReporter(),
+ processingEnv,
+ executable,
+ builtType,
+ autoBuilderType,
+ propertyInitializers.keySet(),
+ nullables);
if (!maybeClassifier.isPresent() || errorReporter().errorCount() > 0) {
// We've already output one or more error messages.
return;
}
BuilderMethodClassifier<VariableElement> classifier = maybeClassifier.get();
- Map<String, String> propertyToGetterName =
- Maps.transformValues(classifier.builderGetters(), PropertyGetter::getName);
+ ImmutableMap<String, String> propertyToGetterName =
+ propertyToGetterName(executable, autoBuilderType);
AutoBuilderTemplateVars vars = new AutoBuilderTemplateVars();
- vars.props = propertySet(autoBuilderType, executable, propertyToGetterName);
+ vars.props =
+ propertySet(
+ executable,
+ propertyToGetterName,
+ propertyInitializers,
+ nullables);
builder.defineVars(vars, classifier);
vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION);
String generatedClassName = generatedClassName(autoBuilderType, "AutoBuilder_");
vars.builderName = TypeSimplifier.simpleNameOf(generatedClassName);
vars.builtType = TypeEncoder.encode(builtType);
- vars.build = build(executable);
- vars.types = typeUtils();
- vars.toBuilderConstructor = false;
+ vars.builderAnnotations = copiedClassAnnotations(autoBuilderType);
+ Optional<String> forwardingClassName = maybeForwardingClass(autoBuilderType, executable);
+ vars.build =
+ forwardingClassName
+ .map(n -> TypeSimplifier.simpleNameOf(n) + ".of")
+ .orElseGet(executable::invoke);
+ vars.toBuilderConstructor = !propertyToGetterName.isEmpty();
vars.toBuilderMethods = ImmutableList.of();
- defineSharedVarsForType(autoBuilderType, ImmutableSet.of(), vars);
+ defineSharedVarsForType(
+ autoBuilderType, ImmutableSet.of(), nullables, vars);
String text = vars.toText();
text = TypeEncoder.decode(text, processingEnv, vars.pkg, autoBuilderType.asType());
text = Reformatter.fixup(text);
writeSourceFile(generatedClassName, text, autoBuilderType);
+ forwardingClassName.ifPresent(
+ n -> generateForwardingClass(n, executable, builtType, autoBuilderType));
+ }
+
+ /**
+ * Generates a class that will call the synthetic Kotlin constructor that is used to specify which
+ * optional parameters are defaulted. Because it is synthetic, it can't be called from Java source
+ * code. Instead, Java source code calls the {@code of} method in the class we generate here.
+ */
+ private void generateForwardingClass(
+ String forwardingClassName,
+ Executable executable,
+ TypeMirror builtType,
+ TypeElement autoBuilderType) {
+ // The synthetic constructor has the same parameters as the user-written constructor, plus as
+ // many `int` bitmasks as are needed to have one bit for each of those parameters, plus a dummy
+ // parameter of type kotlin.jvm.internal.DefaultConstructorMarker to avoid confusion with a
+ // constructor that might have its own `int` parameters where the bitmasks are.
+ // This ABI is not publicly specified (as far as we know) but JetBrains has confirmed orally
+ // that it unlikely to change, and if it does it will be in a backward-compatible way.
+ ImmutableList.Builder<TypeMirror> constructorParameters = ImmutableList.builder();
+ executable.parameters().stream()
+ .map(Element::asType)
+ .map(typeUtils()::erasure)
+ .forEach(constructorParameters::add);
+ int bitmaskCount = (executable.optionalParameterCount() + 31) / 32;
+ constructorParameters.addAll(
+ Collections.nCopies(bitmaskCount, typeUtils().getPrimitiveType(TypeKind.INT)));
+ String marker = "kot".concat("lin.jvm.internal.DefaultConstructorMarker"); // defeat shading
+ constructorParameters.add(elementUtils().getTypeElement(marker).asType());
+ byte[] classBytes =
+ ForwardingClassGenerator.makeConstructorForwarder(
+ forwardingClassName, builtType, constructorParameters.build());
+ try {
+ JavaFileObject trampoline =
+ processingEnv.getFiler().createClassFile(forwardingClassName, autoBuilderType);
+ try (OutputStream out = trampoline.openOutputStream()) {
+ out.write(classBytes);
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private Optional<String> maybeForwardingClass(
+ TypeElement autoBuilderType, Executable executable) {
+ return executable.optionalParameterCount() == 0
+ ? Optional.empty()
+ : Optional.of(generatedClassName(autoBuilderType, "AutoBuilderBridge_"));
}
private ImmutableSet<Property> propertySet(
- 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();
+ Executable executable,
+ Map<String, String> propertyToGetterName,
+ ImmutableMap<String, String> builderInitializers,
+ Nullables nullables) {
// 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 =
- executable.getParameters().stream()
+ executable.parameters().stream()
.collect(toMap(v -> v, v -> v.getSimpleName().toString()));
fixReservedIdentifiers(identifiers);
- return executable.getParameters().stream()
+ return executable.parameters().stream()
.map(
v -> {
String name = v.getSimpleName().toString();
@@ -166,7 +302,9 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
v,
identifiers.get(v),
propertyToGetterName.get(name),
- Optional.ofNullable(builderInitializers.get(name)));
+ Optional.ofNullable(builderInitializers.get(name)),
+ executable.isOptional(name),
+ nullables);
})
.collect(toImmutableSet());
}
@@ -175,7 +313,9 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
VariableElement var,
String identifier,
String getterName,
- Optional<String> builderInitializer) {
+ Optional<String> builderInitializer,
+ boolean hasDefault,
+ Nullables nullables) {
String name = var.getSimpleName().toString();
TypeMirror type = var.asType();
Optional<String> nullableAnnotation = nullableAnnotationFor(var, var.asType());
@@ -185,15 +325,23 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
TypeEncoder.encode(type),
type,
nullableAnnotation,
+ nullables,
getterName,
- builderInitializer);
+ builderInitializer,
+ hasDefault);
}
- private ImmutableMap<String, String> autoAnnotationInitializers(
- TypeElement autoBuilderType, ExecutableElement autoAnnotationMethod) {
+ private ImmutableMap<String, String> propertyInitializers(
+ TypeElement autoBuilderType, Executable executable) {
+ boolean autoAnnotation =
+ MoreElements.getAnnotationMirror(executable.executableElement(), AUTO_ANNOTATION_NAME)
+ .isPresent();
+ if (!autoAnnotation) {
+ return ImmutableMap.of();
+ }
// 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();
+ TypeMirror returnType = executable.builtType();
if (!returnType.getKind().equals(TypeKind.DECLARED)) {
return ImmutableMap.of();
}
@@ -214,12 +362,80 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
return builder.build();
}
- private ExecutableElement findExecutable(
+ /**
+ * Returns a map from property names to the corresponding getters in the built type. The built
+ * type is the return type of the given {@code executable}, and the property names are the names
+ * of its parameters. If the return type is a {@link DeclaredType} {@code Foo} and if every
+ * property name {@code bar} matches a method {@code bar()} or {@code getBar()} in {@code Foo},
+ * then the method returns a map where {@code bar} maps to {@code bar} or {@code getBar}. If these
+ * conditions are not met then the method returns an empty map.
+ *
+ * <p>The method name match is case-insensitive, so we will also accept {@code baR()} or {@code
+ * getbar()}. For a property of type {@code boolean}, we also accept {@code isBar()} (or {@code
+ * isbar()} etc).
+ *
+ * <p>The return type of each getter method must match the type of the corresponding parameter
+ * exactly. This will always be true for our principal use cases, Java records and Kotlin data
+ * classes. For other use cases, we may in the future accept getters where we know how to convert,
+ * for example if the getter has type {@code ImmutableList<Baz>} and the parameter has type
+ * {@code Baz[]}. We already have similar logic for the parameter types of builder setters.
+ */
+ private ImmutableMap<String, String> propertyToGetterName(
+ Executable executable, TypeElement autoBuilderType) {
+ TypeMirror builtType = executable.builtType();
+ if (builtType.getKind() != TypeKind.DECLARED) {
+ return ImmutableMap.of();
+ }
+ TypeElement type = MoreTypes.asTypeElement(builtType);
+ Map<String, ExecutableElement> nameToMethod =
+ MoreElements.getLocalAndInheritedMethods(type, typeUtils(), elementUtils()).stream()
+ .filter(m -> m.getParameters().isEmpty())
+ .filter(m -> !m.getModifiers().contains(Modifier.STATIC))
+ .filter(m -> visibleFrom(autoBuilderType, getPackage(autoBuilderType)))
+ .collect(
+ toMap(
+ m -> m.getSimpleName().toString(),
+ m -> m,
+ (a, b) -> a,
+ () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)));
+ ImmutableMap<String, String> propertyToGetterName =
+ executable.parameters().stream()
+ .map(
+ param -> {
+ String name = param.getSimpleName().toString();
+ // Parameter name is `bar`; we look for `bar()` and `getBar()` (or `getbar()` etc)
+ // in that order. If `bar` is boolean we also look for `isBar()`.
+ ExecutableElement getter = nameToMethod.get(name);
+ if (getter == null) {
+ getter = nameToMethod.get("get" + name);
+ if (getter == null && param.asType().getKind() == TypeKind.BOOLEAN) {
+ getter = nameToMethod.get("is" + name);
+ }
+ }
+ if (getter != null
+ && !typeUtils().isAssignable(getter.getReturnType(), param.asType())
+ && !MoreTypes.equivalence()
+ .equivalent(getter.getReturnType(), param.asType())) {
+ // TODO(b/268680785): we should not need to have two type checks here
+ getter = null;
+ }
+ return new SimpleEntry<>(name, getter);
+ })
+ .filter(entry -> entry.getValue() != null)
+ .collect(
+ toImmutableMap(
+ Map.Entry::getKey, entry -> entry.getValue().getSimpleName().toString()));
+ return (propertyToGetterName.size() == executable.parameters().size())
+ ? propertyToGetterName
+ : ImmutableMap.of();
+ }
+
+ private Executable findExecutable(
TypeElement ofClass,
String callMethod,
TypeElement autoBuilderType,
- ImmutableSet<ExecutableElement> methods) {
- List<ExecutableElement> executables =
+ ImmutableSet<ExecutableElement> methodsInAutoBuilderType) {
+ ImmutableList<Executable> executables =
findRelevantExecutables(ofClass, callMethod, autoBuilderType);
String description =
callMethod.isEmpty() ? "constructor" : "static method named \"" + callMethod + "\"";
@@ -234,28 +450,33 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
case 1:
return executables.get(0);
default:
- return matchingExecutable(autoBuilderType, executables, methods, description);
+ return matchingExecutable(
+ autoBuilderType, executables, methodsInAutoBuilderType, description);
}
}
- private ImmutableList<ExecutableElement> findRelevantExecutables(
+ private ImmutableList<Executable> findRelevantExecutables(
TypeElement ofClass, String callMethod, TypeElement autoBuilderType) {
+ Optional<AnnotationMirror> kotlinMetadata = kotlinMetadataAnnotation(ofClass);
List<? extends Element> elements = ofClass.getEnclosedElements();
- Stream<ExecutableElement> relevantExecutables =
+ Stream<Executable> relevantExecutables =
callMethod.isEmpty()
- ? constructorsIn(elements).stream()
+ ? kotlinMetadata
+ .map(a -> kotlinConstructorsIn(a, ofClass).stream())
+ .orElseGet(() -> constructorsIn(elements).stream().map(Executable::of))
: methodsIn(elements).stream()
.filter(m -> m.getSimpleName().contentEquals(callMethod))
- .filter(m -> m.getModifiers().contains(Modifier.STATIC));
+ .filter(m -> m.getModifiers().contains(Modifier.STATIC))
+ .map(Executable::of);
return relevantExecutables
- .filter(c -> visibleFrom(c, getPackage(autoBuilderType)))
+ .filter(e -> visibleFrom(e.executableElement(), getPackage(autoBuilderType)))
.collect(toImmutableList());
}
- private ExecutableElement matchingExecutable(
+ private Executable matchingExecutable(
TypeElement autoBuilderType,
- List<ExecutableElement> executables,
- ImmutableSet<ExecutableElement> methods,
+ List<Executable> executables,
+ ImmutableSet<ExecutableElement> methodsInAutoBuilderType,
String description) {
// There's more than one visible executable (constructor or method). We try to find the one that
// corresponds to the methods in the @AutoBuilder interface. This is a bit approximate. We're
@@ -266,8 +487,10 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
// more parameters, then this is indeed the one we want. If we later get errors when we try to
// analyze the interface in detail, those are probably legitimate errors and not because we
// picked the wrong executable.
- ImmutableList<ExecutableElement> matches =
- executables.stream().filter(x -> executableMatches(x, methods)).collect(toImmutableList());
+ ImmutableList<Executable> matches =
+ executables.stream()
+ .filter(x -> executableMatches(x, methodsInAutoBuilderType))
+ .collect(toImmutableList());
switch (matches.size()) {
case 0:
throw errorReporter()
@@ -282,9 +505,9 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
default:
// More than one match, let's see if we can find the best one.
}
- int max = matches.stream().mapToInt(c -> c.getParameters().size()).max().getAsInt();
- ImmutableList<ExecutableElement> maxMatches =
- matches.stream().filter(c -> c.getParameters().size() == max).collect(toImmutableList());
+ int max = matches.stream().mapToInt(e -> e.parameters().size()).max().getAsInt();
+ ImmutableList<Executable> maxMatches =
+ matches.stream().filter(c -> c.parameters().size() == max).collect(toImmutableList());
if (maxMatches.size() > 1) {
throw errorReporter()
.abortWithError(
@@ -296,25 +519,14 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
return maxMatches.get(0);
}
- private String executableListString(List<ExecutableElement> executables) {
+ private String executableListString(List<Executable> executables) {
return executables.stream()
- .map(AutoBuilderProcessor::executableString)
+ .map(Object::toString)
.collect(joining("\n ", " ", ""));
}
- static String executableString(ExecutableElement executable) {
- Element nameSource =
- executable.getKind() == ElementKind.CONSTRUCTOR
- ? executable.getEnclosingElement()
- : executable;
- return nameSource.getSimpleName()
- + executable.getParameters().stream()
- .map(v -> v.asType() + " " + v.getSimpleName())
- .collect(joining(", ", "(", ")"));
- }
-
private boolean executableMatches(
- ExecutableElement executable, ImmutableSet<ExecutableElement> methods) {
+ Executable executable, ImmutableSet<ExecutableElement> methodsInAutoBuilderType) {
// Start with the complete set of parameter names and remove them one by one as we find
// corresponding methods. We ignore case, under the assumption that it is unlikely that a case
// difference is going to allow a candidate to match when another one is better.
@@ -326,11 +538,9 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
// There are further constraints, including on the types X and Y, that will later be imposed by
// BuilderMethodClassifier, but here we just require that there be at least one method with
// one of these shapes for foo.
- NavigableSet<String> parameterNames =
- executable.getParameters().stream()
- .map(v -> v.getSimpleName().toString())
- .collect(toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER)));
- for (ExecutableElement method : methods) {
+ NavigableSet<String> parameterNames = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+ parameterNames.addAll(executable.parameterNames());
+ for (ExecutableElement method : methodsInAutoBuilderType) {
String name = method.getSimpleName().toString();
if (name.endsWith("Builder")) {
String property = name.substring(0, name.length() - "Builder".length());
@@ -365,30 +575,89 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
}
}
- private TypeMirror builtType(ExecutableElement executable) {
- switch (executable.getKind()) {
- case CONSTRUCTOR:
- return executable.getEnclosingElement().asType();
- case METHOD:
- return executable.getReturnType();
- default:
- throw new VerifyException("Unexpected executable kind " + executable.getKind());
- }
+ private Optional<AnnotationMirror> kotlinMetadataAnnotation(Element element) {
+ // It would be MUCH simpler if we could just use ofClass.getAnnotation(Metadata.class).
+ // However that would be unsound. We want to shade the Kotlin runtime, including
+ // kotlin.Metadata, so as not to interfere with other things on the annotation classpath that
+ // might have a different version of the runtime. That means that if we referenced
+ // kotlin.Metadata.class here we would actually be referencing
+ // autovalue.shaded.kotlin.Metadata.class. Obviously the Kotlin class doesn't have that
+ // annotation.
+ return element.getAnnotationMirrors().stream()
+ .filter(
+ a ->
+ asTypeElement(a.getAnnotationType())
+ .getQualifiedName()
+ .contentEquals(KOTLIN_METADATA_NAME))
+ .<AnnotationMirror>map(a -> a) // get rid of that stupid wildcard
+ .findFirst();
}
- private String build(ExecutableElement executable) {
- TypeElement enclosing = MoreElements.asType(executable.getEnclosingElement());
- String type = TypeEncoder.encodeRaw(enclosing.asType());
- switch (executable.getKind()) {
- case CONSTRUCTOR:
- boolean generic = !enclosing.getTypeParameters().isEmpty();
- String typeParams = generic ? "<>" : "";
- return "new " + type + typeParams;
- case METHOD:
- return type + "." + executable.getSimpleName();
- default:
- throw new VerifyException("Unexpected executable kind " + executable.getKind());
+ /**
+ * Use Kotlin reflection to build {@link Executable} instances for the constructors in {@code
+ * ofClass} that include information about which parameters have default values.
+ */
+ private ImmutableList<Executable> kotlinConstructorsIn(
+ AnnotationMirror metadata, TypeElement ofClass) {
+ ImmutableMap<String, AnnotationValue> annotationValues =
+ AnnotationMirrors.getAnnotationValuesWithDefaults(metadata).entrySet().stream()
+ .collect(toImmutableMap(e -> e.getKey().getSimpleName().toString(), e -> e.getValue()));
+ // We match the KmConstructor instances with the ExecutableElement instances based on the
+ // parameter names. We could possibly just assume that the constructors are in the same order.
+ Map<ImmutableSet<String>, ExecutableElement> map =
+ constructorsIn(ofClass.getEnclosedElements()).stream()
+ .collect(toMap(c -> parameterNames(c), c -> c, (a, b) -> a, LinkedHashMap::new));
+ ImmutableMap<ImmutableSet<String>, ExecutableElement> paramNamesToConstructor =
+ ImmutableMap.copyOf(map);
+ KotlinClassHeader header =
+ new KotlinClassHeader(
+ (Integer) annotationValues.get("k").getValue(),
+ intArrayValue(annotationValues.get("mv")),
+ stringArrayValue(annotationValues.get("d1")),
+ stringArrayValue(annotationValues.get("d2")),
+ (String) annotationValues.get("xs").getValue(),
+ (String) annotationValues.get("pn").getValue(),
+ (Integer) annotationValues.get("xi").getValue());
+ KotlinClassMetadata.Class classMetadata =
+ (KotlinClassMetadata.Class) KotlinClassMetadata.read(header);
+ KmClass kmClass = classMetadata.toKmClass();
+ ImmutableList.Builder<Executable> kotlinConstructorsBuilder = ImmutableList.builder();
+ for (KmConstructor constructor : kmClass.getConstructors()) {
+ ImmutableSet.Builder<String> allBuilder = ImmutableSet.builder();
+ ImmutableSet.Builder<String> optionalBuilder = ImmutableSet.builder();
+ for (KmValueParameter param : constructor.getValueParameters()) {
+ String name = param.getName();
+ allBuilder.add(name);
+ if (Flag.ValueParameter.DECLARES_DEFAULT_VALUE.invoke(param.getFlags())) {
+ optionalBuilder.add(name);
+ }
+ }
+ ImmutableSet<String> optional = optionalBuilder.build();
+ ImmutableSet<String> all = allBuilder.build();
+ ExecutableElement javaConstructor = paramNamesToConstructor.get(all);
+ if (javaConstructor != null) {
+ kotlinConstructorsBuilder.add(Executable.of(javaConstructor, optional));
+ }
}
+ return kotlinConstructorsBuilder.build();
+ }
+
+ private static int[] intArrayValue(AnnotationValue value) {
+ @SuppressWarnings("unchecked")
+ List<AnnotationValue> list = (List<AnnotationValue>) value.getValue();
+ return list.stream().mapToInt(v -> (int) v.getValue()).toArray();
+ }
+
+ private static String[] stringArrayValue(AnnotationValue value) {
+ @SuppressWarnings("unchecked")
+ List<AnnotationValue> list = (List<AnnotationValue>) value.getValue();
+ return list.stream().map(AnnotationValue::getValue).toArray(String[]::new);
+ }
+
+ private static ImmutableSet<String> parameterNames(ExecutableElement executableElement) {
+ return executableElement.getParameters().stream()
+ .map(v -> v.getSimpleName().toString())
+ .collect(toImmutableSet());
}
private static final ElementKind ELEMENT_KIND_RECORD = elementKindRecord();
@@ -453,4 +722,70 @@ public class AutoBuilderProcessor extends AutoValueishProcessor {
// TODO(b/183005059): implement
return Optional.empty();
}
+
+ private void buildAnnotation(
+ TypeElement autoBuilderType, TypeElement annotationType, String callMethod) {
+ if (!callMethod.isEmpty()) {
+ errorReporter()
+ .abortWithError(
+ autoBuilderType,
+ "[AutoBuilderAnnotationMethod] @AutoBuilder for an annotation must have an empty"
+ + " callMethod, not \"%s\"",
+ callMethod);
+ }
+ String autoAnnotationClassName =
+ generatedClassName(autoBuilderType, AUTO_ANNOTATION_CLASS_PREFIX);
+ TypeElement autoAnnotationClass = elementUtils().getTypeElement(autoAnnotationClassName);
+ if (autoAnnotationClass != null) {
+ processType(autoBuilderType, autoAnnotationClass, "newAnnotation");
+ return;
+ }
+ AutoBuilderAnnotationTemplateVars vars = new AutoBuilderAnnotationTemplateVars();
+ vars.autoBuilderType = TypeEncoder.encode(autoBuilderType.asType());
+ vars.props = annotationBuilderPropertySet(annotationType);
+ vars.pkg = TypeSimplifier.packageNameOf(autoBuilderType);
+ vars.generated =
+ generatedAnnotation(elementUtils(), processingEnv.getSourceVersion())
+ .map(annotation -> TypeEncoder.encode(annotation.asType()))
+ .orElse("");
+ vars.className = TypeSimplifier.simpleNameOf(autoAnnotationClassName);
+ vars.annotationType = TypeEncoder.encode(annotationType.asType());
+ String text = vars.toText();
+ text = TypeEncoder.decode(text, processingEnv, vars.pkg, /* baseType= */ javaLangVoid);
+ text = Reformatter.fixup(text);
+ writeSourceFile(autoAnnotationClassName, text, autoBuilderType);
+ addDeferredType(autoBuilderType, autoAnnotationClassName);
+ }
+
+ private ImmutableSet<Property> annotationBuilderPropertySet(TypeElement annotationType) {
+ // Annotation methods can't have their own annotations so there's nowhere for us to discover
+ // a user @Nullable. We can only use our default @Nullable type annotation.
+ Nullables nullables = Nullables.fromMethods(processingEnv, ImmutableList.of());
+ // Translate the annotation elements into fake Property instances. We're really only interested
+ // in the name and type, so we can use them to declare a parameter of the generated
+ // @AutoAnnotation method. We'll generate a parameter for every element, even elements that
+ // don't have setters in the builder. The generated builder implementation will pass the default
+ // value from the annotation to those parameters.
+ return methodsIn(annotationType.getEnclosedElements()).stream()
+ .filter(m -> m.getParameters().isEmpty() && !m.getModifiers().contains(Modifier.STATIC))
+ .map(method -> annotationBuilderProperty(method, nullables))
+ .collect(toImmutableSet());
+ }
+
+ private static Property annotationBuilderProperty(
+ ExecutableElement annotationMethod,
+ Nullables nullables) {
+ String name = annotationMethod.getSimpleName().toString();
+ TypeMirror type = annotationMethod.getReturnType();
+ return new Property(
+ name,
+ name,
+ TypeEncoder.encode(type),
+ type,
+ /* nullableAnnotation= */ Optional.empty(),
+ nullables,
+ /* getter= */ "",
+ /* maybeBuilderInitializer= */ Optional.empty(),
+ /* hasDefault= */ false);
+ }
}
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 4d19d216..72550b71 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
@@ -53,7 +53,7 @@ import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
* one-of} types; user code never references this class.
*
* @author Éamonn McManus
- * @see <a href="https://github.com/google/auto/tree/master/value">AutoValue User's Guide</a>
+ * @see <a href="https://github.com/google/auto/tree/main/value">AutoValue User's Guide</a>
*/
@AutoService(Processor.class)
@SupportedAnnotationTypes(AUTO_ONE_OF_NAME)
@@ -111,8 +111,9 @@ public class AutoOneOfProcessor extends AutoValueishProcessor {
AutoOneOfTemplateVars vars = new AutoOneOfTemplateVars();
vars.generatedClass = TypeSimplifier.simpleNameOf(subclass);
vars.propertyToKind = propertyToKind;
- defineSharedVarsForType(autoOneOfType, methods, vars);
- defineVarsForType(autoOneOfType, vars, propertyMethodsAndTypes, kindGetter);
+ Nullables nullables = Nullables.fromMethods(processingEnv, methods);
+ defineSharedVarsForType(autoOneOfType, methods, nullables, vars);
+ defineVarsForType(autoOneOfType, vars, propertyMethodsAndTypes, kindGetter, nullables);
String text = vars.toText();
text = TypeEncoder.decode(text, processingEnv, vars.pkg, autoOneOfType.asType());
@@ -257,10 +258,14 @@ public class AutoOneOfProcessor extends AutoValueishProcessor {
TypeElement type,
AutoOneOfTemplateVars vars,
ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes,
- ExecutableElement kindGetter) {
+ ExecutableElement kindGetter,
+ Nullables nullables) {
vars.props =
propertySet(
- propertyMethodsAndTypes, ImmutableListMultimap.of(), ImmutableListMultimap.of());
+ propertyMethodsAndTypes,
+ /* annotatedPropertyFields= */ ImmutableListMultimap.of(),
+ /* annotatedPropertyMethods= */ ImmutableListMultimap.of(),
+ nullables);
vars.kindGetter = kindGetter.getSimpleName().toString();
vars.kindType = TypeEncoder.encode(kindGetter.getReturnType());
TypeElement javaIoSerializable = elementUtils().getTypeElement("java.io.Serializable");
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 9fbc1652..db711857 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
@@ -22,8 +22,6 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import java.util.Optional;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.util.Types;
/**
* Variables to substitute into the autovalue.vm or builder.vm template.
@@ -114,7 +112,7 @@ abstract class AutoValueOrBuilderTemplateVars extends AutoValueishTemplateVars {
* <li>it has a property-builder method (in which case it defaults to empty).
* </ul>
*/
- ImmutableSet<Property> builderRequiredProperties = ImmutableSet.of();
+ BuilderRequiredProperties builderRequiredProperties = BuilderRequiredProperties.EMPTY;
/**
* A map from property names to information about the associated property getter. A property
@@ -126,15 +124,14 @@ abstract class AutoValueOrBuilderTemplateVars extends AutoValueishTemplateVars {
/**
* True if the generated builder should have a second constructor with a parameter of the built
- * class. The constructor produces a new builder that starts off with the values from the
+ * type. The constructor produces a new builder that starts off with the values from the
* parameter.
*/
Boolean toBuilderConstructor;
/**
* Any {@code toBuilder()} methods, that is methods that return the builder type. AutoBuilder does
- * not currently support this, but it's included in these shared variables to simplify the
- * template.
+ * not support this, but it's included in these shared variables to simplify the template.
*/
ImmutableList<SimpleMethod> toBuilderMethods;
@@ -150,7 +147,4 @@ abstract class AutoValueOrBuilderTemplateVars extends AutoValueishTemplateVars {
* subclasses)
*/
Boolean isFinal = false;
-
- /** The type utilities returned by {@link ProcessingEnvironment#getTypeUtils()}. */
- Types types;
}
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 4479a056..73815f28 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
@@ -57,7 +57,7 @@ import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
* Javac annotation processor (compiler plugin) for value types; user code never references this
* class.
*
- * @see <a href="https://github.com/google/auto/tree/master/value">AutoValue User's Guide</a>
+ * @see <a href="https://github.com/google/auto/tree/main/value">AutoValue User's Guide</a>
* @author Éamonn McManus
*/
@AutoService(Processor.class)
@@ -178,7 +178,7 @@ public class AutoValueProcessor extends AutoValueishProcessor {
.abortWithError(
type,
"[AutoValueImplAnnotation] @AutoValue may not be used to implement an annotation"
- + " interface; try using @AutoAnnotation instead");
+ + " interface; try using @AutoAnnotation or @AutoBuilder instead");
}
// We are going to classify the methods of the @AutoValue class into several categories.
@@ -243,10 +243,16 @@ public class AutoValueProcessor extends AutoValueishProcessor {
String finalSubclass = TypeSimplifier.simpleNameOf(generatedSubclassName(type, 0));
AutoValueTemplateVars vars = new AutoValueTemplateVars();
- vars.types = processingEnv.getTypeUtils();
vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION);
- defineSharedVarsForType(type, methods, vars);
- defineVarsForType(type, vars, toBuilderMethods, propertyMethodsAndTypes, builder);
+ Nullables nullables = Nullables.fromMethods(processingEnv, methods);
+ defineSharedVarsForType(type, methods, nullables, vars);
+ defineVarsForType(
+ type,
+ vars,
+ toBuilderMethods,
+ propertyMethodsAndTypes,
+ builder,
+ nullables);
vars.builtType = vars.origClass + vars.actualTypes;
vars.build = "new " + finalSubclass + vars.actualTypes;
@@ -423,7 +429,8 @@ public class AutoValueProcessor extends AutoValueishProcessor {
AutoValueTemplateVars vars,
ImmutableSet<ExecutableElement> toBuilderMethods,
ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes,
- Optional<BuilderSpec.Builder> maybeBuilder) {
+ Optional<BuilderSpec.Builder> maybeBuilder,
+ Nullables nullables) {
ImmutableSet<ExecutableElement> propertyMethods = propertyMethodsAndTypes.keySet();
vars.toBuilderMethods =
toBuilderMethods.stream().map(SimpleMethod::new).collect(toImmutableList());
@@ -431,15 +438,19 @@ public class AutoValueProcessor extends AutoValueishProcessor {
ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyFields =
propertyFieldAnnotationMap(type, propertyMethods);
ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyMethods =
- propertyMethodAnnotationMap(type, propertyMethods);
+ propertyMethodAnnotationMap(type, propertyMethods, typeUtils());
vars.props =
- propertySet(propertyMethodsAndTypes, annotatedPropertyFields, annotatedPropertyMethods);
+ propertySet(
+ propertyMethodsAndTypes,
+ annotatedPropertyFields,
+ annotatedPropertyMethods,
+ nullables);
// Check for @AutoValue.Builder and add appropriate variables if it is present.
maybeBuilder.ifPresent(
builder -> {
ImmutableBiMap<ExecutableElement, String> methodToPropertyName =
propertyNameToMethodMap(propertyMethods).inverse();
- builder.defineVarsForAutoValue(vars, methodToPropertyName);
+ builder.defineVarsForAutoValue(vars, methodToPropertyName, nullables);
vars.builderName = "Builder";
vars.builderAnnotations = copiedClassAnnotations(builder.builderType());
});
@@ -493,7 +504,7 @@ public class AutoValueProcessor extends AutoValueishProcessor {
if (Collections.disjoint(a, b)) {
return a;
} else {
- return ImmutableSet.copyOf(difference(a, b));
+ return difference(a, b).immutableCopy();
}
}
}
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 31f1ec1c..b7bee429 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
@@ -20,6 +20,7 @@ import static com.google.auto.common.GeneratedAnnotations.generatedAnnotation;
import static com.google.auto.common.MoreElements.getPackage;
import static com.google.auto.common.MoreElements.isAnnotationPresent;
import static com.google.auto.common.MoreStreams.toImmutableList;
+import static com.google.auto.common.MoreStreams.toImmutableMap;
import static com.google.auto.common.MoreStreams.toImmutableSet;
import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_PACKAGE_NAME;
import static com.google.auto.value.processor.ClassNames.COPY_ANNOTATIONS_NAME;
@@ -27,7 +28,6 @@ import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.Sets.union;
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;
@@ -78,6 +78,7 @@ import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleAnnotationValueVisitor8;
@@ -98,8 +99,9 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
/**
* 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.
+ * The corresponding value tells the name of the missing type, if known, or is empty otherwise.
*/
- private final List<String> deferredTypeNames = new ArrayList<>();
+ private final Map<String, String> deferredTypeNames = new LinkedHashMap<>();
AutoValueishProcessor(String annotationClassName, boolean appliesToInterfaces) {
this.annotationClassName = annotationClassName;
@@ -114,13 +116,11 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
private String simpleAnnotationName;
private ErrorReporter errorReporter;
- private Nullables nullables;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
errorReporter = new ErrorReporter(processingEnv);
- nullables = new Nullables(processingEnv);
annotationType = elementUtils().getTypeElement(annotationClassName);
if (annotationType != null) {
simpleAnnotationName = annotationType.getSimpleName().toString();
@@ -145,7 +145,7 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
* This is used by tests.
*/
final ImmutableList<String> deferredTypeNames() {
- return ImmutableList.copyOf(deferredTypeNames);
+ return ImmutableList.copyOf(deferredTypeNames.keySet());
}
@Override
@@ -154,7 +154,7 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
}
/**
- * A property of an {@code @AutoValue} or {@code @AutoOneOf} class, defined by one of its abstract
+ * A property of an {@code @AutoValue} (etc) class, defined by one of its abstract
* methods. An instance of this class is made available to the Velocity template engine for each
* property. The public methods of this class define JavaBeans-style properties that are
* accessible from templates. For example {@link #getType()} means we can write {@code $p.type}
@@ -166,9 +166,11 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
private final String type;
private final TypeMirror typeMirror;
private final Optional<String> nullableAnnotation;
+ private final ImmutableList<AnnotationMirror> availableNullableTypeAnnotations; // 0 or 1
private final Optionalish optional;
private final String getter;
private final String builderInitializer; // empty, or with initial ` = `.
+ private final boolean hasDefault;
Property(
String name,
@@ -176,30 +178,33 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
String type,
TypeMirror typeMirror,
Optional<String> nullableAnnotation,
+ Nullables nullables,
String getter,
- Optional<String> maybeBuilderInitializer) {
+ Optional<String> maybeBuilderInitializer,
+ boolean hasDefault) {
this.name = name;
this.identifier = identifier;
this.type = type;
this.typeMirror = typeMirror;
this.nullableAnnotation = nullableAnnotation;
+ this.availableNullableTypeAnnotations = nullables.nullableTypeAnnotations();
this.optional = Optionalish.createIfOptional(typeMirror);
this.builderInitializer =
maybeBuilderInitializer.isPresent()
? " = " + maybeBuilderInitializer.get()
- : builderInitializer();
+ : builderInitializer(typeMirror, nullableAnnotation);
this.getter = getter;
+ this.hasDefault = hasDefault;
}
/**
- * 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()}.
+ * Returns the appropriate initializer for a builder property. The caller of the {@code
+ * Property} 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() {
+ private static String builderInitializer(
+ TypeMirror typeMirror, Optional<String> nullableAnnotation) {
if (nullableAnnotation.isPresent()) {
return "";
}
@@ -211,6 +216,31 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
}
/**
+ * Returns the appropriate type for a builder field that will eventually be assigned to this
+ * property. This is the same as the final property type, except that it may have an additional
+ * {@code @Nullable} annotation. Some builder fields start off null and then acquire a value
+ * when the corresponding setter is called. Builder fields should have an extra
+ * {@code @Nullable} if all of the following conditions are met:
+ *
+ * <ul>
+ * <li>the property is not primitive;
+ * <li>the property is not already nullable;
+ * <li>there is no explicit initializer (for example {@code Optional} properties start off as
+ * {@code Optional.empty()});
+ * <li>we have found a {@code @Nullable} type annotation that can be applied.
+ * </ul>
+ */
+ public String getBuilderFieldType() {
+ if (typeMirror.getKind().isPrimitive()
+ || nullableAnnotation.isPresent()
+ || !builderInitializer.isEmpty()
+ || availableNullableTypeAnnotations.isEmpty()) {
+ return type;
+ }
+ return TypeEncoder.encodeWithAnnotations(typeMirror, availableNullableTypeAnnotations);
+ }
+
+ /**
* 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
@@ -231,7 +261,7 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
return name;
}
- public TypeMirror getTypeMirror() {
+ TypeMirror getTypeMirror() {
return typeMirror;
}
@@ -279,12 +309,16 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
/**
* Returns the name of the getter method for this property as defined by the {@code @AutoValue}
* or {@code @AutoBuilder} class. For property {@code foo}, this will be {@code foo} or {@code
- * getFoo} or {@code isFoo}. For AutoValue, this will also be the name of a getter method in a
- * builder; in the case of AutoBuilder it will only be that and may be null.
+ * getFoo} or {@code isFoo}. For AutoBuilder, the getter in question is the one that will be
+ * called on the built type to derive the value of the property, in the copy constructor.
*/
public String getGetter() {
return getter;
}
+
+ boolean hasDefault() {
+ return hasDefault;
+ }
}
/** A {@link Property} that corresponds to an abstract getter method in the source. */
@@ -297,18 +331,22 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
String name,
String identifier,
ExecutableElement method,
- String type,
+ TypeMirror typeMirror,
+ String typeString,
ImmutableList<String> fieldAnnotations,
ImmutableList<String> methodAnnotations,
- Optional<String> nullableAnnotation) {
+ Optional<String> nullableAnnotation,
+ Nullables nullables) {
super(
name,
identifier,
- type,
- method.getReturnType(),
+ typeString,
+ typeMirror,
nullableAnnotation,
+ nullables,
method.getSimpleName().toString(),
- Optional.empty());
+ Optional.empty(),
+ /* hasDefault= */ false);
this.method = method;
this.fieldAnnotations = fieldAnnotations;
this.methodAnnotations = methodAnnotations;
@@ -345,6 +383,13 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
}
}
+ void addDeferredType(TypeElement type, String missingType) {
+ // We save the name of the type containing the problem, rather than its TypeElement, because it
+ // is not guaranteed that it will be represented by the same TypeElement on the next round. We
+ // save the name of the missing type for better diagnostics. (It may be empty.)
+ deferredTypeNames.put(type.getQualifiedName().toString(), missingType);
+ }
+
@Override
public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (annotationType == null) {
@@ -359,30 +404,34 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
+ " because the annotation class was not found");
return false;
}
- List<TypeElement> deferredTypes =
- deferredTypeNames.stream()
- .map(name -> elementUtils().getTypeElement(name))
- .collect(toList());
+ ImmutableMap<TypeElement, String> deferredTypes =
+ deferredTypeNames.entrySet().stream()
+ .collect(
+ toImmutableMap(
+ entry -> elementUtils().getTypeElement(entry.getKey()), Map.Entry::getValue));
if (roundEnv.processingOver()) {
// This means that the previous round didn't generate any new sources, so we can't have found
// any new instances of @AutoValue; and we can't have any new types that are the reason a type
// was in deferredTypes.
- for (TypeElement type : deferredTypes) {
- errorReporter.reportError(
- type,
- "[%sUndefined] Did not generate @%s class for %s because it references"
- + " undefined types",
- simpleAnnotationName,
- simpleAnnotationName,
- type.getQualifiedName());
- }
+ deferredTypes.forEach(
+ (type, missing) -> {
+ String including = missing.isEmpty() ? "" : ("including " + missing);
+ errorReporter.reportError(
+ type,
+ "[%sUndefined] Did not generate @%s class for %s because it references"
+ + " undefined types %s",
+ simpleAnnotationName,
+ simpleAnnotationName,
+ type.getQualifiedName(),
+ including);
+ });
return false;
}
Collection<? extends Element> annotatedElements =
roundEnv.getElementsAnnotatedWith(annotationType);
List<TypeElement> types =
new ImmutableList.Builder<TypeElement>()
- .addAll(deferredTypes)
+ .addAll(deferredTypes.keySet())
.addAll(ElementFilter.typesIn(annotatedElements))
.build();
deferredTypeNames.clear();
@@ -396,10 +445,8 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
// We abandoned this type, but only because we needed another type that it references and
// that other type was missing. It is possible that the missing type will be generated by
// further annotation processing, so we will try again on the next round (perhaps failing
- // again and adding it back to the list). We save the name of the @AutoValue type rather
- // than its TypeElement because it is not guaranteed that it will be represented by
- // the same TypeElement on the next round.
- deferredTypeNames.add(type.getQualifiedName().toString());
+ // again and adding it back to the list).
+ addDeferredType(type, e.getMessage());
} catch (RuntimeException e) {
String trace = Throwables.getStackTraceAsString(e);
errorReporter.reportError(
@@ -481,7 +528,8 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
final ImmutableSet<Property> propertySet(
ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes,
ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyFields,
- ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyMethods) {
+ ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyMethods,
+ Nullables nullables) {
ImmutableBiMap<ExecutableElement, String> methodToPropertyName =
propertyNameToMethodMap(propertyMethodsAndTypes.keySet()).inverse();
Map<ExecutableElement, String> methodToIdentifier = new LinkedHashMap<>(methodToPropertyName);
@@ -490,7 +538,7 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
ImmutableSet.Builder<Property> props = ImmutableSet.builder();
propertyMethodsAndTypes.forEach(
(propertyMethod, returnType) -> {
- String propertyType =
+ String propertyTypeString =
TypeEncoder.encodeWithAnnotations(
returnType, ImmutableList.of(), getExcludedAnnotationTypes(propertyMethod));
String propertyName = methodToPropertyName.get(propertyMethod);
@@ -506,10 +554,12 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
propertyName,
identifier,
propertyMethod,
- propertyType,
+ returnType,
+ propertyTypeString,
fieldAnnotations,
methodAnnotations,
- nullableAnnotation);
+ nullableAnnotation,
+ nullables);
props.add(p);
if (p.isNullable() && returnType.getKind().isPrimitive()) {
errorReporter()
@@ -524,7 +574,10 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
/** Defines the template variables that are shared by AutoValue, AutoOneOf, and AutoBuilder. */
final void defineSharedVarsForType(
- TypeElement type, ImmutableSet<ExecutableElement> methods, AutoValueishTemplateVars vars) {
+ TypeElement type,
+ ImmutableSet<ExecutableElement> methods,
+ Nullables nullables,
+ AutoValueishTemplateVars vars) {
vars.pkg = TypeSimplifier.packageNameOf(type);
vars.origClass = TypeSimplifier.classNameOf(type);
vars.simpleClassName = TypeSimplifier.simpleNameOf(vars.origClass);
@@ -541,8 +594,8 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
vars.toString = methodsToGenerate.containsKey(ObjectMethod.TO_STRING);
vars.equals = methodsToGenerate.containsKey(ObjectMethod.EQUALS);
vars.hashCode = methodsToGenerate.containsKey(ObjectMethod.HASH_CODE);
- Optional<AnnotationMirror> nullable = nullables.appropriateNullableGivenMethods(methods);
- vars.equalsParameterType = equalsParameterType(methodsToGenerate, nullable);
+ vars.equalsParameterType =
+ equalsParameterType(methodsToGenerate, nullables);
vars.serialVersionUID = getSerialVersionUID(type);
}
@@ -663,8 +716,7 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
* parameter type for a parameter.
*/
static Optional<String> nullableAnnotationFor(Element element, TypeMirror elementType) {
- List<? extends AnnotationMirror> typeAnnotations = elementType.getAnnotationMirrors();
- if (nullableAnnotationIndex(typeAnnotations).isPresent()) {
+ if (isNullable(elementType)) {
return Optional.of("");
}
List<? extends AnnotationMirror> elementAnnotations = element.getAnnotationMirrors();
@@ -684,6 +736,34 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
.findFirst();
}
+ private static boolean isNullable(TypeMirror type) {
+ return isNullable(type, 0);
+ }
+
+ private static boolean isNullable(TypeMirror type, int depth) {
+ // Some versions of the Eclipse compiler can report that the upper bound of a type variable T
+ // is another T, and if you ask for the upper bound of that other T you'll get a third T, and so
+ // ad infinitum. To avoid StackOverflowError, we bottom out after 10 iterations.
+ if (depth > 10) {
+ return false;
+ }
+ List<? extends AnnotationMirror> typeAnnotations = type.getAnnotationMirrors();
+ // TODO(emcmanus): also check if there is a @NonNull bound and return false if so.
+ if (nullableAnnotationIndex(typeAnnotations).isPresent()) {
+ return true;
+ }
+ if (type.getKind().equals(TypeKind.TYPEVAR)) {
+ TypeVariable typeVariable = MoreTypes.asTypeVariable(type);
+ TypeMirror bound = typeVariable.getUpperBound();
+ if (bound.getKind().equals(TypeKind.INTERSECTION)) {
+ return MoreTypes.asIntersection(bound).getBounds().stream()
+ .allMatch(t -> isNullable(t, depth + 1));
+ }
+ return isNullable(bound, depth + 1);
+ }
+ return false;
+ }
+
private static boolean isNullable(AnnotationMirror annotation) {
return annotation.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable");
}
@@ -824,7 +904,7 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
* @param nullable the type of a {@code @Nullable} type annotation that we have found, if any
*/
static String equalsParameterType(
- Map<ObjectMethod, ExecutableElement> methodsToGenerate, Optional<AnnotationMirror> nullable) {
+ Map<ObjectMethod, ExecutableElement> methodsToGenerate, Nullables nullables) {
ExecutableElement equals = methodsToGenerate.get(ObjectMethod.EQUALS);
if (equals == null) {
return ""; // this will not be referenced because no equals method will be generated
@@ -834,10 +914,10 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
// The @Nullable we add will be a type annotation, but if the parameter already has @Nullable
// then that might be a type annotation or an annotation on the parameter.
ImmutableList<AnnotationMirror> extraAnnotations =
- nullable.isPresent() && !nullableAnnotationFor(equals, parameterType).isPresent()
- ? ImmutableList.of(nullable.get())
- : ImmutableList.of();
- return TypeEncoder.encodeWithAnnotations(parameterType, extraAnnotations, ImmutableSet.of());
+ nullableAnnotationFor(equals, parameterType).isPresent()
+ ? ImmutableList.of()
+ : nullables.nullableTypeAnnotations();
+ return TypeEncoder.encodeWithAnnotations(parameterType, extraAnnotations);
}
/**
@@ -984,8 +1064,11 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
}
/** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */
- ImmutableList<AnnotationMirror> annotationsToCopy(
- Element autoValueType, Element typeOrMethod, Set<String> excludedAnnotations) {
+ static ImmutableList<AnnotationMirror> annotationsToCopy(
+ Element autoValueType,
+ Element typeOrMethod,
+ Set<String> excludedAnnotations,
+ Types typeUtils) {
ImmutableList.Builder<AnnotationMirror> result = ImmutableList.builder();
for (AnnotationMirror annotation : typeOrMethod.getAnnotationMirrors()) {
String annotationFqName = getAnnotationFqName(annotation);
@@ -993,7 +1076,7 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
// and it should not be in the excludedAnnotations set.
if (!isInAutoValuePackage(annotationFqName)
&& !excludedAnnotations.contains(annotationFqName)
- && annotationVisibleFrom(annotation, autoValueType)) {
+ && annotationVisibleFrom(annotation, autoValueType, typeUtils)) {
result.add(annotation);
}
}
@@ -1006,7 +1089,7 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
* the class name contains {@code Test}, since many AutoValue tests under com.google.auto.value
* define their own annotations.
*/
- private boolean isInAutoValuePackage(String className) {
+ private static boolean isInAutoValuePackage(String className) {
return className.startsWith(AUTO_VALUE_PACKAGE_NAME) && !className.contains("Test");
}
@@ -1041,10 +1124,10 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
}
/** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */
- private ImmutableList<String> copyAnnotations(
+ ImmutableList<String> copyAnnotations(
Element autoValueType, Element typeOrMethod, Set<String> excludedAnnotations) {
ImmutableList<AnnotationMirror> annotationsToCopy =
- annotationsToCopy(autoValueType, typeOrMethod, excludedAnnotations);
+ annotationsToCopy(autoValueType, typeOrMethod, excludedAnnotations, typeUtils());
return annotationStrings(annotationsToCopy);
}
@@ -1052,7 +1135,7 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
* Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of
* {@code TypeMirror} where each type is an annotation type.
*/
- private Set<TypeMirror> getExcludedAnnotationTypes(Element element) {
+ private static Set<TypeMirror> getExcludedAnnotationTypes(Element element) {
Optional<AnnotationMirror> maybeAnnotation =
getAnnotationMirror(element, COPY_ANNOTATIONS_NAME);
if (!maybeAnnotation.isPresent()) {
@@ -1071,14 +1154,14 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
* Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of
* strings that are fully-qualified class names.
*/
- private Set<String> getExcludedAnnotationClassNames(Element element) {
+ static Set<String> getExcludedAnnotationClassNames(Element element) {
return getExcludedAnnotationTypes(element).stream()
.map(MoreTypes::asTypeElement)
.map(typeElement -> typeElement.getQualifiedName().toString())
.collect(toSet());
}
- private static Set<String> getAnnotationsMarkedWithInherited(Element element) {
+ static Set<String> getAnnotationsMarkedWithInherited(Element element) {
return element.getAnnotationMirrors().stream()
.filter(a -> isAnnotationPresent(a.getAnnotationType().asElement(), Inherited.class))
.map(a -> getAnnotationFqName(a))
@@ -1095,18 +1178,18 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
.toString();
}
- final ImmutableListMultimap<ExecutableElement, AnnotationMirror> propertyMethodAnnotationMap(
- TypeElement type, ImmutableSet<ExecutableElement> propertyMethods) {
+ static ImmutableListMultimap<ExecutableElement, AnnotationMirror> propertyMethodAnnotationMap(
+ TypeElement type, ImmutableSet<ExecutableElement> propertyMethods, Types typeUtils) {
ImmutableListMultimap.Builder<ExecutableElement, AnnotationMirror> builder =
ImmutableListMultimap.builder();
for (ExecutableElement propertyMethod : propertyMethods) {
- builder.putAll(propertyMethod, propertyMethodAnnotations(type, propertyMethod));
+ builder.putAll(propertyMethod, propertyMethodAnnotations(type, propertyMethod, typeUtils));
}
return builder.build();
}
- private ImmutableList<AnnotationMirror> propertyMethodAnnotations(
- TypeElement type, ExecutableElement method) {
+ static ImmutableList<AnnotationMirror> propertyMethodAnnotations(
+ TypeElement type, ExecutableElement method, Types typeUtils) {
ImmutableSet<String> excludedAnnotations =
ImmutableSet.<String>builder()
.addAll(getExcludedAnnotationClassNames(method))
@@ -1117,7 +1200,7 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
// they will be output as part of the method's return type.
Set<String> returnTypeAnnotations = getReturnTypeAnnotations(method, a -> true);
Set<String> excluded = union(excludedAnnotations, returnTypeAnnotations);
- return annotationsToCopy(type, method, excluded);
+ return annotationsToCopy(type, method, excluded, typeUtils);
}
final ImmutableListMultimap<ExecutableElement, AnnotationMirror> propertyFieldAnnotationMap(
@@ -1159,10 +1242,10 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
.addAll(returnTypeAnnotations)
.addAll(nonFieldAnnotations)
.build();
- return annotationsToCopy(type, method, excluded);
+ return annotationsToCopy(type, method, excluded, typeUtils());
}
- private Set<String> getReturnTypeAnnotations(
+ private static Set<String> getReturnTypeAnnotations(
ExecutableElement method, Predicate<TypeElement> typeFilter) {
return method.getReturnType().getAnnotationMirrors().stream()
.map(a -> a.getAnnotationType().asElement())
@@ -1177,7 +1260,8 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
return target == null || Arrays.asList(target.value()).contains(ElementType.FIELD);
}
- private boolean annotationVisibleFrom(AnnotationMirror annotation, Element from) {
+ private static boolean annotationVisibleFrom(
+ AnnotationMirror annotation, Element from, Types typeUtils) {
Element annotationElement = annotation.getAnnotationType().asElement();
Visibility visibility = Visibility.effectiveVisibilityOfElement(annotationElement);
switch (visibility) {
@@ -1196,8 +1280,7 @@ abstract class AutoValueishProcessor extends AbstractProcessor {
// AutoValue_Foo is a top-level class, so an annotation on it cannot be in the body of a
// subclass of anything.
return getPackage(annotationElement).equals(getPackage(from))
- || typeUtils()
- .isSubtype(from.asType(), annotationElement.getEnclosingElement().asType());
+ || typeUtils.isSubtype(from.asType(), annotationElement.getEnclosingElement().asType());
case DEFAULT:
return getPackage(annotationElement).equals(getPackage(from));
default:
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 a4336f5e..b0872009 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
@@ -69,6 +69,7 @@ abstract class BuilderMethodClassifier<E extends Element> {
private final Elements elementUtils;
private final TypeMirror builtType;
private final TypeElement builderType;
+ private final ImmutableSet<String> propertiesWithDefaults;
/**
* Property types, rewritten to refer to type variables in the builder. For example, suppose you
@@ -93,6 +94,7 @@ abstract class BuilderMethodClassifier<E extends Element> {
private final Multimap<String, PropertySetter> propertyNameToUnprefixedSetters =
LinkedListMultimap.create();
private final EclipseHack eclipseHack;
+ private final Nullables nullables;
private boolean settersPrefixed;
@@ -101,14 +103,18 @@ abstract class BuilderMethodClassifier<E extends Element> {
ProcessingEnvironment processingEnv,
TypeMirror builtType,
TypeElement builderType,
- ImmutableMap<String, TypeMirror> rewrittenPropertyTypes) {
+ ImmutableMap<String, TypeMirror> rewrittenPropertyTypes,
+ ImmutableSet<String> propertiesWithDefaults,
+ Nullables nullables) {
this.errorReporter = errorReporter;
this.typeUtils = processingEnv.getTypeUtils();
this.elementUtils = processingEnv.getElementUtils();
this.builtType = builtType;
this.builderType = builderType;
this.rewrittenPropertyTypes = rewrittenPropertyTypes;
+ this.propertiesWithDefaults = propertiesWithDefaults;
this.eclipseHack = new EclipseHack(processingEnv);
+ this.nullables = nullables;
}
/**
@@ -193,7 +199,7 @@ abstract class BuilderMethodClassifier<E extends Element> {
propertyBuilder.getBuilderTypeMirror(),
propertyType);
}
- } else if (!hasSetter) {
+ } else if (!hasSetter && !propertiesWithDefaults.contains(property)) {
// We have neither barBuilder() nor setBar(Bar), so we should complain.
String setterName = settersPrefixed ? prefixWithSet(property) : property;
errorReporter.reportError(
@@ -244,8 +250,15 @@ abstract class BuilderMethodClassifier<E extends Element> {
TypeMirror returnType = builderMethodReturnType(method);
if (methodName.endsWith("Builder")) {
- String property = methodName.substring(0, methodName.length() - "Builder".length());
- if (rewrittenPropertyTypes.containsKey(property)) {
+ String prefix = methodName.substring(0, methodName.length() - "Builder".length());
+ String property =
+ rewrittenPropertyTypes.containsKey(prefix)
+ ? prefix
+ : rewrittenPropertyTypes.keySet().stream()
+ .filter(p -> PropertyNames.decapitalizeNormally(p).equals(prefix))
+ .findFirst()
+ .orElse(null);
+ if (property != null) {
PropertyBuilderClassifier propertyBuilderClassifier =
new PropertyBuilderClassifier(
errorReporter,
@@ -254,7 +267,8 @@ abstract class BuilderMethodClassifier<E extends Element> {
this,
this::propertyIsNullable,
rewrittenPropertyTypes,
- eclipseHack);
+ eclipseHack,
+ nullables);
Optional<PropertyBuilder> propertyBuilder =
propertyBuilderClassifier.makePropertyBuilder(method, property);
if (propertyBuilder.isPresent()) {
@@ -427,7 +441,8 @@ abstract class BuilderMethodClassifier<E extends Element> {
this,
this::propertyIsNullable,
rewrittenPropertyTypes,
- eclipseHack);
+ eclipseHack,
+ nullables);
Optional<PropertyBuilder> maybePropertyBuilder =
propertyBuilderClassifier.makePropertyBuilder(method, property);
maybePropertyBuilder.ifPresent(
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoBuilder.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoBuilder.java
index 55a31983..d9900b57 100644
--- a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoBuilder.java
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoBuilder.java
@@ -18,13 +18,12 @@ package com.google.auto.value.processor;
import static com.google.auto.common.MoreStreams.toImmutableBiMap;
import static com.google.auto.common.MoreStreams.toImmutableMap;
-import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.common.base.Equivalence;
-import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -41,18 +40,27 @@ import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.Types;
class BuilderMethodClassifierForAutoBuilder extends BuilderMethodClassifier<VariableElement> {
- private final ExecutableElement executable;
+ private final Executable executable;
private final ImmutableBiMap<VariableElement, String> paramToPropertyName;
private BuilderMethodClassifierForAutoBuilder(
ErrorReporter errorReporter,
ProcessingEnvironment processingEnv,
- ExecutableElement executable,
+ Executable executable,
TypeMirror builtType,
TypeElement builderType,
ImmutableBiMap<VariableElement, String> paramToPropertyName,
- ImmutableMap<String, TypeMirror> rewrittenPropertyTypes) {
- super(errorReporter, processingEnv, builtType, builderType, rewrittenPropertyTypes);
+ ImmutableMap<String, TypeMirror> rewrittenPropertyTypes,
+ ImmutableSet<String> propertiesWithDefaults,
+ Nullables nullables) {
+ super(
+ errorReporter,
+ processingEnv,
+ builtType,
+ builderType,
+ rewrittenPropertyTypes,
+ propertiesWithDefaults,
+ nullables);
this.executable = executable;
this.paramToPropertyName = paramToPropertyName;
}
@@ -66,6 +74,8 @@ class BuilderMethodClassifierForAutoBuilder extends BuilderMethodClassifier<Vari
* @param executable the constructor or static method that AutoBuilder will call.
* @param builtType the type to be built.
* @param builderType the builder class or interface within {@code ofClass}.
+ * @param propertiesWithDefaults properties that have a default value, so it is not an error for
+ * them not to have a setter.
* @return an {@code Optional} that contains the results of the classification if it was
* successful or nothing if it was not.
*/
@@ -73,11 +83,13 @@ class BuilderMethodClassifierForAutoBuilder extends BuilderMethodClassifier<Vari
Iterable<ExecutableElement> methods,
ErrorReporter errorReporter,
ProcessingEnvironment processingEnv,
- ExecutableElement executable,
+ Executable executable,
TypeMirror builtType,
- TypeElement builderType) {
+ TypeElement builderType,
+ ImmutableSet<String> propertiesWithDefaults,
+ Nullables nullables) {
ImmutableBiMap<VariableElement, String> paramToPropertyName =
- executable.getParameters().stream()
+ executable.parameters().stream()
.collect(toImmutableBiMap(v -> v, v -> v.getSimpleName().toString()));
ImmutableMap<String, TypeMirror> rewrittenPropertyTypes =
rewriteParameterTypes(executable, builderType, errorReporter, processingEnv.getTypeUtils());
@@ -89,7 +101,9 @@ class BuilderMethodClassifierForAutoBuilder extends BuilderMethodClassifier<Vari
builtType,
builderType,
paramToPropertyName,
- rewrittenPropertyTypes);
+ rewrittenPropertyTypes,
+ propertiesWithDefaults,
+ nullables);
if (classifier.classifyMethods(methods, false)) {
return Optional.of(classifier);
} else {
@@ -134,11 +148,11 @@ class BuilderMethodClassifierForAutoBuilder extends BuilderMethodClassifier<Vari
// MoreTypes.equivalence to compare those, and that returns true for distinct type variables if
// they have the same name and bounds.
private static ImmutableMap<String, TypeMirror> rewriteParameterTypes(
- ExecutableElement executable,
+ Executable executable,
TypeElement builderType,
ErrorReporter errorReporter,
Types typeUtils) {
- ImmutableList<TypeParameterElement> executableTypeParams = executableTypeParams(executable);
+ ImmutableList<TypeParameterElement> executableTypeParams = executable.typeParameters();
List<? extends TypeParameterElement> builderTypeParams = builderType.getTypeParameters();
if (!BuilderSpec.sameTypeParameters(executableTypeParams, builderTypeParams)) {
errorReporter.abortWithError(
@@ -146,12 +160,12 @@ class BuilderMethodClassifierForAutoBuilder extends BuilderMethodClassifier<Vari
"[AutoBuilderTypeParams] Builder type parameters %s must match type parameters %s of %s",
TypeEncoder.typeParametersString(builderTypeParams),
TypeEncoder.typeParametersString(executableTypeParams),
- AutoBuilderProcessor.executableString(executable));
+ executable);
}
if (executableTypeParams.isEmpty()) {
// Optimization for a common case. No point in doing all that type visiting if we have no
// variables to substitute.
- return executable.getParameters().stream()
+ return executable.parameters().stream()
.collect(toImmutableMap(v -> v.getSimpleName().toString(), Element::asType));
}
Map<Equivalence.Wrapper<TypeVariable>, TypeMirror> typeVariables = new LinkedHashMap<>();
@@ -162,32 +176,13 @@ class BuilderMethodClassifierForAutoBuilder extends BuilderMethodClassifier<Vari
}
Function<TypeVariable, TypeMirror> substitute =
v -> typeVariables.get(MoreTypes.equivalence().wrap(v));
- return executable.getParameters().stream()
+ return executable.parameters().stream()
.collect(
toImmutableMap(
v -> v.getSimpleName().toString(),
v -> TypeVariables.substituteTypeVariables(v.asType(), substitute, typeUtils)));
}
- private static ImmutableList<TypeParameterElement> executableTypeParams(
- ExecutableElement executable) {
- switch (executable.getKind()) {
- case CONSTRUCTOR:
- // A constructor can have its own type parameters, in addition to any that its containing
- // class has. That's pretty unusual, but we allow it, requiring the builder to have type
- // parameters that are the concatenation of the class's and the constructor's.
- TypeElement container = MoreElements.asType(executable.getEnclosingElement());
- return ImmutableList.<TypeParameterElement>builder()
- .addAll(container.getTypeParameters())
- .addAll(executable.getTypeParameters())
- .build();
- case METHOD:
- return ImmutableList.copyOf(executable.getTypeParameters());
- default:
- throw new VerifyException("Unexpected executable kind " + executable.getKind());
- }
- }
-
@Override
Optional<String> propertyForBuilderGetter(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
@@ -224,10 +219,7 @@ class BuilderMethodClassifierForAutoBuilder extends BuilderMethodClassifier<Vari
@Override
String propertyString(VariableElement propertyElement) {
- return "parameter \""
- + propertyElement.getSimpleName()
- + "\" of "
- + AutoBuilderProcessor.executableString(executable);
+ return "parameter \"" + propertyElement.getSimpleName() + "\" of " + executable;
}
@Override
@@ -237,7 +229,7 @@ class BuilderMethodClassifierForAutoBuilder extends BuilderMethodClassifier<Vari
@Override
String getterMustMatch() {
- return "a parameter of " + AutoBuilderProcessor.executableString(executable);
+ return "a parameter of " + executable;
}
@Override
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java
index dde449bb..67b7c01b 100644
--- a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java
@@ -40,8 +40,16 @@ class BuilderMethodClassifierForAutoValue extends BuilderMethodClassifier<Execut
TypeMirror builtType,
TypeElement builderType,
ImmutableBiMap<ExecutableElement, String> getterToPropertyName,
- ImmutableMap<String, TypeMirror> rewrittenPropertyTypes) {
- super(errorReporter, processingEnv, builtType, builderType, rewrittenPropertyTypes);
+ ImmutableMap<String, TypeMirror> rewrittenPropertyTypes,
+ Nullables nullables) {
+ super(
+ errorReporter,
+ processingEnv,
+ builtType,
+ builderType,
+ rewrittenPropertyTypes,
+ ImmutableSet.of(),
+ nullables);
this.errorReporter = errorReporter;
this.getterToPropertyName = getterToPropertyName;
this.getterNameToGetter =
@@ -74,6 +82,7 @@ class BuilderMethodClassifierForAutoValue extends BuilderMethodClassifier<Execut
TypeElement builderType,
ImmutableBiMap<ExecutableElement, String> getterToPropertyName,
ImmutableMap<String, TypeMirror> rewrittenPropertyTypes,
+ Nullables nullables,
boolean autoValueHasToBuilder) {
BuilderMethodClassifier<ExecutableElement> classifier =
new BuilderMethodClassifierForAutoValue(
@@ -82,7 +91,8 @@ class BuilderMethodClassifierForAutoValue extends BuilderMethodClassifier<Execut
autoValueClass.asType(),
builderType,
getterToPropertyName,
- rewrittenPropertyTypes);
+ rewrittenPropertyTypes,
+ nullables);
if (classifier.classifyMethods(methods, autoValueHasToBuilder)) {
return Optional.of(classifier);
} else {
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderRequiredProperties.java b/value/src/main/java/com/google/auto/value/processor/BuilderRequiredProperties.java
new file mode 100644
index 00000000..e99f8fd8
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderRequiredProperties.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.processor;
+
+import static com.google.auto.common.MoreStreams.toImmutableList;
+import static com.google.auto.common.MoreStreams.toImmutableMap;
+import static java.lang.Math.min;
+import static java.util.stream.Collectors.joining;
+
+import com.google.auto.value.processor.AutoValueishProcessor.Property;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+/**
+ * Code generation to track which properties have been set in a builder.
+ *
+ * <p>Every property in an {@code @AutoValue} or {@code @AutoBuilder} builder must be set before the
+ * {@code build()} method is called, with a few exceptions like {@code @Nullable} and {@code
+ * Optional} properties. That means we must keep track of which ones have in fact been set. We do
+ * that in two ways: for reference (non-primitive) types, we use {@code null} to indicate that the
+ * value has not been set, while for primitive types we use a bitmask where each bit indicates
+ * whether a certain primitive property has been set.
+ *
+ * <p>Additionally, for Kotlin constructors with default parameters, we track exactly which
+ * properties have been set so we can invoke the constructor thas has a bitmask indicating the
+ * properties to be defaulted.
+ *
+ * <p>The public methods in this class are accessed reflectively from the {@code builder.vm}
+ * template. In that template, {@code $builderRequiredProperties} references an instance of this
+ * class corresponding to the builder being generated. A reference like {@code
+ * $builderRequiredProperties.markAsSet($p)} calls the method {@link #markAsSet} with the given
+ * parameter. A reference like {@code $builderRequiredProperties.requiredProperties} is shorthand
+ * for {@link #getRequiredProperties() $builderRequiredProperties.getProperties()}.
+ */
+public abstract class BuilderRequiredProperties {
+ static final BuilderRequiredProperties EMPTY = of(ImmutableSet.of(), ImmutableSet.of());
+
+ // Bitmasks are a bit fiddly because we use them in a couple of ways. The first way is where
+ // we are just using the bitmasks to track which primitive properties have been set. Then if
+ // we have three primitive properties we can just check that the bitmask is (1 << 3) - 1, the
+ // all-ones bitmask, to see that they have all been set. The second way is when we are also
+ // handling optional Kotlin parameters. Then the bitmasks are different: we have one bit for every
+ // property, primitive or not, optional or not. To check that the required primitive properties
+ // have been set, we need to check specific bits. For example if properties 1 and 3 are primitive
+ // then we need to check (~set$0 & ((1 << 1) | (1 << 3))) == 0. That tests that bits 1 and 3 are
+ // set, since if either of them is 0 then it will be 1 in ~set$0 and will survive the AND. We can
+ // also isolate the bits representing optional Kotlin parameters similarly, and pass those to the
+ // special Kotlin constructor that handles default parameters. Kotlin uses bitmasks for that too:
+ // they have one bit per parameter, optional or not, but only the bits for optional parameters
+ // matter. We isolate those bits with `&` operations similar to what was described for primitive
+ // properties. We also need the all-ones bitmask to implement a "copy constructor" builder, which
+ // starts out with all properties set.
+
+ /** All required properties. */
+ final ImmutableSet<Property> requiredProperties;
+
+ /**
+ * The bit index for each tracked property. Properties are tracked if they are primitive, or if
+ * this is a Kotlin constructor with default parameters. Non-tracked properties do not appear in
+ * this map.
+ */
+ final ImmutableMap<Property, Integer> trackedPropertyToIndex;
+
+ /**
+ * The integer fields that store the bitmask. In the usual case, where there are ≤32 tracked
+ * properties, we can pack the bitmask into one integer field. Its type is the smallest one that
+ * fits the required number of bits, for example {@code byte} if there are ≤8 tracked properties.
+ *
+ * <p>If there are {@literal >32} tracked properties, we will pack them into as few integer fields
+ * as possible. For example if there are 75 tracked properties (this can happen) then we will put
+ * numbers 0 to 31 in an {@code int}, 32 to 63 in a second {@code int}, and 64 to 75 in a {@code
+ * short}.
+ *
+ * <p>When there are {@literal >32} tracked properties, we could potentially pack them better if
+ * we used {@code long}. But sometimes AutoValue code gets translated into JavaScript, which
+ * doesn't handle long values natively. By the time you have that many properties you are probably
+ * not going to notice the difference between 5 ints or 2 longs plus an int.
+ */
+ final ImmutableList<BitmaskField> bitmaskFields;
+
+ /**
+ * Represents a field in which we will record which tracked properties from a certain set have
+ * been given a value.
+ */
+ private static class BitmaskField {
+ final Class<?> type;
+ final String name;
+
+ /**
+ * The source representation of the value this field has when all properties have been given a
+ * value.
+ */
+ final String allSetBitmask;
+
+ /**
+ * The source representation of the value this field has when all required properties have been
+ * given a value.
+ */
+ final String allRequiredBitmask;
+
+ BitmaskField(Class<?> type, String name, String allSetBitmask, String allRequiredBitmask) {
+ this.type = type;
+ this.name = name;
+ this.allSetBitmask = allSetBitmask;
+ this.allRequiredBitmask = allRequiredBitmask;
+ }
+ }
+
+ static BuilderRequiredProperties of(
+ ImmutableSet<Property> allProperties, ImmutableSet<Property> requiredProperties) {
+ boolean hasDefaults = allProperties.stream().anyMatch(Property::hasDefault);
+ return hasDefaults
+ ? new WithDefaults(allProperties, requiredProperties)
+ : new NoDefaults(requiredProperties);
+ }
+
+ private BuilderRequiredProperties(
+ ImmutableSet<Property> requiredProperties, ImmutableList<Property> trackedProperties) {
+ this.requiredProperties = requiredProperties;
+
+ int trackedCount = trackedProperties.size();
+ this.trackedPropertyToIndex =
+ IntStream.range(0, trackedCount)
+ .boxed()
+ .collect(toImmutableMap(trackedProperties::get, i -> i));
+
+ this.bitmaskFields =
+ IntStream.range(0, (trackedCount + 31) / 32)
+ .mapToObj(
+ i -> {
+ int bitBase = i * 32;
+ int remainingBits = trackedCount - bitBase;
+ Class<?> type = classForBits(remainingBits);
+ String name = "set$" + i;
+ String allSetBitmask =
+ (remainingBits >= 32) ? "-1" : hex((1 << remainingBits) - 1);
+ String allRequiredBitmask =
+ allRequiredBitmask(trackedProperties, bitBase, remainingBits);
+ return new BitmaskField(type, name, allSetBitmask, allRequiredBitmask);
+ })
+ .collect(toImmutableList());
+ }
+
+ abstract String allRequiredBitmask(
+ ImmutableList<Property> trackedProperties, int bitBase, int remainingBits);
+
+ public ImmutableSet<Property> getRequiredProperties() {
+ return requiredProperties;
+ }
+
+ /**
+ * Returns code to declare any fields needed to track which properties have been set. Each line in
+ * the returned list should appear on a line of its own.
+ */
+ public ImmutableList<String> getFieldDeclarations() {
+ return bitmaskFields.stream()
+ .map(field -> "private " + field.type + " " + field.name + ";")
+ .collect(toImmutableList());
+ }
+
+ /**
+ * Returns code to indicate that all tracked properties have received a value. This is needed in
+ * the {@code toBuilder()} constructor, since it assigns to the corresponding fields directly
+ * without going through their setters.
+ */
+ public ImmutableList<String> getInitToAllSet() {
+ return bitmaskFields.stream()
+ .map(field -> field.name + " = " + cast(field.type, field.allSetBitmask) + ";")
+ .collect(toImmutableList());
+ }
+
+ /**
+ * Returns code to indicate that the given property has been set, if assigning to the property
+ * field is not enough. For reference (non-primitive) properties, assignment <i>is</i> enough, but
+ * for primitive properties we also need to set a bit in the bitmask.
+ */
+ public String markAsSet(Property p) {
+ Integer index = trackedPropertyToIndex.get(p);
+ if (index == null) {
+ return "";
+ }
+ BitmaskField field = bitmaskFields.get(index / 32);
+ // This use-case is why Java reduces int shift amounts mod 32. :-)
+ return field.name + " |= " + cast(field.type, hex(1 << index)) + ";";
+ }
+
+ /**
+ * Returns an expression that is true if the given property is required but has not been set.
+ * Returns null if the property is not required.
+ */
+ public String missingRequiredProperty(Property p) {
+ return requiredProperties.contains(p) ? propertyNotSet(p) : null;
+ }
+
+ /**
+ * Returns an expression that is true if the given property has not been given a value. That's
+ * only different from {@link #missingRequiredProperty} if the property has a Kotlin default. If
+ * so, we don't require it to be set at build time (because Kotlin will supply the default), but
+ * we do require it to be set if it is accessed with a getter on the builder. We don't have access
+ * to Kotlin parameter defaults so we can't arrange for the builder field to have the same default
+ * value. Rather than returning a bogus zero value we say the value is unset.
+ */
+ public String noValueToGet(Property p) {
+ return (requiredProperties.contains(p) || p.hasDefault()) ? propertyNotSet(p) : null;
+ }
+
+ private String propertyNotSet(Property p) {
+ Integer index = trackedPropertyToIndex.get(p);
+ if (index == null) {
+ return "this." + p + " == null";
+ }
+ BitmaskField field = bitmaskFields.get(index / 32);
+ return "(" + field.name + " & " + hex(1 << index) + ") == 0";
+ }
+
+ /**
+ * Returns an expression that is true if any required properties have not been set. Should not be
+ * called if there are no required properties.
+ */
+ public abstract String getAnyMissing();
+
+ /**
+ * Returns additional constructor parameters to indicate what properties have been defaulted, or
+ * an empty string if there are none.
+ */
+ public abstract String getDefaultedBitmaskParameters();
+
+ /**
+ * The smallest primitive integer type that has at least this many bits, or {@code int} if the
+ * number of bits is more than 32.
+ */
+ private static Class<?> classForBits(int bits) {
+ return bits <= 8 ? byte.class : bits <= 16 ? short.class : int.class;
+ }
+
+ private static String cast(Class<?> type, String number) {
+ return (type == int.class) ? number : ("(" + type + ") " + number);
+ }
+
+ @VisibleForTesting
+ static String hex(int number) {
+ if (number >= 0) {
+ if (number < 10) {
+ return Integer.toHexString(number);
+ }
+ if (number <= 0xffff) {
+ return "0x" + Integer.toHexString(number);
+ }
+ }
+ // It's harder to tell 0x7fffffff from 0x7ffffff than to tell 0x7fff_ffff from 0x7ff_ffff.
+ String lowNybble = Integer.toHexString(number & 0xffff);
+ String pad = "000".substring(lowNybble.length() - 1);
+ return "0x" + Integer.toHexString(number >>> 16) + "_" + pad + lowNybble;
+ }
+
+ /** Subclass for when there are no Kotlin default properties. */
+ private static final class NoDefaults extends BuilderRequiredProperties {
+ NoDefaults(ImmutableSet<Property> requiredProperties) {
+ super(requiredProperties, primitivePropertiesIn(requiredProperties));
+ }
+
+ private static ImmutableList<Property> primitivePropertiesIn(
+ ImmutableSet<Property> properties) {
+ return properties.stream().filter(p -> p.getKind().isPrimitive()).collect(toImmutableList());
+ }
+
+ @Override
+ String allRequiredBitmask(
+ ImmutableList<Property> trackedProperties, int bitBase, int remainingBits) {
+ // We have to be a bit careful with sign-extension. If we're using a byte and
+ // the mask is 0xff, then we'll write -1 instead. The comparison set$0 == 0xff
+ // would always fail since the byte value gets sign-extended to 0xffff_ffff.
+ // We should also write -1 if this is not the last field.
+ boolean minusOne = remainingBits >= 32 || remainingBits == 16 || remainingBits == 8;
+ return minusOne ? "-1" : hex((1 << remainingBits) - 1);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>We check the bitmask for primitive properties, and null checks for non-primitive ones.
+ */
+ @Override
+ public String getAnyMissing() {
+ Stream<String> primitiveConditions =
+ bitmaskFields.stream().map(field -> field.name + " != " + field.allRequiredBitmask);
+ Stream<String> nonPrimitiveConditions =
+ requiredProperties.stream()
+ .filter(p -> !trackedPropertyToIndex.containsKey(p))
+ .map(this::missingRequiredProperty);
+ return Stream.concat(primitiveConditions, nonPrimitiveConditions).collect(joining("\n|| "));
+ }
+
+ @Override
+ public String getDefaultedBitmaskParameters() {
+ return "";
+ }
+ }
+
+ /** Subclass for when there are Kotlin default properties. */
+ private static final class WithDefaults extends BuilderRequiredProperties {
+ private final ImmutableList<Property> allProperties;
+
+ WithDefaults(ImmutableSet<Property> allProperties, ImmutableSet<Property> requiredProperties) {
+ super(requiredProperties, allProperties.asList());
+ this.allProperties = allProperties.asList();
+ }
+
+ @Override
+ String allRequiredBitmask(
+ ImmutableList<Property> trackedProperties, int bitBase, int remainingBits) {
+ int requiredBits = 0;
+ for (int bit = 0; bit < remainingBits; bit++) {
+ Property p = trackedProperties.get(bitBase + bit);
+ if (requiredProperties.contains(p)) {
+ requiredBits |= 1 << bit;
+ }
+ }
+ return hex(requiredBits);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Everything can be checked with bitmask operations. If bit <i>i</i> represents a required
+ * property then it must be 1 in the bitmask field. So if we invert it we must get 0, and if we
+ * do that for the field as a whole and AND with a bitmask selecting only required properties we
+ * should get 0.
+ */
+ @Override
+ public String getAnyMissing() {
+ return bitmaskFields.stream()
+ .filter(field -> !field.allRequiredBitmask.equals("0"))
+ .map(field -> "(~" + field.name + " & " + field.allRequiredBitmask + ") != 0")
+ .collect(joining("\n|| "));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>When there are default parameters, we're calling the special constructor that has one or
+ * more bitmask parameters at the end. Bit <i>i</i> is set if parameter <i>i</i> (zero-origin)
+ * has its default value, and then the actual value passed for that parameter is ignored. Our
+ * bitmask field has a 1 for any parameter that has been set, meaning it has a 0 for any
+ * parameter that has been defaulted. So we need to invert it, and we also want to AND it with a
+ * bitmask that selects just the bits for parameters with defaults. (The AND probably isn't
+ * strictly necessary, since the constructor code doesn't actually look at those other bits, but
+ * it seems cleaner.) If the bitmask for parameters with defaults is 0 then we can just use 0
+ * for that bitmask, and if it is ~0 (all 1 bits) then we can skip the AND.
+ *
+ * <p>That special constructor has an additional dummy parameter of type {@code
+ * DefaultConstructorMarker}. We just pass {@code null} to that parameter.
+ */
+ @Override
+ public String getDefaultedBitmaskParameters() {
+ ImmutableList.Builder<Integer> defaultedBitmasksBuilder = ImmutableList.builder();
+ for (int bitBase = 0; bitBase < allProperties.size(); bitBase += 32) {
+ int bitCount = min(32, allProperties.size() - bitBase);
+ int defaultedBitmask = 0;
+ for (int i = 0; i < bitCount; i++) {
+ if (allProperties.get(bitBase + i).hasDefault()) {
+ defaultedBitmask |= 1 << i;
+ }
+ }
+ defaultedBitmasksBuilder.add(defaultedBitmask);
+ }
+ ImmutableList<Integer> defaultedBitmasks = defaultedBitmasksBuilder.build();
+ return IntStream.range(0, bitmaskFields.size())
+ .mapToObj(
+ i -> {
+ int defaultedBitmask = defaultedBitmasks.get(i);
+ switch (defaultedBitmask) {
+ case 0:
+ return "0";
+ case ~0:
+ return "~" + bitmaskFields.get(i).name;
+ default:
+ return "~" + bitmaskFields.get(i).name + " & " + hex(defaultedBitmask);
+ }
+ })
+ .collect(joining(",\n", ",\n", ",\nnull"));
+ }
+ }
+}
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 b612c104..5ecee2b0 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
@@ -270,7 +270,8 @@ class BuilderSpec {
void defineVarsForAutoValue(
AutoValueOrBuilderTemplateVars vars,
- ImmutableBiMap<ExecutableElement, String> getterToPropertyName) {
+ ImmutableBiMap<ExecutableElement, String> getterToPropertyName,
+ Nullables nullables) {
Iterable<ExecutableElement> builderMethods =
abstractMethods(builderTypeElement, processingEnv);
boolean autoValueHasToBuilder = toBuilderMethods != null && !toBuilderMethods.isEmpty();
@@ -293,6 +294,7 @@ class BuilderSpec {
builderTypeElement,
getterToPropertyName,
rewrittenPropertyTypes.build(),
+ nullables,
autoValueHasToBuilder);
if (!optionalClassifier.isPresent()) {
return;
@@ -327,6 +329,7 @@ class BuilderSpec {
autoValueClass,
typeParamsString());
}
+ errorReporter.abortIfAnyError();
return;
}
this.buildMethod = Iterables.getOnlyElement(buildMethods);
@@ -342,12 +345,15 @@ class BuilderSpec {
vars.builderPropertyBuilders =
ImmutableMap.copyOf(classifier.propertyNameToPropertyBuilder());
- vars.builderRequiredProperties =
+ ImmutableSet<Property> requiredProperties =
vars.props.stream()
.filter(p -> !p.isNullable())
.filter(p -> p.getBuilderInitializer().isEmpty())
+ .filter(p -> !p.hasDefault())
.filter(p -> !vars.builderPropertyBuilders.containsKey(p.getName()))
.collect(toImmutableSet());
+ vars.builderRequiredProperties =
+ BuilderRequiredProperties.of(vars.props, requiredProperties);
}
}
@@ -387,8 +393,7 @@ class BuilderSpec {
this.optional = optional;
}
- // Not accessed from templates so doesn't have to be public.
- String getName() {
+ public String getName() {
return name;
}
diff --git a/value/src/main/java/com/google/auto/value/processor/ClassNames.java b/value/src/main/java/com/google/auto/value/processor/ClassNames.java
index 76c50710..dea20ea6 100644
--- a/value/src/main/java/com/google/auto/value/processor/ClassNames.java
+++ b/value/src/main/java/com/google/auto/value/processor/ClassNames.java
@@ -30,5 +30,5 @@ final class ClassNames {
static final String AUTO_VALUE_BUILDER_NAME = AUTO_VALUE_NAME + ".Builder";
static final String AUTO_BUILDER_NAME = AUTO_VALUE_PACKAGE_NAME + "AutoBuilder";
static final String COPY_ANNOTATIONS_NAME = AUTO_VALUE_NAME + ".CopyAnnotations";
- static final String KOTLIN_METADATA_NAME = "kotlin.Metadata";
+ static final String KOTLIN_METADATA_NAME = "kot".concat("lin.Metadata"); // defeat shading
}
diff --git a/value/src/main/java/com/google/auto/value/processor/Executable.java b/value/src/main/java/com/google/auto/value/processor/Executable.java
new file mode 100644
index 00000000..aeaada9d
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/Executable.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.processor;
+
+import static com.google.auto.common.MoreStreams.toImmutableList;
+import static java.util.stream.Collectors.joining;
+
+import com.google.auto.common.MoreElements;
+import com.google.common.base.VerifyException;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * A wrapper for an {@link ExecutableElement}, representing a static method or a constructor. This
+ * wrapper then allows us to attach additional information, such as which parameters have Kotlin
+ * defaults.
+ */
+class Executable {
+ private final ExecutableElement executableElement;
+
+ private final ImmutableList<VariableElement> parameters;
+ private final ImmutableSet<String> optionalParameters;
+ private final ImmutableList<TypeParameterElement> typeParameters;
+
+ private Executable(ExecutableElement executableElement, ImmutableSet<String> optionalParameters) {
+ this.executableElement = executableElement;
+ this.parameters = ImmutableList.copyOf(executableElement.getParameters());
+ this.optionalParameters = optionalParameters;
+
+ switch (executableElement.getKind()) {
+ case CONSTRUCTOR:
+ // A constructor can have its own type parameters, in addition to any that its containing
+ // class has. That's pretty unusual, but we allow it, requiring the builder to have type
+ // parameters that are the concatenation of the class's and the constructor's.
+ TypeElement container = MoreElements.asType(executableElement.getEnclosingElement());
+ this.typeParameters = ImmutableList.<TypeParameterElement>builder()
+ .addAll(container.getTypeParameters())
+ .addAll(executableElement.getTypeParameters())
+ .build();
+ break;
+ case METHOD:
+ this.typeParameters = ImmutableList.copyOf(executableElement.getTypeParameters());
+ break;
+ default:
+ throw new VerifyException("Unexpected executable kind " + executableElement.getKind());
+ }
+ }
+
+ static Executable of(ExecutableElement executableElement) {
+ return of(executableElement, ImmutableSet.of());
+ }
+
+ static Executable of(
+ ExecutableElement executableElement, ImmutableSet<String> optionalParameters) {
+ return new Executable(executableElement, optionalParameters);
+ }
+
+ ExecutableElement executableElement() {
+ return executableElement;
+ }
+
+ ImmutableList<VariableElement> parameters() {
+ return parameters;
+ }
+
+ ImmutableList<String> parameterNames() {
+ return parameters.stream().map(v -> v.getSimpleName().toString()).collect(toImmutableList());
+ }
+
+ boolean isOptional(String parameterName) {
+ return optionalParameters.contains(parameterName);
+ }
+
+ int optionalParameterCount() {
+ return optionalParameters.size();
+ }
+
+ ImmutableList<TypeParameterElement> typeParameters() {
+ return typeParameters;
+ }
+
+ TypeMirror builtType() {
+ switch (executableElement.getKind()) {
+ case CONSTRUCTOR:
+ return executableElement.getEnclosingElement().asType();
+ case METHOD:
+ return executableElement.getReturnType();
+ default:
+ throw new VerifyException("Unexpected executable kind " + executableElement.getKind());
+ }
+ }
+
+ /**
+ * The Java code to invoke this constructor or method, up to just before the opening {@code (}.
+ */
+ String invoke() {
+ TypeElement enclosing = MoreElements.asType(executableElement.getEnclosingElement());
+ String type = TypeEncoder.encodeRaw(enclosing.asType());
+ switch (executableElement.getKind()) {
+ case CONSTRUCTOR:
+ boolean generic = !enclosing.getTypeParameters().isEmpty();
+ String typeParams = generic ? "<>" : "";
+ return "new " + type + typeParams;
+ case METHOD:
+ return type + "." + executableElement.getSimpleName();
+ default:
+ throw new VerifyException("Unexpected executable kind " + executableElement.getKind());
+ }
+ }
+
+ // Used in error messages, for example if more than one constructor matches your setters.
+ @Override
+ public String toString() {
+ ExecutableElement executable = executableElement;
+ Element nameSource =
+ executable.getKind() == ElementKind.CONSTRUCTOR
+ ? executable.getEnclosingElement()
+ : executable;
+ return nameSource.getSimpleName()
+ + executable.getParameters().stream()
+ .map(v -> v.asType() + " " + v.getSimpleName())
+ .collect(joining(", ", "(", ")"));
+ }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/ExtensionContext.java b/value/src/main/java/com/google/auto/value/processor/ExtensionContext.java
index da844b0e..ca48fdbe 100644
--- a/value/src/main/java/com/google/auto/value/processor/ExtensionContext.java
+++ b/value/src/main/java/com/google/auto/value/processor/ExtensionContext.java
@@ -15,15 +15,20 @@
*/
package com.google.auto.value.processor;
+import static com.google.auto.value.processor.ClassNames.COPY_ANNOTATIONS_NAME;
+
import com.google.auto.value.extension.AutoValueExtension;
import com.google.auto.value.extension.AutoValueExtension.BuilderContext;
+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.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
@@ -91,6 +96,44 @@ class ExtensionContext implements AutoValueExtension.Context {
}
@Override
+ public List<AnnotationMirror> classAnnotationsToCopy(TypeElement classToCopyFrom) {
+ // Only copy annotations from a class if it has @AutoValue.CopyAnnotations.
+ if (!AutoValueishProcessor.hasAnnotationMirror(classToCopyFrom, COPY_ANNOTATIONS_NAME)) {
+ return ImmutableList.of();
+ }
+
+ ImmutableSet<String> excludedAnnotations =
+ ImmutableSet.<String>builder()
+ .addAll(AutoValueishProcessor.getExcludedAnnotationClassNames(classToCopyFrom))
+ .addAll(AutoValueishProcessor.getAnnotationsMarkedWithInherited(classToCopyFrom))
+ //
+ // Kotlin classes have an intrinsic @Metadata annotation generated
+ // onto them by kotlinc. This annotation is specific to the annotated
+ // class and should not be implicitly copied. Doing so can mislead
+ // static analysis or metaprogramming tooling that reads the data
+ // contained in these annotations.
+ //
+ // It may be surprising to see AutoValue classes written in Kotlin
+ // when they could be written as Kotlin data classes, but this can
+ // come up in cases where consumers rely on AutoValue features or
+ // extensions that are not available in data classes.
+ //
+ // See: https://github.com/google/auto/issues/1087
+ //
+ .add(ClassNames.KOTLIN_METADATA_NAME)
+ .build();
+
+ return AutoValueishProcessor.annotationsToCopy(
+ autoValueClass, classToCopyFrom, excludedAnnotations, processingEnvironment.getTypeUtils());
+ }
+
+ @Override
+ public List<AnnotationMirror> methodAnnotationsToCopy(ExecutableElement method) {
+ return AutoValueishProcessor.propertyMethodAnnotations(
+ autoValueClass, method, processingEnvironment.getTypeUtils());
+ }
+
+ @Override
public Optional<BuilderContext> builder() {
return builderContext;
}
diff --git a/value/src/main/java/com/google/auto/value/processor/ForwardingClassGenerator.java b/value/src/main/java/com/google/auto/value/processor/ForwardingClassGenerator.java
new file mode 100644
index 00000000..71f035d0
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/ForwardingClassGenerator.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.processor;
+
+import static com.google.auto.common.MoreTypes.asArray;
+import static com.google.auto.common.MoreTypes.asTypeElement;
+import static java.util.stream.Collectors.joining;
+import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
+import static org.objectweb.asm.Opcodes.ACC_FINAL;
+import static org.objectweb.asm.Opcodes.ACC_STATIC;
+import static org.objectweb.asm.Opcodes.ACC_SUPER;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.DLOAD;
+import static org.objectweb.asm.Opcodes.DUP;
+import static org.objectweb.asm.Opcodes.FLOAD;
+import static org.objectweb.asm.Opcodes.ILOAD;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.LLOAD;
+import static org.objectweb.asm.Opcodes.NEW;
+import static org.objectweb.asm.Opcodes.V1_7;
+
+import com.google.auto.common.MoreElements;
+import com.google.common.collect.ImmutableList;
+import javax.lang.model.element.NestingKind;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * Generates a class that invokes the constructor of another class.
+ *
+ * <p>The point here is that the constructor might be synthetic, in which case it can't be called
+ * directly from Java source code. Say we want to call the constructor {@code ConstructMe(int,
+ * String, long)} with parameters {@code 1, "2", 3L}. If the constructor is synthetic, then Java
+ * source code can't just do {@code new ConstructMe(1, "2", 3L)}. So this class allows you to
+ * generate a class file, say {@code Forwarder}, that is basically what you would get if you could
+ * compile this:
+ *
+ * <pre>
+ * final class Forwarder {
+ * private Forwarder() {}
+ *
+ * static ConstructMe of(int a, String b, long c) {
+ * return new ConstructMe(a, b, c);
+ * }
+ * }
+ * </pre>
+ *
+ * <p>Because the class file is assembled directly, rather than being produced by the Java compiler,
+ * it <i>can</i> call the synthetic constructor. Then regular Java source code can do {@code
+ * Forwarder.of(1, "2", 3L)} to call the constructor.
+ */
+final class ForwardingClassGenerator {
+ /**
+ * Assembles a class with a static method {@code of} that calls the constructor of another class
+ * with the same parameters.
+ *
+ * <p>It would be simpler if we could just pass in an {@code ExecutableElement} representing the
+ * constructor, but if it is synthetic then it won't be visible to the {@code javax.lang.model}
+ * APIs. So we have to pass the constructed type and the constructor parameter types separately.
+ *
+ * @param forwardingClassName the fully-qualified name of the class to generate
+ * @param classToConstruct the type whose constructor will be invoked ({@code ConstructMe} in the
+ * example above)
+ * @param constructorParameters the erased types of the constructor parameters, which will also be
+ * the types of the generated {@code of} method. We require the types to be erased so as not
+ * to require an instance of the {@code Types} interface to erase them here. Having to deal
+ * with generics would complicate things unnecessarily.
+ * @return a byte array making up the new class file
+ */
+ static byte[] makeConstructorForwarder(
+ String forwardingClassName,
+ TypeMirror classToConstruct,
+ ImmutableList<TypeMirror> constructorParameters) {
+
+ ClassWriter classWriter = new ClassWriter(COMPUTE_MAXS);
+ classWriter.visit(
+ V1_7,
+ ACC_FINAL | ACC_SUPER,
+ internalName(forwardingClassName),
+ null,
+ "java/lang/Object",
+ null);
+ classWriter.visitSource(forwardingClassName, null);
+
+ // Generate the `of` method.
+ // TODO(emcmanus): cleaner generics. If we're constructing Foo<T extends Number> then we should
+ // generate a generic signature for the `of` method, as if the Java declaration were this:
+ // static <T extends Number> Foo<T> of(...)
+ // Currently we just generate:
+ // static Foo of(...)
+ // which returns the raw Foo type.
+ String parameterSignature =
+ constructorParameters.stream()
+ .map(ForwardingClassGenerator::signatureEncoding)
+ .collect(joining(""));
+ String internalClassToConstruct = internalName(asTypeElement(classToConstruct));
+ String ofMethodSignature = "(" + parameterSignature + ")L" + internalClassToConstruct + ";";
+ MethodVisitor ofMethodVisitor =
+ classWriter.visitMethod(ACC_STATIC, "of", ofMethodSignature, null, null);
+ ofMethodVisitor.visitCode();
+
+ // The remaining instructions are basically what ASMifier generates for a class like the
+ // `Forwarder` class in the example above.
+ ofMethodVisitor.visitTypeInsn(NEW, internalClassToConstruct);
+ ofMethodVisitor.visitInsn(DUP);
+
+ int local = 0;
+ for (TypeMirror type : constructorParameters) {
+ ofMethodVisitor.visitVarInsn(loadInstruction(type), local);
+ local += localSize(type);
+ }
+ String constructorToCallSignature = "(" + parameterSignature + ")V";
+ ofMethodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ internalClassToConstruct,
+ "<init>",
+ constructorToCallSignature,
+ /* isInterface= */ false);
+
+ ofMethodVisitor.visitInsn(ARETURN);
+ ofMethodVisitor.visitMaxs(0, 0);
+ ofMethodVisitor.visitEnd();
+ classWriter.visitEnd();
+ return classWriter.toByteArray();
+ }
+
+ /** The bytecode instruction that copies a parameter of the given type onto the JVM stack. */
+ private static int loadInstruction(TypeMirror type) {
+ switch (type.getKind()) {
+ case DECLARED:
+ case ARRAY:
+ return ALOAD;
+ case LONG:
+ return LLOAD;
+ case FLOAT:
+ return FLOAD;
+ case DOUBLE:
+ return DLOAD;
+ case BYTE:
+ case SHORT:
+ case CHAR:
+ case INT:
+ case BOOLEAN:
+ // These are all represented as int local variables.
+ return ILOAD;
+ default:
+ // We expect the caller to have erased the parameters so we shouldn't be seeing type
+ // variables or whatever.
+ throw new IllegalArgumentException("Unexpected type " + type);
+ }
+ }
+
+ /**
+ * The size in the local variable array of a value of the given type. A quirk of the JVM means
+ * that long and double variables each take up two consecutive slots in the local variable array.
+ * (The first n local variables are the parameters, so we need to know their sizes when iterating
+ * over them.)
+ */
+ private static int localSize(TypeMirror type) {
+ switch (type.getKind()) {
+ case LONG:
+ case DOUBLE:
+ return 2;
+ default:
+ return 1;
+ }
+ }
+
+ private static String internalName(String className) {
+ return className.replace('.', '/');
+ }
+
+ /**
+ * Given a class like {@code foo.bar.Outer.Inner}, produces a string like {@code
+ * "foo/bar/Outer$Inner"}, which is the way the class is referenced in the JVM.
+ */
+ private static String internalName(TypeElement typeElement) {
+ if (typeElement.getNestingKind().equals(NestingKind.MEMBER)) {
+ TypeElement enclosing = MoreElements.asType(typeElement.getEnclosingElement());
+ return internalName(enclosing) + "$" + typeElement.getSimpleName();
+ }
+ return internalName(typeElement.getQualifiedName().toString());
+ }
+
+ private static String signatureEncoding(TypeMirror type) {
+ switch (type.getKind()) {
+ case ARRAY:
+ return "[" + signatureEncoding(asArray(type).getComponentType());
+ case BYTE:
+ return "B";
+ case SHORT:
+ return "S";
+ case INT:
+ return "I";
+ case LONG:
+ return "J";
+ case FLOAT:
+ return "F";
+ case DOUBLE:
+ return "D";
+ case CHAR:
+ return "C";
+ case BOOLEAN:
+ return "Z";
+ case DECLARED:
+ return "L" + internalName(asTypeElement(type)) + ";";
+ default:
+ throw new AssertionError("Bad signature type " + type);
+ }
+ }
+
+ private ForwardingClassGenerator() {}
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/MissingTypes.java b/value/src/main/java/com/google/auto/value/processor/MissingTypes.java
index ea8126b5..8bc0119e 100644
--- a/value/src/main/java/com/google/auto/value/processor/MissingTypes.java
+++ b/value/src/main/java/com/google/auto/value/processor/MissingTypes.java
@@ -54,7 +54,7 @@ final class MissingTypes {
// Although it is not specified as such, in practice ErrorType.toString() is the type name
// that appeared in the source code. Showing it here can help in debugging issues with
// deferral.
- super(missingType == null ? null : missingType.toString());
+ super(missingType == null ? "" : missingType.toString());
}
}
diff --git a/value/src/main/java/com/google/auto/value/processor/Nullables.java b/value/src/main/java/com/google/auto/value/processor/Nullables.java
index c07656f1..20cf495a 100644
--- a/value/src/main/java/com/google/auto/value/processor/Nullables.java
+++ b/value/src/main/java/com/google/auto/value/processor/Nullables.java
@@ -18,7 +18,6 @@ package com.google.auto.value.processor;
import static java.util.stream.Collectors.toList;
import com.google.auto.common.MoreTypes;
-import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -52,19 +51,59 @@ class Nullables {
// We write this using .concat in order to hide it from rewriting rules.
private static final String DEFAULT_NULLABLE = "org".concat(".jspecify.nullness.Nullable");
- private final Optional<AnnotationMirror> defaultNullable;
+ private final Optional<AnnotationMirror> nullableTypeAnnotation;
- Nullables(ProcessingEnvironment processingEnv) {
+ private Nullables(Optional<AnnotationMirror> nullableTypeAnnotation) {
+ this.nullableTypeAnnotation = nullableTypeAnnotation;
+ }
+
+ /**
+ * Make an instance where the default {@code @Nullable} type annotation is discovered by looking
+ * for methods whose parameter or return types have such an annotation. If there are none, use a
+ * default {@code @Nullable} type annotation if it is available.
+ *
+ * @param methods the methods to examine
+ * @param processingEnv the {@link ProcessingEnvironment}, or null if one is unavailable
+ * (typically in tests)
+ */
+ static Nullables fromMethods(
+ /* @Nullable */ ProcessingEnvironment processingEnv, Collection<ExecutableElement> methods) {
+ Optional<AnnotationMirror> nullableTypeAnnotation =
+ methods.stream()
+ .flatMap(
+ method ->
+ Stream.concat(
+ Stream.of(method.getReturnType()),
+ method.getParameters().stream().map(Element::asType)))
+ .map(Nullables::nullableIn)
+ .filter(Optional::isPresent)
+ .findFirst()
+ .orElseGet(() -> defaultNullableTypeAnnotation(processingEnv));
+ return new Nullables(nullableTypeAnnotation);
+ }
+
+ /**
+ * Returns a list that is either empty or contains a single element that is an appropriate
+ * {@code @Nullable} type-annotation.
+ */
+ ImmutableList<AnnotationMirror> nullableTypeAnnotations() {
+ return nullableTypeAnnotation.map(ImmutableList::of).orElse(ImmutableList.of());
+ }
+
+ private static Optional<AnnotationMirror> defaultNullableTypeAnnotation(
+ /* @Nullable */ ProcessingEnvironment processingEnv) {
+ if (processingEnv == null) {
+ return Optional.empty();
+ }
// -Afoo without `=` sets "foo" to null in the getOptions() map.
String nullableOption =
Strings.nullToEmpty(
processingEnv.getOptions().getOrDefault(NULLABLE_OPTION, DEFAULT_NULLABLE));
- this.defaultNullable =
- (!nullableOption.isEmpty()
- && processingEnv.getSourceVersion().ordinal() >= SourceVersion.RELEASE_8.ordinal())
- ? Optional.ofNullable(processingEnv.getElementUtils().getTypeElement(nullableOption))
- .map(t -> annotationMirrorOf(MoreTypes.asDeclared(t.asType())))
- : Optional.empty();
+ return (!nullableOption.isEmpty()
+ && processingEnv.getSourceVersion().ordinal() >= SourceVersion.RELEASE_8.ordinal())
+ ? Optional.ofNullable(processingEnv.getElementUtils().getTypeElement(nullableOption))
+ .map(t -> annotationMirrorOf(MoreTypes.asDeclared(t.asType())))
+ : Optional.empty();
}
private static AnnotationMirror annotationMirrorOf(DeclaredType annotationType) {
@@ -82,38 +121,6 @@ class Nullables {
};
}
- /**
- * Returns the type of a {@code @Nullable} type-annotation, if one is found anywhere in the
- * signatures of the given methods.
- */
- @VisibleForTesting
- static Optional<AnnotationMirror> nullableMentionedInMethods(
- Collection<ExecutableElement> methods) {
- return methods.stream()
- .flatMap(
- method ->
- Stream.concat(
- Stream.of(method.getReturnType()),
- method.getParameters().stream().map(Element::asType)))
- .map(Nullables::nullableIn)
- .filter(Optional::isPresent)
- .findFirst()
- .orElse(Optional.empty());
- }
-
- /**
- * Returns the type of an appropriate {@code @Nullable} type-annotation, given a set of methods
- * that are known to be in the same compilation as the code being generated. If one of those
- * methods contains an appropriate {@code @Nullable} annotation on a parameter or return type,
- * this method will return that. Otherwise, if the <a href="http://jspecify.org">JSpecify</a>
- * {@code @Nullable} is available, this method will return it. Otherwise, this method will return
- * an empty {@code Optional}.
- */
- Optional<AnnotationMirror> appropriateNullableGivenMethods(
- Collection<ExecutableElement> methods) {
- return nullableMentionedInMethods(methods).map(Optional::of).orElse(defaultNullable);
- }
-
private static Optional<AnnotationMirror> nullableIn(TypeMirror type) {
return new NullableFinder().visit(type);
}
diff --git a/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java b/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java
index 5d0168e4..f1c7533a 100644
--- a/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java
+++ b/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java
@@ -57,6 +57,7 @@ class PropertyBuilderClassifier {
private final Predicate<String> propertyIsNullable;
private final ImmutableMap<String, TypeMirror> propertyTypes;
private final EclipseHack eclipseHack;
+ private final Nullables nullables;
PropertyBuilderClassifier(
ErrorReporter errorReporter,
@@ -65,7 +66,8 @@ class PropertyBuilderClassifier {
BuilderMethodClassifier<?> builderMethodClassifier,
Predicate<String> propertyIsNullable,
ImmutableMap<String, TypeMirror> propertyTypes,
- EclipseHack eclipseHack) {
+ EclipseHack eclipseHack,
+ Nullables nullables) {
this.errorReporter = errorReporter;
this.typeUtils = typeUtils;
this.elementUtils = elementUtils;
@@ -73,6 +75,7 @@ class PropertyBuilderClassifier {
this.propertyIsNullable = propertyIsNullable;
this.propertyTypes = propertyTypes;
this.eclipseHack = eclipseHack;
+ this.nullables = nullables;
}
/**
@@ -85,7 +88,9 @@ class PropertyBuilderClassifier {
private final ExecutableElement propertyBuilderMethod;
private final String name;
private final String builderType;
+ private final String nullableBuilderType;
private final TypeMirror builderTypeMirror;
+ private final String build;
private final String initializer;
private final String beforeInitDefault;
private final String initDefault;
@@ -95,7 +100,9 @@ class PropertyBuilderClassifier {
PropertyBuilder(
ExecutableElement propertyBuilderMethod,
String builderType,
+ String nullableBuilderType,
TypeMirror builderTypeMirror,
+ String build,
String initializer,
String beforeInitDefault,
String initDefault,
@@ -104,7 +111,9 @@ class PropertyBuilderClassifier {
this.propertyBuilderMethod = propertyBuilderMethod;
this.name = propertyBuilderMethod.getSimpleName() + "$";
this.builderType = builderType;
+ this.nullableBuilderType = nullableBuilderType;
this.builderTypeMirror = builderTypeMirror;
+ this.build = build;
this.initializer = initializer;
this.beforeInitDefault = beforeInitDefault;
this.initDefault = initDefault;
@@ -117,6 +126,11 @@ class PropertyBuilderClassifier {
return propertyBuilderMethod;
}
+ /** The name of the property builder method. */
+ public String getMethodName() {
+ return propertyBuilderMethod.getSimpleName().toString();
+ }
+
/** The property builder method parameters, for example {@code Comparator<T> comparator} */
public String getPropertyBuilderMethodParameters() {
return propertyBuilderMethod.getParameters().stream()
@@ -139,10 +153,20 @@ class PropertyBuilderClassifier {
return builderType;
}
+ /** The type of the builder with an appropriate {@code @Nullable} type annotation. */
+ public String getNullableBuilderType() {
+ return nullableBuilderType;
+ }
+
TypeMirror getBuilderTypeMirror() {
return builderTypeMirror;
}
+ /** The name of the build method, {@code build} or {@code buildOrThrow}. */
+ public String getBuild() {
+ return build;
+ }
+
/** An initializer for the builder field, for example {@code ImmutableSet.builder()}. */
public String getInitializer() {
return initializer;
@@ -192,8 +216,9 @@ class PropertyBuilderClassifier {
// doesn't have to be the name of `Bar` with `Builder` stuck on the end), but `barBuilder()` does
// have to be the name of the property with `Builder` stuck on the end. The requirements for the
// `BarBuilder` type are:
- // (1) It must have an instance method called `build()` that returns `Bar`. If the type of
- // `bar()` is `Bar<String>` then the type of `build()` must be `Bar<String>`.
+ // (1) It must have an instance method called `build()` or `buildOrThrow() that returns `Bar`. If
+ // the type of `bar()` is `Bar<String>` then the type of the build method must be
+ // `Bar<String>`.
// (2) `BarBuilder` must have a public no-arg constructor, or `Bar` must have a static method
// `naturalOrder(), `builder()`, or `newBuilder()` that returns `BarBuilder`. The
// `naturalOrder()` case is specifically for ImmutableSortedSet and ImmutableSortedMap.
@@ -234,27 +259,32 @@ class PropertyBuilderClassifier {
TypeElement barTypeElement = MoreTypes.asTypeElement(barTypeMirror);
Map<String, ExecutableElement> barNoArgMethods = noArgMethodsOf(barTypeElement);
- // Condition (1), must have build() method returning Bar.
- ExecutableElement build = barBuilderNoArgMethods.get("build");
+ // Condition (1), must have build() or buildOrThrow() method returning Bar.
+ ExecutableElement build = barBuilderNoArgMethods.get("buildOrThrow");
+ if (build == null) {
+ build = barBuilderNoArgMethods.get("build");
+ }
if (build == null || build.getModifiers().contains(Modifier.STATIC)) {
errorReporter.reportError(
method,
"[AutoValueBuilderNotBuildable] Method looks like a property builder, but it returns %s"
- + " which does not have a non-static build() method",
+ + " which does not have a non-static build() or buildOrThrow() method",
barBuilderTypeElement);
return Optional.empty();
}
- // We've determined that `BarBuilder` has a method `build()`. But it must return `Bar`.
- // And if the type of `bar()` is Bar<String> then `BarBuilder.build()` must return Bar<String>.
+ // We've determined that `BarBuilder` has a method `build()` or `buildOrThrow(). But it must
+ // return `Bar`. And if the type of `bar()` is Bar<String> then `BarBuilder.build()` must return
+ // something that can be assigned to Bar<String>.
TypeMirror buildType = eclipseHack.methodReturnType(build, barBuilderDeclaredType);
- if (!MoreTypes.equivalence().equivalent(barTypeMirror, buildType)) {
+ if (!typeUtils.isAssignable(buildType, barTypeMirror)) {
errorReporter.reportError(
method,
- "[AutoValueBuilderWrongType] Property builder for %s has type %s whose build() method"
+ "[AutoValueBuilderWrongType] Property builder for %s has type %s whose %s() method"
+ " returns %s instead of %s",
property,
barBuilderTypeElement,
+ build.getSimpleName(),
buildType,
barTypeMirror);
return Optional.empty();
@@ -281,6 +311,9 @@ class PropertyBuilderClassifier {
ExecutableElement builderMaker = maybeBuilderMaker.get();
String barBuilderType = TypeEncoder.encodeWithAnnotations(barBuilderTypeMirror);
+ String nullableBarBuilderType =
+ TypeEncoder.encodeWithAnnotations(
+ barBuilderTypeMirror, nullables.nullableTypeAnnotations());
String rawBarType = TypeEncoder.encodeRaw(barTypeMirror);
String arguments =
method.getParameters().isEmpty()
@@ -322,15 +355,17 @@ class PropertyBuilderClassifier {
initDefault = rawBarType + ".of()";
} else {
String localBuilder = property + "$builder";
- beforeInitDefault = barBuilderType + " " + localBuilder + " = " + initializer + ";";
- initDefault = localBuilder + ".build()";
+ beforeInitDefault = nullableBarBuilderType + " " + localBuilder + " = " + initializer + ";";
+ initDefault = localBuilder + "." + build.getSimpleName() + "()";
}
PropertyBuilder propertyBuilder =
new PropertyBuilder(
method,
barBuilderType,
+ nullableBarBuilderType,
barBuilderTypeMirror,
+ build.getSimpleName().toString(),
initializer,
beforeInitDefault,
initDefault,
diff --git a/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java b/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java
index 81968a50..2643d12d 100644
--- a/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java
+++ b/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java
@@ -107,6 +107,18 @@ final class TypeEncoder {
* covers the details of annotation encoding.
*
* @param extraAnnotations additional type annotations to include with the type
+ */
+ static String encodeWithAnnotations(
+ TypeMirror type,
+ ImmutableList<AnnotationMirror> extraAnnotations) {
+ return encodeWithAnnotations(type, extraAnnotations, ImmutableSet.of());
+ }
+
+ /**
+ * Encodes the given type and its type annotations. The class comment for {@link TypeEncoder}
+ * covers the details of annotation encoding.
+ *
+ * @param extraAnnotations additional type annotations to include with the type
* @param excludedAnnotationTypes annotations not to include in the encoding. For example, if
* {@code com.example.Nullable} is in this set then the encoding will not include this
* {@code @Nullable} annotation.
diff --git a/value/src/main/java/com/google/auto/value/processor/autobuilder.vm b/value/src/main/java/com/google/auto/value/processor/autobuilder.vm
index 01f61b9c..0aa0c19e 100644
--- a/value/src/main/java/com/google/auto/value/processor/autobuilder.vm
+++ b/value/src/main/java/com/google/auto/value/processor/autobuilder.vm
@@ -12,7 +12,7 @@
## See the License for the specific language governing permissions and
## limitations under the License.
-## Template for each generated AutoValue_Foo class.
+## Template for each generated AutoBuilder_Foo class.
## This template uses the Apache Velocity Template Language (VTL).
## The variables ($pkg, $props, and so on) are defined by the fields of AutoBuilderTemplateVars.
##
diff --git a/value/src/main/java/com/google/auto/value/processor/autobuilderannotation.vm b/value/src/main/java/com/google/auto/value/processor/autobuilderannotation.vm
new file mode 100644
index 00000000..427957cc
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/autobuilderannotation.vm
@@ -0,0 +1,54 @@
+## Copyright 2022 Google LLC
+##
+## Licensed under the Apache License, Version 2.0 (the "License");
+## you may not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+
+## Template for each generated AutoBuilderAnnotation_Foo_Bar class.
+## This template uses the Apache Velocity Template Language (VTL).
+## The variables ($pkg, $props, and so on) are defined by the fields of
+## AutoBuilderAnnotationTemplateVars.
+##
+## Comments, like this one, begin with ##. The comment text extends up to and including the newline
+## character at the end of the line. So comments also serve to join a line to the next one.
+## Velocity deletes a newline after a directive (#if, #foreach, #end etc) so ## is not needed there.
+## That does mean that we sometimes need an extra blank line after such a directive.
+##
+## Post-processing will remove unwanted spaces and blank lines, but will not join two lines.
+## It will also replace classes spelled as (e.g.) `java.util.Arrays`, with the backquotes, to
+## use just Arrays if that class can be imported unambiguously, or java.util.Arrays if not.
+
+#if (!$pkg.empty)
+package $pkg;
+#end
+
+## The following line will be replaced by the required imports during post-processing.
+`import`
+
+#if (!$generated.empty)
+@${generated}("com.google.auto.value.processor.AutoBuilderProcessor")
+#else
+// Generated by com.google.auto.value.processor.AutoBuilderProcessor
+#end
+class $className {
+ @`com.google.auto.value.AutoAnnotation`
+ static ${annotationType} newAnnotation(
+#foreach ($p in $props)
+ $p.type $p #if ($foreach.hasNext) , #end
+#end
+ ) {
+ return new AutoAnnotation_${className}_newAnnotation(
+#foreach ($p in $props)
+ $p #if ($foreach.hasNext) , #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 b1787f25..3b906196 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
@@ -37,26 +37,22 @@ class ${builderName}${builderFormalTypes} ##
${builderTypeName}${builderActualTypes} {
#foreach ($p in $props)
-
- #if ($p.kind.primitive)
-
- private $types.boxedClass($p.typeMirror).simpleName $p $p.builderInitializer;
-
- #else
-
- #if ($builderPropertyBuilders[$p.name])
+ #if ($builderPropertyBuilders[$p.name])
## If you have ImmutableList.Builder<String> stringsBuilder() then we define two fields:
## private ImmutableList.Builder<String> stringsBuilder$;
## private ImmutableList<String> strings;
- private ${builderPropertyBuilders[$p.name].builderType} ##
+ private ${builderPropertyBuilders[$p.name].nullableBuilderType} ##
${builderPropertyBuilders[$p.name].name};
#end
- private $p.type $p $p.builderInitializer;
+ private $p.builderFieldType $p $p.builderInitializer;
- #end
+#end
+
+#foreach ($decl in $builderRequiredProperties.fieldDeclarations)
+ $decl
#end
${builderName}() {
@@ -64,7 +60,8 @@ class ${builderName}${builderFormalTypes} ##
#if ($toBuilderConstructor)
- private ${builderName}(${origClass}${actualTypes} source) {
+ #if (!$autoBuilder) private #end##
+ ${builderName}($builtType source) {
#foreach ($p in $props)
@@ -72,6 +69,10 @@ class ${builderName}${builderFormalTypes} ##
#end
+ #foreach ($init in $builderRequiredProperties.initToAllSet)
+ $init
+ #end
+
}
#end
@@ -112,6 +113,9 @@ class ${builderName}${builderFormalTypes} ##
#end
this.$p = ${setter.copy($p)};
+
+ $builderRequiredProperties.markAsSet($p)
+
return this;
}
@@ -120,24 +124,27 @@ class ${builderName}${builderFormalTypes} ##
#if ($propertyBuilder)
@`java.lang.Override`
- ${propertyBuilder.access}$propertyBuilder.builderType ${p.name}Builder($propertyBuilder.propertyBuilderMethodParameters) {
+ ${propertyBuilder.access}$propertyBuilder.builderType ##
+ ${propertyBuilder.methodName}($propertyBuilder.propertyBuilderMethodParameters) {
if (${propertyBuilder.name} == null) {
## This is the first time someone has asked for the builder. If the property it sets already
- ## has a value (because it came from a toBuilder() call on the AutoValue class, or because
- ## there is also a setter for this property) then we copy that value into the builder.
+ ## has a value (because it came from the copy constructor, or because there is also a setter
+ ## for this property) then we copy that value into the builder.
## Otherwise the builder starts out empty.
## If we have neither a setter nor a toBuilder() method, then the builder always starts
## off empty.
- #if ($builderSetters[$p.name].empty && $toBuilderMethods.empty)
+ #if ($builderSetters[$p.name].empty && !$toBuilderConstructor)
${propertyBuilder.name} = ${propertyBuilder.initializer};
+ $builderRequiredProperties.markAsSet($p)
#else
if ($p == null) {
${propertyBuilder.name} = ${propertyBuilder.initializer};
+ $builderRequiredProperties.markAsSet($p)
} else {
#if (${propertyBuilder.builtToBuilder})
@@ -173,31 +180,36 @@ class ${builderName}${builderFormalTypes} ##
## Getter
- #if ($builderGetters[$p.name])
+ #set ($builderGetter = $builderGetters[$p.name])
+ #if ($builderGetter)
@`java.lang.Override`
- ${p.nullableAnnotation}${builderGetters[$p.name].access}$builderGetters[$p.name].type ${p.getter}() {
- #if ($builderGetters[$p.name].optional)
+ ${p.nullableAnnotation}${builderGetter.access}$builderGetter.type ${builderGetter.name}() {
+ #set ($noValueToGetCondition = $builderRequiredProperties.noValueToGet($p))
+ #if ($builderGetters[$p.name].optional)
+ #if ($noValueToGetCondition)
+ if ($noValueToGetCondition) {
+ return $builderGetter.optional.empty;
+ }
+ #else
if ($p == null) {
- return $builderGetters[$p.name].optional.empty;
- } else {
- return ${builderGetters[$p.name].optional.rawType}.of($p);
+ return $builderGetter.optional.empty;
}
+ #end
+ return ${builderGetter.optional.rawType}.of($p);
#else
- #if ($builderRequiredProperties.contains($p))
-
- if ($p == null) {
+ #if ($noValueToGetCondition)
+ if ($noValueToGetCondition) {
throw new IllegalStateException(#if ($identifiers)"Property \"$p.name\" has not been set"#end);
}
-
#end
#if ($propertyBuilder)
if (${propertyBuilder.name} != null) {
- return ${propertyBuilder.name}.build();
+ return ${propertyBuilder.name}.${propertyBuilder.build}();
}
if ($p == null) {
${propertyBuilder.beforeInitDefault}
@@ -225,7 +237,7 @@ class ${builderName}${builderFormalTypes} ##
#if ($propertyBuilder)
if (${propertyBuilder.name} != null) {
- this.$p = ${propertyBuilder.name}.build();
+ this.$p = ${propertyBuilder.name}.${propertyBuilder.build}();
} else if (this.$p == null) {
${propertyBuilder.beforeInitDefault}
this.$p = ${propertyBuilder.initDefault};
@@ -234,29 +246,22 @@ class ${builderName}${builderFormalTypes} ##
#end
#end
-#if (!$builderRequiredProperties.empty)
- if (#foreach ($p in $builderRequiredProperties)##
- this.$p == null##
- #if ($foreach.hasNext)
-
- || #end
- #end) {
+#if (!$builderRequiredProperties.requiredProperties.empty)
+ if ($builderRequiredProperties.anyMissing) {
#if ($identifiers) ## build a friendly message showing all missing properties
- #if ($builderRequiredProperties.size() == 1)
+ #if ($builderRequiredProperties.requiredProperties.size() == 1)
- `java.lang.String` missing = " $builderRequiredProperties.iterator().next()";
+ `java.lang.String` missing = " $builderRequiredProperties.requiredProperties.iterator().next()";
#else
`java.lang.StringBuilder` missing = new `java.lang.StringBuilder`();
- #foreach ($p in $builderRequiredProperties)
-
- if (this.$p == null) {
+ #foreach ($p in $builderRequiredProperties.requiredProperties)
+ if ($builderRequiredProperties.missingRequiredProperty($p)) {
missing.append(" $p.name");
}
-
#end
#end
@@ -276,6 +281,7 @@ class ${builderName}${builderFormalTypes} ##
#foreach ($p in $props)
this.$p #if ($foreach.hasNext) , #end
-#end );
+#end
+ $builderRequiredProperties.defaultedBitmaskParameters );
}
}
diff --git a/value/src/main/java/com/google/auto/value/processor/equalshashcode.vm b/value/src/main/java/com/google/auto/value/processor/equalshashcode.vm
index 03e25c2b..0afcdb63 100644
--- a/value/src/main/java/com/google/auto/value/processor/equalshashcode.vm
+++ b/value/src/main/java/com/google/auto/value/processor/equalshashcode.vm
@@ -42,9 +42,9 @@
## A reminder that trailing ## here serves to delete the newline, which we don't want in the output.
#macro (equalsThatExpression $p $subclass)
#if ($p.kind == "FLOAT")
- Float.floatToIntBits(this.$p) == Float.floatToIntBits(that.${p.getter}()) ##
+ `java.lang.Float`.floatToIntBits(this.$p) == `java.lang.Float`.floatToIntBits(that.${p.getter}()) ##
#elseif ($p.kind == "DOUBLE")
- Double.doubleToLongBits(this.$p) == Double.doubleToLongBits(that.${p.getter}()) ##
+ `java.lang.Double`.doubleToLongBits(this.$p) == `java.lang.Double`.doubleToLongBits(that.${p.getter}()) ##
#elseif ($p.kind.primitive)
this.$p == that.${p.getter}() ##
#elseif ($p.kind == "ARRAY")
@@ -65,9 +65,9 @@
#if ($p.kind == "LONG")
(int) (($p >>> 32) ^ $p) ##
#elseif ($p.kind == "FLOAT")
- Float.floatToIntBits($p) ##
+ `java.lang.Float`.floatToIntBits($p) ##
#elseif ($p.kind == "DOUBLE")
- (int) ((Double.doubleToLongBits($p) >>> 32) ^ Double.doubleToLongBits($p)) ##
+ (int) ((`java.lang.Double`.doubleToLongBits($p) >>> 32) ^ `java.lang.Double`.doubleToLongBits($p)) ##
#elseif ($p.kind == "BOOLEAN")
$p ? 1231 : 1237 ##
#elseif ($p.kind.primitive)
diff --git a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java
index 5beb686b..85c3231e 100644
--- a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java
+++ b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java
@@ -15,7 +15,9 @@
*/
package com.google.auto.value.extension.memoized;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
+import static java.util.Arrays.stream;
import static org.junit.Assert.fail;
import com.google.auto.value.AutoValue;
@@ -27,6 +29,7 @@ import com.google.errorprone.annotations.ImmutableTypeParameter;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,6 +47,8 @@ public class MemoizedTest {
abstract boolean getNative0();
+ abstract String getNotKeyword();
+
@Memoized
boolean getMemoizedNative() {
return getNative();
@@ -59,10 +64,25 @@ public class MemoizedTest {
@CopyAnnotations
@javax.annotation.Nullable
abstract static class ValueWithCopyAnnotations {
- abstract boolean getNative();
+ abstract String getNative();
@Memoized
- boolean getMemoizedNative() {
+ @javax.annotation.Nullable
+ public String getMemoizedNative() {
+ return getNative();
+ }
+ }
+
+ @AutoValue
+ @CopyAnnotations(exclude = javax.annotation.Nullable.class)
+ @javax.annotation.Nullable
+ abstract static class ValueWithExcludedCopyAnnotations {
+ abstract String getNative();
+
+ @Memoized
+ @CopyAnnotations(exclude = javax.annotation.Nullable.class)
+ @javax.annotation.Nullable
+ public String getMemoizedNative() {
return getNative();
}
}
@@ -70,10 +90,11 @@ public class MemoizedTest {
@AutoValue
@javax.annotation.Nullable
abstract static class ValueWithoutCopyAnnotations {
- abstract boolean getNative();
+ abstract String getNative();
@Memoized
- boolean getMemoizedNative() {
+ @javax.annotation.Nullable
+ public String getMemoizedNative() {
return getNative();
}
}
@@ -342,29 +363,96 @@ public class MemoizedTest {
}
@Test
- public void keywords() {
- ValueWithKeywordName value = new AutoValue_MemoizedTest_ValueWithKeywordName(true, false);
+ public void keywords() throws Exception {
+ ValueWithKeywordName value =
+ new AutoValue_MemoizedTest_ValueWithKeywordName(true, false, "foo");
assertThat(value.getNative()).isTrue();
assertThat(value.getMemoizedNative()).isTrue();
assertThat(value.getNative0()).isFalse();
assertThat(value.getMemoizedNative0()).isFalse();
+
+ Constructor<?> constructor =
+ value.getClass().getDeclaredConstructor(boolean.class, boolean.class, String.class);
+ ImmutableList<String> names =
+ stream(constructor.getParameters()).map(Parameter::getName).collect(toImmutableList());
+ assertThat(names).contains("notKeyword");
}
@Test
- public void copyAnnotations() {
+ public void copyClassAnnotations_valueWithCopyAnnotations_copiesAnnotation() throws Exception {
ValueWithCopyAnnotations valueWithCopyAnnotations =
- new AutoValue_MemoizedTest_ValueWithCopyAnnotations(true);
+ new AutoValue_MemoizedTest_ValueWithCopyAnnotations("test");
+
+ assertThat(
+ valueWithCopyAnnotations
+ .getClass()
+ .isAnnotationPresent(javax.annotation.Nullable.class))
+ .isTrue();
+ }
+
+ @Test
+ public void copyClassAnnotations_valueWithoutCopyAnnotations_doesNotCopyAnnotation()
+ throws Exception {
ValueWithoutCopyAnnotations valueWithoutCopyAnnotations =
- new AutoValue_MemoizedTest_ValueWithoutCopyAnnotations(true);
+ new AutoValue_MemoizedTest_ValueWithoutCopyAnnotations("test");
+
+ assertThat(
+ valueWithoutCopyAnnotations
+ .getClass()
+ .isAnnotationPresent(javax.annotation.Nullable.class))
+ .isFalse();
+ }
+
+ @Test
+ public void copyClassAnnotations_valueWithExcludedCopyAnnotations_doesNotCopyAnnotation()
+ throws Exception {
+ ValueWithExcludedCopyAnnotations valueWithExcludedCopyAnnotations =
+ new AutoValue_MemoizedTest_ValueWithExcludedCopyAnnotations("test");
+
+ assertThat(
+ valueWithExcludedCopyAnnotations
+ .getClass()
+ .isAnnotationPresent(javax.annotation.Nullable.class))
+ .isFalse();
+ }
+
+ @Test
+ public void copyMethodAnnotations_valueWithCopyAnnotations_copiesAnnotation() throws Exception {
+ ValueWithCopyAnnotations valueWithCopyAnnotations =
+ new AutoValue_MemoizedTest_ValueWithCopyAnnotations("test");
assertThat(
valueWithCopyAnnotations
.getClass()
+ .getMethod("getMemoizedNative")
.isAnnotationPresent(javax.annotation.Nullable.class))
.isTrue();
+ }
+
+ @Test
+ public void copyMethodAnnotations_valueWithoutCopyAnnotations_copiesAnnotation()
+ throws Exception {
+ ValueWithoutCopyAnnotations valueWithoutCopyAnnotations =
+ new AutoValue_MemoizedTest_ValueWithoutCopyAnnotations("test");
+
assertThat(
valueWithoutCopyAnnotations
.getClass()
+ .getMethod("getMemoizedNative")
+ .isAnnotationPresent(javax.annotation.Nullable.class))
+ .isTrue();
+ }
+
+ @Test
+ public void copyMethodAnnotations_valueWithExcludedCopyAnnotations_doesNotCopyAnnotation()
+ throws Exception {
+ ValueWithExcludedCopyAnnotations valueWithExcludedCopyAnnotations =
+ new AutoValue_MemoizedTest_ValueWithExcludedCopyAnnotations("test");
+
+ assertThat(
+ valueWithExcludedCopyAnnotations
+ .getClass()
+ .getMethod("getMemoizedNative")
.isAnnotationPresent(javax.annotation.Nullable.class))
.isFalse();
}
diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java
index 064406da..cbfdcafd 100644
--- a/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java
+++ b/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java
@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth8.assertThat;
import static org.junit.Assert.assertThrows;
import com.google.auto.value.AutoValue;
+import com.google.auto.value.extension.memoized.Memoized;
import com.google.auto.value.extension.serializable.SerializableAutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -449,4 +450,48 @@ public final class SerializableAutoValueExtensionTest {
assertThat(reserialized).isEqualTo(complexType);
}
+
+ /**
+ * Type that uses both {@code @SerializableAutoValue} and {@code @Memoized}, showing that the two
+ * extensions work correctly together.
+ */
+ @SerializableAutoValue
+ @AutoValue
+ abstract static class SerializeMemoize implements Serializable {
+ abstract Optional<Integer> number();
+ private transient int methodCount;
+
+ @Memoized
+ Optional<Integer> negate() {
+ methodCount++;
+ return number().map(n -> -n);
+ }
+
+ static SerializeMemoize.Builder builder() {
+ return new AutoValue_SerializableAutoValueExtensionTest_SerializeMemoize.Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder setNumber(Optional<Integer> number);
+ abstract SerializeMemoize build();
+ }
+ }
+
+ @Test
+ public void serializeMemoize() {
+ SerializeMemoize instance1 = SerializeMemoize.builder().setNumber(Optional.of(17)).build();
+ assertThat(instance1.methodCount).isEqualTo(0);
+ assertThat(instance1.negate()).hasValue(-17);
+ assertThat(instance1.methodCount).isEqualTo(1);
+ assertThat(instance1.negate()).hasValue(-17);
+ assertThat(instance1.methodCount).isEqualTo(1);
+ SerializeMemoize instance2 = SerializableTester.reserialize(instance1);
+ assertThat(instance2.methodCount).isEqualTo(0);
+ assertThat(instance2.negate()).hasValue(-17);
+ assertThat(instance2.methodCount).isEqualTo(1);
+ assertThat(instance2.negate()).hasValue(-17);
+ assertThat(instance2.methodCount).isEqualTo(1);
+
+ }
}
diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/CompilationAbstractTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/CompilationAbstractTest.java
index 0eb634a6..9fd89e75 100644
--- a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/CompilationAbstractTest.java
+++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/CompilationAbstractTest.java
@@ -15,11 +15,12 @@
*/
package com.google.auto.value.extension.serializable.serializer.utils;
-import static org.mockito.Mockito.when;
-
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
+import com.google.common.reflect.Reflection;
import com.google.testing.compile.CompilationRule;
+import java.lang.reflect.InvocationHandler;
import java.util.Arrays;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
@@ -32,30 +33,47 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
@RunWith(JUnit4.class)
public abstract class CompilationAbstractTest {
@Rule public final CompilationRule compilationRule = new CompilationRule();
- @Rule public final MockitoRule mockito = MockitoJUnit.rule();
- @Mock protected ProcessingEnvironment mockProcessingEnvironment;
- @Mock protected Messager mockMessager;
+ protected ProcessingEnvironment mockProcessingEnvironment = mockProcessingEnvironment();
+ protected Messager mockMessager = mockMessager();
protected Types typeUtils;
protected Elements elementUtils;
+ private ProcessingEnvironment mockProcessingEnvironment() {
+ InvocationHandler handler = (proxy, method, args) -> {
+ switch (method.getName()) {
+ case "getTypeUtils":
+ return typeUtils;
+ case "getElementUtils":
+ return elementUtils;
+ case "getMessager":
+ return mockMessager;
+ case "getOptions":
+ return ImmutableMap.of();
+ case "toString":
+ return "MockProcessingEnvironment";
+ default:
+ throw new IllegalArgumentException("Unsupported method: " + method);
+ }
+ };
+ return Reflection.newProxy(ProcessingEnvironment.class, handler);
+ }
+
+ private Messager mockMessager() {
+ InvocationHandler handler = (proxy, method, args) -> null;
+ return Reflection.newProxy(Messager.class, handler);
+ }
+
@Before
public final void setUp() {
typeUtils = compilationRule.getTypes();
elementUtils = compilationRule.getElements();
-
- when(mockProcessingEnvironment.getTypeUtils()).thenReturn(typeUtils);
- when(mockProcessingEnvironment.getElementUtils()).thenReturn(elementUtils);
- when(mockProcessingEnvironment.getMessager()).thenReturn(mockMessager);
}
protected TypeElement typeElementOf(Class<?> c) {
diff --git a/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java
index 094b570d..fa4854bb 100644
--- a/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java
@@ -62,29 +62,6 @@ public class AutoAnnotationErrorsTest {
}
@Test
- public void testNotStatic() {
- JavaFileObject testSource =
- JavaFileObjects.forSourceLines(
- "com.foo.Test",
- "package com.foo;",
- "",
- "import com.example.TestAnnotation;",
- "import com.google.auto.value.AutoAnnotation;",
- "",
- "class Test {",
- " @AutoAnnotation TestAnnotation newTestAnnotation(int value) {",
- " return new AutoAnnotation_Test_newTestAnnotation(value);",
- " }",
- "}");
- Compilation compilation =
- javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource);
- assertThat(compilation)
- .hadErrorContaining("must be static")
- .inFile(testSource)
- .onLineContaining("TestAnnotation newTestAnnotation(int value)");
- }
-
- @Test
public void testDoesNotReturnAnnotation() {
JavaFileObject testSource =
JavaFileObjects.forSourceLines(
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 96649bd1..c85deb5a 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
@@ -19,6 +19,8 @@ import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_V
import static com.google.common.truth.TruthJUnit.assume;
import static com.google.testing.compile.CompilationSubject.assertThat;
import static com.google.testing.compile.Compiler.javac;
+import static java.util.Arrays.stream;
+import static java.util.stream.Collectors.joining;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.JavaFileObjects;
@@ -34,17 +36,27 @@ public final class AutoBuilderCompilationTest {
"foo.bar.AutoBuilder_Baz_Builder",
"package foo.bar;",
"",
- GeneratedImport.importGeneratedAnnotationType(),
+ sorted(
+ GeneratedImport.importGeneratedAnnotationType(),
+ "import org.checkerframework.checker.nullness.qual.Nullable;"),
"",
"@Generated(\"" + AutoBuilderProcessor.class.getName() + "\")",
"class AutoBuilder_Baz_Builder implements Baz.Builder {",
- " private Integer anInt;",
- " private String aString;",
+ " private int anInt;",
+ " private @Nullable String aString;",
+ " private byte set$0;",
"",
" AutoBuilder_Baz_Builder() {}",
"",
+ " AutoBuilder_Baz_Builder(Baz source) {",
+ " this.anInt = source.anInt();",
+ " this.aString = source.aString();",
+ " set$0 = (byte) 1;",
+ " }",
+ "",
" @Override public Baz.Builder setAnInt(int anInt) {",
" this.anInt = anInt;",
+ " set$0 |= (byte) 0x1;",
" return this;",
" }",
"",
@@ -58,10 +70,10 @@ public final class AutoBuilderCompilationTest {
"",
" @Override",
" public Baz build() {",
- " if (this.anInt == null",
- " || this.aString == null) {",
+ " if (set$0 != 0x1",
+ " || this.aString == null) {",
" StringBuilder missing = new StringBuilder();",
- " if (this.anInt == null) {",
+ " if ((set$0 & 0x1) == 0) {",
" missing.append(\" anInt\");",
" }",
" if (this.aString == null) {",
@@ -115,6 +127,7 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
+ .withOptions("-A" + Nullables.NULLABLE_OPTION + "=org.checkerframework.checker.nullness.qual.Nullable")
.compile(javaFileObject);
assertThat(compilation)
.generatedSourceFile("foo.bar.AutoBuilder_Baz_Builder")
@@ -147,6 +160,7 @@ public final class AutoBuilderCompilationTest {
Compilation compilation =
javac()
.withProcessors(new AutoBuilderProcessor())
+ .withOptions("-A" + Nullables.NULLABLE_OPTION + "=org.checkerframework.checker.nullness.qual.Nullable")
.compile(javaFileObject);
assertThat(compilation)
.generatedSourceFile("foo.bar.AutoBuilder_Baz_Builder")
@@ -307,6 +321,53 @@ public final class AutoBuilderCompilationTest {
}
@Test
+ public void autoBuilderMissingBuildMethod() {
+ JavaFileObject javaFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoBuilder;",
+ "",
+ "public class Baz {",
+ " private final int anInt;",
+ " private final String aString;",
+ "",
+ " public Baz(int anInt, String aString) {",
+ " this.anInt = anInt;",
+ " this.aString = aString;",
+ " }",
+ "",
+ " public int anInt() {",
+ " return anInt;",
+ " }",
+ "",
+ " public String aString() {",
+ " return aString;",
+ " }",
+ "",
+ " public static Builder builder() {",
+ " return new AutoBuilder_Baz_Builder();",
+ " }",
+ "",
+ " @AutoBuilder",
+ " public interface Builder {",
+ " Builder setAnInt(int x);",
+ " Builder setAString(String x);",
+ " }",
+ "}");
+ Compilation compilation =
+ javac().withProcessors(new AutoBuilderProcessor()).compile(javaFileObject);
+ assertThat(compilation).failed();
+ assertThat(compilation)
+ .hadErrorContaining(
+ "[AutoValueBuilderBuild] Builder must have a single no-argument method, typically"
+ + " called build(), that returns foo.bar.Baz")
+ .inFile(javaFileObject)
+ .onLineContaining("interface Builder");
+ }
+
+ @Test
public void autoBuilderNestedInPrivate() {
JavaFileObject javaFileObject =
JavaFileObjects.forSourceLines(
@@ -777,7 +838,7 @@ public final class AutoBuilderCompilationTest {
"package foo.bar;",
"",
"import com.google.auto.value.AutoBuilder;",
- "import org.checkerframework.checker.nullness.qual.Nullable;",
+ "import com.example.annotations.Nullable;",
"",
"class Baz {",
" Baz(String thing) {}",
@@ -790,8 +851,8 @@ public final class AutoBuilderCompilationTest {
"}");
JavaFileObject nullableFileObject =
JavaFileObjects.forSourceLines(
- "org.checkerframework.checker.nullness.qual.Nullable",
- "package org.jspecify.nullness;",
+ "com.example.annotations.Nullable",
+ "package com.example.annotations;",
"",
"import java.lang.annotation.ElementType;",
"import java.lang.annotation.Target;",
@@ -908,4 +969,39 @@ public final class AutoBuilderCompilationTest {
.inFile(javaFileObject)
.onLineContaining("interface Builder<E>");
}
+
+ @Test
+ public void annotationWithCallMethod() {
+ JavaFileObject javaFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoBuilder;",
+ "",
+ "class Baz {",
+ " @interface MyAnnot {",
+ " boolean broken();",
+ " }",
+ "",
+ " @AutoBuilder(callMethod = \"annotationType\", ofClass = MyAnnot.class)",
+ " interface Builder {",
+ " abstract Builder broken(boolean x);",
+ " abstract MyAnnot build();",
+ " }",
+ "}");
+ Compilation compilation =
+ javac().withProcessors(new AutoBuilderProcessor()).compile(javaFileObject);
+ assertThat(compilation).failed();
+ assertThat(compilation)
+ .hadErrorContaining(
+ "[AutoBuilderAnnotationMethod] @AutoBuilder for an annotation must have an empty"
+ + " callMethod, not \"annotationType\"")
+ .inFile(javaFileObject)
+ .onLineContaining("interface Builder");
+ }
+
+ private static String sorted(String... imports) {
+ return stream(imports).sorted().collect(joining("\n"));
+ }
}
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 09d4faf9..01ec81fe 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
@@ -15,10 +15,13 @@
*/
package com.google.auto.value.processor;
+import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
import static com.google.testing.compile.CompilationSubject.assertThat;
import static com.google.testing.compile.CompilationSubject.compilations;
import static com.google.testing.compile.Compiler.javac;
+import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining;
import com.google.common.collect.ImmutableList;
@@ -33,7 +36,6 @@ import java.io.UncheckedIOException;
import java.io.Writer;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
@@ -54,6 +56,11 @@ import org.junit.runners.JUnit4;
public class AutoValueCompilationTest {
@Rule public final Expect expect = Expect.create();
+ // Sadly we can't rely on JDK 8 to handle type annotations correctly.
+ // Some versions do, some don't. So skip the test unless we are on at least JDK 9.
+ private boolean typeAnnotationsWork =
+ Double.parseDouble(JAVA_SPECIFICATION_VERSION.value()) >= 9.0;
+
@Test
public void simpleSuccess() {
// Positive test case that ensures we generate the expected code for at least one case.
@@ -254,6 +261,7 @@ public class AutoValueCompilationTest {
@Test
public void testNestedParameterizedTypesWithTypeAnnotations() {
+ assume().that(typeAnnotationsWork).isTrue();
JavaFileObject annotFileObject =
JavaFileObjects.forSourceLines(
"foo.bar.Annot",
@@ -964,6 +972,8 @@ public class AutoValueCompilationTest {
.hadErrorContaining("MissingType")
.inFile(javaFileObject)
.onLineContaining("MissingType");
+ assertThat(compilation)
+ .hadErrorContaining("references undefined types including MissingType");
}
@Test
@@ -1063,10 +1073,11 @@ public class AutoValueCompilationTest {
"",
"import com.google.auto.value.AutoValue;",
"import com.google.common.base.Optional;",
- "import com.google.common.collect.ImmutableList;",
+ "import com.google.common.collect.ImmutableMap;",
"",
"import java.util.ArrayList;",
"import java.util.List;",
+ "import java.util.Map;",
"import javax.annotation.Nullable;",
"",
"@AutoValue",
@@ -1077,7 +1088,7 @@ public class AutoValueCompilationTest {
" @SuppressWarnings(\"mutable\")",
" @Nullable public abstract int[] aNullableIntArray();",
" public abstract List<T> aList();",
- " public abstract ImmutableList<T> anImmutableList();",
+ " public abstract ImmutableMap<T, String> anImmutableMap();",
" public abstract Optional<String> anOptionalString();",
" public abstract NestedAutoValue<T> aNestedAutoValue();",
"",
@@ -1089,8 +1100,8 @@ public class AutoValueCompilationTest {
" public abstract Builder<T> aByteArray(byte[] x);",
" public abstract Builder<T> aNullableIntArray(@Nullable int[] x);",
" public abstract Builder<T> aList(List<T> x);",
- " public abstract Builder<T> anImmutableList(List<T> x);",
- " public abstract ImmutableList.Builder<T> anImmutableListBuilder();",
+ " public abstract Builder<T> anImmutableMap(Map<T, String> x);",
+ " public abstract ImmutableMap.Builder<T, String> anImmutableMapBuilder();",
" public abstract Builder<T> anOptionalString(Optional<String> s);",
" public abstract Builder<T> anOptionalString(String s);",
" public abstract NestedAutoValue.Builder<T> aNestedAutoValueBuilder();",
@@ -1102,7 +1113,7 @@ public class AutoValueCompilationTest {
"",
" public abstract Optional<Integer> anInt();",
" public abstract List<T> aList();",
- " public abstract ImmutableList<T> anImmutableList();",
+ " public abstract ImmutableMap<T, String> anImmutableMap();",
"",
" public abstract Baz<T> build();",
" }",
@@ -1140,9 +1151,10 @@ public class AutoValueCompilationTest {
"package foo.bar;",
"",
"import com.google.common.base.Optional;",
- "import com.google.common.collect.ImmutableList;",
+ "import com.google.common.collect.ImmutableMap;",
"import java.util.Arrays;",
"import java.util.List;",
+ "import java.util.Map;",
sorted(
GeneratedImport.importGeneratedAnnotationType(),
"import javax.annotation.Nullable;"),
@@ -1153,7 +1165,7 @@ public class AutoValueCompilationTest {
" private final byte[] aByteArray;",
" private final int[] aNullableIntArray;",
" private final List<T> aList;",
- " private final ImmutableList<T> anImmutableList;",
+ " private final ImmutableMap<T, String> anImmutableMap;",
" private final Optional<String> anOptionalString;",
" private final NestedAutoValue<T> aNestedAutoValue;",
"",
@@ -1162,14 +1174,14 @@ public class AutoValueCompilationTest {
" byte[] aByteArray,",
" @Nullable int[] aNullableIntArray,",
" List<T> aList,",
- " ImmutableList<T> anImmutableList,",
+ " ImmutableMap<T, String> anImmutableMap,",
" Optional<String> anOptionalString,",
" NestedAutoValue<T> aNestedAutoValue) {",
" this.anInt = anInt;",
" this.aByteArray = aByteArray;",
" this.aNullableIntArray = aNullableIntArray;",
" this.aList = aList;",
- " this.anImmutableList = anImmutableList;",
+ " this.anImmutableMap = anImmutableMap;",
" this.anOptionalString = anOptionalString;",
" this.aNestedAutoValue = aNestedAutoValue;",
" }",
@@ -1193,8 +1205,8 @@ public class AutoValueCompilationTest {
" return aList;",
" }",
"",
- " @Override public ImmutableList<T> anImmutableList() {",
- " return anImmutableList;",
+ " @Override public ImmutableMap<T, String> anImmutableMap() {",
+ " return anImmutableMap;",
" }",
"",
" @Override public Optional<String> anOptionalString() {",
@@ -1211,7 +1223,7 @@ public class AutoValueCompilationTest {
" + \"aByteArray=\" + Arrays.toString(aByteArray) + \", \"",
" + \"aNullableIntArray=\" + Arrays.toString(aNullableIntArray) + \", \"",
" + \"aList=\" + aList + \", \"",
- " + \"anImmutableList=\" + anImmutableList + \", \"",
+ " + \"anImmutableMap=\" + anImmutableMap + \", \"",
" + \"anOptionalString=\" + anOptionalString + \", \"",
" + \"aNestedAutoValue=\" + aNestedAutoValue",
" + \"}\";",
@@ -1231,7 +1243,7 @@ public class AutoValueCompilationTest {
+ "(that instanceof AutoValue_Baz) "
+ "? ((AutoValue_Baz<?>) that).aNullableIntArray : that.aNullableIntArray())",
" && this.aList.equals(that.aList())",
- " && this.anImmutableList.equals(that.anImmutableList())",
+ " && this.anImmutableMap.equals(that.anImmutableMap())",
" && this.anOptionalString.equals(that.anOptionalString())",
" && this.aNestedAutoValue.equals(that.aNestedAutoValue());",
" }",
@@ -1249,7 +1261,7 @@ public class AutoValueCompilationTest {
" h$ *= 1000003;",
" h$ ^= aList.hashCode();",
" h$ *= 1000003;",
- " h$ ^= anImmutableList.hashCode();",
+ " h$ ^= anImmutableMap.hashCode();",
" h$ *= 1000003;",
" h$ ^= anOptionalString.hashCode();",
" h$ *= 1000003;",
@@ -1262,15 +1274,16 @@ public class AutoValueCompilationTest {
" }",
"",
" static final class Builder<T extends Number> extends Baz.Builder<T> {",
- " private Integer anInt;",
+ " private int anInt;",
" private byte[] aByteArray;",
" private int[] aNullableIntArray;",
" private List<T> aList;",
- " private ImmutableList.Builder<T> anImmutableListBuilder$;",
- " private ImmutableList<T> anImmutableList;",
+ " private ImmutableMap.Builder<T, String> anImmutableMapBuilder$;",
+ " private ImmutableMap<T, String> anImmutableMap;",
" private Optional<String> anOptionalString = Optional.absent();",
" private NestedAutoValue.Builder<T> aNestedAutoValueBuilder$;",
" private NestedAutoValue<T> aNestedAutoValue;",
+ " private byte set$0;",
"",
" Builder() {",
" }",
@@ -1280,24 +1293,25 @@ public class AutoValueCompilationTest {
" this.aByteArray = source.aByteArray();",
" this.aNullableIntArray = source.aNullableIntArray();",
" this.aList = source.aList();",
- " this.anImmutableList = source.anImmutableList();",
+ " this.anImmutableMap = source.anImmutableMap();",
" this.anOptionalString = source.anOptionalString();",
" this.aNestedAutoValue = source.aNestedAutoValue();",
+ " set$0 = (byte) 1;",
" }",
"",
" @Override",
" public Baz.Builder<T> anInt(int anInt) {",
" this.anInt = anInt;",
+ " set$0 |= (byte) 1;",
" return this;",
" }",
"",
" @Override",
" public Optional<Integer> anInt() {",
- " if (anInt == null) {",
+ " if ((set$0 & 1) == 0) {",
" return Optional.absent();",
- " } else {",
- " return Optional.of(anInt);",
" }",
+ " return Optional.of(anInt);",
" }",
"",
" @Override",
@@ -1326,45 +1340,45 @@ public class AutoValueCompilationTest {
"",
" @Override",
" public List<T> aList() {",
- " if (aList == null) {",
+ " if (this.aList == null) {",
" throw new IllegalStateException(\"Property \\\"aList\\\" has not been set\");",
" }",
" return aList;",
" }",
"",
" @Override",
- " public Baz.Builder<T> anImmutableList(List<T> anImmutableList) {",
- " if (anImmutableListBuilder$ != null) {",
+ " public Baz.Builder<T> anImmutableMap(Map<T, String> anImmutableMap) {",
+ " if (anImmutableMapBuilder$ != null) {",
" throw new IllegalStateException("
- + "\"Cannot set anImmutableList after calling anImmutableListBuilder()\");",
+ + "\"Cannot set anImmutableMap after calling anImmutableMapBuilder()\");",
" }",
- " this.anImmutableList = ImmutableList.copyOf(anImmutableList);",
+ " this.anImmutableMap = ImmutableMap.copyOf(anImmutableMap);",
" return this;",
" }",
"",
" @Override",
- " public ImmutableList.Builder<T> anImmutableListBuilder() {",
- " if (anImmutableListBuilder$ == null) {",
- " if (anImmutableList == null) {",
- " anImmutableListBuilder$ = ImmutableList.builder();",
+ " public ImmutableMap.Builder<T, String> anImmutableMapBuilder() {",
+ " if (anImmutableMapBuilder$ == null) {",
+ " if (anImmutableMap == null) {",
+ " anImmutableMapBuilder$ = ImmutableMap.builder();",
" } else {",
- " anImmutableListBuilder$ = ImmutableList.builder();",
- " anImmutableListBuilder$.addAll(anImmutableList);",
- " anImmutableList = null;",
+ " anImmutableMapBuilder$ = ImmutableMap.builder();",
+ " anImmutableMapBuilder$.putAll(anImmutableMap);",
+ " anImmutableMap = null;",
" }",
" }",
- " return anImmutableListBuilder$;",
+ " return anImmutableMapBuilder$;",
" }",
"",
" @Override",
- " public ImmutableList<T> anImmutableList() {",
- " if (anImmutableListBuilder$ != null) {",
- " return anImmutableListBuilder$.build();",
+ " public ImmutableMap<T, String> anImmutableMap() {",
+ " if (anImmutableMapBuilder$ != null) {",
+ " return anImmutableMapBuilder$.buildOrThrow();",
" }",
- " if (anImmutableList == null) {",
- " anImmutableList = ImmutableList.of();",
+ " if (anImmutableMap == null) {",
+ " anImmutableMap = ImmutableMap.of();",
" }",
- " return anImmutableList;",
+ " return anImmutableMap;",
" }",
"",
" @Override",
@@ -1397,10 +1411,10 @@ public class AutoValueCompilationTest {
"",
" @Override",
" public Baz<T> build() {",
- " if (anImmutableListBuilder$ != null) {",
- " this.anImmutableList = anImmutableListBuilder$.build();",
- " } else if (this.anImmutableList == null) {",
- " this.anImmutableList = ImmutableList.of();",
+ " if (anImmutableMapBuilder$ != null) {",
+ " this.anImmutableMap = anImmutableMapBuilder$.buildOrThrow();",
+ " } else if (this.anImmutableMap == null) {",
+ " this.anImmutableMap = ImmutableMap.of();",
" }",
" if (aNestedAutoValueBuilder$ != null) {",
" this.aNestedAutoValue = aNestedAutoValueBuilder$.build();",
@@ -1409,12 +1423,12 @@ public class AutoValueCompilationTest {
+ "NestedAutoValue.builder();",
" this.aNestedAutoValue = aNestedAutoValue$builder.build();",
" }",
- " if (this.anInt == null",
+ " if (set$0 != 1",
" || this.aByteArray == null",
" || this.aList == null) {",
" StringBuilder missing = new StringBuilder();",
- " if (this.anInt == null) {",
- " missing.append(\" anInt\");",
+ " if ((set$0 & 1) == 0) {",
+ " missing.append(\" anInt\");",
" }",
" if (this.aByteArray == null) {",
" missing.append(\" aByteArray\");",
@@ -1429,7 +1443,7 @@ public class AutoValueCompilationTest {
" this.aByteArray,",
" this.aNullableIntArray,",
" this.aList,",
- " this.anImmutableList,",
+ " this.anImmutableMap,",
" this.anOptionalString,",
" this.aNestedAutoValue);",
" }",
@@ -1448,6 +1462,301 @@ public class AutoValueCompilationTest {
}
@Test
+ public void builderWithNullableTypeAnnotation() {
+ assume().that(typeAnnotationsWork).isTrue();
+ JavaFileObject javaFileObject =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "import com.google.common.base.Optional;",
+ "import com.google.common.collect.ImmutableMap;",
+ "",
+ "import java.util.ArrayList;",
+ "import java.util.List;",
+ "import java.util.Map;",
+ "import org.checkerframework.checker.nullness.qual.Nullable;",
+ "",
+ "@AutoValue",
+ "public abstract class Baz<T extends Number> {",
+ " public abstract int anInt();",
+ " @SuppressWarnings(\"mutable\")",
+ " public abstract byte[] aByteArray();",
+ " @SuppressWarnings(\"mutable\")",
+ " public abstract int @Nullable [] aNullableIntArray();",
+ " public abstract List<T> aList();",
+ " public abstract ImmutableMap<T, String> anImmutableMap();",
+ " public abstract Optional<String> anOptionalString();",
+ "",
+ " public abstract Builder<T> toBuilder();",
+ "",
+ " @AutoValue.Builder",
+ " public abstract static class Builder<T extends Number> {",
+ " public abstract Builder<T> anInt(int x);",
+ " public abstract Builder<T> aByteArray(byte[] x);",
+ " public abstract Builder<T> aNullableIntArray(int @Nullable [] x);",
+ " public abstract Builder<T> aList(List<T> x);",
+ " public abstract Builder<T> anImmutableMap(Map<T, String> x);",
+ " public abstract ImmutableMap.Builder<T, String> anImmutableMapBuilder();",
+ " public abstract Builder<T> anOptionalString(Optional<String> s);",
+ " public abstract Baz<T> build();",
+ " }",
+ "",
+ " public static <T extends Number> Builder<T> builder() {",
+ " return AutoValue_Baz.builder();",
+ " }",
+ "}");
+ JavaFileObject expectedOutput =
+ JavaFileObjects.forSourceLines(
+ "foo.bar.AutoValue_Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.common.base.Optional;",
+ "import com.google.common.collect.ImmutableMap;",
+ "import java.util.Arrays;",
+ "import java.util.List;",
+ "import java.util.Map;",
+ sorted(
+ GeneratedImport.importGeneratedAnnotationType(),
+ "import org.checkerframework.checker.nullness.qual.Nullable;"),
+ "",
+ "@Generated(\"" + AutoValueProcessor.class.getName() + "\")",
+ "final class AutoValue_Baz<T extends Number> extends Baz<T> {",
+ " private final int anInt;",
+ " private final byte[] aByteArray;",
+ " private final int @Nullable [] aNullableIntArray;",
+ " private final List<T> aList;",
+ " private final ImmutableMap<T, String> anImmutableMap;",
+ " private final Optional<String> anOptionalString;",
+ "",
+ " private AutoValue_Baz(",
+ " int anInt,",
+ " byte[] aByteArray,",
+ " int @Nullable [] aNullableIntArray,",
+ " List<T> aList,",
+ " ImmutableMap<T, String> anImmutableMap,",
+ " Optional<String> anOptionalString) {",
+ " this.anInt = anInt;",
+ " this.aByteArray = aByteArray;",
+ " this.aNullableIntArray = aNullableIntArray;",
+ " this.aList = aList;",
+ " this.anImmutableMap = anImmutableMap;",
+ " this.anOptionalString = anOptionalString;",
+ " }",
+ "",
+ " @Override public int anInt() {",
+ " return anInt;",
+ " }",
+ "",
+ " @SuppressWarnings(\"mutable\")",
+ " @Override public byte[] aByteArray() {",
+ " return aByteArray;",
+ " }",
+ "",
+ " @SuppressWarnings(\"mutable\")",
+ " @Override public int @Nullable [] aNullableIntArray() {",
+ " return aNullableIntArray;",
+ " }",
+ "",
+ " @Override public List<T> aList() {",
+ " return aList;",
+ " }",
+ "",
+ " @Override public ImmutableMap<T, String> anImmutableMap() {",
+ " return anImmutableMap;",
+ " }",
+ "",
+ " @Override public Optional<String> anOptionalString() {",
+ " return anOptionalString;",
+ " }",
+ "",
+ " @Override public String toString() {",
+ " return \"Baz{\"",
+ " + \"anInt=\" + anInt + \", \"",
+ " + \"aByteArray=\" + Arrays.toString(aByteArray) + \", \"",
+ " + \"aNullableIntArray=\" + Arrays.toString(aNullableIntArray) + \", \"",
+ " + \"aList=\" + aList + \", \"",
+ " + \"anImmutableMap=\" + anImmutableMap + \", \"",
+ " + \"anOptionalString=\" + anOptionalString",
+ " + \"}\";",
+ " }",
+ "",
+ " @Override public boolean equals(@Nullable Object o) {",
+ " if (o == this) {",
+ " return true;",
+ " }",
+ " if (o instanceof Baz) {",
+ " Baz<?> that = (Baz<?>) o;",
+ " return this.anInt == that.anInt()",
+ " && Arrays.equals(this.aByteArray, "
+ + "(that instanceof AutoValue_Baz) "
+ + "? ((AutoValue_Baz<?>) that).aByteArray : that.aByteArray())",
+ " && Arrays.equals(this.aNullableIntArray, "
+ + "(that instanceof AutoValue_Baz) "
+ + "? ((AutoValue_Baz<?>) that).aNullableIntArray : that.aNullableIntArray())",
+ " && this.aList.equals(that.aList())",
+ " && this.anImmutableMap.equals(that.anImmutableMap())",
+ " && this.anOptionalString.equals(that.anOptionalString());",
+ " }",
+ " return false;",
+ " }",
+ "",
+ " @Override public int hashCode() {",
+ " int h$ = 1;",
+ " h$ *= 1000003;",
+ " h$ ^= anInt;",
+ " h$ *= 1000003;",
+ " h$ ^= Arrays.hashCode(aByteArray);",
+ " h$ *= 1000003;",
+ " h$ ^= Arrays.hashCode(aNullableIntArray);",
+ " h$ *= 1000003;",
+ " h$ ^= aList.hashCode();",
+ " h$ *= 1000003;",
+ " h$ ^= anImmutableMap.hashCode();",
+ " h$ *= 1000003;",
+ " h$ ^= anOptionalString.hashCode();",
+ " return h$;",
+ " }",
+ "",
+ " @Override public Baz.Builder<T> toBuilder() {",
+ " return new Builder<T>(this);",
+ " }",
+ "",
+ " static final class Builder<T extends Number> extends Baz.Builder<T> {",
+ " private int anInt;",
+ " private byte @Nullable [] aByteArray;",
+ " private int @Nullable [] aNullableIntArray;",
+ " private @Nullable List<T> aList;",
+ " private ImmutableMap.@Nullable Builder<T, String> anImmutableMapBuilder$;",
+ " private @Nullable ImmutableMap<T, String> anImmutableMap;",
+ " private Optional<String> anOptionalString = Optional.absent();",
+ " private byte set$0;",
+ "",
+ " Builder() {",
+ " }",
+ "",
+ " private Builder(Baz<T> source) {",
+ " this.anInt = source.anInt();",
+ " this.aByteArray = source.aByteArray();",
+ " this.aNullableIntArray = source.aNullableIntArray();",
+ " this.aList = source.aList();",
+ " this.anImmutableMap = source.anImmutableMap();",
+ " this.anOptionalString = source.anOptionalString();",
+ " set$0 = (byte) 1;",
+ " }",
+ "",
+ " @Override",
+ " public Baz.Builder<T> anInt(int anInt) {",
+ " this.anInt = anInt;",
+ " set$0 |= (byte) 1;",
+ " return this;",
+ " }",
+ "",
+ " @Override",
+ " public Baz.Builder<T> aByteArray(byte[] aByteArray) {",
+ " if (aByteArray == null) {",
+ " throw new NullPointerException(\"Null aByteArray\");",
+ " }",
+ " this.aByteArray = aByteArray;",
+ " return this;",
+ " }",
+ "",
+ " @Override",
+ " public Baz.Builder<T> aNullableIntArray(int @Nullable [] aNullableIntArray) {",
+ " this.aNullableIntArray = aNullableIntArray;",
+ " return this;",
+ " }",
+ "",
+ " @Override",
+ " public Baz.Builder<T> aList(List<T> aList) {",
+ " if (aList == null) {",
+ " throw new NullPointerException(\"Null aList\");",
+ " }",
+ " this.aList = aList;",
+ " return this;",
+ " }",
+ "",
+ " @Override",
+ " public Baz.Builder<T> anImmutableMap(Map<T, String> anImmutableMap) {",
+ " if (anImmutableMapBuilder$ != null) {",
+ " throw new IllegalStateException("
+ + "\"Cannot set anImmutableMap after calling anImmutableMapBuilder()\");",
+ " }",
+ " this.anImmutableMap = ImmutableMap.copyOf(anImmutableMap);",
+ " return this;",
+ " }",
+ "",
+ " @Override",
+ " public ImmutableMap.Builder<T, String> anImmutableMapBuilder() {",
+ " if (anImmutableMapBuilder$ == null) {",
+ " if (anImmutableMap == null) {",
+ " anImmutableMapBuilder$ = ImmutableMap.builder();",
+ " } else {",
+ " anImmutableMapBuilder$ = ImmutableMap.builder();",
+ " anImmutableMapBuilder$.putAll(anImmutableMap);",
+ " anImmutableMap = null;",
+ " }",
+ " }",
+ " return anImmutableMapBuilder$;",
+ " }",
+ "",
+ " @Override",
+ " public Baz.Builder<T> anOptionalString(Optional<String> anOptionalString) {",
+ " if (anOptionalString == null) {",
+ " throw new NullPointerException(\"Null anOptionalString\");",
+ " }",
+ " this.anOptionalString = anOptionalString;",
+ " return this;",
+ " }",
+ "",
+ " @Override",
+ " public Baz<T> build() {",
+ " if (anImmutableMapBuilder$ != null) {",
+ " this.anImmutableMap = anImmutableMapBuilder$.buildOrThrow();",
+ " } else if (this.anImmutableMap == null) {",
+ " this.anImmutableMap = ImmutableMap.of();",
+ " }",
+ " if (set$0 != 1",
+ " || this.aByteArray == null",
+ " || this.aList == null) {",
+ " StringBuilder missing = new StringBuilder();",
+ " if ((set$0 & 1) == 0) {",
+ " missing.append(\" anInt\");",
+ " }",
+ " if (this.aByteArray == null) {",
+ " missing.append(\" aByteArray\");",
+ " }",
+ " if (this.aList == null) {",
+ " missing.append(\" aList\");",
+ " }",
+ " throw new IllegalStateException(\"Missing required properties:\" + missing);",
+ " }",
+ " return new AutoValue_Baz<T>(",
+ " this.anInt,",
+ " this.aByteArray,",
+ " this.aNullableIntArray,",
+ " this.aList,",
+ " this.anImmutableMap,",
+ " this.anOptionalString);",
+ " }",
+ " }",
+ "}");
+ Compilation compilation =
+ javac()
+ .withProcessors(new AutoValueProcessor())
+ .withOptions(
+ "-Xlint:-processing",
+ "-implicit:none",
+ "-A" + Nullables.NULLABLE_OPTION + "=org.checkerframework.checker.nullness.qual.Nullable")
+ .compile(javaFileObject);
+ assertThat(compilation).succeededWithoutWarnings();
+ assertThat(compilation)
+ .generatedSourceFile("foo.bar.AutoValue_Baz")
+ .hasSourceEquivalentTo(expectedOutput);
+ }
+
+ @Test
public void autoValueBuilderOnTopLevelClass() {
JavaFileObject javaFileObject =
JavaFileObjects.forSourceLines(
@@ -2071,7 +2380,7 @@ public class AutoValueCompilationTest {
assertThat(compilation)
.hadErrorContaining(
"Method looks like a property builder, but it returns java.lang.StringBuilder which "
- + "does not have a non-static build() method")
+ + "does not have a non-static build() or buildOrThrow() method")
.inFile(javaFileObject)
.onLineContaining("StringBuilder blimBuilder()");
}
@@ -2265,7 +2574,7 @@ public class AutoValueCompilationTest {
assertThat(compilation)
.hadErrorContaining(
"Method looks like a property builder, but it returns java.lang.StringBuilder which "
- + "does not have a non-static build() method")
+ + "does not have a non-static build() or buildOrThrow() method")
.inFile(javaFileObject)
.onLineContaining("StringBuilder blimBuilder()");
}
@@ -2303,7 +2612,7 @@ public class AutoValueCompilationTest {
assertThat(compilation)
.hadErrorContaining(
"Method looks like a property builder, but it returns foo.bar.Baz.StringFactory which "
- + "does not have a non-static build() method")
+ + "does not have a non-static build() or buildOrThrow() method")
.inFile(javaFileObject)
.onLineContaining("StringFactory blimBuilder()");
}
@@ -3667,7 +3976,7 @@ public class AutoValueCompilationTest {
.doesNotContain("kotlin.Metadata");
}
- private String sorted(String... imports) {
- return Arrays.stream(imports).sorted().collect(joining("\n"));
+ private static String sorted(String... imports) {
+ return stream(imports).sorted().collect(joining("\n"));
}
}
diff --git a/value/src/test/java/com/google/auto/value/processor/AutoValueModuleCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoValueModuleCompilationTest.java
new file mode 100644
index 00000000..e2362a7f
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/AutoValueModuleCompilationTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.processor;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.testing.compile.JavaFileObjects;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import javax.tools.Diagnostic;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.ToolProvider;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class AutoValueModuleCompilationTest {
+ @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ /**
+ * This test currently confirms that AutoValue has a bug with module handling. If your AutoValue
+ * class inherits an abstract method from a module, and that abstract method has an annotation
+ * that is not exported from the module, then AutoValue will incorrectly try to copy the
+ * annotation onto the generated implementation of the abstract method.
+ */
+ @Test
+ public void nonVisibleMethodAnnotationFromOtherModule() throws Exception {
+ JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
+ Path tempDir = temporaryFolder.newFolder().toPath();
+ Path srcDir = tempDir.resolve("src");
+ Path outDir = tempDir.resolve("out");
+ Path fooOut = outDir.resolve("foo");
+ writeFile(srcDir, "foo/module-info.java", "module foo {", " exports foo.exported;", "}");
+ writeFile(
+ srcDir,
+ "foo/exported/Foo.java",
+ "package foo.exported;",
+ "",
+ "import foo.unexported.UnexportedAnnotation;",
+ "",
+ "public interface Foo {",
+ " @ExportedAnnotation",
+ " @UnexportedAnnotation",
+ " String name();",
+ "}");
+ writeFile(
+ srcDir,
+ "foo/exported/ExportedAnnotation.java",
+ "package foo.exported;",
+ "",
+ "import java.lang.annotation.*;",
+ "",
+ "@Retention(RetentionPolicy.RUNTIME)",
+ "public @interface ExportedAnnotation {}");
+ writeFile(
+ srcDir,
+ "foo/unexported/UnexportedAnnotation.java",
+ "package foo.unexported;",
+ "",
+ "import java.lang.annotation.*;",
+ "",
+ "@Retention(RetentionPolicy.RUNTIME)",
+ "public @interface UnexportedAnnotation {}");
+ JavaCompiler.CompilationTask fooCompilationTask =
+ javaCompiler.getTask(
+ /* out= */ null,
+ /* fileManager= */ null,
+ /* diagnosticListener= */ null,
+ /* options= */ ImmutableList.of(
+ "--module",
+ "foo",
+ "-d",
+ outDir.toString(),
+ "--module-source-path",
+ srcDir.toString()),
+ /* classes= */ null,
+ /* compilationUnits= */ null);
+ fooCompilationTask.setProcessors(ImmutableList.of(new AutoValueProcessor()));
+ boolean fooSuccess = fooCompilationTask.call();
+ assertThat(fooSuccess).isTrue();
+
+ JavaFileObject barFile =
+ JavaFileObjects.forSourceLines(
+ "bar.Value",
+ "package bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "import foo.exported.Foo;",
+ "",
+ "@AutoValue",
+ "public abstract class Value implements Foo {",
+ " public static Value of(String name) {",
+ " return new AutoValue_Value(name);",
+ " }",
+ "}");
+ List<Diagnostic<?>> diagnostics = new ArrayList<>();
+ JavaCompiler.CompilationTask barCompilationTask =
+ javaCompiler.getTask(
+ /* out= */ null,
+ /* fileManager= */ null,
+ /* diagnosticListener= */ diagnostics::add,
+ /* options= */ ImmutableList.of(
+ "-d",
+ outDir.toString(),
+ "--module-path",
+ fooOut.toString(),
+ "--add-modules",
+ "foo"),
+ /* classes= */ null,
+ /* compilationUnits= */ ImmutableList.of(barFile));
+ boolean barSuccess = barCompilationTask.call();
+ assertThat(barSuccess).isFalse();
+ assertThat(
+ diagnostics.stream()
+ .map(d -> d.getMessage(Locale.ROOT))
+ .collect(toImmutableList())
+ .toString())
+ .contains("package foo.unexported is declared in module foo, which does not export it");
+ }
+
+ private static void writeFile(Path srcDir, String relativeName, String... lines)
+ throws IOException {
+ Path path = srcDir.resolve(relativeName);
+ Files.createDirectories(path.getParent());
+ Files.writeString(path, String.join("\n", lines));
+ }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/BuilderRequiredPropertiesTest.java b/value/src/test/java/com/google/auto/value/processor/BuilderRequiredPropertiesTest.java
new file mode 100644
index 00000000..fe7ad321
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/BuilderRequiredPropertiesTest.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.processor;
+
+import static com.google.auto.common.MoreStreams.toImmutableList;
+import static com.google.auto.common.MoreStreams.toImmutableSet;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.google.auto.value.processor.AutoValueishProcessor.Property;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.testing.compile.CompilationRule;
+import java.util.Optional;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BuilderRequiredProperties}. */
+@RunWith(JUnit4.class)
+public final class BuilderRequiredPropertiesTest {
+ @ClassRule public static final CompilationRule compilationRule = new CompilationRule();
+
+ private TypeMirror intType;
+ private TypeMirror stringType;
+
+ @Before
+ public void initTypes() {
+ intType = compilationRule.getTypes().getPrimitiveType(TypeKind.INT);
+ stringType = compilationRule.getElements().getTypeElement("java.lang.String").asType();
+ }
+
+ @Test
+ public void fieldDeclarations() {
+ assertThat(fieldDeclarations(0)).isEmpty();
+ assertThat(fieldDeclarations(1)).containsExactly("private byte set$0;");
+ assertThat(fieldDeclarations(8)).containsExactly("private byte set$0;");
+ assertThat(fieldDeclarations(9)).containsExactly("private short set$0;");
+ assertThat(fieldDeclarations(16)).containsExactly("private short set$0;");
+ assertThat(fieldDeclarations(17)).containsExactly("private int set$0;");
+ assertThat(fieldDeclarations(32)).containsExactly("private int set$0;");
+ assertThat(fieldDeclarations(33)).containsExactly("private int set$0;", "private byte set$1;");
+ assertThat(fieldDeclarations(40)).containsExactly("private int set$0;", "private byte set$1;");
+ assertThat(fieldDeclarations(41)).containsExactly("private int set$0;", "private short set$1;");
+ assertThat(fieldDeclarations(48)).containsExactly("private int set$0;", "private short set$1;");
+ assertThat(fieldDeclarations(49)).containsExactly("private int set$0;", "private int set$1;");
+ assertThat(fieldDeclarations(64)).containsExactly("private int set$0;", "private int set$1;");
+ assertThat(fieldDeclarations(65))
+ .containsExactly("private int set$0;", "private int set$1;", "private byte set$2;");
+ assertThat(fieldDeclarations(144))
+ .containsExactly(
+ "private int set$0;",
+ "private int set$1;",
+ "private int set$2;",
+ "private int set$3;",
+ "private short set$4;");
+ }
+
+ private ImmutableList<String> fieldDeclarations(int size) {
+ return builderRequiredProperties(size).getFieldDeclarations();
+ }
+
+ @Test
+ public void initToAllSet() {
+ assertThat(initToAllSet(0)).isEmpty();
+ assertThat(initToAllSet(1)).containsExactly("set$0 = (byte) 1;");
+ assertThat(initToAllSet(8)).containsExactly("set$0 = (byte) 0xff;");
+ assertThat(initToAllSet(9)).containsExactly("set$0 = (short) 0x1ff;");
+ assertThat(initToAllSet(16)).containsExactly("set$0 = (short) 0xffff;");
+ assertThat(initToAllSet(17)).containsExactly("set$0 = 0x1_ffff;");
+ assertThat(initToAllSet(31)).containsExactly("set$0 = 0x7fff_ffff;");
+ assertThat(initToAllSet(32)).containsExactly("set$0 = -1;");
+ assertThat(initToAllSet(33)).containsExactly("set$0 = -1;", "set$1 = (byte) 1;");
+ assertThat(initToAllSet(63)).containsExactly("set$0 = -1;", "set$1 = 0x7fff_ffff;");
+ assertThat(initToAllSet(64)).containsExactly("set$0 = -1;", "set$1 = -1;");
+ assertThat(initToAllSet(144))
+ .containsExactly(
+ "set$0 = -1;", "set$1 = -1;", "set$2 = -1;", "set$3 = -1;", "set$4 = (short) 0xffff;");
+ }
+
+ private ImmutableList<String> initToAllSet(int size) {
+ return builderRequiredProperties(size).getInitToAllSet();
+ }
+
+ @Test
+ public void markAsSet_reference() {
+ BuilderRequiredProperties onlyString = builderRequiredProperties(0);
+ Property stringProperty = Iterables.getOnlyElement(onlyString.getRequiredProperties());
+ assertThat(onlyString.markAsSet(stringProperty)).isEmpty();
+ }
+
+ @Test
+ public void markAsSet_byte() {
+ BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(8);
+ ImmutableList<Property> primitives = requiredPrimitiveProperties(builderRequiredProperties);
+ assertThat(primitives).hasSize(8);
+ assertThat(builderRequiredProperties.markAsSet(primitives.get(0)))
+ .isEqualTo("set$0 |= (byte) 1;");
+ assertThat(builderRequiredProperties.markAsSet(primitives.get(7)))
+ .isEqualTo("set$0 |= (byte) 0x80;");
+ }
+
+ @Test
+ public void markAsSet_short() {
+ BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(16);
+ ImmutableList<Property> primitives = requiredPrimitiveProperties(builderRequiredProperties);
+ assertThat(primitives).hasSize(16);
+ assertThat(builderRequiredProperties.markAsSet(primitives.get(0)))
+ .isEqualTo("set$0 |= (short) 1;");
+ assertThat(builderRequiredProperties.markAsSet(primitives.get(7)))
+ .isEqualTo("set$0 |= (short) 0x80;");
+ assertThat(builderRequiredProperties.markAsSet(primitives.get(15)))
+ .isEqualTo("set$0 |= (short) 0x8000;");
+ }
+
+ @Test
+ public void markAsSet_int() {
+ BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(32);
+ ImmutableList<Property> primitives = requiredPrimitiveProperties(builderRequiredProperties);
+ assertThat(primitives).hasSize(32);
+ assertThat(builderRequiredProperties.markAsSet(primitives.get(0))).isEqualTo("set$0 |= 1;");
+ assertThat(builderRequiredProperties.markAsSet(primitives.get(31)))
+ .isEqualTo("set$0 |= 0x8000_0000;");
+ }
+
+ @Test
+ public void markAsSet_intPlusByte() {
+ BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(34);
+ ImmutableList<Property> primitives = requiredPrimitiveProperties(builderRequiredProperties);
+ assertThat(primitives).hasSize(34);
+ assertThat(builderRequiredProperties.markAsSet(primitives.get(0))).isEqualTo("set$0 |= 1;");
+ assertThat(builderRequiredProperties.markAsSet(primitives.get(31)))
+ .isEqualTo("set$0 |= 0x8000_0000;");
+ assertThat(builderRequiredProperties.markAsSet(primitives.get(32)))
+ .isEqualTo("set$1 |= (byte) 1;");
+ assertThat(builderRequiredProperties.markAsSet(primitives.get(33)))
+ .isEqualTo("set$1 |= (byte) 2;");
+ }
+
+ @Test
+ public void missingRequiredProperty_reference() {
+ BuilderRequiredProperties onlyString = builderRequiredProperties(0);
+ Property stringProperty = Iterables.getOnlyElement(onlyString.getRequiredProperties());
+ assertThat(onlyString.missingRequiredProperty(stringProperty)).isEqualTo("this.string == null");
+ }
+
+ @Test
+ public void missingRequiredProperty_byte() {
+ BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(8);
+ ImmutableList<Property> primitives = requiredPrimitiveProperties(builderRequiredProperties);
+ assertThat(primitives).hasSize(8);
+ assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(0)))
+ .isEqualTo("(set$0 & 1) == 0");
+ assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(7)))
+ .isEqualTo("(set$0 & 0x80) == 0");
+ }
+
+ @Test
+ public void missingRequiredProperty_short() {
+ BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(16);
+ ImmutableList<Property> primitives = requiredPrimitiveProperties(builderRequiredProperties);
+ assertThat(primitives).hasSize(16);
+ assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(0)))
+ .isEqualTo("(set$0 & 1) == 0");
+ assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(7)))
+ .isEqualTo("(set$0 & 0x80) == 0");
+ assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(15)))
+ .isEqualTo("(set$0 & 0x8000) == 0");
+ }
+
+ @Test
+ public void missingRequiredProperty_int() {
+ BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(32);
+ ImmutableList<Property> primitives = requiredPrimitiveProperties(builderRequiredProperties);
+ assertThat(primitives).hasSize(32);
+ assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(0)))
+ .isEqualTo("(set$0 & 1) == 0");
+ assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(31)))
+ .isEqualTo("(set$0 & 0x8000_0000) == 0");
+ }
+
+ @Test
+ public void missingRequiredProperty_intPlusByte() {
+ BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(34);
+ ImmutableList<Property> primitives = requiredPrimitiveProperties(builderRequiredProperties);
+ assertThat(primitives).hasSize(34);
+ assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(0)))
+ .isEqualTo("(set$0 & 1) == 0");
+ assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(31)))
+ .isEqualTo("(set$0 & 0x8000_0000) == 0");
+ assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(32)))
+ .isEqualTo("(set$1 & 1) == 0");
+ assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(33)))
+ .isEqualTo("(set$1 & 2) == 0");
+ }
+
+ @Test
+ public void noValueToGet_noDefaults() {
+ BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(34);
+ ImmutableList<Property> primitives = requiredPrimitiveProperties(builderRequiredProperties);
+ assertThat(primitives).hasSize(34);
+ for (Property property : primitives) {
+ assertWithMessage("For property %s", property)
+ .that(builderRequiredProperties.noValueToGet(property))
+ .isEqualTo(builderRequiredProperties.missingRequiredProperty(property));
+ }
+ }
+
+ @Test
+ public void noValueToGet_withDefaults() {
+ ImmutableSet<Property> allProperties = fakePropertiesWithDefaults(0);
+ BuilderRequiredProperties builderRequiredProperties =
+ BuilderRequiredProperties.of(allProperties, /* requiredProperties= */ ImmutableSet.of());
+ ImmutableList<Property> allPropertiesList = allProperties.asList();
+ assertThat(allPropertiesList.get(0).hasDefault()).isFalse();
+ assertThat(allPropertiesList.get(1).hasDefault()).isTrue();
+ assertThat(allPropertiesList.get(2).hasDefault()).isTrue();
+ assertThat(builderRequiredProperties.noValueToGet(allPropertiesList.get(0))).isNull();
+ assertThat(builderRequiredProperties.noValueToGet(allPropertiesList.get(1)))
+ .isEqualTo("(set$0 & 2) == 0");
+ assertThat(builderRequiredProperties.noValueToGet(allPropertiesList.get(2)))
+ .isEqualTo("(set$0 & 4) == 0");
+ }
+
+ @Test
+ public void getAnyMissing() {
+ assertThat(builderRequiredProperties(0).getAnyMissing()).isEqualTo("this.string == null");
+ assertThat(builderRequiredProperties(1).getAnyMissing())
+ .isEqualTo("set$0 != 1\n|| this.string == null");
+ assertThat(builderRequiredProperties(16).getAnyMissing())
+ .isEqualTo("set$0 != -1\n|| this.string == null");
+ assertThat(builderRequiredProperties(17).getAnyMissing())
+ .isEqualTo("set$0 != 0x1_ffff\n|| this.string == null");
+ assertThat(builderRequiredProperties(31).getAnyMissing())
+ .isEqualTo("set$0 != 0x7fff_ffff\n|| this.string == null");
+ assertThat(builderRequiredProperties(32).getAnyMissing())
+ .isEqualTo("set$0 != -1\n|| this.string == null");
+ assertThat(builderRequiredProperties(33).getAnyMissing())
+ .isEqualTo("set$0 != -1\n|| set$1 != 1\n|| this.string == null");
+ assertThat(builderRequiredProperties(64).getAnyMissing())
+ .isEqualTo("set$0 != -1\n|| set$1 != -1\n|| this.string == null");
+ }
+
+ @Test
+ public void getAnyMissing_withDefaults() {
+ assertThat(builderRequiredPropertiesWithDefaults(0).getAnyMissing())
+ .isEqualTo("(~set$0 & 1) != 0");
+ assertThat(builderRequiredPropertiesWithDefaults(1).getAnyMissing())
+ .isEqualTo("(~set$0 & 3) != 0");
+ assertThat(builderRequiredPropertiesWithDefaults(15).getAnyMissing())
+ .isEqualTo("(~set$0 & 0xffff) != 0");
+ assertThat(builderRequiredPropertiesWithDefaults(16).getAnyMissing())
+ .isEqualTo("(~set$0 & 0x1_ffff) != 0");
+ assertThat(builderRequiredPropertiesWithDefaults(17).getAnyMissing())
+ .isEqualTo("(~set$0 & 0x3_ffff) != 0");
+
+ // TODO(emcmanus): remove the no-op `& 0xfff_ffff`
+ assertThat(builderRequiredPropertiesWithDefaults(31).getAnyMissing())
+ .isEqualTo("(~set$0 & 0xffff_ffff) != 0");
+ assertThat(builderRequiredPropertiesWithDefaults(32).getAnyMissing())
+ .isEqualTo("(~set$0 & 0xffff_ffff) != 0\n|| (~set$1 & 1) != 0");
+ assertThat(builderRequiredPropertiesWithDefaults(33).getAnyMissing())
+ .isEqualTo("(~set$0 & 0xffff_ffff) != 0\n|| (~set$1 & 3) != 0");
+ assertThat(builderRequiredPropertiesWithDefaults(63).getAnyMissing())
+ .isEqualTo("(~set$0 & 0xffff_ffff) != 0\n|| (~set$1 & 0xffff_ffff) != 0");
+ assertThat(builderRequiredPropertiesWithDefaults(64).getAnyMissing())
+ .isEqualTo(
+ "(~set$0 & 0xffff_ffff) != 0\n|| (~set$1 & 0xffff_ffff) != 0\n|| (~set$2 & 1) != 0");
+ }
+
+ @Test
+ public void hex() {
+ assertThat(BuilderRequiredProperties.hex(0x0)).isEqualTo("0");
+ assertThat(BuilderRequiredProperties.hex(0x1)).isEqualTo("1");
+ assertThat(BuilderRequiredProperties.hex(0x9)).isEqualTo("9");
+ assertThat(BuilderRequiredProperties.hex(0xa)).isEqualTo("0xa");
+ assertThat(BuilderRequiredProperties.hex(0xffff)).isEqualTo("0xffff");
+ assertThat(BuilderRequiredProperties.hex(0x1_0000)).isEqualTo("0x1_0000");
+ assertThat(BuilderRequiredProperties.hex(0x7fff_ffff)).isEqualTo("0x7fff_ffff");
+ assertThat(BuilderRequiredProperties.hex(0xffff_ffff)).isEqualTo("0xffff_ffff");
+ }
+
+ private ImmutableList<Property> requiredPrimitiveProperties(
+ BuilderRequiredProperties builderRequiredProperties) {
+ return builderRequiredProperties.getRequiredProperties().stream()
+ .filter(p -> p.getTypeMirror().getKind().isPrimitive())
+ .collect(toImmutableList());
+ }
+
+ private BuilderRequiredProperties builderRequiredProperties(int primitiveCount) {
+ ImmutableSet<Property> properties = fakeProperties(primitiveCount);
+ return BuilderRequiredProperties.of(properties, properties);
+ }
+
+ private BuilderRequiredProperties builderRequiredPropertiesWithDefaults(int primitiveCount) {
+ ImmutableSet<Property> allProperties = fakePropertiesWithDefaults(primitiveCount);
+ ImmutableSet<Property> requiredProperties =
+ allProperties.stream().filter(p -> !p.hasDefault()).collect(toImmutableSet());
+ return BuilderRequiredProperties.of(allProperties, requiredProperties);
+ }
+
+ private ImmutableSet<Property> fakeProperties(int primitiveCount) {
+ return Stream.concat(
+ Stream.of(fakeProperty("string", stringType, /* hasDefault= */ false)),
+ IntStream.range(0, primitiveCount)
+ .mapToObj(i -> fakeProperty("x" + i, intType, /* hasDefault= */ false)))
+ .collect(toImmutableSet());
+ }
+
+ private ImmutableSet<Property> fakePropertiesWithDefaults(int primitiveCount) {
+ ImmutableSet<Property> requiredProperties = fakeProperties(primitiveCount);
+ return ImmutableSet.<Property>builder()
+ .addAll(requiredProperties)
+ .add(fakeProperty("stringWithDefault", stringType, /* hasDefault= */ true))
+ .add(fakeProperty("intWithDefault", intType, /* hasDefault= */ true))
+ .build();
+ }
+
+ private Property fakeProperty(String name, TypeMirror type, boolean hasDefault) {
+ return new Property(
+ /* name= */ name,
+ /* identifier= */ name,
+ /* type= */ type.toString(),
+ /* typeMirror= */ type,
+ /* nullableAnnotation= */ Optional.empty(),
+ /* nullables= */ Nullables.fromMethods(null, ImmutableList.of()),
+ /* getter= */ name,
+ /* maybeBuilderInitializer= */ Optional.empty(),
+ /* hasDefault= */ hasDefault);
+ }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/ForwardingClassGeneratorTest.java b/value/src/test/java/com/google/auto/value/processor/ForwardingClassGeneratorTest.java
new file mode 100644
index 00000000..383c46b2
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/ForwardingClassGeneratorTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.processor;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.Arrays.stream;
+import static javax.lang.model.util.ElementFilter.constructorsIn;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.testing.compile.CompilationRule;
+import java.lang.reflect.Method;
+import java.util.function.Supplier;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ForwardingClassGeneratorTest {
+ @Rule public final CompilationRule compilationRule = new CompilationRule();
+
+ public static class Simple implements Supplier<ImmutableList<Object>> {
+ final int anInt;
+
+ public Simple(int anInt) {
+ this.anInt = anInt;
+ }
+
+ @Override
+ public ImmutableList<Object> get() {
+ return ImmutableList.of(anInt);
+ }
+ }
+
+ @Test
+ public void simple() throws Exception {
+ testClass(Simple.class, ImmutableList.of(23));
+ }
+
+ public static class Outer {
+ public static class Inner {}
+ }
+
+ public static class KitchenSink implements Supplier<ImmutableList<Object>> {
+ final byte aByte;
+ final short aShort;
+ final int anInt;
+ final long aLong;
+ final float aFloat;
+ final double aDouble;
+ final char aChar;
+ final boolean aBoolean;
+ final String aString;
+ final ImmutableList<String> aStringList;
+ final String[] aStringArray;
+ final byte[] aByteArray;
+ final Outer.Inner anInner;
+
+ public KitchenSink(
+ byte aByte,
+ short aShort,
+ int anInt,
+ long aLong,
+ float aFloat,
+ double aDouble,
+ char aChar,
+ boolean aBoolean,
+ String aString,
+ ImmutableList<String> aStringList,
+ String[] aStringArray,
+ byte[] aByteArray,
+ Outer.Inner anInner) {
+ this.aByte = aByte;
+ this.aShort = aShort;
+ this.anInt = anInt;
+ this.aLong = aLong;
+ this.aFloat = aFloat;
+ this.aDouble = aDouble;
+ this.aChar = aChar;
+ this.aBoolean = aBoolean;
+ this.aString = aString;
+ this.aStringList = aStringList;
+ this.aStringArray = aStringArray;
+ this.aByteArray = aByteArray;
+ this.anInner = anInner;
+ }
+
+ @Override
+ public ImmutableList<Object> get() {
+ return ImmutableList.of(
+ aByte,
+ aShort,
+ anInt,
+ aLong,
+ aFloat,
+ aDouble,
+ aChar,
+ aBoolean,
+ aString,
+ aStringList,
+ aStringArray,
+ aByteArray,
+ anInner);
+ }
+ }
+
+ @Test
+ public void kitchenSink() throws Exception {
+ testClass(
+ KitchenSink.class,
+ ImmutableList.of(
+ (byte) 1,
+ (short) 2,
+ 3,
+ 4L,
+ 5f,
+ 6d,
+ '7',
+ true,
+ "9",
+ ImmutableList.of("10"),
+ new String[] {"11"},
+ new byte[] {12},
+ new Outer.Inner()));
+ }
+
+ /**
+ * Tests that we can successfully generate a forwarding class that calls the constructor of the
+ * given class. We'll then load the created class and call the forwarding method, checking that it
+ * does indeed call the constructor.
+ */
+ private void testClass(
+ Class<? extends Supplier<ImmutableList<Object>>> c,
+ ImmutableList<Object> constructorParameters)
+ throws ReflectiveOperationException {
+ TypeElement typeElement = compilationRule.getElements().getTypeElement(c.getCanonicalName());
+ ExecutableElement constructorExecutable =
+ Iterables.getOnlyElement(constructorsIn(typeElement.getEnclosedElements()));
+ ImmutableList<TypeMirror> parameterTypeMirrors =
+ constructorExecutable.getParameters().stream()
+ .map(Element::asType)
+ .map(compilationRule.getTypes()::erasure)
+ .collect(toImmutableList());
+ String className = "com.example.Forwarder";
+ byte[] bytes =
+ ForwardingClassGenerator.makeConstructorForwarder(
+ className, typeElement.asType(), parameterTypeMirrors);
+ // Now load the class we just generated, and use reflection to call its forwarding method.
+ // That should give us an instance of the target class `c`, obtained by the call to its
+ // constructor from the forwarding method.
+ ClassLoader loader =
+ new ClassLoader() {
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ if (name.equals(className)) {
+ return defineClass(className, bytes, 0, bytes.length);
+ }
+ throw new ClassNotFoundException(name);
+ }
+ };
+ Class<?> forwardingClass = Class.forName(className, true, loader);
+ Method ofMethod = stream(forwardingClass.getDeclaredMethods()).findFirst().get();
+ assertThat(ofMethod.getName()).isEqualTo("of");
+ ofMethod.setAccessible(true);
+ Supplier<ImmutableList<Object>> constructed =
+ c.cast(ofMethod.invoke(null, constructorParameters.toArray()));
+ ImmutableList<Object> retrievedParameters = constructed.get();
+ assertThat(retrievedParameters).isEqualTo(constructorParameters);
+ }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/GeneratedDoesNotExistTest.java b/value/src/test/java/com/google/auto/value/processor/GeneratedDoesNotExistTest.java
index 18cca5e4..6157935a 100644
--- a/value/src/test/java/com/google/auto/value/processor/GeneratedDoesNotExistTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/GeneratedDoesNotExistTest.java
@@ -22,11 +22,11 @@ import static com.google.testing.compile.Compiler.javac;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.Reflection;
+import com.google.errorprone.annotations.Keep;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.JavaFileObjects;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
-import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.processing.ProcessingEnvironment;
@@ -52,24 +52,28 @@ public class GeneratedDoesNotExistTest {
ImmutableList.of("-A" + Nullables.NULLABLE_OPTION + "=");
@Parameters(name = "{0}")
- public static Collection<Object[]> data() {
+ public static ImmutableList<Object[]> data() {
ImmutableList.Builder<Object[]> params = ImmutableList.builder();
- if (SourceVersion.latestSupported().compareTo(SourceVersion.RELEASE_8) > 0) {
- // use default options when running on JDK > 8
- // TODO(b/72513371): use --release 8 once compile-testing supports that
+ int release = SourceVersion.latestSupported().ordinal(); // 8 for Java 8, etc.
+ if (release == 8) {
+ params.add(new Object[] {STANDARD_OPTIONS, "javax.annotation.Generated"});
+ } else {
params.add(
new Object[] {
STANDARD_OPTIONS, "javax.annotation.processing.Generated",
});
+ if (release < 20) {
+ // starting with 20 we get a warning about --release 8 going away soon
+ params.add(
+ new Object[] {
+ ImmutableList.<String>builder()
+ .addAll(STANDARD_OPTIONS)
+ .add("--release", "8")
+ .build(),
+ "javax.annotation.Generated",
+ });
+ }
}
- params.add(
- new Object[] {
- ImmutableList.<String>builder()
- .addAll(STANDARD_OPTIONS)
- .add("-source", "8", "-target", "8")
- .build(),
- "javax.annotation.Generated",
- });
return params.build();
}
@@ -136,6 +140,7 @@ public class GeneratedDoesNotExistTest {
this.ignoredGenerated = ignoredGenerated;
}
+ @Keep
public TypeElement getTypeElement(CharSequence name) {
if (GENERATED_ANNOTATIONS.contains(name.toString())) {
ignoredGenerated.add(name.toString());
@@ -157,6 +162,7 @@ public class GeneratedDoesNotExistTest {
this.noGeneratedElements = partialProxy(Elements.class, elementsHandler);
}
+ @Keep
public Elements getElementUtils() {
return noGeneratedElements;
}
@@ -170,6 +176,7 @@ public class GeneratedDoesNotExistTest {
this.ignoredGenerated = ignoredGenerated;
}
+ @Keep
public void init(ProcessingEnvironment processingEnv) {
ProcessingEnvironmentHandler processingEnvironmentHandler =
new ProcessingEnvironmentHandler(processingEnv, ignoredGenerated);
diff --git a/value/src/test/java/com/google/auto/value/processor/NullablesTest.java b/value/src/test/java/com/google/auto/value/processor/NullablesTest.java
index 9e345f53..f5bd1bd3 100644
--- a/value/src/test/java/com/google/auto/value/processor/NullablesTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/NullablesTest.java
@@ -16,7 +16,7 @@
package com.google.auto.value.processor;
import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
-import static com.google.common.truth.OptionalSubject.optionals;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
import static com.google.testing.compile.CompilationSubject.assertThat;
@@ -147,8 +147,7 @@ public class NullablesTest {
List<ExecutableElement> notNullableMethods = partitionedMethods.get(false);
expect
- .about(optionals())
- .that(Nullables.nullableMentionedInMethods(notNullableMethods))
+ .that(Nullables.fromMethods(null, notNullableMethods).nullableTypeAnnotations())
.isEmpty();
TypeElement nullableElement =
@@ -165,11 +164,13 @@ public class NullablesTest {
.build();
expect
.withMessage("method %s should have @Nullable", nullableMethod)
- .about(optionals())
.that(
- Nullables.nullableMentionedInMethods(notNullablePlusNullable)
- .map(AnnotationMirror::getAnnotationType))
- .hasValue(nullableType);
+ Nullables.fromMethods(null, notNullablePlusNullable)
+ .nullableTypeAnnotations()
+ .stream()
+ .map(AnnotationMirror::getAnnotationType)
+ .collect(toImmutableList()))
+ .containsExactly(nullableType);
}
ran = true;
}
diff --git a/value/userguide/autobuilder.md b/value/userguide/autobuilder.md
index af9058bd..8045192d 100644
--- a/value/userguide/autobuilder.md
+++ b/value/userguide/autobuilder.md
@@ -17,7 +17,7 @@ method. Apart from that, the two are very similar.
Here is a simple example:
-```
+```java
@AutoBuilder(ofClass = Person.class)
abstract class PersonBuilder {
static PersonBuilder personBuilder() {
@@ -32,13 +32,13 @@ abstract class PersonBuilder {
It might be used like this:
-```
+```java
Person p = PersonBuilder.personBuilder().setName("Priz").setId(6).build();
```
That would have the same effect as this:
-```
+```java
Person p = new Person("Priz", 6);
```
@@ -51,7 +51,7 @@ and likewise with `setId`.
There is also a `build()` method. Calling that method invokes the `Person`
constructor with the parameters that were previously set.
-## Example: calling a Kotlin constructor
+## <a name="kotlin"></a> Example: calling a Kotlin constructor
Kotlin has named arguments and default arguments for constructors and functions,
which means there is not much need for anything like AutoBuilder there. But if
@@ -60,21 +60,22 @@ AutoBuilder can help.
Given this trivial Kotlin data class:
-```
-class KotlinData(val int: Int, val string: String?)
+```kotlin
+class KotlinData(val level: Int, val name: String?, val id: Long = -1L)
```
You might make a builder for it like this:
-```
+```java
@AutoBuilder(ofClass = KotlinData.class)
public abstract class KotlinDataBuilder {
public static KotlinDataBuilder kotlinDataBuilder() {
return new AutoBuilder_KotlinDataBuilder();
}
- public abstract setInt(int x);
- public abstract setString(@Nullable String x);
+ public abstract KotlinDataBuilder setLevel(int x);
+ public abstract KotlinDataBuilder setName(@Nullable String x);
+ public abstract KotlinDataBuilder setId(long x);
public abstract KotlinData build();
}
```
@@ -83,6 +84,41 @@ The Kotlin type `String?` corresponds to `@Nullable String` in the AutoBuilder
class, where `@Nullable` is any annotation with that name, such as
`org.jetbrains.annotations.Nullable`.
+The `id` parameter has a default value of `-1L`, which means that if `setId` is
+not called then the `id` field of the built `KotlinData` will be `-1L`.
+
+If you are using [kapt](https://kotlinlang.org/docs/kapt.html) then you can also
+define the builder in the data class itself:
+
+```kotlin
+class KotlinData(val level: Int, val name: String?, val id: Long = -1L) {
+ @AutoBuilder // we don't need ofClass: by default it is the containing class
+ interface Builder {
+ fun setLevel(x: Int): Builder
+ fun setName(x: String?): Builder
+ fun setId(x: Long): Builder
+ fun build(): KotlinData
+ }
+
+ fun toBuilder(): Builder = AutoBuilder_KotlinData_Builder(this)
+
+ companion object {
+ @JvmStatic fun builder(): Builder = AutoBuilder_KotlinData_Builder()
+ }
+}
+```
+
+This example uses an interface rather than an abstract class for the builder,
+but both are possible. Java code would then construct instances like this:
+
+```java
+KotlinData k = KotlinData.builder().setLevel(23).build();
+```
+
+The example also implements a `toBuilder()` method to get a builder that starts
+out with values from the given instance. See [below](#to_builder) for more
+details on that.
+
## The generated subclass
Like `@AutoValue.Builder`, compiling an `@AutoBuilder` class will generate a
@@ -94,7 +130,7 @@ will typically be the only reference to the generated class.
If the `@AutoBuilder` type is nested then the name of the generated class
reflects that nesting. For example:
-```
+```java
class Outer {
static class Inner {
@AutoBuilder
@@ -123,7 +159,7 @@ it is nested then it must be static.
### Both `callMethod` and `ofClass`
-```
+```java
@AutoBuilder(callMethod = "of", ofClass = LocalTime.class)
interface LocalTimeBuilder {
...
@@ -133,7 +169,7 @@ interface LocalTimeBuilder {
### Only `ofClass`
-```
+```java
@AutoBuilder(ofClass = Thread.class)
interface ThreadBuilder {
...
@@ -143,7 +179,7 @@ interface ThreadBuilder {
### Only `callMethod`
-```
+```java
class Foo {
static String concat(String first, String middle, String last) {...}
@@ -160,7 +196,7 @@ Notice in this example that the static method returns `String`. The implicit
### Neither `callMethod` nor `ofClass`
-```
+```java
class Person {
Person(String name, int id) {...}
@@ -185,7 +221,7 @@ the return type just described and that does not correspond to a parameter name.
The following example uses the name `call()` since that more accurately reflects
what it does:
-```
+```java
public class LogUtil {
public static void log(Level severity, String message, Object... params) {...}
@@ -198,6 +234,44 @@ public class LogUtil {
}
```
+## <a name="to_builder"></a> Making a builder from a built instance
+
+It is not always possible to map back from the result of a constructor or method
+call to a builder that might have produced it. But in one important case, it
+*is* possible. That's when every parameter in the constructor or method
+corresponds to a "getter method" in the built type. This will always be true
+when building a Java record or a Kotlin data class (provided its getters are
+visible to the builder). In this case, the generated builder class will have a
+second constructor that takes an object of the built type as a parameter and
+produces a builder that starts out with values from that object. That can then
+be used to produce a new object that may differ from the first one in just one
+or two properties. (This is very similar to AutoValue's
+[`toBuilder()`](builders-howto.md#to_builder) feature.)
+
+If the constructor or method has a parameter `String bar` then the built type
+must have a visible method `String bar()` or `String getBar()`. (Java records
+have the first and Kotlin data classes have the second.) If there is a
+similar corresponding method for every parameter then the second constructor is
+generated.
+
+If you are able to change the built type, the most convenient way to use this is
+to add a `toBuilder()` instance method that calls `new AutoBuilder_Foo(this)`.
+We saw this in the [Kotlin example](#kotlin) earlier. Otherwise, you can have
+a second static `builder` method, like this:
+
+```java
+@AutoBuilder(ofClass = Person.class)
+abstract class PersonBuilder {
+ static PersonBuilder personBuilder() {
+ return new AutoBuilder_PersonBuilder();
+ }
+ static PersonBuilder personBuilder(Person person) {
+ return new AutoBuilder_PersonBuilder(person);
+ }
+ ...
+}
+```
+
## Overloaded constructors or methods
There might be more than one constructor or static method that matches the
@@ -212,7 +286,7 @@ one such method or constructor.
If the builder calls the constructor of a generic type, then it must have the
same type parameters as that type, as in this example:
-```
+```java
class NumberPair<T extends Number> {
NumberPair(T first, T second) {...}
@@ -228,7 +302,7 @@ class NumberPair<T extends Number> {
If the builder calls a static method with type parameters, then it must have the
same type parameters, as in this example:
-```
+```java
class Utils {
static <K extends Number, V> Map<K, V> singletonNumberMap(K key, V value) {...}
@@ -246,7 +320,7 @@ separately from any that its containing class might have. A builder that calls a
constructor like that must have the type parameters of the class followed by the
type parameters of the constructor:
-```
+```java
class CheckedSet<E> implements Set<E> {
<T extends E> CheckedSet(Class<T> type) {...}
@@ -262,13 +336,14 @@ class CheckedSet<E> implements Set<E> {
Parameters that are annotated `@Nullable` are null by default. Parameters of
type `Optional`, `OptionalInt`, `OptionalLong`, and `OptionalDouble` are empty
+by default. Kotlin constructor parameters with default values get those values
by default. Every other parameter is _required_, meaning that the build method
will throw `IllegalStateException` if any are omitted.
To establish default values for parameters, set them in the `builder()` method
before returning the builder.
-```
+```java
class Foo {
Foo(String bar, @Nullable String baz, String buh) {...}
@@ -312,7 +387,7 @@ exception in the first case or return an empty `Optional` in the second.
In this example, the `nickname` parameter defaults to the same value as the
`name` parameter but can also be set to a different value:
-```
+```java
public class Named {
Named(String name, String nickname) {...}
@@ -342,6 +417,13 @@ The builder in the example is an abstract class rather than an interface. An
abstract class allows us to distinguish between public methods for users of the
builder to call, and package-private methods that the builder's own logic uses.
+## Building annotation instances
+
+AutoBuilder can build instances of annotation interfaces. When the annotation
+has no elements (methods in the annotation), or only one, then AutoAnnotation is
+simpler to use. But when there are several elements, a builder is helpful. See
+[here](howto.md#annotation) for examples of both.
+
## Naming conventions
A setter method for the parameter `foo` can be called either `setFoo` or `foo`.
@@ -360,7 +442,7 @@ If class `Foo` has a nested `@AutoBuilder` that builds instances of `Foo`, then
conventionally that type is called `Builder`, and instances of it are obtained
by calling a static `Foo.builder()` method:
-```
+```java
Foo foo1 = Foo.builder().setBar(bar).setBaz(baz).build();
Foo.Builder fooBuilder = Foo.builder();
```
@@ -370,7 +452,7 @@ typically be called `FooBuilder` and it will have a static `fooBuilder()` method
that returns an instance of `FooBuilder`. That way callers can statically import
`FooBuilder.fooBuilder` and just write `fooBuilder()` in their code.
-```
+```java
@AutoBuilder(ofClass = Foo.class)
public abstract class FooBuilder {
public static FooBuilder fooBuilder() {
@@ -385,7 +467,7 @@ If an `@AutoBuilder` is designed to call a static method that is not a factory
method, the word "call" is better than "build" in the name of the type
(`FooCaller`), the static method (`fooCaller()`), and the "build" method (`call()`).
-```
+```java
@AutoBuilder(callMethod = "log", ofClass = MyLogger.class)
public abstract class LogCaller {
public static LogCaller logCaller() {
@@ -407,11 +489,6 @@ because they are the same as for `@AutoValue.Builder`. They include:
* [Special treatment of collections](builders-howto.md#collection)
* [Handling of nested builders](builders-howto.md#nested_builders)
-There is currently no equivalent of AutoValue's
-[`toBuilder()`](builders-howto.md#to_builder). Unlike AutoValue, there is not
-generally a mapping back from the result of the constructor or method to its
-parameters.
-
## When parameter names are unavailable
AutoBuilder depends on knowing the names of parameters. But parameter names are
@@ -436,7 +513,7 @@ are available.
Here's an example of fixing a problem this way. The code here typically will not
compile, since parameter names of JDK methods are not available:
-```
+```java
import java.time.LocalTime;
public class TimeUtils {
@@ -467,11 +544,11 @@ have the real names.
Introducing a static method fixes the problem:
-```
+```java
import java.time.LocalTime;
public class TimeUtils {
- static LocalTime localTimeOf(int hour, int second, int second) {
+ static LocalTime localTimeOf(int hour, int minute, int second) {
return LocalTime.of(hour, minute, second);
}
diff --git a/value/userguide/builders-howto.md b/value/userguide/builders-howto.md
index 3ff89468..f1f94809 100644
--- a/value/userguide/builders-howto.md
+++ b/value/userguide/builders-howto.md
@@ -34,6 +34,8 @@ How do I...
* ... [access nested builders while building?](#nested_builders)
* ... [create a "step builder"?](#step)
* ... [create a builder for something other than an `@AutoValue`?](#autobuilder)
+* ... [use a different build method for a
+ property?](#build_method)
## <a name="beans"></a>... use (or not use) `set` prefixes?
@@ -251,13 +253,13 @@ non-[nullable](howto.md#nullable) property, `IllegalStateException` is thrown.
Getters should generally only be used within the `Builder` as shown, so they are
not public.
-As an alternative to returning the same type as the property accessor method,
-the builder getter can return an Optional wrapping of that type. This can be
-used if you want to supply a default, but only if the property has not been set.
-(The [usual way](#default) of supplying defaults means that the property always
-appears to have been set.) For example, suppose you wanted the default name of
-your Animal to be something like "4-legged creature", where 4 is the
-`numberOfLegs()` property. You might write this:
+<p id="optional-getter">As an alternative to returning the same type as the
+property accessor method, the builder getter can return an Optional wrapping of
+that type. This can be used if you want to supply a default, but only if the
+property has not been set. (The [usual way](#default) of supplying defaults
+means that the property always appears to have been set.) For example, suppose
+you wanted the default name of your Animal to be something like "4-legged
+creature", where 4 is the `numberOfLegs()` property. You might write this:
```java
@AutoValue
@@ -700,4 +702,65 @@ build something other than an `@AutoValue` class, or even call a static method.
In that case you can use `@AutoBuilder`. See
[its documentation](autobuilder.md).
+Sometimes you want to use a different build method for your property. This is
+especially applicable for `ImmutableMap`, which has two different build methods.
+`builder.buildOrThrow()` is used as the default build method for AutoValue. You
+might prefer to use `builder.buildKeepingLast()` instead, so if the same key is
+put more than once then the last value is retained rather than throwing an
+exception. AutoValue doesn't currently have a way to request this, but here is a
+workaround if you need it. Let's say you have a class like this:
+
+```java
+ @AutoValue
+ public abstract class Foo {
+ public abstract ImmutableMap<Integer, String> map();
+ ...
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract ImmutableMap.Builder<Integer, String> mapBuilder();
+ public abstract Foo build();
+ }
+ }
+```
+
+Instead, you could write this:
+
+```java
+ @AutoValue
+ public abstract class Foo {
+ public abstract ImmutableMap<Integer, String> map();
+
+ // #start
+ // Needed only if your class has toBuilder() method
+ public Builder toBuilder() {
+ Builder builder = autoToBuilder();
+ builder.mapBuilder().putAll(map());
+ return builder;
+ }
+
+ abstract Builder autoToBuilder(); // not public
+ // #end
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ private final ImmutableMap.Builder<Integer, String> mapBuilder = ImmutableMap.builder();
+
+ public ImmutableMap.Builder<Integer, String> mapBuilder() {
+ return mapBuilder;
+ }
+
+ abstract Builder setMap(ImmutableMap<Integer, String> map); // not public
+
+ abstract Foo autoBuild(); // not public
+
+ public Foo build() {
+ setMap(mapBuilder.buildKeepingLast());
+ return autoBuild();
+ }
+ }
+ }
+```
+
[protobuf]: https://developers.google.com/protocol-buffers/docs/reference/java-generated#builders
diff --git a/value/userguide/builders.md b/value/userguide/builders.md
index 1de6bfa4..ead1ae37 100644
--- a/value/userguide/builders.md
+++ b/value/userguide/builders.md
@@ -28,6 +28,8 @@ abstract class Animal {
abstract int numberOfLegs();
static Builder builder() {
+ // The naming here will be different if you are using a nested class
+ // e.g. `return new AutoValue_OuterClass_InnerClass.Builder();`
return new AutoValue_Animal.Builder();
}
@@ -104,3 +106,5 @@ exposing yourself to initialization-order problems.
* ... [create a "step builder"?](builders-howto.md#step)
* ... [create a builder for something other than an
`@AutoValue`?](builders-howto.md#autobuilder)
+* ... [use a different build method for a
+ property?](builders-howto.md#build_method)
diff --git a/value/userguide/extensions.md b/value/userguide/extensions.md
index 6acb3ca9..261869dc 100644
--- a/value/userguide/extensions.md
+++ b/value/userguide/extensions.md
@@ -36,6 +36,6 @@ behavior by overriding or implementing new methods.
* How to distribute extensions.
* List of known extensions.
-[AutoService]: https://github.com/google/auto/tree/master/service
-[`AutoValueExtension`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java
+[AutoService]: https://github.com/google/auto/tree/main/service
+[`AutoValueExtension`]: https://github.com/google/auto/blob/main/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java
[`ServiceLoader`]: http://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html
diff --git a/value/userguide/howto.md b/value/userguide/howto.md
index 0a7607b5..5acbd024 100644
--- a/value/userguide/howto.md
+++ b/value/userguide/howto.md
@@ -254,8 +254,8 @@ abstract class IgnoreExample {
}
```
-Note that this means the field is also ignored by `toString`; to AutoValue
-it simply doesn't exist.
+Note that this means the field is also ignored by `toString`; to AutoValue the
+private field simply doesn't exist.
Note that we use `AtomicReference<String>` to ensure that other threads will
correctly see the value that was written. You could also make the field
@@ -292,6 +292,9 @@ good to go.
## <a name="annotation"></a>... use AutoValue to implement an annotation type?
+Note: If you are writing your annotation in Kotlin, you don't need to use
+`@AutoAnnotation`, since Kotlin allows you to instantiate annotations directly.
+
Most users should never have the need to programmatically create "fake"
annotation instances. But if you do, using `@AutoValue` in the usual way will
fail because the `Annotation.hashCode` specification is incompatible with
@@ -315,8 +318,38 @@ public class Names {
}
```
+If your annotation has several elements, you may prefer to use `@AutoBuilder`:
+
+```java
+public @interface Named {
+ String value();
+ int priority() default 0;
+ int size() default 0;
+}
+
+public class Names {
+ @AutoBuilder(ofClass = Named.class)
+ public interface NamedBuilder {
+ NamedBuilder value(String x);
+ NamedBuilder priority(int x);
+ NamedBuilder size(int x);
+ Named build();
+ }
+
+ public static NamedBuilder namedBuilder() {
+ return new AutoBuilder_Names_namedBuilder();
+ }
+
+ ...
+ Named named1 = namedBuilder().value("O'Cruiskeen").priority(17).size(23).build();
+ Named named2 = namedBuilder().value("O'Cruiskeen").build();
+ // priority and size get their default values
+ ...
+}
+```
+
For more details, see the [`AutoAnnotation`
-javadoc](http://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/AutoAnnotation.java#L24).
+javadoc](http://github.com/google/auto/blob/main/value/src/main/java/com/google/auto/value/AutoAnnotation.java#L24).
## <a name="setters"></a>... also include setter (mutator) methods?
@@ -434,7 +467,7 @@ If a `@Memoized` method is also annotated with `@Nullable`, then `null` values
will be stored; if not, then the overriding method throws `NullPointerException`
when the annotated method returns `null`.
-[`@Memoized`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/memoized/Memoized.java
+[`@Memoized`]: https://github.com/google/auto/blob/main/value/src/main/java/com/google/auto/value/extension/memoized/Memoized.java
## <a name="memoize_hash_tostring"></a>... memoize the result of `hashCode` or `toString`?
@@ -721,4 +754,4 @@ Song {
default AutoValue-generated `toString()` implementation, or on another
user-defined method.
-[`@ToPrettyString`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/toprettystring/ToPrettyString.java
+[`@ToPrettyString`]: https://github.com/google/auto/blob/main/value/src/main/java/com/google/auto/value/extension/toprettystring/ToPrettyString.java
diff --git a/value/userguide/index.md b/value/userguide/index.md
index d6ef95c7..1a86aee0 100644
--- a/value/userguide/index.md
+++ b/value/userguide/index.md
@@ -31,6 +31,16 @@ AutoValue provides an easier way to create immutable value classes, with a lot
less code and less room for error, while **not restricting your freedom** to
code almost any aspect of your class exactly the way you want it.
+**Note**: If you are using Kotlin then its
+[data classes](https://kotlinlang.org/docs/data-classes.html) are usually more
+appropriate than AutoValue. Likewise, if you are using a version of Java that
+has [records](https://docs.oracle.com/en/java/javase/17/language/records.html),
+then those are usually more appropriate. For a detailed comparison of AutoValue
+and records, including information on how to migrate from one to the other, see
+[here](records.md).<br>
+You can still use [AutoBuilder](autobuilder.md) to make builders for data
+classes or records.
+
This page will walk you through how to use AutoValue. Looking for a little more
persuasion? Please see [Why AutoValue?](why.md).
@@ -72,7 +82,6 @@ Note that in real life, some classes and methods would presumably be public and
have Javadoc. We're leaving these off in the User Guide only to keep the
examples short and simple.
-
### With Maven
You will need `auto-value-annotations-${auto-value.version}.jar` in your
@@ -149,14 +158,15 @@ Gradle users can declare the dependencies in their `build.gradle` script:
```groovy
dependencies {
- compileOnlyApi "com.google.auto.value:auto-value-annotations:${autoValueVersion}"
+ compileOnly "com.google.auto.value:auto-value-annotations:${autoValueVersion}"
annotationProcessor "com.google.auto.value:auto-value:${autoValueVersion}"
}
```
-Note: If you are using a version of Gradle prior to 6.7, use `compile` or (for
-Android or java-library projects) `api` instead of `compileOnlyApi`. If you are
-using a version prior to 4.6, you must apply an annotation processing plugin
+Note: For java-library projects, use `compileOnlyApi` (or `api` for Gradle
+versions prior to 6.7) instead of `compileOnly`. For Android projects, use `api`
+instead of `compileOnly`. If you are using a version prior to 4.6, you must
+apply an annotation processing plugin
[as described in these instructions][tbroyer-apt].
[tbroyer-apt]: https://plugins.gradle.org/plugin/net.ltgt.apt
@@ -188,8 +198,8 @@ public void testAnimal() {
AutoValue runs inside `javac` as a standard annotation processor. It reads your
abstract class and infers what the implementation class should look like. It
-generates source code, in your package, of a concrete implementation class
-which extends your abstract class, having:
+generates source code, in your package, of a concrete implementation class which
+extends your abstract class, having:
* package visibility (non-public)
* one field for each of your abstract accessor methods
@@ -277,5 +287,4 @@ How do I...
class/method/field?](howto.md#copy_annotations)
* ... [create a **pretty string** representation?](howto.md#toprettystring)
-
<!-- TODO(kevinb): should the above be only a selected subset? -->
diff --git a/value/userguide/practices.md b/value/userguide/practices.md
index e78c6e5a..5c9232d7 100644
--- a/value/userguide/practices.md
+++ b/value/userguide/practices.md
@@ -3,9 +3,11 @@
## <a name="interchangeable"></a>"Equals means interchangeable"
-Don't use AutoValue to implement value semantics unless you really want value
-semantics. In particular, you should never care about the difference between two
-equal instances.
+Use AutoValue when you want value semantics. Under value semantics, if `a` and
+`b` are instances of the same AutoValue class, and `a.equals(b)`, then `a` and
+`b` are considered interchangeable, and `a` can be used in place of `b`
+everywhere and vice versa. If your AutoValue use case does not satisfy these
+contracts, then AutoValue may not be a good fit.
## <a name="mutable_properties"></a>Avoid mutable property types
diff --git a/value/userguide/records.md b/value/userguide/records.md
new file mode 100644
index 00000000..6b043ccf
--- /dev/null
+++ b/value/userguide/records.md
@@ -0,0 +1,560 @@
+# AutoValue and Java Records
+
+
+Starting with Java 16,
+[records](https://docs.oracle.com/en/java/javase/19/language/records.html) are a
+standard feature of the language. If records are available to you, is there any
+reason to use AutoValue?
+
+## <a id="summary"></a>The short answer
+
+Generally, **use records** when you can. They have a very concise and readable
+syntax, they produce less code, and they don't need any special configuration or
+dependency. They are obviously a better choice when your class is just an
+aggregation of values, for example to allow a method to return multiple values
+or to combine values into a map key.
+
+(This was by design: the AutoValue authors were part of the
+[Project Amber](https://openjdk.org/projects/amber/) working group, where our
+goal was to make the records feature the best AutoValue replacement it could
+be.)
+
+If you have existing code that has AutoValue classes, you might want to migrate
+some or all of those classes to be records instead. In this document we will
+explain how to do this, and in what cases you might prefer not to.
+
+## <a id="notyet"></a>Can't use Java records yet?
+
+If you're creating new AutoValue classes for Java 15 or earlier, **follow this
+advice** to make sure your future conversion to records will be straightforward:
+
+* Extend `Object` only (implementing interfaces is fine).
+* Don't use JavaBeans-style prefixes: use `abstract int bar()`, not `abstract
+ int getBar()`.
+* Don't declare any non-static fields of your own.
+* Give the factory method and accessors the same visibility level as the
+ class.
+* Avoid using [extensions](extensions.md).
+
+Adopting AutoValue at this time is still a good idea! There is no better way to
+make sure your code is as ready as possible to migrate to records later.
+
+## <a id="whynot"></a>Reasons to stick with AutoValue
+
+While records are usually better, there are some AutoValue features that have no
+simple equivalent with records. So you might prefer not to try migrating
+AutoValue classes that use those features, and you might even sometimes make new
+AutoValue classes even if records are available to you.
+
+### Extensions
+
+AutoValue has [extensions](extensions.md). Some are built in, like the
+[`@Memoized`](https://javadoc.io/static/com.google.auto.value/auto-value-annotations/1.10/com/google/auto/value/extension/memoized/Memoized.html),
+[`@ToPrettyString`](https://javadoc.io/static/com.google.auto.value/auto-value-annotations/1.10/com/google/auto/value/extension/toprettystring/ToPrettyString.html),
+and
+[`@SerializableAutoValue`](https://javadoc.io/static/com.google.auto.value/auto-value-annotations/1.10/com/google/auto/value/extension/serializable/SerializableAutoValue.html)
+extensions. Most extensions will have no real equivalent with records.
+
+### <a id="staticfactory"></a> Keeping the static factory method
+
+AutoValue has very few API-visible "quirks", but one is that it forces you to
+use a static factory method as your class's creation API. A record can have this
+too, but it can't prevent its constructor from *also* being visible, and
+exposing two ways to do the same thing can be dangerous.
+
+We think most users will be happy to switch to constructors and drop the factory
+methods, but you might want to keep a factory method in some records. Perhaps
+for compatibility reasons, or because you are normalizing input data to
+different types, such as from `List` to `ImmutableList`.
+
+In this event, you can still *discourage* calling the constructor by marking it
+deprecated. More on this [below](#deprecating).
+
+Clever ways do exist to make calling the constructor impossible, but it's
+probably simpler to keep using AutoValue.
+
+### Superclass
+
+The superclass of a record is always `java.lang.Record`. Occasionally the
+superclass of an AutoValue class is something other than `Object`, for example
+when two AutoValue classes share a subset of their properties.
+
+You might still be able to convert to records if you can convert these classes
+into interfaces.
+
+### Derived properties
+
+Records can't have instance fields (other than their properties). So it is hard
+to cache a derived property, for example. AutoValue makes this trivial with
+[`@Memoized`](https://javadoc.io/static/com.google.auto.value/auto-value-annotations/1.10/com/google/auto/value/extension/memoized/Memoized.html).
+
+We suggest ways to achieve the same effect with records [below](#derived), but
+it might be simpler to stick with AutoValue.
+
+### Primitive array properties
+
+AutoValue allows properties of primitive array types such as `byte[]` or `int[]`
+and it will implement `equals` and `hashCode` using the methods of
+`java.util.Arrays`. Records do not have any special treatment for primitive
+arrays, so by default they will use the `equals` and `hashCode` methods of the
+arrays. So two distinct arrays will never compare equal even if they have the
+same contents.
+
+The best way to avoid this problem is not to have properties with primitive
+array type, perhaps using alternatives such as
+[`ImmutableIntArray`](http://guava.dev/ImmutableIntArray). Alternatively you can
+define custom implementations of `equals` and `hashCode` as described in the
+[section](#eqhc) on that topic. But again, you might prefer to keep using
+AutoValue.
+
+(AutoValue doesn't allow properties of non-primitive array types.)
+
+## Translating an AutoValue class into a record
+
+Suppose you have existing AutoValue classes that you do want to translate into
+records, and the [above reasons](#whynot) not to don't apply. What does the
+translation look like?
+
+One important difference is that AutoValue does not allow properties to be
+`null` unless they are marked `@Nullable`. Records require explicit null checks
+to achieve the same effect, typically with
+[`Objects.requireNonNull`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Objects.html#requireNonNull\(T\)).
+
+This might also be a good time to start using a nullness-analysis tool on your
+code; see [NullAway](https://github.com/uber/NullAway) for example.
+
+The examples below show some before-and-after for various migration scenarios.
+For brevity, we've mostly omitted the javadoc comments that good code should
+have on its public classes and methods.
+
+### Basic example with only primitive properties
+
+Before:
+
+```java
+@AutoValue
+public abstract class Point {
+ public abstract int x();
+ public abstract int y();
+
+ public static Point of(int x, int y) {
+ return new AutoValue_Point(x, y);
+ }
+}
+```
+
+After:
+
+```java
+public record Point(int x, int y) {
+ /** @deprecated Call the constructor directly. */
+ @Deprecated
+ public static Point of(int x, int y) {
+ return new Point(x, y);
+ }
+}
+```
+
+The static factory method `of` is retained so clients of the `Point` class don't
+have to be updated. If possible, you should migrate clients to call `new
+Point(...)` instead. Then the record can be as simple as this:
+
+```java
+public record Point(int x, int y) {}
+```
+
+We've omitted the static factory methods from the other examples, but the
+general approach applies: keep the method initially but deprecate it and change
+its body so it just calls the constructor; migrate the callers so they call the
+constructor directly; delete the method. You might be able to use the
+[`InlineMe`](https://errorprone.info/docs/inlineme) mechanism from the Error
+Prone project to encourage this migration:
+
+```java
+package com.example.geometry;
+
+public record Point(int x, int y) {
+ /** @deprecated Call the constructor directly. */
+ @Deprecated
+ @InlineMe(replacement = "new Point(x, y)", imports = "com.example.geometry.Point")
+ public static Point of(int x, int y) {
+ return new Point(x, y);
+ }
+}
+```
+
+### Non-primitive properties that are not `@Nullable`
+
+Before:
+
+```java
+@AutoValue
+public abstract class Person {
+ public abstract String name();
+ public abstract int id();
+
+ public static Person create(String name, int id) {
+ return new AutoValue_Person(name, id);
+ }
+}
+```
+
+After:
+
+```java
+public record Person(String name, int id) {
+ public Person {
+ Objects.requireNonNull(name, "name");
+ }
+}
+```
+
+### Non-primitive properties that are all `@Nullable`
+
+Before:
+
+```java
+@AutoValue
+public abstract class Person {
+ public abstract @Nullable String name();
+ public abstract int id();
+
+ public static Person create(@Nullable String name, int id) {
+ return new AutoValue_Person(name, id);
+ }
+}
+```
+
+After:
+
+```java
+public record Person(@Nullable String name, int id) {}
+```
+
+### Validation
+
+Before:
+
+```java
+@AutoValue
+public abstract class Person {
+ public abstract String name();
+ public abstract int id();
+
+ public static Person create(String name, int id) {
+ if (id <= 0) {
+ throw new IllegalArgumentException("Id must be positive: " + id);
+ }
+ return new AutoValue_Person(name, id);
+ }
+}
+```
+
+After:
+
+```java
+public record Person(String name, int id) {
+ public Person {
+ Objects.requireNonNull(name, "name");
+ if (id <= 0) {
+ throw new IllegalArgumentException("Id must be positive: " + id);
+ }
+ }
+}
+```
+
+### Normalization
+
+With records, you can rewrite the constructor parameters to apply normalization
+or canonicalization rules.
+
+In this example we have two `int` values, but we don't care which order they are
+supplied in. Therefore we have to put them in a standard order, or else `equals`
+won't behave as expected.
+
+Before:
+
+```java
+@AutoValue
+public abstract class UnorderedPair {
+ public abstract int left();
+ public abstract int right();
+
+ public static UnorderedPair of(int left, int right) {
+ int min = Math.min(left, right);
+ int max = Math.max(left, right);
+ return new AutoValue_UnorderedPair(min, max);
+ }
+}
+```
+
+After:
+
+```java
+public record UnorderedPair(int left, int right) {
+ public UnorderedPair {
+ int min = Math.min(left, right);
+ int max = Math.max(left, right);
+ left = min;
+ right = max;
+ }
+}
+```
+
+If your normalization results in different types (or more or fewer separate
+fields) than the parameters, you will need to keep the static factory method. On
+a more subtle note, the user of this record might be surprised that what they
+passed in as `left` doesn't always come out as `left()`; keeping the static
+factory method would also allow the parameters to be named differently. See the
+section on the [static factory](#staticfactory) method.
+
+### <a id="beans"></a> JavaBeans prefixes (`getFoo()`)
+
+AutoValue allows you to prefix every property getter with `get`, but records
+don't have any special treatment here. Imagine you have a class like this:
+
+```java
+@AutoValue
+public abstract class Person {
+ public abstract String getName();
+ public abstract int getId();
+
+ public static Person create(String name, int id) {
+ return new AutoValue_Person(name, id);
+ }
+}
+```
+
+The names of the fields in `Person`, and the names in its `toString()`, don't
+have the `get` prefix:
+
+```
+jshell> Person.create("Priz", 6)
+$1 ==> Person{name=Priz, id=6}
+jshell> $1.getName()
+$2 ==> Priz
+jshell> List<String> showFields(Class<?> c) {
+ ...> return Arrays.stream(c.getDeclaredFields()).map(Field::getName).toList();
+ ...> }
+jshell> showFields($1.getClass())
+$3 ==> [name, id]
+```
+
+You can translate this directly to a record if you don't mind a slightly strange
+`toString()`, and strange field names from reflection and debuggers:
+
+```java
+public record Person(String getName, int getId) {
+ public Person {
+ Objects.requireNonNull(getName);
+ }
+}
+```
+
+```
+jshell> Person.create("Priz", 6)
+$1 ==> Person[getName=Priz, getId=6]
+jshell> $1.getName()
+$2 ==> Priz
+jshell> showFields($1.getClass())
+$3 ==> [getName, getId]
+```
+
+Alternatively, you can alias `Person.getName()` to be `Person.name()`, etc.:
+
+```java
+public record Person(String name, int id) {
+ public Person {
+ Objects.requireNonNull(name);
+ }
+
+ public String getName() {
+ return name();
+ }
+
+ public int getId() {
+ return id();
+ }
+}
+```
+
+So both `Person.getName()` and `Person.name()` are allowed. You might want to
+deprecate the `get-` methods so you can eventually remove them.
+
+### <a id="derived"></a> Caching derived properties
+
+A record has an instance field for each of its properties, but cannot have other
+instance fields. That means in particular that it is not easy to cache derived
+properties, as you can with AutoValue and [`@Memoized`](howto.md#memoize).
+
+Records *can* have static fields, so one way to cache derived properties is to
+map from record instances to their derived properties.
+
+Before:
+
+```java
+@AutoValue
+public abstract class Person {
+ public abstract String name();
+ public abstract int id();
+
+ @Memoized
+ public UUID derivedProperty() {
+ return expensiveFunction(this);
+ }
+
+ public static Person create(String name, int id) {
+ return new AutoValue_Person(name, id);
+ }
+}
+```
+
+After:
+
+```java
+public record Person(String name, int id) {
+ public Person {
+ Objects.requireNonNull(name);
+ }
+
+ private static final Map<Person, String> derivedPropertyCache = new WeakHashMap<>();
+
+ public UUID derivedProperty() {
+ synchronized (derivedPropertyCache) {
+ return derivedPropertyCache.computeIfAbsent(this, person -> expensiveFunction(person)));
+ }
+ }
+}
+```
+
+It's very important to use **`WeakHashMap`** (or similar) or you might suffer a
+memory leak. As usual with `WeakHashMap`, you have to be sure that the values in
+the map don't reference the keys. For more caching options, consider using
+[Caffeine](https://github.com/ben-manes/caffeine).
+
+You might decide that AutoValue with `@Memoized` is simpler than records for
+this case, though.
+
+### Builders
+
+Builders are still available when using records. Instead of
+`@AutoValue.Builder`, you use [`@AutoBuilder`](autobuilder.md).
+
+Before:
+
+```java
+@AutoValue
+public abstract class Person {
+ public abstract String name();
+ public abstract int id();
+
+ public static Builder builder() {
+ return new AutoValue_Person.Builder();
+ }
+
+ @AutoValue.Builder
+ public interface Builder {
+ Builder name(String name);
+ Builder id(int id);
+ Person build();
+ }
+}
+
+Person p = Person.builder().name("Priz").id(6).build();
+```
+
+After:
+
+```java
+public record Person(String name, int id) {
+ public static Builder builder() {
+ return new AutoBuilder_Person_Builder();
+ }
+
+ @AutoBuilder
+ public interface Builder {
+ Builder name(String name);
+ Builder id(int id);
+ Person build();
+ }
+}
+
+Person p = Person.builder().name("Priz").id(6).build();
+```
+
+#### <a id="deprecating"></a>Deprecating the constructor
+
+As mentioned [above](#staticfactory), the primary constructor is always visible.
+In the preceding example, the builder will enforce that the `name` property is
+not null (since it is not marked @Nullable), but someone calling the constructor
+will bypass that check. You could deprecate the constructor to discourage this:
+
+```java
+public record Person(String name, int id) {
+ /** @deprecated Obtain instances using the {@link #builder()} instead. */
+ @Deprecated
+ public Person {}
+
+ public static Builder builder() {
+ return new AutoBuilder_Person_Builder();
+ }
+
+ @AutoBuilder
+ public interface Builder {
+ Builder name(String name);
+ Builder id(int id);
+ Person build();
+ }
+}
+```
+
+### Custom `toString()`
+
+A record can define its own `toString()` in exactly the same way as an AutoValue
+class.
+
+### <a id="eqhc"></a> Custom `equals` and `hashCode`
+
+As with AutoValue, it's unusual to want to change the default implementations of
+these methods, and if you do you run the risk of making subtle mistakes. Anyway,
+the idea is the same with both AutoValue and records.
+
+Before:
+
+```java
+@AutoValue
+public abstract class Person {
+ ...
+
+ @Override public boolean equals(Object o) {
+ return o instanceof Person that
+ && Ascii.equalsIgnoreCase(this.name(), that.name())
+ && this.id() == that.id();
+ }
+
+ @Override public int hashCode() {
+ return Objects.hash(Ascii.toLowerCase(name()), id());
+ }
+}
+```
+
+After:
+
+```java
+public record Person(String name, int id) {
+ ...
+
+ @Override public boolean equals(Object o) {
+ return o instanceof Person that
+ && Ascii.equalsIgnoreCase(this.name, that.name)
+ && this.id == that.id;
+ }
+
+ @Override public int hashCode() {
+ return Objects.hash(Ascii.toLowerCase(name), id);
+ }
+}
+```
+
+With records, the methods can access fields directly or use the corresponding
+methods (`this.name` or `this.name()`).
diff --git a/value/userguide/why.md b/value/userguide/why.md
index 40c994f6..4ebb31f5 100644
--- a/value/userguide/why.md
+++ b/value/userguide/why.md
@@ -1,8 +1,10 @@
# Why use AutoValue?
-AutoValue is the only solution to the value class problem in Java having all of
-the following characteristics:
+In versions of Java preceding
+[records](https://docs.oracle.com/en/java/javase/16/language/records.html),
+AutoValue is the only solution to the value class problem having all of the
+following characteristics:
* **API-invisible** (callers cannot become dependent on your choice to use it)
* No runtime dependencies