diff options
author | Colin Cross <ccross@android.com> | 2022-03-08 00:16:22 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2022-03-08 00:16:22 +0000 |
commit | 6393823b21bc49c53454fbcabd77d20b9594f021 (patch) | |
tree | e7dfe5daeab98b225bb9f472e43ea319259f4e73 | |
parent | 5fe506f8a9f6ec343bad9a1ee9af9f1c7f0c6a6e (diff) | |
parent | 76a5a3911d2da67090da329a1e35da99061e651f (diff) | |
download | auto-6393823b21bc49c53454fbcabd77d20b9594f021.tar.gz |
Merge changes from topic "google-java-format-jdk17"
* changes:
Add auto_oneof_plugin
Update Android.bp for upstream merge
Merge commit 'auto-value-1.8.2^'
231 files changed, 12135 insertions, 3360 deletions
diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..18cc4cef --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,22 @@ +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/common" + schedule: + interval: "daily" + - package-ecosystem: "maven" + directory: "/factory" + schedule: + interval: "daily" + - package-ecosystem: "maven" + directory: "/service" + schedule: + interval: "daily" + - package-ecosystem: "maven" + directory: "/value" + schedule: + interval: "daily" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..ec922435 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,97 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + test: + name: "JDK ${{ matrix.java }}" + strategy: + matrix: + java: [ 8, 11 ] + 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.0 + with: + access_token: ${{ github.token }} + - name: 'Check out repository' + uses: actions/checkout@v2.3.4 + - name: 'Cache local Maven repository' + uses: actions/cache@v2.1.6 + with: + path: ~/.m2/repository + key: maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + maven- + - name: 'Set up JDK ${{ matrix.java }}' + uses: actions/setup-java@v2 + with: + java-version: ${{ matrix.java }} + distribution: 'zulu' + - name: 'Install' + shell: bash + run: mvn -B dependency:go-offline test clean -U --quiet --fail-never -DskipTests=true -f build-pom.xml + - name: 'Test' + shell: bash + run: mvn -B verify -U --fail-at-end -Dsource.skip=true -Dmaven.javadoc.skip=true -f build-pom.xml + + publish_snapshot: + name: 'Publish snapshot' + needs: test + if: github.event_name == 'push' && github.repository == 'google/auto' + runs-on: ubuntu-latest + steps: + - name: 'Check out repository' + uses: actions/checkout@v2.3.4 + - name: 'Cache local Maven repository' + uses: actions/cache@v2.1.6 + with: + path: ~/.m2/repository + key: maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + maven- + - name: 'Set up JDK 11' + uses: actions/setup-java@v2 + with: + java-version: 11 + distribution: 'zulu' + server-id: sonatype-nexus-snapshots + server-username: CI_DEPLOY_USERNAME + server-password: CI_DEPLOY_PASSWORD + - name: 'Publish' + env: + CI_DEPLOY_USERNAME: ${{ secrets.CI_DEPLOY_USERNAME }} + CI_DEPLOY_PASSWORD: ${{ secrets.CI_DEPLOY_PASSWORD }} + run: ./util/publish-snapshot-on-commit.sh + + generate_docs: + name: 'Generate latest docs' + needs: test + if: github.event_name == 'push' && github.repository == 'google/auto' + runs-on: ubuntu-latest + steps: + - name: 'Check out repository' + uses: actions/checkout@v2.3.4 + - name: 'Cache local Maven repository' + uses: actions/cache@v2.1.6 + with: + path: ~/.m2/repository + key: maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + maven- + - name: 'Set up JDK 11' + uses: actions/setup-java@v2 + with: + java-version: 11 + distribution: 'zulu' + - name: 'Generate latest docs' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./util/generate-latest-docs.sh diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b0be6faa..00000000 --- a/.travis.yml +++ /dev/null @@ -1,28 +0,0 @@ -sudo: false - -language: java - -install: - - mvn -B -U -f build-pom.xml dependency:go-offline test clean --quiet --fail-never -DskipTests=true - -script: - - mvn -B -U -f build-pom.xml verify --fail-at-end -Dsource.skip=true -Dmaven.javadoc.skip=true - -jdk: - - openjdk9 - - openjdk8 - -env: - global: - - secure: "bWNSSMURwYC0oZWIMZRd7dy5+JdoyZ060d427TAqFRJmOkYtlR+dBbBggjeJmM0PEkQDzeBrWwln/Vq3lnRPv8czA7hSg/R33r3GzTyi1GZhjCYN2mPW8qp4qgqlloh78aaOODUNSJsOtQqPDJPmhLLfD6UCY0eq9zHhweIjYdw=" - - secure: "s5V9d8MKl7ZHqCxuYLljLSD4sp9KLtYkk9hVxEPqCLAi4zA70WkX9h+GZI1gAOpcavomfrWcgSDT2ZReiuNpwx7OtczdS4zB+s6mo4F598iRs4bhSLiPT+Hzvx6BSwf1ZKZTYEhrUPGmKOp2T29AxMV7D0Q+P7n574ubvpUuZmA=" - - secure: "T24JAd60zthkeLBmenvZn6+qI43uvfuLwVb70Ljhbc19XDYEZV4Zm/kaafsisP5+F6kV4GjFaT+NCq2sJlwvPSMMRvU1JJgmNVh8TmtswkC/PHKonkMkOsj2KmFP0RRSPdvQv2NrSguZUq8mg+2pvnPO0qoPg4VeIODPGtAxNb8=" - -after_success: - - util/generate-latest-docs.sh - - util/publish-snapshot-on-commit.sh - -branches: - only: - - master - - /^release.*$/ @@ -1,6 +1,6 @@ # Auto -[![Build Status](https://travis-ci.org/google/auto.svg?branch=master)](https://travis-ci.org/google/auto) +[![Build Status](https://github.com/google/auto/actions/workflows/ci.yml/badge.svg)](https://github.com/google/auto/actions/workflows/ci.yml) A collection of source code generators for [Java][java]. diff --git a/android-annotation-stubs/gen_annotations.sh b/android-annotation-stubs/gen_annotations.sh index 90c9fcfb..4766af97 100755 --- a/android-annotation-stubs/gen_annotations.sh +++ b/android-annotation-stubs/gen_annotations.sh @@ -6,6 +6,7 @@ declare -A IMPORT ANNOTATIONS=( net.ltgt.gradle.incap.IncrementalAnnotationProcessor + org.checkerframework.checker.nullness.qual.Nullable ) PARAMETER["net.ltgt.gradle.incap.IncrementalAnnotationProcessor"]="IncrementalAnnotationProcessorType" diff --git a/android-annotation-stubs/src/org/checkerframework/checker/nullness/qual/Nullable.java b/android-annotation-stubs/src/org/checkerframework/checker/nullness/qual/Nullable.java new file mode 100644 index 00000000..276d64c1 --- /dev/null +++ b/android-annotation-stubs/src/org/checkerframework/checker/nullness/qual/Nullable.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * 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 org.checkerframework.checker.nullness.qual; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/* This is an annotation stub to avoid dependencies on annotations that aren't + * in the Android platform source tree. */ + +@Target({ + ElementType.ANNOTATION_TYPE, + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.LOCAL_VARIABLE, + ElementType.METHOD, + ElementType.PACKAGE, + ElementType.PARAMETER, + ElementType.TYPE, + ElementType.TYPE_PARAMETER, + ElementType.TYPE_USE +}) +@Retention(RetentionPolicy.SOURCE) +public @interface Nullable {} diff --git a/common/Android.bp b/common/Android.bp index 25f70998..19e2acdc 100644 --- a/common/Android.bp +++ b/common/Android.bp @@ -11,6 +11,7 @@ java_library_host { name: "auto_common", srcs: ["src/main/java/**/*.java"], libs: [ + "auto_android_annotation_stubs", "guava", "javapoet", ], diff --git a/common/README.md b/common/README.md index 990aa31b..9f8eb79e 100644 --- a/common/README.md +++ b/common/README.md @@ -1,28 +1,30 @@ -Auto Common Utilities -======== +# Auto Common Utilities ## Overview -The Auto project has a set of common utilities to help ease use of the annotation processing -environment. +The Auto project has a set of common utilities to help ease use of the +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 - - implement a validating annotation processor - - defer invalid elements until later - - break processor actions into multiple steps (which may each handle different annotations) +* 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 + - implement a validating annotation processor + - defer invalid elements until later + - break processor actions into multiple steps (which may each handle + different annotations) ## Usage/Setup -Auto common utilities have a standard [Maven](http://maven.apache.org) setup which can also be -used from Gradle, Ivy, Ant, or other systems which consume binary artifacts from the central Maven -binary artifact repositories. +Auto common utilities have a standard [Maven](http://maven.apache.org) setup +which can also be used from Gradle, Ivy, Ant, or other systems which consume +binary artifacts from the central Maven binary artifact repositories. ```xml <dependency> @@ -31,49 +33,3 @@ binary artifact repositories. <version>1.0-SNAPSHOT</version> <!-- or use a known release version --> </dependency> ``` - -## Processor Resilience - -Auto Common Utilities is used by a variety of annotation processors in Google and new versions -may have breaking changes. Users of auto-common are urged to use -[shade](https://maven.apache.org/plugins/maven-shade-plugin/) or -[jarjar](https://code.google.com/p/jarjar/) (or something similar) in packaging their processors -so that conflicting versions of this library do not adversely interact with each other. - -For example, in a Maven build you can repackage `com.google.auto.common` into -`your.processor.shaded.auto.common` like this: - -```xml -<project> - <!-- your other config --> - <build> - <plugins> - <plugin> - <artifactId>maven-shade-plugin</artifactId> - <executions> - <execution> - <phase>package</phase> - <goals> - <goal>shade</goal> - </goals> - <configuration> - <artifactSet> - <excludes> - <!-- exclude dependencies you don't want to bundle in your processor --> - </excludes> - </artifactSet> - <relocations> - <relocation> - <pattern>com.google.auto.common</pattern> - <shadedPattern>your.processor.shaded.auto.common</shadedPattern> - </relocation> - </relocations> - </configuration> - </execution> - </executions> - </plugin> - </plugins> - </build> -</project> -``` - diff --git a/common/pom.xml b/common/pom.xml index e4a23e02..2754b81d 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -36,8 +36,8 @@ <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> - <guava.version>29.0-jre</guava.version> - <truth.version>1.0.1</truth.version> + <guava.version>30.1.1-jre</guava.version> + <truth.version>1.1.3</truth.version> </properties> <scm> @@ -89,13 +89,13 @@ <dependency> <groupId>com.google.testing.compile</groupId> <artifactId>compile-testing</artifactId> - <version>0.18</version> + <version>0.19</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> - <version>4.12</version> + <version>4.13.2</version> <scope>test</scope> </dependency> <dependency> @@ -107,7 +107,7 @@ <dependency> <groupId>org.eclipse.jdt</groupId> <artifactId>ecj</artifactId> - <version>3.22.0</version> + <version>3.25.0</version> <scope>test</scope> </dependency> </dependencies> @@ -116,7 +116,7 @@ <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> - <version>3.7.0</version> + <version>3.8.1</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> @@ -128,14 +128,14 @@ <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>0.9.4</version> + <version>1.0.7</version> </dependency> </dependencies> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.2.0</version> </plugin> </plugins> </build> 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 62e5834e..9ce5cd9b 100644 --- a/common/src/main/java/com/google/auto/common/AnnotationMirrors.java +++ b/common/src/main/java/com/google/auto/common/AnnotationMirrors.java @@ -16,21 +16,21 @@ package com.google.auto.common; import static com.google.auto.common.MoreElements.isAnnotationPresent; +import static com.google.auto.common.MoreStreams.toImmutableSet; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Collections.unmodifiableMap; import com.google.common.base.Equivalence; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.lang.annotation.Annotation; import java.util.Arrays; -import java.util.List; import java.util.Map; 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.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; @@ -45,20 +45,32 @@ public final class AnnotationMirrors { new Equivalence<AnnotationMirror>() { @Override protected boolean doEquivalent(AnnotationMirror left, AnnotationMirror right) { - return MoreTypes.equivalence().equivalent(left.getAnnotationType(), - right.getAnnotationType()) && AnnotationValues.equivalence().pairwise().equivalent( - getAnnotationValuesWithDefaults(left).values(), - getAnnotationValuesWithDefaults(right).values()); + return MoreTypes.equivalence() + .equivalent(left.getAnnotationType(), right.getAnnotationType()) + && AnnotationValues.equivalence() + .pairwise() + .equivalent( + getAnnotationValuesWithDefaults(left).values(), + getAnnotationValuesWithDefaults(right).values()); } + @Override protected int doHash(AnnotationMirror annotation) { DeclaredType type = annotation.getAnnotationType(); Iterable<AnnotationValue> annotationValues = getAnnotationValuesWithDefaults(annotation).values(); - return Arrays.hashCode(new int[] {MoreTypes.equivalence().hash(type), - AnnotationValues.equivalence().pairwise().hash(annotationValues)}); + return Arrays.hashCode( + new int[] { + MoreTypes.equivalence().hash(type), + AnnotationValues.equivalence().pairwise().hash(annotationValues) + }); + } + + @Override + public String toString() { + return "AnnotationMirrors.equivalence()"; } - }; + }; /** * Returns an {@link Equivalence} for {@link AnnotationMirror} as some implementations @@ -83,8 +95,10 @@ public final class AnnotationMirrors { public static ImmutableMap<ExecutableElement, AnnotationValue> getAnnotationValuesWithDefaults( AnnotationMirror annotation) { ImmutableMap.Builder<ExecutableElement, AnnotationValue> values = ImmutableMap.builder(); - Map<? extends ExecutableElement, ? extends AnnotationValue> declaredValues = - annotation.getElementValues(); + // Use unmodifiableMap to eliminate wildcards, which cause issues for our nullness checker. + @SuppressWarnings("GetElementValues") + Map<ExecutableElement, AnnotationValue> declaredValues = + unmodifiableMap(annotation.getElementValues()); for (ExecutableElement method : ElementFilter.methodsIn(annotation.getAnnotationType().asElement().getEnclosedElements())) { // Must iterate and put in this order, to ensure consistency in generated code. @@ -95,8 +109,10 @@ public final class AnnotationMirrors { } else { throw new IllegalStateException( "Unset annotation value without default should never happen: " - + MoreElements.asType(method.getEnclosingElement()).getQualifiedName() - + '.' + method.getSimpleName() + "()"); + + MoreElements.asType(method.getEnclosingElement()).getQualifiedName() + + '.' + + method.getSimpleName() + + "()"); } } return values.build(); @@ -131,25 +147,48 @@ public final class AnnotationMirrors { return entry; } } - throw new IllegalArgumentException(String.format("@%s does not define an element %s()", - MoreElements.asType(annotationMirror.getAnnotationType().asElement()).getQualifiedName(), - elementName)); + throw new IllegalArgumentException( + String.format( + "@%s does not define an element %s()", + MoreElements.asType(annotationMirror.getAnnotationType().asElement()) + .getQualifiedName(), + elementName)); + } + + /** + * 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( + Element element, Class<? extends Annotation> annotationClass) { + String name = annotationClass.getCanonicalName(); + if (name == null) { + return ImmutableSet.of(); + } + return getAnnotatedAnnotations(element, name); + } + + /** + * 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( + Element element, TypeElement annotation) { + return element.getAnnotationMirrors().stream() + .filter(input -> isAnnotationPresent(input.getAnnotationType().asElement(), annotation)) + .collect(toImmutableSet()); } /** - * Returns all {@linkplain AnnotationMirror annotations} that are present on the given - * {@link Element} which are themselves annotated with {@code annotationType}. + * Returns all {@linkplain AnnotationMirror annotations} that are present on the given {@link + * Element} which are themselves annotated with an annotation whose type's canonical name is + * {@code annotationName}. */ - public static ImmutableSet<? extends AnnotationMirror> getAnnotatedAnnotations(Element element, - final Class<? extends Annotation> annotationType) { - List<? extends AnnotationMirror> annotations = element.getAnnotationMirrors(); - return FluentIterable.from(annotations) - .filter(new Predicate<AnnotationMirror>() { - @Override public boolean apply(AnnotationMirror input) { - return isAnnotationPresent(input.getAnnotationType().asElement(), annotationType); - } - }) - .toSet(); + public static ImmutableSet<? extends AnnotationMirror> getAnnotatedAnnotations( + Element element, String annotationName) { + return element.getAnnotationMirrors().stream() + .filter(input -> isAnnotationPresent(input.getAnnotationType().asElement(), annotationName)) + .collect(toImmutableSet()); } private AnnotationMirrors() {} 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 e2339018..0712e56a 100644 --- a/common/src/main/java/com/google/auto/common/AnnotationValues.java +++ b/common/src/main/java/com/google/auto/common/AnnotationValues.java @@ -15,8 +15,8 @@ */ package com.google.auto.common; +import static com.google.auto.common.MoreStreams.toImmutableList; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableList.toImmutableList; import com.google.common.base.Equivalence; import com.google.common.collect.ImmutableList; @@ -37,91 +37,120 @@ import javax.lang.model.util.SimpleAnnotationValueVisitor8; public final class AnnotationValues { private static final Equivalence<AnnotationValue> ANNOTATION_VALUE_EQUIVALENCE = new Equivalence<AnnotationValue>() { - @Override protected boolean doEquivalent(AnnotationValue left, AnnotationValue right) { - return left.accept(new SimpleAnnotationValueVisitor8<Boolean, AnnotationValue>() { - // LHS is not an annotation or array of annotation values, so just test equality. - @Override protected Boolean defaultAction(Object left, AnnotationValue right) { - return left.equals(right.accept( - new SimpleAnnotationValueVisitor8<Object, Void>() { - @Override protected Object defaultAction(Object object, Void unused) { - return object; - } - }, null)); - } - - // LHS is an annotation mirror so test equivalence for RHS annotation mirrors - // and false for other types. - @Override public Boolean visitAnnotation(AnnotationMirror left, AnnotationValue right) { - return right.accept( - new SimpleAnnotationValueVisitor8<Boolean, AnnotationMirror>() { - @Override protected Boolean defaultAction(Object right, AnnotationMirror left) { - return false; // Not an annotation mirror, so can't be equal to such. - } - @Override - public Boolean visitAnnotation(AnnotationMirror right, AnnotationMirror left) { - return AnnotationMirrors.equivalence().equivalent(left, right); - } - }, left); - } - - // LHS is a list of annotation values have to collect-test equivalences, or false - // for any other types. - @Override - public Boolean visitArray(List<? extends AnnotationValue> left, AnnotationValue right) { - return right.accept( - new SimpleAnnotationValueVisitor8<Boolean, List<? extends AnnotationValue>>() { - @Override protected Boolean defaultAction( - Object ignored, List<? extends AnnotationValue> alsoIgnored) { - return false; // Not an array, so can't be equal to such. - } - - @SuppressWarnings("unchecked") // safe covariant cast - @Override public Boolean visitArray( - List<? extends AnnotationValue> right , - List<? extends AnnotationValue> left) { - return AnnotationValues.equivalence().pairwise().equivalent( - (List<AnnotationValue>) left, (List<AnnotationValue>) right); - } - }, left); - } - - @Override - public Boolean visitType(TypeMirror left, AnnotationValue right) { - return right.accept( - new SimpleAnnotationValueVisitor8<Boolean, TypeMirror>() { - @Override protected Boolean defaultAction( - Object ignored, TypeMirror alsoIgnored) { - return false; // Not an annotation mirror, so can't be equal to such. - } - - @Override public Boolean visitType(TypeMirror right, TypeMirror left) { - return MoreTypes.equivalence().equivalent(left, right); - } - }, left); - } - }, right); + @Override + protected boolean doEquivalent(AnnotationValue left, AnnotationValue right) { + return left.accept( + new SimpleAnnotationValueVisitor8<Boolean, AnnotationValue>() { + // LHS is not an annotation or array of annotation values, so just test equality. + @Override + protected Boolean defaultAction(Object left, AnnotationValue right) { + return left.equals( + right.accept( + new SimpleAnnotationValueVisitor8<Object, Void>() { + @Override + protected Object defaultAction(Object object, Void unused) { + return object; + } + }, + null)); + } + + // LHS is an annotation mirror so test equivalence for RHS annotation mirrors + // and false for other types. + @Override + public Boolean visitAnnotation(AnnotationMirror left, AnnotationValue right) { + return right.accept( + new SimpleAnnotationValueVisitor8<Boolean, AnnotationMirror>() { + @Override + protected Boolean defaultAction(Object right, AnnotationMirror left) { + return false; // Not an annotation mirror, so can't be equal to such. + } + + @Override + public Boolean visitAnnotation( + AnnotationMirror right, AnnotationMirror left) { + return AnnotationMirrors.equivalence().equivalent(left, right); + } + }, + left); + } + + // LHS is a list of annotation values have to collect-test equivalences, or false + // for any other types. + @Override + public Boolean visitArray( + List<? extends AnnotationValue> left, AnnotationValue right) { + return right.accept( + new SimpleAnnotationValueVisitor8< + Boolean, List<? extends AnnotationValue>>() { + @Override + protected Boolean defaultAction( + Object ignored, List<? extends AnnotationValue> alsoIgnored) { + return false; // Not an array, so can't be equal to such. + } + + @SuppressWarnings("unchecked") // safe covariant cast + @Override + public Boolean visitArray( + List<? extends AnnotationValue> right, + List<? extends AnnotationValue> left) { + return AnnotationValues.equivalence() + .pairwise() + .equivalent( + (List<AnnotationValue>) left, (List<AnnotationValue>) right); + } + }, + left); + } + + @Override + public Boolean visitType(TypeMirror left, AnnotationValue right) { + return right.accept( + new SimpleAnnotationValueVisitor8<Boolean, TypeMirror>() { + @Override + protected Boolean defaultAction(Object ignored, TypeMirror alsoIgnored) { + return false; // Not an annotation mirror, so can't be equal to such. + } + + @Override + public Boolean visitType(TypeMirror right, TypeMirror left) { + return MoreTypes.equivalence().equivalent(left, right); + } + }, + left); + } + }, + right); } - @Override protected int doHash(AnnotationValue value) { - return value.accept(new SimpleAnnotationValueVisitor8<Integer, Void>() { - @Override public Integer visitAnnotation(AnnotationMirror value, Void ignore) { - return AnnotationMirrors.equivalence().hash(value); - } - - @SuppressWarnings("unchecked") // safe covariant cast - @Override public Integer visitArray( - List<? extends AnnotationValue> values, Void ignore) { - return AnnotationValues.equivalence().pairwise().hash((List<AnnotationValue>) values); - } - - @Override public Integer visitType(TypeMirror value, Void ignore) { - return MoreTypes.equivalence().hash(value); - } - - @Override protected Integer defaultAction(Object value, Void ignored) { - return value.hashCode(); - } - }, null); + @Override + protected int doHash(AnnotationValue value) { + return value.accept( + new SimpleAnnotationValueVisitor8<Integer, Void>() { + @Override + public Integer visitAnnotation(AnnotationMirror value, Void ignore) { + return AnnotationMirrors.equivalence().hash(value); + } + + @SuppressWarnings("unchecked") // safe covariant cast + @Override + public Integer visitArray(List<? extends AnnotationValue> values, Void ignore) { + return AnnotationValues.equivalence() + .pairwise() + .hash((List<AnnotationValue>) values); + } + + @Override + public Integer visitType(TypeMirror value, Void ignore) { + return MoreTypes.equivalence().hash(value); + } + + @Override + protected Integer defaultAction(Object value, Void ignored) { + return value.hashCode(); + } + }, + null); } }; @@ -209,7 +238,6 @@ public final class AnnotationValues { return value; } } - ; /** * Returns the value as a VariableElement. @@ -486,4 +514,3 @@ public final class AnnotationValues { private AnnotationValues() {} } - 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 375a4cb8..d951aaf4 100644 --- a/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java +++ b/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java @@ -17,14 +17,14 @@ 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.MoreStreams.toImmutableMap; +import static com.google.auto.common.MoreStreams.toImmutableSet; import static com.google.auto.common.SuperficialValidation.validateElement; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Multimaps.filterKeys; -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toMap; +import static java.util.Objects.requireNonNull; import static javax.lang.model.element.ElementKind.PACKAGE; import static javax.tools.Diagnostic.Kind.ERROR; @@ -56,6 +56,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.type.ErrorType; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleElementVisitor8; +import org.checkerframework.checker.nullness.qual.Nullable; /** * An abstract {@link Processor} implementation that defers processing of {@link Element}s to later @@ -162,14 +163,14 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { checkState(steps != null); return steps.stream() .flatMap(step -> getSupportedAnnotationTypeElements(step).stream()) - .collect(collectingAndThen(toList(), ImmutableSet::copyOf)); + .collect(toImmutableSet()); } private ImmutableSet<TypeElement> getSupportedAnnotationTypeElements(Step step) { return step.annotations().stream() .map(elements::getTypeElement) .filter(Objects::nonNull) - .collect(collectingAndThen(toList(), ImmutableSet::copyOf)); + .collect(toImmutableSet()); } /** @@ -181,7 +182,7 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { checkState(steps != null); return steps.stream() .flatMap(step -> step.annotations().stream()) - .collect(collectingAndThen(toList(), ImmutableSet::copyOf)); + .collect(toImmutableSet()); } @Override @@ -287,10 +288,7 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { // Look at the elements we've found and the new elements from this round and validate them. for (TypeElement annotationType : getSupportedAnnotationTypeElements()) { - Set<? extends Element> roundElements = - (annotationType == null) - ? ImmutableSet.of() - : roundEnv.getElementsAnnotatedWith(annotationType); + Set<? extends Element> roundElements = roundEnv.getElementsAnnotatedWith(annotationType); ImmutableSet<Element> prevRoundElements = deferredElementsByAnnotation.get(annotationType); for (Element element : Sets.union(roundElements, prevRoundElements)) { ElementName elementName = ElementName.forAnnotatedElement(element); @@ -376,7 +374,7 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { * IllegalArgumentException} if the provided {@link Element} is a {@link PackageElement} or is * otherwise not enclosed by a type. */ - // TODO(cgruber) move to MoreElements and make public. + // TODO(user) move to MoreElements and make public. private static TypeElement getEnclosingType(Element element) { return element.accept( new SimpleElementVisitor8<TypeElement, Void>() { @@ -478,10 +476,9 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { this.annotationsByName = processingStep.annotations().stream() .collect( - collectingAndThen( - toMap( - Class::getCanonicalName, (Class<? extends Annotation> aClass) -> aClass), - ImmutableMap::copyOf)); + toImmutableMap( + c -> requireNonNull(c.getCanonicalName()), + (Class<? extends Annotation> aClass) -> aClass)); } @Override @@ -502,8 +499,12 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { elements .asMap() .forEach( - (annotation, annotatedElements) -> - builder.putAll(annotationsByName.get(annotation), annotatedElements)); + (annotationName, annotatedElements) -> { + Class<? extends Annotation> annotation = annotationsByName.get(annotationName); + if (annotation != null) { // should not be null + builder.putAll(annotation, annotatedElements); + } + }); return builder.build(); } } @@ -561,7 +562,7 @@ public abstract class BasicAnnotationProcessor extends AbstractProcessor { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { if (!(object instanceof ElementName)) { return false; } diff --git a/common/src/main/java/com/google/auto/common/GeneratedAnnotationSpecs.java b/common/src/main/java/com/google/auto/common/GeneratedAnnotationSpecs.java index bb35e22f..09d9029e 100644 --- a/common/src/main/java/com/google/auto/common/GeneratedAnnotationSpecs.java +++ b/common/src/main/java/com/google/auto/common/GeneratedAnnotationSpecs.java @@ -27,7 +27,7 @@ public final class GeneratedAnnotationSpecs { private GeneratedAnnotationSpecs() {} /** - * Returns {@code @Generated("processorClass"} if either {@code + * Returns {@code @Generated("processorClass")} if either {@code * javax.annotation.processing.Generated} or {@code javax.annotation.Generated} is {@linkplain * GeneratedAnnotations#generatedAnnotation(Elements) available at compile time}. * @@ -41,7 +41,7 @@ public final class GeneratedAnnotationSpecs { } /** - * Returns {@code @Generated(value = "processorClass", comments = "comments"} if either {@code + * Returns {@code @Generated(value = "processorClass", comments = "comments")} if either {@code * javax.annotation.processing.Generated} or {@code javax.annotation.Generated} is {@linkplain * GeneratedAnnotations#generatedAnnotation(Elements) available at compile time}. * @@ -55,7 +55,7 @@ public final class GeneratedAnnotationSpecs { } /** - * Returns {@code @Generated("processorClass"} for the target {@code SourceVersion}. + * Returns {@code @Generated("processorClass")} for the target {@code SourceVersion}. * * <p>Returns {@code javax.annotation.processing.Generated} for JDK 9 and newer, {@code * javax.annotation.Generated} for earlier releases, and Optional#empty()} if the annotation is @@ -68,7 +68,7 @@ public final class GeneratedAnnotationSpecs { } /** - * Returns {@code @Generated(value = "processorClass", comments = "comments"} for the target + * Returns {@code @Generated(value = "processorClass", comments = "comments")} for the target * {@code SourceVersion}. * * <p>Returns {@code javax.annotation.processing.Generated} for JDK 9 and newer, {@code diff --git a/common/src/main/java/com/google/auto/common/MoreElements.java b/common/src/main/java/com/google/auto/common/MoreElements.java index 5e8e3541..dfbbaeef 100644 --- a/common/src/main/java/com/google/auto/common/MoreElements.java +++ b/common/src/main/java/com/google/auto/common/MoreElements.java @@ -16,6 +16,7 @@ */ package com.google.auto.common; +import static com.google.auto.common.MoreStreams.toImmutableSet; import static javax.lang.model.element.ElementKind.PACKAGE; import static javax.lang.model.element.Modifier.STATIC; @@ -212,29 +213,80 @@ public final class MoreElements { } /** - * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose - * {@linkplain AnnotationMirror#getAnnotationType() annotation type} has the same canonical name - * as that of {@code annotationClass}. This method is a safer alternative to calling - * {@link Element#getAnnotation} and checking for {@code null} as it avoids any interaction with + * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@linkplain + * AnnotationMirror#getAnnotationType() annotation type} has the same canonical name as that of + * {@code annotationClass}. This method is a safer alternative to calling {@link + * Element#getAnnotation} and checking for {@code null} as it avoids any interaction with * annotation proxies. */ - public static boolean isAnnotationPresent(Element element, - Class<? extends Annotation> annotationClass) { + public static boolean isAnnotationPresent( + Element element, Class<? extends Annotation> annotationClass) { return getAnnotationMirror(element, annotationClass).isPresent(); } /** + * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@linkplain + * AnnotationMirror#getAnnotationType() annotation type} has the same fully qualified name as that + * of {@code annotation}. This method is a safer alternative to calling {@link + * Element#getAnnotation} and checking for {@code null} as it avoids any interaction with + * annotation proxies. + */ + public static boolean isAnnotationPresent(Element element, TypeElement annotation) { + return getAnnotationMirror(element, annotation).isPresent(); + } + + /** + * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@linkplain + * AnnotationMirror#getAnnotationType() annotation type} has {@code annotationName} as its + * canonical name. This method is a safer alternative to calling {@link Element#getAnnotation} and + * checking for {@code null} as it avoids any interaction with annotation proxies. + */ + public static boolean isAnnotationPresent(Element element, String annotationName) { + return getAnnotationMirror(element, annotationName).isPresent(); + } + + /** * Returns an {@link AnnotationMirror} for the annotation of type {@code annotationClass} on * {@code element}, or {@link Optional#absent()} if no such annotation exists. This method is a * safer alternative to calling {@link Element#getAnnotation} as it avoids any interaction with * annotation proxies. */ - public static Optional<AnnotationMirror> getAnnotationMirror(Element element, - Class<? extends Annotation> annotationClass) { - String annotationClassName = annotationClass.getCanonicalName(); + public static Optional<AnnotationMirror> getAnnotationMirror( + Element element, Class<? extends Annotation> annotationClass) { + String name = annotationClass.getCanonicalName(); + if (name == null) { + return Optional.absent(); + } + return getAnnotationMirror(element, name); + } + + /** + * Returns an {@link AnnotationMirror} for the annotation of type {@code annotation} on {@code + * element}, or {@link Optional#absent()} if no such annotation exists. This method is a safer + * alternative to calling {@link Element#getAnnotation} as it avoids any interaction with + * annotation proxies. + */ + public static Optional<AnnotationMirror> getAnnotationMirror( + Element element, TypeElement annotation) { + for (AnnotationMirror elementAnnotation : element.getAnnotationMirrors()) { + if (elementAnnotation.getAnnotationType().asElement().equals(annotation)) { + return Optional.of(elementAnnotation); + } + } + return Optional.absent(); + } + + /** + * Returns an {@link AnnotationMirror} for the annotation whose type's canonical name is on {@code + * element}, or {@link Optional#absent()} if no such annotation exists. This method is a safer + * alternative to calling {@link Element#getAnnotation} as it avoids any interaction with + * annotation proxies. + */ + public static Optional<AnnotationMirror> getAnnotationMirror( + Element element, String annotationName) { for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { TypeElement annotationTypeElement = asType(annotationMirror.getAnnotationType().asElement()); - if (annotationTypeElement.getQualifiedName().contentEquals(annotationClassName)) { + if (annotationTypeElement.getQualifiedName().contentEquals(annotationName)) { return Optional.of(annotationMirror); } } @@ -435,9 +487,9 @@ public final class MoreElements { } } } - Set<ExecutableElement> methods = new LinkedHashSet<ExecutableElement>(methodMap.values()); - methods.removeAll(overridden); - return ImmutableSet.copyOf(methods); + return methodMap.values().stream() + .filter(m -> !overridden.contains(m)) + .collect(toImmutableSet()); } // Add to `methods` the static and instance methods from `type`. This means all methods from diff --git a/common/src/main/java/com/google/auto/common/MoreStreams.java b/common/src/main/java/com/google/auto/common/MoreStreams.java new file mode 100644 index 00000000..934514ab --- /dev/null +++ b/common/src/main/java/com/google/auto/common/MoreStreams.java @@ -0,0 +1,75 @@ +/* + * Copyright 2021 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.common; + +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toList; + +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 com.google.common.collect.Maps; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +/** + * A utility class that provides Android compatible alternatives to Guava's streaming APIs. + * + * <p>This is useful when the Android flavor of Guava somehow finds its way onto the processor + * classpath. + */ +public final class MoreStreams { + + /** Returns a collector for an {@link ImmutableList}. */ + public static <T> Collector<T, ?, ImmutableList<T>> toImmutableList() { + return collectingAndThen(toList(), ImmutableList::copyOf); + } + + /** Returns a collector for an {@link ImmutableSet}. */ + public static <T> Collector<T, ?, ImmutableSet<T>> toImmutableSet() { + return collectingAndThen(toList(), ImmutableSet::copyOf); + } + + /** Returns a collector for an {@link ImmutableMap}. */ + public static <T, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap( + Function<? super T, K> keyMapper, Function<? super T, V> valueMapper) { + return Collectors.mapping( + value -> Maps.immutableEntry(keyMapper.apply(value), valueMapper.apply(value)), + Collector.of( + ImmutableMap::builder, + (ImmutableMap.Builder<K, V> builder, Map.Entry<K, V> entry) -> builder.put(entry), + (left, right) -> left.putAll(right.build()), + ImmutableMap.Builder::build)); + } + + /** Returns a collector for an {@link ImmutableBiMap}. */ + public static <T, K, V> Collector<T, ?, ImmutableBiMap<K, V>> toImmutableBiMap( + Function<? super T, K> keyMapper, Function<? super T, V> valueMapper) { + return Collectors.mapping( + value -> Maps.immutableEntry(keyMapper.apply(value), valueMapper.apply(value)), + Collector.of( + ImmutableBiMap::builder, + (ImmutableBiMap.Builder<K, V> builder, Map.Entry<K, V> entry) -> builder.put(entry), + (left, right) -> left.putAll(right.build()), + ImmutableBiMap.Builder::build)); + } + + private MoreStreams() {} +} 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 e09680bc..1a490626 100644 --- a/common/src/main/java/com/google/auto/common/MoreTypes.java +++ b/common/src/main/java/com/google/auto/common/MoreTypes.java @@ -25,7 +25,6 @@ import static javax.lang.model.type.TypeKind.TYPEVAR; import static javax.lang.model.type.TypeKind.WILDCARD; import com.google.common.base.Equivalence; -import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -55,6 +54,7 @@ import javax.lang.model.type.WildcardType; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleTypeVisitor8; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Utilities related to {@link TypeMirror} instances. @@ -75,6 +75,11 @@ public final class MoreTypes { protected int doHash(TypeMirror t) { return MoreTypes.hash(t, ImmutableSet.<Element>of()); } + + @Override + public String toString() { + return "MoreTypes.equivalence()"; + } } /** @@ -135,13 +140,11 @@ public final class MoreTypes { } @Override - public boolean equals(Object o) { + 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()) { + 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. return false; } @@ -294,7 +297,14 @@ public final class MoreTypes { } @SuppressWarnings("TypeEquals") - private static boolean equal(TypeMirror a, TypeMirror b, Set<ComparedElements> visiting) { + private static boolean equal( + @Nullable TypeMirror a, @Nullable TypeMirror b, Set<ComparedElements> visiting) { + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } // TypeMirror.equals is not guaranteed to return true for types that are equal, but we can // assume that if it does return true then the types are equal. This check also avoids getting // stuck in infinite recursion when Eclipse decrees that the upper bound of the second K in @@ -302,13 +312,15 @@ public final class MoreTypes { // The javac implementation of ExecutableType, at least in some versions, does not take thrown // exceptions into account in its equals implementation, so avoid this optimization for // ExecutableType. - if (Objects.equal(a, b) && !(a instanceof ExecutableType)) { + @SuppressWarnings("TypesEquals") + boolean equal = a.equals(b); + if (equal && !(a instanceof ExecutableType)) { return true; } EqualVisitorParam p = new EqualVisitorParam(); p.type = b; p.visiting = visiting; - return (a == b) || (a != null && b != null && a.accept(EqualVisitor.INSTANCE, p)); + return a.accept(EqualVisitor.INSTANCE, p); } /** @@ -318,7 +330,7 @@ public final class MoreTypes { * <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=508222">this bug</a> whereby * the Eclipse compiler returns a value for static classes that is not NoType. */ - private static TypeMirror enclosingType(DeclaredType t) { + private static @Nullable TypeMirror enclosingType(DeclaredType t) { TypeMirror enclosing = t.getEnclosingType(); if (enclosing.getKind().equals(TypeKind.NONE) || t.asElement().getModifiers().contains(Modifier.STATIC)) { @@ -337,16 +349,14 @@ public final class MoreTypes { Iterator<? extends TypeMirror> aIterator = a.iterator(); Iterator<? extends TypeMirror> bIterator = b.iterator(); while (aIterator.hasNext()) { - if (!bIterator.hasNext()) { - return false; - } + // We checked that the lists have the same size, so we know that bIterator.hasNext() too. TypeMirror nextMirrorA = aIterator.next(); TypeMirror nextMirrorB = bIterator.next(); if (!equal(nextMirrorA, nextMirrorB, visiting)) { return false; } } - return !aIterator.hasNext(); + return true; } private static final int HASH_SEED = 17; @@ -433,7 +443,7 @@ public final class MoreTypes { public Integer visitUnknown(TypeMirror t, Set<Element> visiting) { throw new UnsupportedOperationException(); } - }; + } private static int hashList(List<? extends TypeMirror> mirrors, Set<Element> visiting) { int result = HASH_SEED; @@ -460,17 +470,17 @@ public final class MoreTypes { } private static final class ReferencedTypes - extends SimpleTypeVisitor8<Void, ImmutableSet.Builder<TypeElement>> { + extends SimpleTypeVisitor8<@Nullable Void, ImmutableSet.Builder<TypeElement>> { private static final ReferencedTypes INSTANCE = new ReferencedTypes(); @Override - public Void visitArray(ArrayType t, ImmutableSet.Builder<TypeElement> p) { + public @Nullable Void visitArray(ArrayType t, ImmutableSet.Builder<TypeElement> p) { t.getComponentType().accept(this, p); return null; } @Override - public Void visitDeclared(DeclaredType t, ImmutableSet.Builder<TypeElement> p) { + public @Nullable Void visitDeclared(DeclaredType t, ImmutableSet.Builder<TypeElement> p) { p.add(MoreElements.asType(t.asElement())); for (TypeMirror typeArgument : t.getTypeArguments()) { typeArgument.accept(this, p); @@ -479,14 +489,14 @@ public final class MoreTypes { } @Override - public Void visitTypeVariable(TypeVariable t, ImmutableSet.Builder<TypeElement> p) { + public @Nullable Void visitTypeVariable(TypeVariable t, ImmutableSet.Builder<TypeElement> p) { t.getLowerBound().accept(this, p); t.getUpperBound().accept(this, p); return null; } @Override - public Void visitWildcard(WildcardType t, ImmutableSet.Builder<TypeElement> p) { + public @Nullable Void visitWildcard(WildcardType t, ImmutableSet.Builder<TypeElement> p) { TypeMirror extendsBound = t.getExtendsBound(); if (extendsBound != null) { extendsBound.accept(this, p); @@ -534,7 +544,8 @@ public final class MoreTypes { public Element visitTypeVariable(TypeVariable t, Void p) { return t.asElement(); } - }; + } + ; // TODO(gak): consider removing these two methods as they're pretty trivial now public static TypeElement asTypeElement(TypeMirror mirror) { @@ -833,6 +844,11 @@ public final class MoreTypes { } @Override + public Boolean visitError(ErrorType errorType, Void p) { + return false; + } + + @Override public Boolean visitPrimitive(PrimitiveType type, Void p) { switch (type.getKind()) { case BOOLEAN: @@ -873,11 +889,11 @@ public final class MoreTypes { * {@link Optional#absent()} if {@code type} is an interface or {@link Object} or its superclass * is {@link Object}. */ - // TODO(user): Remove unused parameter Elements? - public static Optional<DeclaredType> nonObjectSuperclass(Types types, Elements elements, - DeclaredType type) { + // TODO(bcorso): Remove unused parameter Elements? + public static Optional<DeclaredType> nonObjectSuperclass( + Types types, Elements elements, DeclaredType type) { checkNotNull(types); - checkNotNull(elements); // This is no longer used, but here to avoid changing the API. + checkNotNull(elements); // This is no longer used, but here to avoid changing the API. checkNotNull(type); TypeMirror superclassType = asTypeElement(type).getSuperclass(); @@ -885,7 +901,7 @@ public final class MoreTypes { return Optional.absent(); } - DeclaredType superclass = asDeclared(superclassType); + DeclaredType superclass = asDeclared(superclassType); if (isObjectType(superclass)) { return Optional.absent(); } @@ -912,8 +928,8 @@ public final class MoreTypes { * {@code container} of type {@code Set<String>}, and a variable corresponding to the {@code E e} * parameter in the {@code Set.add(E e)} method, this will return a TypeMirror for {@code String}. */ - public static TypeMirror asMemberOf(Types types, DeclaredType container, - VariableElement variable) { + public static TypeMirror asMemberOf( + Types types, DeclaredType container, VariableElement variable) { if (variable.getKind().equals(ElementKind.PARAMETER)) { ExecutableElement methodOrConstructor = MoreElements.asExecutable(variable.getEnclosingElement()); 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 19a45862..775c304a 100644 --- a/common/src/main/java/com/google/auto/common/Overrides.java +++ b/common/src/main/java/com/google/auto/common/Overrides.java @@ -15,6 +15,8 @@ */ package com.google.auto.common; +import static java.util.stream.Collectors.toList; + import com.google.common.base.Preconditions; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; @@ -38,6 +40,7 @@ import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleTypeVisitor8; 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: @@ -114,12 +117,11 @@ abstract class Overrides { // can't be overridden. return false; } - TypeElement overriddenType; if (!(overridden.getEnclosingElement() instanceof TypeElement)) { return false; // We don't know how this could happen but we avoid blowing up if it does. } - overriddenType = MoreElements.asType(overridden.getEnclosingElement()); + TypeElement overriddenType = MoreElements.asType(overridden.getEnclosingElement()); // We erase the types before checking subtypes, because the TypeMirror we get for List<E> is // not a subtype of the one we get for Collection<E> since the two E instances are not the // same. For the purposes of overriding, type parameters in the containing type should not @@ -141,7 +143,8 @@ abstract class Overrides { // the enclosing elements rather than the methods themselves for the reason described // at the start of the method. ExecutableElement inherited = methodFromSuperclasses(in, overridden); - return !overridden.getEnclosingElement().equals(inherited.getEnclosingElement()); + return inherited != null + && !overridden.getEnclosingElement().equals(inherited.getEnclosingElement()); } else if (overriddenType.getKind().isInterface()) { // ...overrides from C another method mI declared in interface I. We've already checked // the conditions (assuming that the only alternative to mI being abstract or default is @@ -157,7 +160,8 @@ abstract class Overrides { // to methodFromSuperclasses above. if (overrider.getModifiers().contains(Modifier.ABSTRACT)) { ExecutableElement inherited = methodFromSuperinterfaces(in, overridden); - return !overridden.getEnclosingElement().equals(inherited.getEnclosingElement()); + return inherited != null + && !overridden.getEnclosingElement().equals(inherited.getEnclosingElement()); } else { return true; } @@ -215,6 +219,7 @@ abstract class Overrides { * implements List<E>}. The parameter types are erased since the purpose of this method is to * determine whether two methods are candidates for one to override the other. */ + @Nullable ImmutableList<TypeMirror> erasedParameterTypes(ExecutableElement method, TypeElement in) { if (method.getParameters().isEmpty()) { return ImmutableList.of(); @@ -241,6 +246,7 @@ abstract class Overrides { */ private final Map<TypeParameterElement, TypeMirror> typeBindings = Maps.newLinkedHashMap(); + @Nullable ImmutableList<TypeMirror> erasedParameterTypes(ExecutableElement method, TypeElement in) { if (method.getEnclosingElement().equals(in)) { ImmutableList.Builder<TypeMirror> params = ImmutableList.builder(); @@ -261,6 +267,10 @@ abstract class Overrides { TypeElement element = MoreElements.asType(declared.asElement()); List<? extends TypeMirror> actuals = declared.getTypeArguments(); List<? extends TypeParameterElement> formals = element.getTypeParameters(); + if (actuals.isEmpty()) { + // Either the formal type arguments are also empty or `declared` is raw. + actuals = formals.stream().map(t -> t.getBounds().get(0)).collect(toList()); + } Verify.verify(actuals.size() == formals.size()); for (int i = 0; i < actuals.size(); i++) { typeBindings.put(formals.get(i), actuals.get(i)); @@ -315,7 +325,7 @@ abstract class Overrides { * or the nearest override in a superclass of the given type, or null if the method is not * found in the given type or any of its superclasses. */ - ExecutableElement methodFromSuperclasses(TypeElement in, ExecutableElement method) { + @Nullable ExecutableElement methodFromSuperclasses(TypeElement in, ExecutableElement method) { for (TypeElement t = in; t != null; t = superclass(t)) { ExecutableElement tMethod = methodInType(t, method); if (tMethod != null) { @@ -330,6 +340,7 @@ abstract class Overrides { * itself, or the nearest override in a superinterface of the given type, or null if the method * is not found in the given type or any of its transitive superinterfaces. */ + @Nullable ExecutableElement methodFromSuperinterfaces(TypeElement in, ExecutableElement method) { TypeElement methodContainer = MoreElements.asType(method.getEnclosingElement()); Preconditions.checkArgument(methodContainer.getKind().isInterface()); @@ -366,7 +377,7 @@ abstract class Overrides { * Returns the method from within the given type that has the same erased signature as the given * method, or null if there is no such method. */ - private ExecutableElement methodInType(TypeElement type, ExecutableElement method) { + private @Nullable ExecutableElement methodInType(TypeElement type, ExecutableElement method) { int nParams = method.getParameters().size(); List<TypeMirror> params = erasedParameterTypes(method, type); if (params == null) { @@ -388,7 +399,7 @@ abstract class Overrides { return null; } - private TypeElement superclass(TypeElement type) { + private @Nullable TypeElement superclass(TypeElement type) { TypeMirror sup = type.getSuperclass(); if (sup.getKind() == TypeKind.DECLARED) { return MoreElements.asType(typeUtils.asElement(sup)); diff --git a/common/src/main/java/com/google/auto/common/SimpleAnnotationMirror.java b/common/src/main/java/com/google/auto/common/SimpleAnnotationMirror.java index 7d508e32..7952eb37 100644 --- a/common/src/main/java/com/google/auto/common/SimpleAnnotationMirror.java +++ b/common/src/main/java/com/google/auto/common/SimpleAnnotationMirror.java @@ -16,8 +16,8 @@ package com.google.auto.common; +import static com.google.auto.common.MoreStreams.toImmutableMap; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.ImmutableMap.toImmutableMap; import static javax.lang.model.util.ElementFilter.methodsIn; import com.google.common.base.Joiner; @@ -32,6 +32,7 @@ import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A simple implementation of the {@link AnnotationMirror} interface. @@ -65,7 +66,7 @@ public final class SimpleAnnotationMirror implements AnnotationMirror { missingMembers.add(memberName); } } - + checkArgument( unusedValues.isEmpty(), "namedValues has entries for members that are not in %s: %s", @@ -77,8 +78,7 @@ public final class SimpleAnnotationMirror implements AnnotationMirror { this.annotationType = annotationType; this.namedValues = ImmutableMap.copyOf(namedValues); this.elementValues = - methodsIn(annotationType.getEnclosedElements()) - .stream() + methodsIn(annotationType.getEnclosedElements()).stream() .collect(toImmutableMap(e -> e, e -> values.get(e.getSimpleName().toString()))); } @@ -123,7 +123,7 @@ public final class SimpleAnnotationMirror implements AnnotationMirror { } @Override - public boolean equals(Object other) { + public boolean equals(@Nullable Object other) { return other instanceof AnnotationMirror && AnnotationMirrors.equivalence().equivalent(this, (AnnotationMirror) other); } diff --git a/common/src/main/java/com/google/auto/common/SuperficialValidation.java b/common/src/main/java/com/google/auto/common/SuperficialValidation.java index 5ef4dbf2..614e2626 100644 --- a/common/src/main/java/com/google/auto/common/SuperficialValidation.java +++ b/common/src/main/java/com/google/auto/common/SuperficialValidation.java @@ -57,23 +57,27 @@ public final class SuperficialValidation { private static final ElementVisitor<Boolean, Void> ELEMENT_VALIDATING_VISITOR = new AbstractElementVisitor8<Boolean, Void>() { - @Override public Boolean visitPackage(PackageElement e, Void p) { + @Override + public Boolean visitPackage(PackageElement e, Void p) { // don't validate enclosed elements because it will return types in the package return validateAnnotations(e.getAnnotationMirrors()); } - @Override public Boolean visitType(TypeElement e, Void p) { + @Override + public Boolean visitType(TypeElement e, Void p) { return isValidBaseElement(e) && validateElements(e.getTypeParameters()) && validateTypes(e.getInterfaces()) && validateType(e.getSuperclass()); } - @Override public Boolean visitVariable(VariableElement e, Void p) { + @Override + public Boolean visitVariable(VariableElement e, Void p) { return isValidBaseElement(e); } - @Override public Boolean visitExecutable(ExecutableElement e, Void p) { + @Override + public Boolean visitExecutable(ExecutableElement e, Void p) { AnnotationValue defaultValue = e.getDefaultValue(); return isValidBaseElement(e) && (defaultValue == null || validateAnnotationValue(defaultValue, e.getReturnType())) @@ -83,12 +87,13 @@ public final class SuperficialValidation { && validateElements(e.getParameters()); } - @Override public Boolean visitTypeParameter(TypeParameterElement e, Void p) { - return isValidBaseElement(e) - && validateTypes(e.getBounds()); + @Override + public Boolean visitTypeParameter(TypeParameterElement e, Void p) { + return isValidBaseElement(e) && validateTypes(e.getBounds()); } - @Override public Boolean visitUnknown(Element e, Void p) { + @Override + public Boolean visitUnknown(Element e, Void p) { // just assume that unknown elements are OK return true; } @@ -206,16 +211,19 @@ public final class SuperficialValidation { private static final AnnotationValueVisitor<Boolean, TypeMirror> VALUE_VALIDATING_VISITOR = new SimpleAnnotationValueVisitor8<Boolean, TypeMirror>() { - @Override protected Boolean defaultAction(Object o, TypeMirror expectedType) { + @Override + protected Boolean defaultAction(Object o, TypeMirror expectedType) { return MoreTypes.isTypeOf(o.getClass(), expectedType); } - @Override public Boolean visitUnknown(AnnotationValue av, TypeMirror expectedType) { + @Override + public Boolean visitUnknown(AnnotationValue av, TypeMirror expectedType) { // just take the default action for the unknown return defaultAction(av, expectedType); } - @Override public Boolean visitAnnotation(AnnotationMirror a, TypeMirror expectedType) { + @Override + public Boolean visitAnnotation(AnnotationMirror a, TypeMirror expectedType) { return MoreTypes.equivalence().equivalent(a.getAnnotationType(), expectedType) && validateAnnotation(a); } @@ -235,7 +243,8 @@ public final class SuperficialValidation { && validateElement(enumConstant); } - @Override public Boolean visitType(TypeMirror type, TypeMirror ignored) { + @Override + public Boolean visitType(TypeMirror type, TypeMirror ignored) { // We could check assignability here, but would require a Types instance. Since this // isn't really the sort of thing that shows up in a bad AST from upstream compilation // we ignore the expected type and just validate the type. It might be wrong, but @@ -243,35 +252,43 @@ public final class SuperficialValidation { return validateType(type); } - @Override public Boolean visitBoolean(boolean b, TypeMirror expectedType) { + @Override + public Boolean visitBoolean(boolean b, TypeMirror expectedType) { return MoreTypes.isTypeOf(Boolean.TYPE, expectedType); } - @Override public Boolean visitByte(byte b, TypeMirror expectedType) { + @Override + public Boolean visitByte(byte b, TypeMirror expectedType) { return MoreTypes.isTypeOf(Byte.TYPE, expectedType); } - @Override public Boolean visitChar(char c, TypeMirror expectedType) { + @Override + public Boolean visitChar(char c, TypeMirror expectedType) { return MoreTypes.isTypeOf(Character.TYPE, expectedType); } - @Override public Boolean visitDouble(double d, TypeMirror expectedType) { + @Override + public Boolean visitDouble(double d, TypeMirror expectedType) { return MoreTypes.isTypeOf(Double.TYPE, expectedType); } - @Override public Boolean visitFloat(float f, TypeMirror expectedType) { + @Override + public Boolean visitFloat(float f, TypeMirror expectedType) { return MoreTypes.isTypeOf(Float.TYPE, expectedType); } - @Override public Boolean visitInt(int i, TypeMirror expectedType) { + @Override + public Boolean visitInt(int i, TypeMirror expectedType) { return MoreTypes.isTypeOf(Integer.TYPE, expectedType); } - @Override public Boolean visitLong(long l, TypeMirror expectedType) { + @Override + public Boolean visitLong(long l, TypeMirror expectedType) { return MoreTypes.isTypeOf(Long.TYPE, expectedType); } - @Override public Boolean visitShort(short s, TypeMirror expectedType) { + @Override + public Boolean visitShort(short s, TypeMirror expectedType) { return MoreTypes.isTypeOf(Short.TYPE, expectedType); } }; diff --git a/common/src/main/java/com/google/auto/common/Visibility.java b/common/src/main/java/com/google/auto/common/Visibility.java index f82fdd59..36f4ad6d 100644 --- a/common/src/main/java/com/google/auto/common/Visibility.java +++ b/common/src/main/java/com/google/auto/common/Visibility.java @@ -16,14 +16,15 @@ package com.google.auto.common; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Comparators.min; import static javax.lang.model.element.ElementKind.PACKAGE; import com.google.common.base.Enums; -import com.google.common.collect.Ordering; import java.util.Set; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Represents the visibility of a given {@link Element}: {@code public}, {@code protected}, @@ -41,7 +42,7 @@ public enum Visibility { // TODO(ronshapiro): remove this and reference ElementKind.MODULE directly once we start building // with -source 9 - private static final ElementKind MODULE = + private static final @Nullable ElementKind MODULE = Enums.getIfPresent(ElementKind.class, "MODULE").orNull(); /** @@ -76,8 +77,7 @@ public enum Visibility { Visibility effectiveVisibility = PUBLIC; Element currentElement = element; while (currentElement != null) { - effectiveVisibility = - Ordering.natural().min(effectiveVisibility, ofElement(currentElement)); + effectiveVisibility = min(effectiveVisibility, ofElement(currentElement)); currentElement = currentElement.getEnclosingElement(); } return effectiveVisibility; diff --git a/common/src/main/java/com/google/auto/common/package-info.java b/common/src/main/java/com/google/auto/common/package-info.java new file mode 100644 index 00000000..22b0c45a --- /dev/null +++ b/common/src/main/java/com/google/auto/common/package-info.java @@ -0,0 +1,17 @@ +/* + * Copyright 2021 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.common; + diff --git a/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java b/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java index dfc043ab..b1dfe3b3 100644 --- a/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java +++ b/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java @@ -22,12 +22,17 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.testing.compile.CompilationSubject.assertThat; import static org.junit.Assert.fail; +import com.google.common.collect.ImmutableSet; import com.google.common.testing.EquivalenceTester; +import com.google.common.truth.Correspondence; import com.google.testing.compile.CompilationRule; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Map; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleAnnotationValueVisitor6; @@ -46,110 +51,134 @@ public class AnnotationMirrorsTest { private Elements elements; - @Before public void setUp() { + @Before + public void setUp() { this.elements = compilationRule.getElements(); } @interface SimpleAnnotation {} - @SimpleAnnotation class SimplyAnnotated {} - @SimpleAnnotation class AlsoSimplyAnnotated {} + @SimpleAnnotation + static class SimplyAnnotated {} + + @SimpleAnnotation + static class AlsoSimplyAnnotated {} enum SimpleEnum { - BLAH, FOO + BLAH, + FOO } @interface Outer { SimpleEnum value(); } - @Outer(BLAH) static class TestClassBlah {} - @Outer(BLAH) static class TestClassBlah2 {} - @Outer(FOO) static class TestClassFoo {} + @Outer(BLAH) + static class TestClassBlah {} + + @Outer(BLAH) + static class TestClassBlah2 {} + + @Outer(FOO) + static class TestClassFoo {} @interface DefaultingOuter { SimpleEnum value() default SimpleEnum.BLAH; } - @DefaultingOuter class TestWithDefaultingOuterDefault {} - @DefaultingOuter(BLAH) class TestWithDefaultingOuterBlah {} - @DefaultingOuter(FOO) class TestWithDefaultingOuterFoo {} + @DefaultingOuter + static class TestWithDefaultingOuterDefault {} + + @DefaultingOuter(BLAH) + static class TestWithDefaultingOuterBlah {} + + @DefaultingOuter(FOO) + static class TestWithDefaultingOuterFoo {} @interface AnnotatedOuter { DefaultingOuter value(); } - @AnnotatedOuter(@DefaultingOuter) class TestDefaultNestedAnnotated {} - @AnnotatedOuter(@DefaultingOuter(BLAH)) class TestBlahNestedAnnotated {} - @AnnotatedOuter(@DefaultingOuter(FOO)) class TestFooNestedAnnotated {} + @AnnotatedOuter(@DefaultingOuter) + static class TestDefaultNestedAnnotated {} + + @AnnotatedOuter(@DefaultingOuter(BLAH)) + static class TestBlahNestedAnnotated {} + + @AnnotatedOuter(@DefaultingOuter(FOO)) + static class TestFooNestedAnnotated {} @interface OuterWithValueArray { DefaultingOuter[] value() default {}; } - @OuterWithValueArray class TestValueArrayWithDefault {} - @OuterWithValueArray({}) class TestValueArrayWithEmpty {} + @OuterWithValueArray + static class TestValueArrayWithDefault {} + + @OuterWithValueArray({}) + static class TestValueArrayWithEmpty {} - @OuterWithValueArray({@DefaultingOuter}) class TestValueArrayWithOneDefault {} - @OuterWithValueArray(@DefaultingOuter(BLAH)) class TestValueArrayWithOneBlah {} - @OuterWithValueArray(@DefaultingOuter(FOO)) class TestValueArrayWithOneFoo {} + @OuterWithValueArray({@DefaultingOuter}) + static class TestValueArrayWithOneDefault {} + + @OuterWithValueArray(@DefaultingOuter(BLAH)) + static class TestValueArrayWithOneBlah {} + + @OuterWithValueArray(@DefaultingOuter(FOO)) + static class TestValueArrayWithOneFoo {} @OuterWithValueArray({@DefaultingOuter(FOO), @DefaultingOuter}) class TestValueArrayWithFooAndDefaultBlah {} + @OuterWithValueArray({@DefaultingOuter(FOO), @DefaultingOuter(BLAH)}) class TestValueArrayWithFooBlah {} + @OuterWithValueArray({@DefaultingOuter(FOO), @DefaultingOuter(BLAH)}) class TestValueArrayWithFooBlah2 {} // Different instances than on TestValueArrayWithFooBlah. + @OuterWithValueArray({@DefaultingOuter(BLAH), @DefaultingOuter(FOO)}) class TestValueArrayWithBlahFoo {} - @Test public void testEquivalences() { + @Test + public void testEquivalences() { EquivalenceTester<AnnotationMirror> tester = EquivalenceTester.of(AnnotationMirrors.equivalence()); tester.addEquivalenceGroup( - annotationOn(SimplyAnnotated.class), - annotationOn(AlsoSimplyAnnotated.class)); + annotationOn(SimplyAnnotated.class), annotationOn(AlsoSimplyAnnotated.class)); tester.addEquivalenceGroup( - annotationOn(TestClassBlah.class), - annotationOn(TestClassBlah2.class)); + annotationOn(TestClassBlah.class), annotationOn(TestClassBlah2.class)); - tester.addEquivalenceGroup( - annotationOn(TestClassFoo.class)); + tester.addEquivalenceGroup(annotationOn(TestClassFoo.class)); tester.addEquivalenceGroup( annotationOn(TestWithDefaultingOuterDefault.class), annotationOn(TestWithDefaultingOuterBlah.class)); - tester.addEquivalenceGroup( - annotationOn(TestWithDefaultingOuterFoo.class)); + tester.addEquivalenceGroup(annotationOn(TestWithDefaultingOuterFoo.class)); tester.addEquivalenceGroup( annotationOn(TestDefaultNestedAnnotated.class), annotationOn(TestBlahNestedAnnotated.class)); - tester.addEquivalenceGroup( - annotationOn(TestFooNestedAnnotated.class)); + tester.addEquivalenceGroup(annotationOn(TestFooNestedAnnotated.class)); tester.addEquivalenceGroup( - annotationOn(TestValueArrayWithDefault.class), - annotationOn(TestValueArrayWithEmpty.class)); + annotationOn(TestValueArrayWithDefault.class), annotationOn(TestValueArrayWithEmpty.class)); tester.addEquivalenceGroup( annotationOn(TestValueArrayWithOneDefault.class), annotationOn(TestValueArrayWithOneBlah.class)); - tester.addEquivalenceGroup( - annotationOn(TestValueArrayWithOneFoo.class)); + tester.addEquivalenceGroup(annotationOn(TestValueArrayWithOneFoo.class)); tester.addEquivalenceGroup( annotationOn(TestValueArrayWithFooAndDefaultBlah.class), annotationOn(TestValueArrayWithFooBlah.class), annotationOn(TestValueArrayWithFooBlah2.class)); - tester.addEquivalenceGroup( - annotationOn(TestValueArrayWithBlahFoo.class)); + tester.addEquivalenceGroup(annotationOn(TestValueArrayWithBlahFoo.class)); tester.test(); } @@ -158,44 +187,61 @@ public class AnnotationMirrorsTest { String value() default "default"; } - @Stringy class StringyUnset {} - @Stringy("foo") class StringySet {} + @Stringy + static class StringyUnset {} + + @Stringy("foo") + static class StringySet {} - @Test public void testGetDefaultValuesUnset() { + @Test + public void testGetDefaultValuesUnset() { assertThat(annotationOn(StringyUnset.class).getElementValues()).isEmpty(); - Iterable<AnnotationValue> values = AnnotationMirrors.getAnnotationValuesWithDefaults( - annotationOn(StringyUnset.class)).values(); - String value = getOnlyElement(values).accept(new SimpleAnnotationValueVisitor6<String, Void>() { - @Override public String visitString(String value, Void ignored) { - return value; - } - }, null); + Iterable<AnnotationValue> values = + AnnotationMirrors.getAnnotationValuesWithDefaults(annotationOn(StringyUnset.class)) + .values(); + String value = + getOnlyElement(values) + .accept( + new SimpleAnnotationValueVisitor6<String, Void>() { + @Override + public String visitString(String value, Void ignored) { + return value; + } + }, + null); assertThat(value).isEqualTo("default"); } - @Test public void testGetDefaultValuesSet() { - Iterable<AnnotationValue> values = AnnotationMirrors.getAnnotationValuesWithDefaults( - annotationOn(StringySet.class)).values(); - String value = getOnlyElement(values).accept(new SimpleAnnotationValueVisitor6<String, Void>() { - @Override public String visitString(String value, Void ignored) { - return value; - } - }, null); + @Test + public void testGetDefaultValuesSet() { + Iterable<AnnotationValue> values = + AnnotationMirrors.getAnnotationValuesWithDefaults(annotationOn(StringySet.class)).values(); + String value = + getOnlyElement(values) + .accept( + new SimpleAnnotationValueVisitor6<String, Void>() { + @Override + public String visitString(String value, Void ignored) { + return value; + } + }, + null); assertThat(value).isEqualTo("foo"); } - @Test public void testGetValueEntry() { + @Test + public void testGetValueEntry() { Map.Entry<ExecutableElement, AnnotationValue> elementValue = - AnnotationMirrors.getAnnotationElementAndValue( - annotationOn(TestClassBlah.class), "value"); + AnnotationMirrors.getAnnotationElementAndValue(annotationOn(TestClassBlah.class), "value"); assertThat(elementValue.getKey().getSimpleName().toString()).isEqualTo("value"); assertThat(elementValue.getValue().getValue()).isInstanceOf(VariableElement.class); - AnnotationValue value = AnnotationMirrors.getAnnotationValue( - annotationOn(TestClassBlah.class), "value"); + AnnotationValue value = + AnnotationMirrors.getAnnotationValue(annotationOn(TestClassBlah.class), "value"); assertThat(value.getValue()).isInstanceOf(VariableElement.class); } - @Test public void testGetValueEntryFailure() { + @Test + public void testGetValueEntryFailure() { try { AnnotationMirrors.getAnnotationValue(annotationOn(TestClassBlah.class), "a"); } catch (IllegalArgumentException e) { @@ -212,4 +258,52 @@ public class AnnotationMirrorsTest { return getOnlyElement(elements.getTypeElement(clazz.getCanonicalName()).getAnnotationMirrors()); } + @Retention(RetentionPolicy.RUNTIME) + private @interface AnnotatingAnnotation {} + + @AnnotatingAnnotation + @Retention(RetentionPolicy.RUNTIME) + private @interface AnnotatedAnnotation1 {} + + @AnnotatingAnnotation + @Retention(RetentionPolicy.RUNTIME) + private @interface AnnotatedAnnotation2 {} + + @Retention(RetentionPolicy.RUNTIME) + private @interface NotAnnotatedAnnotation {} + + @AnnotatedAnnotation1 + @NotAnnotatedAnnotation + @AnnotatedAnnotation2 + private static final class AnnotatedClass {} + + @Test + public void getAnnotatedAnnotations() { + TypeElement element = elements.getTypeElement(AnnotatedClass.class.getCanonicalName()); + + // Test Class API + getAnnotatedAnnotationsAsserts( + AnnotationMirrors.getAnnotatedAnnotations(element, AnnotatingAnnotation.class)); + + // Test String API + String annotatingAnnotationName = AnnotatingAnnotation.class.getCanonicalName(); + getAnnotatedAnnotationsAsserts( + AnnotationMirrors.getAnnotatedAnnotations(element, annotatingAnnotationName)); + + // Test TypeElement API + TypeElement annotatingAnnotationElement = elements.getTypeElement(annotatingAnnotationName); + getAnnotatedAnnotationsAsserts( + AnnotationMirrors.getAnnotatedAnnotations(element, annotatingAnnotationElement)); + } + + private void getAnnotatedAnnotationsAsserts( + ImmutableSet<? extends AnnotationMirror> annotatedAnnotations) { + assertThat(annotatedAnnotations) + .comparingElementsUsing( + Correspondence.transforming( + (AnnotationMirror a) -> MoreTypes.asTypeElement(a.getAnnotationType()), "has type")) + .containsExactly( + elements.getTypeElement(AnnotatedAnnotation1.class.getCanonicalName()), + elements.getTypeElement(AnnotatedAnnotation2.class.getCanonicalName())); + } } diff --git a/common/src/test/java/com/google/auto/common/GeneratedAnnotationsTest.java b/common/src/test/java/com/google/auto/common/GeneratedAnnotationsTest.java index 1c816c10..f9426527 100644 --- a/common/src/test/java/com/google/auto/common/GeneratedAnnotationsTest.java +++ b/common/src/test/java/com/google/auto/common/GeneratedAnnotationsTest.java @@ -18,6 +18,7 @@ package com.google.auto.common; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; import static org.junit.Assume.assumeTrue; import com.google.common.collect.ImmutableList; @@ -31,6 +32,7 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.net.URI; import java.nio.file.Files; +import java.util.Objects; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; @@ -44,6 +46,7 @@ import javax.tools.SimpleJavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.StandardLocation; import javax.tools.ToolProvider; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -99,12 +102,12 @@ public class GeneratedAnnotationsTest { * Run {@link TestProcessor} in a compilation with the given {@code options}, and prevent the * compilation from accessing classes with the qualified names in {@code maskFromClasspath}. */ - private String runProcessor(ImmutableList<String> options, String packageToMask) + private String runProcessor(ImmutableList<String> options, @Nullable String packageToMask) throws IOException { File tempDir = temporaryFolder.newFolder(); JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager standardFileManager = - compiler.getStandardFileManager(/* diagnostics= */ null, /* locale= */ null, UTF_8); + compiler.getStandardFileManager(/* diagnosticListener= */ null, /* locale= */ null, UTF_8); standardFileManager.setLocation(StandardLocation.CLASS_OUTPUT, ImmutableList.of(tempDir)); StandardJavaFileManager proxyFileManager = Reflection.newProxy( @@ -142,18 +145,20 @@ public class GeneratedAnnotationsTest { */ private static class FileManagerInvocationHandler implements InvocationHandler { private final StandardJavaFileManager fileManager; - private final String packageToMask; + private final @Nullable String packageToMask; - FileManagerInvocationHandler(StandardJavaFileManager fileManager, String packageToMask) { + FileManagerInvocationHandler( + StandardJavaFileManager fileManager, @Nullable String packageToMask) { this.fileManager = fileManager; this.packageToMask = packageToMask; } @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + public Object invoke(Object proxy, Method method, @Nullable Object @Nullable [] args) + throws Throwable { if (method.getName().equals("list")) { - String packageName = (String) args[1]; - if (packageName.equals(packageToMask)) { + String packageName = (String) requireNonNull(args)[1]; + if (Objects.equals(packageName, packageToMask)) { return ImmutableList.of(); } } @@ -187,8 +192,7 @@ public class GeneratedAnnotationsTest { // An alternative would be to delete this test method. JDK8 always has // javax.annotation.Generated so it isn't really meaningful to test it without. ImmutableList<String> options = ImmutableList.of("-source", "8", "-target", "8"); - String generated = - runProcessor(options, "javax.annotation"); + String generated = runProcessor(options, "javax.annotation"); assertThat(generated).doesNotContain(JAVAX_ANNOTATION_GENERATED); assertThat(generated).doesNotContain(JAVAX_ANNOTATION_PROCESSING_GENERATED); } 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 95043cf3..b98b79b9 100644 --- a/common/src/test/java/com/google/auto/common/MoreElementsTest.java +++ b/common/src/test/java/com/google/auto/common/MoreElementsTest.java @@ -18,6 +18,7 @@ package com.google.auto.common; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static java.util.Objects.requireNonNull; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -58,13 +59,14 @@ public class MoreElementsTest { @Rule public CompilationRule compilation = new CompilationRule(); @Rule public Expect expect = Expect.create(); + private Elements elements; private PackageElement javaLangPackageElement; private TypeElement objectElement; private TypeElement stringElement; @Before public void initializeTestElements() { - Elements elements = compilation.getElements(); + this.elements = compilation.getElements(); this.javaLangPackageElement = elements.getPackageElement("java.lang"); this.objectElement = elements.getTypeElement(Object.class.getCanonicalName()); this.stringElement = elements.getTypeElement(String.class.getCanonicalName()); @@ -80,8 +82,7 @@ public class MoreElementsTest { @Test public void asPackage() { - assertThat(MoreElements.asPackage(javaLangPackageElement)) - .isEqualTo(javaLangPackageElement); + assertThat(MoreElements.asPackage(javaLangPackageElement)).isEqualTo(javaLangPackageElement); } @Test @@ -89,19 +90,20 @@ public class MoreElementsTest { try { MoreElements.asPackage(stringElement); fail(); - } catch (IllegalArgumentException expected) {} + } catch (IllegalArgumentException expected) { + } } - @Test public void asTypeElement() { - Element typeElement = - compilation.getElements().getTypeElement(String.class.getCanonicalName()); + @Test + public void asTypeElement() { + Element typeElement = elements.getTypeElement(String.class.getCanonicalName()); assertTrue(MoreElements.isType(typeElement)); assertThat(MoreElements.asType(typeElement)).isEqualTo(typeElement); } - @Test public void asTypeElement_notATypeElement() { - TypeElement typeElement = - compilation.getElements().getTypeElement(String.class.getCanonicalName()); + @Test + public void asTypeElement_notATypeElement() { + TypeElement typeElement = elements.getTypeElement(String.class.getCanonicalName()); for (ExecutableElement e : ElementFilter.methodsIn(typeElement.getEnclosedElements())) { assertFalse(MoreElements.isType(e)); try { @@ -143,7 +145,8 @@ public class MoreElementsTest { try { MoreElements.asType(javaLangPackageElement); fail(); - } catch (IllegalArgumentException expected) {} + } catch (IllegalArgumentException expected) { + } } @Test @@ -158,7 +161,8 @@ public class MoreElementsTest { try { MoreElements.asVariable(javaLangPackageElement); fail(); - } catch (IllegalArgumentException expected) {} + } catch (IllegalArgumentException expected) { + } } @Test @@ -166,8 +170,8 @@ public class MoreElementsTest { for (Element methodElement : ElementFilter.methodsIn(stringElement.getEnclosedElements())) { assertThat(MoreElements.asExecutable(methodElement)).isEqualTo(methodElement); } - for (Element methodElement - : ElementFilter.constructorsIn(stringElement.getEnclosedElements())) { + for (Element methodElement : + ElementFilter.constructorsIn(stringElement.getEnclosedElements())) { assertThat(MoreElements.asExecutable(methodElement)).isEqualTo(methodElement); } } @@ -177,7 +181,8 @@ public class MoreElementsTest { try { MoreElements.asExecutable(javaLangPackageElement); fail(); - } catch (IllegalArgumentException expected) {} + } catch (IllegalArgumentException expected) { + } } @Retention(RetentionPolicy.RUNTIME) @@ -190,39 +195,90 @@ public class MoreElementsTest { @Test public void isAnnotationPresent() { TypeElement annotatedAnnotationElement = - compilation.getElements().getTypeElement(AnnotatedAnnotation.class.getCanonicalName()); - assertThat(MoreElements.isAnnotationPresent(annotatedAnnotationElement, Documented.class)) - .isTrue(); - assertThat(MoreElements.isAnnotationPresent(annotatedAnnotationElement, InnerAnnotation.class)) - .isTrue(); - assertThat(MoreElements.isAnnotationPresent(annotatedAnnotationElement, SuppressWarnings.class)) - .isFalse(); + elements.getTypeElement(AnnotatedAnnotation.class.getCanonicalName()); + + // Test Class API + isAnnotationPresentAsserts( + MoreElements.isAnnotationPresent(annotatedAnnotationElement, Documented.class), + MoreElements.isAnnotationPresent(annotatedAnnotationElement, InnerAnnotation.class), + MoreElements.isAnnotationPresent(annotatedAnnotationElement, SuppressWarnings.class)); + + // Test String API + String documentedName = Documented.class.getCanonicalName(); + String innerAnnotationName = InnerAnnotation.class.getCanonicalName(); + String suppressWarningsName = SuppressWarnings.class.getCanonicalName(); + isAnnotationPresentAsserts( + MoreElements.isAnnotationPresent(annotatedAnnotationElement, documentedName), + MoreElements.isAnnotationPresent(annotatedAnnotationElement, innerAnnotationName), + MoreElements.isAnnotationPresent(annotatedAnnotationElement, suppressWarningsName)); + + // Test TypeElement API + TypeElement documentedElement = elements.getTypeElement(documentedName); + TypeElement innerAnnotationElement = elements.getTypeElement(innerAnnotationName); + TypeElement suppressWarningsElement = elements.getTypeElement(suppressWarningsName); + isAnnotationPresentAsserts( + MoreElements.isAnnotationPresent(annotatedAnnotationElement, documentedElement), + MoreElements.isAnnotationPresent(annotatedAnnotationElement, innerAnnotationElement), + MoreElements.isAnnotationPresent(annotatedAnnotationElement, suppressWarningsElement)); + } + + private void isAnnotationPresentAsserts( + boolean isDocumentedPresent, + boolean isInnerAnnotationPresent, + boolean isSuppressWarningsPresent) { + assertThat(isDocumentedPresent).isTrue(); + assertThat(isInnerAnnotationPresent).isTrue(); + assertThat(isSuppressWarningsPresent).isFalse(); } @Test public void getAnnotationMirror() { TypeElement element = - compilation.getElements().getTypeElement(AnnotatedAnnotation.class.getCanonicalName()); - - Optional<AnnotationMirror> documented = - MoreElements.getAnnotationMirror(element, Documented.class); - Optional<AnnotationMirror> innerAnnotation = - MoreElements.getAnnotationMirror(element, InnerAnnotation.class); - Optional<AnnotationMirror> suppressWarnings = - MoreElements.getAnnotationMirror(element, SuppressWarnings.class); - + elements.getTypeElement(AnnotatedAnnotation.class.getCanonicalName()); + + // Test Class API + getAnnotationMirrorAsserts( + MoreElements.getAnnotationMirror(element, Documented.class), + MoreElements.getAnnotationMirror(element, InnerAnnotation.class), + MoreElements.getAnnotationMirror(element, SuppressWarnings.class)); + + // Test String API + String documentedName = Documented.class.getCanonicalName(); + String innerAnnotationName = InnerAnnotation.class.getCanonicalName(); + String suppressWarningsName = SuppressWarnings.class.getCanonicalName(); + getAnnotationMirrorAsserts( + MoreElements.getAnnotationMirror(element, documentedName), + MoreElements.getAnnotationMirror(element, innerAnnotationName), + MoreElements.getAnnotationMirror(element, suppressWarningsName)); + + // Test TypeElement API + TypeElement documentedElement = elements.getTypeElement(documentedName); + TypeElement innerAnnotationElement = elements.getTypeElement(innerAnnotationName); + TypeElement suppressWarningsElement = elements.getTypeElement(suppressWarningsName); + getAnnotationMirrorAsserts( + MoreElements.getAnnotationMirror(element, documentedElement), + MoreElements.getAnnotationMirror(element, innerAnnotationElement), + MoreElements.getAnnotationMirror(element, suppressWarningsElement)); + } + + private void getAnnotationMirrorAsserts( + Optional<AnnotationMirror> documented, + Optional<AnnotationMirror> innerAnnotation, + Optional<AnnotationMirror> suppressWarnings) { expect.that(documented).isPresent(); expect.that(innerAnnotation).isPresent(); expect.that(suppressWarnings).isAbsent(); Element annotationElement = documented.get().getAnnotationType().asElement(); expect.that(MoreElements.isType(annotationElement)).isTrue(); - expect.that(MoreElements.asType(annotationElement).getQualifiedName().toString()) + expect + .that(MoreElements.asType(annotationElement).getQualifiedName().toString()) .isEqualTo(Documented.class.getCanonicalName()); annotationElement = innerAnnotation.get().getAnnotationType().asElement(); expect.that(MoreElements.isType(annotationElement)).isTrue(); - expect.that(MoreElements.asType(annotationElement).getQualifiedName().toString()) + expect + .that(MoreElements.asType(annotationElement).getQualifiedName().toString()) .isEqualTo(InnerAnnotation.class.getCanonicalName()); } @@ -231,6 +287,7 @@ public class MoreElementsTest { abstract String foo(); + @SuppressWarnings("unused") private void privateMethod() {} } @@ -259,7 +316,6 @@ public class MoreElementsTest { @Test public void getLocalAndInheritedMethods_Old() { - Elements elements = compilation.getElements(); Types types = compilation.getTypes(); TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT); TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG); @@ -270,19 +326,20 @@ public class MoreElementsTest { Set<ExecutableElement> objectMethods = visibleMethodsFromObject(); assertThat(childTypeMethods).containsAtLeastElementsIn(objectMethods); Set<ExecutableElement> nonObjectMethods = Sets.difference(childTypeMethods, objectMethods); - assertThat(nonObjectMethods).containsExactly( + assertThat(nonObjectMethods) + .containsExactly( getMethod(ParentInterface.class, "bar", longMirror), getMethod(ParentClass.class, "foo"), getMethod(Child.class, "bar"), getMethod(Child.class, "baz"), getMethod(Child.class, "buh", intMirror), getMethod(Child.class, "buh", intMirror, intMirror)) - .inOrder();; + .inOrder(); + ; } @Test public void getLocalAndInheritedMethods() { - Elements elements = compilation.getElements(); Types types = compilation.getTypes(); TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT); TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG); @@ -293,7 +350,8 @@ public class MoreElementsTest { Set<ExecutableElement> objectMethods = visibleMethodsFromObject(); assertThat(childTypeMethods).containsAtLeastElementsIn(objectMethods); Set<ExecutableElement> nonObjectMethods = Sets.difference(childTypeMethods, objectMethods); - assertThat(nonObjectMethods).containsExactly( + assertThat(nonObjectMethods) + .containsExactly( getMethod(ParentInterface.class, "bar", longMirror), getMethod(ParentClass.class, "foo"), getMethod(Child.class, "bar"), @@ -305,7 +363,6 @@ public class MoreElementsTest { @Test public void getAllMethods() { - Elements elements = compilation.getElements(); Types types = compilation.getTypes(); TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT); TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG); @@ -316,7 +373,8 @@ public class MoreElementsTest { Set<ExecutableElement> objectMethods = allMethodsFromObject(); assertThat(childTypeMethods).containsAtLeastElementsIn(objectMethods); Set<ExecutableElement> nonObjectMethods = Sets.difference(childTypeMethods, objectMethods); - assertThat(nonObjectMethods).containsExactly( + assertThat(nonObjectMethods) + .containsExactly( getMethod(ParentInterface.class, "staticMethod"), getMethod(ParentInterface.class, "bar", longMirror), getMethod(ParentClass.class, "staticMethod"), @@ -355,10 +413,9 @@ public class MoreElementsTest { // Example from https://github.com/williamlian/daggerbug @Test public void getLocalAndInheritedMethods_DaggerBug() { - Elements elementUtils = compilation.getElements(); - TypeElement main = elementUtils.getTypeElement(Main.ParentComponent.class.getCanonicalName()); - Set<ExecutableElement> methods = MoreElements.getLocalAndInheritedMethods( - main, compilation.getTypes(), elementUtils); + TypeElement main = elements.getTypeElement(Main.ParentComponent.class.getCanonicalName()); + Set<ExecutableElement> methods = + MoreElements.getLocalAndInheritedMethods(main, compilation.getTypes(), elements); assertThat(methods).hasSize(1); ExecutableElement method = methods.iterator().next(); assertThat(method.getSimpleName().toString()).isEqualTo("injectable"); @@ -404,7 +461,7 @@ public class MoreElementsTest { } private ExecutableElement getMethod(Class<?> c, String methodName, TypeMirror... parameterTypes) { - TypeElement type = compilation.getElements().getTypeElement(c.getCanonicalName()); + TypeElement type = elements.getTypeElement(c.getCanonicalName()); Types types = compilation.getTypes(); ExecutableElement found = null; for (ExecutableElement method : ElementFilter.methodsIn(type.getEnclosedElements())) { @@ -423,7 +480,7 @@ public class MoreElementsTest { } } assertWithMessage(methodName + Arrays.toString(parameterTypes)).that(found).isNotNull(); - return found; + return requireNonNull(found); } private abstract static class AbstractAbstractList extends AbstractList<String> {} @@ -458,8 +515,6 @@ public class MoreElementsTest { // are implemented in AbstractList. @Test public void getLocalAndInheritedMethods_AbstractList() { - Elements elements = compilation.getElements(); - TypeElement abstractType = elements.getTypeElement(AbstractAbstractList.class.getCanonicalName()); Set<ExecutableElement> abstractTypeMethods = 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 05a0a119..7cd7865d 100644 --- a/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java +++ b/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java @@ -43,13 +43,15 @@ public class MoreTypesIsTypeOfTest { private Elements elements; - @Before public void setUp() { + @Before + public void setUp() { this.elements = compilationRule.getElements(); } private interface TestType {} - @Test public void isTypeOf_DeclaredType() { + @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())) @@ -63,7 +65,8 @@ public class MoreTypesIsTypeOfTest { String[] array(); } - @Test public void isTypeOf_ArrayType() { + @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") @@ -75,7 +78,8 @@ public class MoreTypesIsTypeOfTest { boolean method(); } - @Test public void isTypeOf_PrimitiveBoolean() { + @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(); @@ -85,7 +89,8 @@ public class MoreTypesIsTypeOfTest { byte method(); } - @Test public void isTypeOf_PrimitiveByte() { + @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(); @@ -95,7 +100,8 @@ public class MoreTypesIsTypeOfTest { char method(); } - @Test public void isTypeOf_PrimitiveChar() { + @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(); @@ -105,7 +111,8 @@ public class MoreTypesIsTypeOfTest { double method(); } - @Test public void isTypeOf_PrimitiveDouble() { + @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(); @@ -115,7 +122,8 @@ public class MoreTypesIsTypeOfTest { float method(); } - @Test public void isTypeOf_PrimitiveFloat() { + @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(); @@ -125,7 +133,8 @@ public class MoreTypesIsTypeOfTest { int method(); } - @Test public void isTypeOf_PrimitiveInt() { + @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(); @@ -135,7 +144,8 @@ public class MoreTypesIsTypeOfTest { long method(); } - @Test public void isTypeOf_PrimitiveLong() { + @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(); @@ -145,7 +155,8 @@ public class MoreTypesIsTypeOfTest { short method(); } - @Test public void isTypeOf_PrimitiveShort() { + @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(); @@ -155,7 +166,8 @@ public class MoreTypesIsTypeOfTest { void method(); } - @Test public void isTypeOf_void() { + @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(); @@ -165,21 +177,25 @@ public class MoreTypesIsTypeOfTest { Void method(); } - @Test public void isTypeOf_Void() { + @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(); } - @Test public void isTypeOf_fail() { - assertFalse(MoreTypes.isType( - getOnlyElement(typeElementFor(DeclaredVoid.class).getEnclosedElements()).asType())); + @Test + public void isTypeOf_fail() { + assertFalse( + MoreTypes.isType( + getOnlyElement(typeElementFor(DeclaredVoid.class).getEnclosedElements()).asType())); TypeMirror method = getOnlyElement(typeElementFor(DeclaredVoid.class).getEnclosedElements()).asType(); try { MoreTypes.isTypeOf(String.class, method); fail(); - } catch (IllegalArgumentException expected) {} + } catch (IllegalArgumentException expected) { + } } // Utility methods for this test. diff --git a/common/src/test/java/com/google/auto/common/MoreTypesTest.java b/common/src/test/java/com/google/auto/common/MoreTypesTest.java index 3cd360db..b8e84e08 100644 --- a/common/src/test/java/com/google/auto/common/MoreTypesTest.java +++ b/common/src/test/java/com/google/auto/common/MoreTypesTest.java @@ -16,11 +16,12 @@ package com.google.auto.common; import static com.google.common.truth.Truth.assertThat; +import static java.util.Objects.requireNonNull; import static javax.lang.model.type.TypeKind.NONE; import static javax.lang.model.type.TypeKind.VOID; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; -import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; @@ -49,6 +50,7 @@ import javax.lang.model.type.WildcardType; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -90,48 +92,51 @@ public class MoreTypesTest { DeclaredType containerOfString = types.getDeclaredType(container, stringType); TypeMirror containedInObject = types.asMemberOf(containerOfObject, contained); TypeMirror containedInString = types.asMemberOf(containerOfString, contained); - EquivalenceTester<TypeMirror> tester = EquivalenceTester.<TypeMirror>of(MoreTypes.equivalence()) - .addEquivalenceGroup(types.getNullType()) - .addEquivalenceGroup(types.getNoType(NONE)) - .addEquivalenceGroup(types.getNoType(VOID)) - .addEquivalenceGroup(objectType) - .addEquivalenceGroup(stringType) - .addEquivalenceGroup(containedInObject) - .addEquivalenceGroup(containedInString) - .addEquivalenceGroup(funkyBounds.asType()) - .addEquivalenceGroup(funkyBounds2.asType()) - .addEquivalenceGroup(funkierBounds.asType()) - .addEquivalenceGroup(funkyBoundsVar, funkyBounds2Var) - .addEquivalenceGroup(funkierBoundsVar) - // Enum<E extends Enum<E>> - .addEquivalenceGroup(enumElement.asType()) - // Map<K, V> - .addEquivalenceGroup(mapType) - .addEquivalenceGroup(mapOfObjectToObjectType) - // Map<?, ?> - .addEquivalenceGroup(types.getDeclaredType(mapElement, wildcard, wildcard)) - // Map - .addEquivalenceGroup(types.erasure(mapType), types.erasure(mapOfObjectToObjectType)) - .addEquivalenceGroup(types.getDeclaredType(mapElement, objectType, stringType)) - .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType, objectType)) - .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType, stringType)) - .addEquivalenceGroup(setOfSetOfObject) - .addEquivalenceGroup(setOfSetOfString) - .addEquivalenceGroup(setOfSetOfSetOfObject) - .addEquivalenceGroup(setOfSetOfSetOfString) - .addEquivalenceGroup(wildcard) - // ? extends Object - .addEquivalenceGroup(types.getWildcardType(objectType, null)) - // ? extends String - .addEquivalenceGroup(types.getWildcardType(stringType, null)) - // ? super String - .addEquivalenceGroup(types.getWildcardType(null, stringType)) - // Map<String, Map<String, Set<Object>>> - .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType, - types.getDeclaredType(mapElement, stringType, - types.getDeclaredType(setElement, objectType)))) - .addEquivalenceGroup(FAKE_ERROR_TYPE) - ; + EquivalenceTester<TypeMirror> tester = + EquivalenceTester.<TypeMirror>of(MoreTypes.equivalence()) + .addEquivalenceGroup(types.getNullType()) + .addEquivalenceGroup(types.getNoType(NONE)) + .addEquivalenceGroup(types.getNoType(VOID)) + .addEquivalenceGroup(objectType) + .addEquivalenceGroup(stringType) + .addEquivalenceGroup(containedInObject) + .addEquivalenceGroup(containedInString) + .addEquivalenceGroup(funkyBounds.asType()) + .addEquivalenceGroup(funkyBounds2.asType()) + .addEquivalenceGroup(funkierBounds.asType()) + .addEquivalenceGroup(funkyBoundsVar, funkyBounds2Var) + .addEquivalenceGroup(funkierBoundsVar) + // Enum<E extends Enum<E>> + .addEquivalenceGroup(enumElement.asType()) + // Map<K, V> + .addEquivalenceGroup(mapType) + .addEquivalenceGroup(mapOfObjectToObjectType) + // Map<?, ?> + .addEquivalenceGroup(types.getDeclaredType(mapElement, wildcard, wildcard)) + // Map + .addEquivalenceGroup(types.erasure(mapType), types.erasure(mapOfObjectToObjectType)) + .addEquivalenceGroup(types.getDeclaredType(mapElement, objectType, stringType)) + .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType, objectType)) + .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType, stringType)) + .addEquivalenceGroup(setOfSetOfObject) + .addEquivalenceGroup(setOfSetOfString) + .addEquivalenceGroup(setOfSetOfSetOfObject) + .addEquivalenceGroup(setOfSetOfSetOfString) + .addEquivalenceGroup(wildcard) + // ? extends Object + .addEquivalenceGroup(types.getWildcardType(objectType, null)) + // ? extends String + .addEquivalenceGroup(types.getWildcardType(stringType, null)) + // ? super String + .addEquivalenceGroup(types.getWildcardType(null, stringType)) + // Map<String, Map<String, Set<Object>>> + .addEquivalenceGroup( + types.getDeclaredType( + mapElement, + stringType, + types.getDeclaredType( + mapElement, stringType, types.getDeclaredType(setElement, objectType)))) + .addEquivalenceGroup(FAKE_ERROR_TYPE); for (TypeKind kind : TypeKind.values()) { if (kind.isPrimitive()) { @@ -144,20 +149,18 @@ public class MoreTypesTest { } } - ImmutableSet<Class<?>> testClasses = ImmutableSet.of( - ExecutableElementsGroupA.class, - ExecutableElementsGroupB.class, - ExecutableElementsGroupC.class, - ExecutableElementsGroupD.class, - ExecutableElementsGroupE.class); + ImmutableSet<Class<?>> testClasses = + ImmutableSet.of( + ExecutableElementsGroupA.class, + ExecutableElementsGroupB.class, + ExecutableElementsGroupC.class, + ExecutableElementsGroupD.class, + ExecutableElementsGroupE.class); for (Class<?> testClass : testClasses) { - ImmutableList<TypeMirror> equivalenceGroup = FluentIterable.from( - elements.getTypeElement(testClass.getCanonicalName()).getEnclosedElements()) - .transform(new Function<Element, TypeMirror>() { - @Override public TypeMirror apply(Element input) { - return input.asType(); - } - }) + ImmutableList<TypeMirror> equivalenceGroup = + FluentIterable.from( + elements.getTypeElement(testClass.getCanonicalName()).getEnclosedElements()) + .transform(Element::asType) .toList(); tester.addEquivalenceGroup(equivalenceGroup); } @@ -168,35 +171,45 @@ public class MoreTypesTest { @SuppressWarnings("unused") private static final class ExecutableElementsGroupA { ExecutableElementsGroupA() {} + void a() {} + public static void b() {} } @SuppressWarnings("unused") private static final class ExecutableElementsGroupB { ExecutableElementsGroupB(String s) {} + void a(String s) {} + public static void b(String s) {} } @SuppressWarnings("unused") private static final class ExecutableElementsGroupC { ExecutableElementsGroupC() throws Exception {} + void a() throws Exception {} + public static void b() throws Exception {} } @SuppressWarnings("unused") private static final class ExecutableElementsGroupD { ExecutableElementsGroupD() throws RuntimeException {} + void a() throws RuntimeException {} + public static void b() throws RuntimeException {} } @SuppressWarnings("unused") private static final class ExecutableElementsGroupE { <T> ExecutableElementsGroupE() {} + <T> void a() {} + public static <T> void b() {} } @@ -214,53 +227,44 @@ public class MoreTypesTest { @SuppressWarnings("unused") private static final class FunkierBounds<T extends Number & Comparable<T> & Cloneable> {} - @Test public void testReferencedTypes() { + @Test + public void testReferencedTypes() { Elements elements = compilationRule.getElements(); - TypeElement testDataElement = elements - .getTypeElement(ReferencedTypesTestData.class.getCanonicalName()); + TypeElement testDataElement = + elements.getTypeElement(ReferencedTypesTestData.class.getCanonicalName()); ImmutableMap<String, VariableElement> fieldIndex = FluentIterable.from(ElementFilter.fieldsIn(testDataElement.getEnclosedElements())) - .uniqueIndex(new Function<VariableElement, String>() { - @Override public String apply(VariableElement input) { - return input.getSimpleName().toString(); - } - }); - - TypeElement objectElement = - elements.getTypeElement(Object.class.getCanonicalName()); - TypeElement stringElement = - elements.getTypeElement(String.class.getCanonicalName()); - TypeElement integerElement = - elements.getTypeElement(Integer.class.getCanonicalName()); - TypeElement setElement = - elements.getTypeElement(Set.class.getCanonicalName()); - TypeElement mapElement = - elements.getTypeElement(Map.class.getCanonicalName()); + .uniqueIndex(input -> input.getSimpleName().toString()); + + TypeElement objectElement = elements.getTypeElement(Object.class.getCanonicalName()); + TypeElement stringElement = elements.getTypeElement(String.class.getCanonicalName()); + TypeElement integerElement = elements.getTypeElement(Integer.class.getCanonicalName()); + TypeElement setElement = elements.getTypeElement(Set.class.getCanonicalName()); + TypeElement mapElement = elements.getTypeElement(Map.class.getCanonicalName()); TypeElement charSequenceElement = elements.getTypeElement(CharSequence.class.getCanonicalName()); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f1").asType())) - .containsExactly(objectElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f2").asType())) - .containsExactly(setElement, stringElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f3").asType())) + assertThat(referencedTypes(fieldIndex, "f1")).containsExactly(objectElement); + assertThat(referencedTypes(fieldIndex, "f2")).containsExactly(setElement, stringElement); + assertThat(referencedTypes(fieldIndex, "f3")) .containsExactly(mapElement, stringElement, objectElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f4").asType())) - .containsExactly(integerElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f5").asType())) - .containsExactly(setElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f6").asType())) - .containsExactly(setElement, charSequenceElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f7").asType())) + assertThat(referencedTypes(fieldIndex, "f4")).containsExactly(integerElement); + assertThat(referencedTypes(fieldIndex, "f5")).containsExactly(setElement); + assertThat(referencedTypes(fieldIndex, "f6")).containsExactly(setElement, charSequenceElement); + assertThat(referencedTypes(fieldIndex, "f7")) .containsExactly(mapElement, stringElement, setElement, charSequenceElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f8").asType())) - .containsExactly(stringElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f9").asType())) - .containsExactly(stringElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f10").asType())).isEmpty(); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f11").asType())).isEmpty(); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f12").asType())) - .containsExactly(setElement, stringElement); + assertThat(referencedTypes(fieldIndex, "f8")).containsExactly(stringElement); + assertThat(referencedTypes(fieldIndex, "f9")).containsExactly(stringElement); + assertThat(referencedTypes(fieldIndex, "f10")).isEmpty(); + assertThat(referencedTypes(fieldIndex, "f11")).isEmpty(); + assertThat(referencedTypes(fieldIndex, "f12")).containsExactly(setElement, stringElement); + } + + private static ImmutableSet<TypeElement> referencedTypes( + ImmutableMap<String, VariableElement> fieldIndex, String fieldName) { + VariableElement field = fieldIndex.get(fieldName); + requireNonNull(field, fieldName); + return MoreTypes.referencedTypes(field.asType()); } @SuppressWarnings("unused") // types used in compiler tests @@ -280,20 +284,23 @@ public class MoreTypesTest { } private static class Parent<T> {} + private static class ChildA extends Parent<Number> {} + private static class ChildB extends Parent<String> {} + private static class GenericChild<T> extends Parent<T> {} + private interface InterfaceType {} @Test public void asElement_throws() { - TypeMirror javaDotLang = - compilationRule.getElements().getPackageElement("java.lang").asType(); + TypeMirror javaDotLang = compilationRule.getElements().getPackageElement("java.lang").asType(); try { MoreTypes.asElement(javaDotLang); fail(); - } catch (IllegalArgumentException expected) {} - + } catch (IllegalArgumentException expected) { + } } @Test @@ -301,8 +308,9 @@ public class MoreTypesTest { Elements elements = compilationRule.getElements(); TypeElement stringElement = elements.getTypeElement("java.lang.String"); assertThat(MoreTypes.asElement(stringElement.asType())).isEqualTo(stringElement); - TypeParameterElement setParameterElement = Iterables.getOnlyElement( - compilationRule.getElements().getTypeElement("java.util.Set").getTypeParameters()); + TypeParameterElement setParameterElement = + Iterables.getOnlyElement( + compilationRule.getElements().getTypeElement("java.util.Set").getTypeParameters()); assertThat(MoreTypes.asElement(setParameterElement.asType())).isEqualTo(setParameterElement); // we don't test error types because those are very hard to get predictably } @@ -320,8 +328,7 @@ public class MoreTypesTest { TypeElement genericChild = elements.getTypeElement(GenericChild.class.getCanonicalName()); TypeMirror genericChildOfNumber = types.getDeclaredType(genericChild, numberType); TypeMirror genericChildOfInteger = types.getDeclaredType(genericChild, integerType); - TypeMirror objectType = - elements.getTypeElement(Object.class.getCanonicalName()).asType(); + TypeMirror objectType = elements.getTypeElement(Object.class.getCanonicalName()).asType(); TypeMirror interfaceType = elements.getTypeElement(InterfaceType.class.getCanonicalName()).asType(); @@ -343,18 +350,20 @@ public class MoreTypesTest { Optional<DeclaredType> parentOfGenericChildOfInteger = MoreTypes.nonObjectSuperclass(types, elements, (DeclaredType) genericChildOfInteger); - EquivalenceTester<TypeMirror> tester = EquivalenceTester.<TypeMirror>of(MoreTypes.equivalence()) - .addEquivalenceGroup(parentOfChildA.get(), - types.getDeclaredType(parent, numberType), - parentOfGenericChildOfNumber.get()) - .addEquivalenceGroup(parentOfChildB.get(), types.getDeclaredType(parent, stringType)) - .addEquivalenceGroup(parentOfGenericChild.get(), parent.asType()) - .addEquivalenceGroup(parentOfGenericChildOfInteger.get(), - types.getDeclaredType(parent, integerType)); + EquivalenceTester<TypeMirror> tester = + EquivalenceTester.<TypeMirror>of(MoreTypes.equivalence()) + .addEquivalenceGroup( + parentOfChildA.get(), + types.getDeclaredType(parent, numberType), + parentOfGenericChildOfNumber.get()) + .addEquivalenceGroup(parentOfChildB.get(), types.getDeclaredType(parent, stringType)) + .addEquivalenceGroup(parentOfGenericChild.get(), parent.asType()) + .addEquivalenceGroup( + parentOfGenericChildOfInteger.get(), types.getDeclaredType(parent, integerType)); tester.test(); } - + @Test public void testAsMemberOf_variableElement() { Types types = compilationRule.getTypes(); @@ -364,11 +373,13 @@ public class MoreTypesTest { TypeMirror integerType = elements.getTypeElement(Integer.class.getCanonicalName()).asType(); TypeElement paramsElement = elements.getTypeElement(Params.class.getCanonicalName()); - VariableElement tParam = Iterables.getOnlyElement(Iterables.getOnlyElement( - ElementFilter.methodsIn(paramsElement.getEnclosedElements())).getParameters()); + VariableElement tParam = + Iterables.getOnlyElement( + Iterables.getOnlyElement(ElementFilter.methodsIn(paramsElement.getEnclosedElements())) + .getParameters()); VariableElement tField = - Iterables.getOnlyElement(ElementFilter.fieldsIn(paramsElement.getEnclosedElements())); - + Iterables.getOnlyElement(ElementFilter.fieldsIn(paramsElement.getEnclosedElements())); + DeclaredType numberParams = (DeclaredType) elements.getTypeElement(NumberParams.class.getCanonicalName()).asType(); DeclaredType stringParams = @@ -376,7 +387,7 @@ public class MoreTypesTest { TypeElement genericParams = elements.getTypeElement(GenericParams.class.getCanonicalName()); DeclaredType genericParamsOfNumber = types.getDeclaredType(genericParams, numberType); DeclaredType genericParamsOfInteger = types.getDeclaredType(genericParams, integerType); - + TypeMirror fieldOfNumberParams = MoreTypes.asMemberOf(types, numberParams, tField); TypeMirror paramOfNumberParams = MoreTypes.asMemberOf(types, numberParams, tParam); TypeMirror fieldOfStringParams = MoreTypes.asMemberOf(types, stringParams, tField); @@ -388,62 +399,76 @@ public class MoreTypesTest { TypeMirror paramOfGenericOfInteger = MoreTypes.asMemberOf(types, genericParamsOfInteger, tParam); - EquivalenceTester<TypeMirror> tester = EquivalenceTester.<TypeMirror>of(MoreTypes.equivalence()) - .addEquivalenceGroup(fieldOfNumberParams, paramOfNumberParams, fieldOfGenericOfNumber, - paramOfGenericOfNumber, numberType) - .addEquivalenceGroup(fieldOfStringParams, paramOfStringParams, stringType) - .addEquivalenceGroup(fieldOfGenericOfInteger, paramOfGenericOfInteger, integerType); + EquivalenceTester<TypeMirror> tester = + EquivalenceTester.<TypeMirror>of(MoreTypes.equivalence()) + .addEquivalenceGroup( + fieldOfNumberParams, + paramOfNumberParams, + fieldOfGenericOfNumber, + paramOfGenericOfNumber, + numberType) + .addEquivalenceGroup(fieldOfStringParams, paramOfStringParams, stringType) + .addEquivalenceGroup(fieldOfGenericOfInteger, paramOfGenericOfInteger, integerType); tester.test(); } - - private static class Params<T> { - @SuppressWarnings("unused") T t; - @SuppressWarnings("unused") void add(T t) {} - } - private static class NumberParams extends Params<Number> {} - private static class StringParams extends Params<String> {} - private static class GenericParams<T> extends Params<T> {} - - private static final ErrorType FAKE_ERROR_TYPE = new ErrorType() { - @Override - public TypeKind getKind() { - return TypeKind.ERROR; - } - @Override - public <R, P> R accept(TypeVisitor<R, P> v, P p) { - return v.visitError(this, p); - } - - @Override - public List<? extends TypeMirror> getTypeArguments() { - return ImmutableList.of(); - } - - @Override - public TypeMirror getEnclosingType() { - return null; - } + private static class Params<T> { + @SuppressWarnings("unused") + T t; - @Override - public Element asElement() { - return null; - } + @SuppressWarnings("unused") + void add(T t) {} + } - // JDK8 Compatibility: + private static class NumberParams extends Params<Number> {} - public <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationType) { - return null; - } + private static class StringParams extends Params<String> {} - public <A extends Annotation> A getAnnotation(Class<A> annotationType) { - return null; - } + private static class GenericParams<T> extends Params<T> {} - public List<? extends AnnotationMirror> getAnnotationMirrors() { - return null; - } - }; + private static final ErrorType FAKE_ERROR_TYPE = + new ErrorType() { + @Override + public TypeKind getKind() { + return TypeKind.ERROR; + } + + @Override + public <R, P> R accept(TypeVisitor<R, P> v, P p) { + return v.visitError(this, p); + } + + @Override + public ImmutableList<? extends TypeMirror> getTypeArguments() { + return ImmutableList.of(); + } + + @Override + public @Nullable TypeMirror getEnclosingType() { + return null; + } + + @Override + public @Nullable Element asElement() { + return null; + } + + @Override + public <A extends Annotation> A @Nullable [] getAnnotationsByType(Class<A> annotationType) { + return null; + } + + @Override + public <A extends Annotation> @Nullable A getAnnotation(Class<A> annotationType) { + return null; + } + + @Override + @SuppressWarnings("MutableMethodReturnType") + public List<? extends AnnotationMirror> getAnnotationMirrors() { + return ImmutableList.of(); + } + }; @Test public void testIsConversionFromObjectUnchecked_yes() { @@ -471,6 +496,21 @@ public class MoreTypesTest { } } + @Test + public void testIsTypeOf() { + Types types = compilationRule.getTypes(); + PrimitiveType intType = types.getPrimitiveType(TypeKind.INT); + TypeMirror integerType = types.boxedClass(intType).asType(); + WildcardType wildcardType = types.getWildcardType(null, null); + expect.that(MoreTypes.isTypeOf(int.class, intType)).isTrue(); + expect.that(MoreTypes.isTypeOf(Integer.class, integerType)).isTrue(); + expect.that(MoreTypes.isTypeOf(Integer.class, intType)).isFalse(); + expect.that(MoreTypes.isTypeOf(int.class, integerType)).isFalse(); + expect.that(MoreTypes.isTypeOf(Integer.class, FAKE_ERROR_TYPE)).isFalse(); + assertThrows( + IllegalArgumentException.class, () -> MoreTypes.isTypeOf(Integer.class, wildcardType)); + } + // The type of every field here is such that casting to it provokes an "unchecked" warning. @SuppressWarnings("unused") private static class Unchecked<T> { 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 afb79760..c5ccc5f6 100644 --- a/common/src/test/java/com/google/auto/common/OverridesTest.java +++ b/common/src/test/java/com/google/auto/common/OverridesTest.java @@ -17,6 +17,7 @@ package com.google.auto.common; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; import static javax.lang.model.util.ElementFilter.methodsIn; import com.google.common.base.Converter; @@ -57,6 +58,7 @@ import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.StandardLocation; import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -103,6 +105,7 @@ public class OverridesTest { abstract void initUtils(OverridesTest test); } + private final CompilerType compilerType; private Types typeUtils; @@ -126,12 +129,15 @@ public class OverridesTest { static class TypesForInheritance { interface One { void m(); + void m(String x); + void n(); } interface Two { void m(); + void m(int x); } @@ -142,28 +148,50 @@ public class OverridesTest { static class ChildOfParent extends Parent {} static class ChildOfOne implements One { - @Override public void m() {} - @Override public void m(String x) {} - @Override public void n() {} + @Override + public void m() {} + + @Override + public void m(String x) {} + + @Override + public void n() {} } static class ChildOfOneAndTwo implements One, Two { - @Override public void m() {} - @Override public void m(String x) {} - @Override public void m(int x) {} - @Override public void n() {} + @Override + public void m() {} + + @Override + public void m(String x) {} + + @Override + public void m(int x) {} + + @Override + public void n() {} } static class ChildOfParentAndOne extends Parent implements One { - @Override public void m() {} - @Override public void m(String x) {} - @Override public void n() {} + @Override + public void m() {} + + @Override + public void m(String x) {} + + @Override + public void n() {} } static class ChildOfParentAndOneAndTwo extends Parent implements One, Two { - @Override public void m(String x) {} - @Override public void m(int x) {} - @Override public void n() {} + @Override + public void m(String x) {} + + @Override + public void m(int x) {} + + @Override + public void n() {} } abstract static class AbstractChildOfOne implements One {} @@ -194,14 +222,20 @@ public class OverridesTest { abstract static class BindingDeclaration implements HasKey { abstract Optional<Element> bindingElement(); + abstract Optional<TypeElement> contributingModule(); } - abstract static class MultibindingDeclaration - extends BindingDeclaration implements HasBindingType, HasContributionType { - @Override public abstract Key key(); - @Override public abstract ContributionType contributionType(); - @Override public abstract BindingType bindingType(); + abstract static class MultibindingDeclaration extends BindingDeclaration + implements HasBindingType, HasContributionType { + @Override + public abstract Key key(); + + @Override + public abstract ContributionType contributionType(); + + @Override + public abstract BindingType bindingType(); } } @@ -221,16 +255,26 @@ public class OverridesTest { } static class TypesForGenerics { - interface XCollection<E> { + interface GCollection<E> { boolean add(E x); } - interface XList<E> extends XCollection<E> { - @Override public boolean add(E x); + interface GList<E> extends GCollection<E> { + @Override + boolean add(E x); } - static class StringList implements XList<String> { - @Override public boolean add(String x) { + static class StringList implements GList<String> { + @Override + public boolean add(String x) { + return false; + } + } + + @SuppressWarnings("rawtypes") + static class RawList implements GList { + @Override + public boolean add(Object x) { return false; } } @@ -243,7 +287,8 @@ public class OverridesTest { } static class RawChildOfRaw extends RawParent { - @Override void frob(List x) {} + @Override + void frob(List x) {} } static class NonRawParent { @@ -251,7 +296,8 @@ public class OverridesTest { } static class RawChildOfNonRaw extends NonRawParent { - @Override void frob(List x) {} + @Override + void frob(List x) {} } } @@ -291,8 +337,9 @@ public class OverridesTest { // since the two Es are not the same. @Test public void overridesDiamond() { - checkOverridesInSet(ImmutableSet.<Class<?>>of( - Collection.class, List.class, AbstractCollection.class, AbstractList.class)); + checkOverridesInSet( + ImmutableSet.<Class<?>>of( + Collection.class, List.class, AbstractCollection.class, AbstractList.class)); } private void checkOverridesInContainedClasses(Class<?> container) { @@ -324,10 +371,13 @@ public class OverridesTest { expect .withMessage( "%s.%s overrides %s.%s in %s: javac says %s, we say %s", - overrider.getEnclosingElement(), overrider, - overridden.getEnclosingElement(), overridden, + overrider.getEnclosingElement(), + overrider, + overridden.getEnclosingElement(), + overridden, in, - javacSays, weSay) + javacSays, + weSay) .fail(); } } @@ -355,7 +405,7 @@ public class OverridesTest { } } assertThat(found).isNotNull(); - return found; + return requireNonNull(found); } // These skeletal parallels to the real collection classes ensure that the test is independent @@ -375,8 +425,8 @@ public class OverridesTest { } } - private abstract static class XAbstractList<E> - extends XAbstractCollection<E> implements XList<E> { + private abstract static class XAbstractList<E> extends XAbstractCollection<E> + implements XList<E> { @Override public boolean add(E e) { return true; @@ -440,7 +490,7 @@ public class OverridesTest { extends Converter<String, Range<T>> { @Override protected String doBackward(Range<T> b) { - return null; + return ""; } } @@ -470,9 +520,8 @@ public class OverridesTest { explicitOverrides.methodFromSuperclasses(xAbstractStringList, add); assertThat(addInAbstractStringList).isNull(); - ExecutableElement addInStringList = - explicitOverrides.methodFromSuperclasses(xStringList, add); - assertThat(addInStringList.getEnclosingElement()).isEqualTo(xAbstractList); + ExecutableElement addInStringList = explicitOverrides.methodFromSuperclasses(xStringList, add); + assertThat(requireNonNull(addInStringList).getEnclosingElement()).isEqualTo(xAbstractList); } @Test @@ -487,20 +536,21 @@ public class OverridesTest { ExecutableElement addInAbstractStringList = explicitOverrides.methodFromSuperinterfaces(xAbstractStringList, add); - assertThat(addInAbstractStringList.getEnclosingElement()).isEqualTo(xCollection); + assertThat(requireNonNull(addInAbstractStringList).getEnclosingElement()) + .isEqualTo(xCollection); ExecutableElement addInNumberList = explicitOverrides.methodFromSuperinterfaces(xNumberList, add); - assertThat(addInNumberList.getEnclosingElement()).isEqualTo(xAbstractList); + assertThat(requireNonNull(addInNumberList).getEnclosingElement()).isEqualTo(xAbstractList); - ExecutableElement addInList = - explicitOverrides.methodFromSuperinterfaces(xList, add); - assertThat(addInList.getEnclosingElement()).isEqualTo(xCollection); + ExecutableElement addInList = explicitOverrides.methodFromSuperinterfaces(xList, add); + assertThat(requireNonNull(addInList).getEnclosingElement()).isEqualTo(xCollection); } - private void assertTypeListsEqual(List<TypeMirror> actual, List<TypeMirror> expected) { - assertThat(actual.size()).isEqualTo(expected.size()); - for (int i = 0; i < actual.size(); i++) { + 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(); } } @@ -552,10 +602,11 @@ public class OverridesTest { // it hard for ecj to find the boot class path. Elsewhere it is unnecessary but harmless. File rtJar = new File(StandardSystemProperty.JAVA_HOME.value() + "/lib/rt.jar"); if (rtJar.exists()) { - List<File> bootClassPath = ImmutableList.<File>builder() - .add(rtJar) - .addAll(fileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH)) - .build(); + List<File> bootClassPath = + ImmutableList.<File>builder() + .add(rtJar) + .addAll(fileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH)) + .build(); fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, bootClassPath); } Iterable<? extends JavaFileObject> sources = fileManager.getJavaFileObjects(dummySourceFile); @@ -583,8 +634,7 @@ public class OverridesTest { } @Override - public boolean process( - Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver()) { ecjCompilation.elements = processingEnv.getElementUtils(); ecjCompilation.types = processingEnv.getTypeUtils(); @@ -643,24 +693,24 @@ public class OverridesTest { private static final TypeVisitor<String, Void> ERASED_STRING_TYPE_VISITOR = new SimpleTypeVisitor6<String, Void>() { - @Override - protected String defaultAction(TypeMirror e, Void p) { - return e.toString(); - } + @Override + protected String defaultAction(TypeMirror e, Void p) { + return e.toString(); + } - @Override - public String visitArray(ArrayType t, Void p) { - return visit(t.getComponentType()) + "[]"; - } + @Override + public String visitArray(ArrayType t, Void p) { + return visit(t.getComponentType()) + "[]"; + } - @Override - public String visitDeclared(DeclaredType t, Void p) { - return MoreElements.asType(t.asElement()).getQualifiedName().toString(); - } + @Override + public String visitDeclared(DeclaredType t, Void p) { + return MoreElements.asType(t.asElement()).getQualifiedName().toString(); + } - @Override - public String visitTypeVariable(TypeVariable t, Void p) { - return visit(t.getUpperBound()); - } - }; + @Override + public String visitTypeVariable(TypeVariable t, Void p) { + return visit(t.getUpperBound()); + } + }; } diff --git a/common/src/test/java/com/google/auto/common/SimpleAnnotationMirrorTest.java b/common/src/test/java/com/google/auto/common/SimpleAnnotationMirrorTest.java index d73e1b6c..0bad83db 100644 --- a/common/src/test/java/com/google/auto/common/SimpleAnnotationMirrorTest.java +++ b/common/src/test/java/com/google/auto/common/SimpleAnnotationMirrorTest.java @@ -46,6 +46,7 @@ public class SimpleAnnotationMirrorTest { @interface MultipleValues { int value1(); + int value2(); } diff --git a/common/src/test/java/com/google/auto/common/SimpleTypeAnnotationValueTest.java b/common/src/test/java/com/google/auto/common/SimpleTypeAnnotationValueTest.java index 4fc61b51..ea85365b 100644 --- a/common/src/test/java/com/google/auto/common/SimpleTypeAnnotationValueTest.java +++ b/common/src/test/java/com/google/auto/common/SimpleTypeAnnotationValueTest.java @@ -28,6 +28,7 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleAnnotationValueVisitor8; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -70,18 +71,21 @@ public class SimpleTypeAnnotationValueTest { @Test public void visitorMethod() { - SimpleTypeAnnotationValue.of(objectType).accept(new SimpleAnnotationValueVisitor8<Void, Void>(){ - @Override - public Void visitType(TypeMirror typeMirror, Void aVoid) { - // do nothing, expected case - return null; - } + SimpleTypeAnnotationValue.of(objectType) + .accept( + new SimpleAnnotationValueVisitor8<@Nullable Void, @Nullable Void>() { + @Override + public @Nullable Void visitType(TypeMirror typeMirror, @Nullable Void aVoid) { + // do nothing, expected case + return null; + } - @Override - protected Void defaultAction(Object o, Void aVoid) { - throw new AssertionError(); - } - }, null); + @Override + protected @Nullable Void defaultAction(Object o, @Nullable Void aVoid) { + throw new AssertionError(); + } + }, + null); } @Test 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 15e54fff..c9bcf778 100644 --- a/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java +++ b/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java @@ -35,231 +35,263 @@ import org.junit.runners.JUnit4; public class SuperficialValidationTest { @Test public void missingReturnType() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "abstract class TestClass {", - " abstract MissingType blah();", - "}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "abstract class TestClass {", + " abstract MissingType blah();", + "}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); + } + }) .failsToCompile(); } @Test public void missingGenericReturnType() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "abstract class TestClass {", - " abstract MissingType<?> blah();", - "}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "abstract class TestClass {", + " abstract MissingType<?> blah();", + "}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); + } + }) .failsToCompile(); } @Test public void missingReturnTypeTypeParameter() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "import java.util.Map;", - "import java.util.Set;", - "", - "abstract class TestClass {", - " abstract Map<Set<?>, MissingType<?>> blah();", - "}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "import java.util.Map;", + "import java.util.Set;", + "", + "abstract class TestClass {", + " abstract Map<Set<?>, MissingType<?>> blah();", + "}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); + } + }) .failsToCompile(); } @Test public void missingTypeParameter() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "class TestClass<T extends MissingType> {}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", // + "package test;", + "", + "class TestClass<T extends MissingType> {}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); + } + }) .failsToCompile(); } @Test public void missingParameterType() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "abstract class TestClass {", - " abstract void foo(MissingType x);", - "}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "abstract class TestClass {", + " abstract void foo(MissingType x);", + "}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); + } + }) .failsToCompile(); } @Test public void missingAnnotation() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "@MissingAnnotation", - "class TestClass {}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", // + "package test;", + "", + "@MissingAnnotation", + "class TestClass {}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); + } + }) .failsToCompile(); } @Test public void handlesRecursiveTypeParams() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "class TestClass<T extends Comparable<T>> {}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", // + "package test;", + "", + "class TestClass<T extends Comparable<T>> {}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue(); + } + }) .compilesWithoutError(); } @Test public void handlesRecursiveType() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "abstract class TestClass {", - " abstract TestClass foo(TestClass x);", - "}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "abstract class TestClass {", + " abstract TestClass foo(TestClass x);", + "}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue(); + } + }) .compilesWithoutError(); } @Test public void missingWildcardBound() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "import java.util.Set;", - "", - "class TestClass {", - " Set<? extends MissingType> extendsTest() {", - " return null;", - " }", - "", - " Set<? super MissingType> superTest() {", - " return null;", - " }", - "}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "import java.util.Set;", + "", + "class TestClass {", + " Set<? extends MissingType> extendsTest() {", + " return null;", + " }", + "", + " Set<? super MissingType> superTest() {", + " return null;", + " }", + "}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); + } + }) .failsToCompile(); } @Test public void missingIntersection() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "class TestClass<T extends Number & Missing> {}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "class TestClass<T extends Number & Missing> {}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); + } + }) .failsToCompile(); } @Test public void invalidAnnotationValue() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines("test.Outer", - "package test;", - "", - "final class Outer {", - " @interface TestAnnotation {", - " Class[] classes();", - " }", - "", - " @TestAnnotation(classes = Foo)", - " static class TestClass {}", - "}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.Outer", + "package test;", + "", + "final class Outer {", + " @interface TestAnnotation {", + " Class[] classes();", + " }", + "", + " @TestAnnotation(classes = Foo)", + " static class TestClass {}", + "}"); assertAbout(javaSource()) .that(javaFileObject) .processedWith( diff --git a/common/src/test/java/com/google/auto/common/VisibilityTest.java b/common/src/test/java/com/google/auto/common/VisibilityTest.java index 6a80b7af..fc5e630b 100644 --- a/common/src/test/java/com/google/auto/common/VisibilityTest.java +++ b/common/src/test/java/com/google/auto/common/VisibilityTest.java @@ -39,9 +39,10 @@ public class VisibilityTest { public void packageVisibility() { assertThat(Visibility.ofElement(compilation.getElements().getPackageElement("java.lang"))) .isEqualTo(PUBLIC); - assertThat(Visibility.ofElement( - compilation.getElements().getPackageElement("com.google.auto.common"))) - .isEqualTo(PUBLIC); + assertThat( + Visibility.ofElement( + compilation.getElements().getPackageElement("com.google.auto.common"))) + .isEqualTo(PUBLIC); } @Test @@ -61,32 +62,44 @@ public class VisibilityTest { @SuppressWarnings("unused") public static class PublicClass { public static class NestedPublicClass {} + protected static class NestedProtectedClass {} + static class NestedDefaultClass {} + private static class NestedPrivateClass {} } @SuppressWarnings("unused") protected static class ProtectedClass { public static class NestedPublicClass {} + protected static class NestedProtectedClass {} + static class NestedDefaultClass {} + private static class NestedPrivateClass {} } @SuppressWarnings("unused") static class DefaultClass { public static class NestedPublicClass {} + protected static class NestedProtectedClass {} + static class NestedDefaultClass {} + private static class NestedPrivateClass {} } @SuppressWarnings("unused") private static class PrivateClass { public static class NestedPublicClass {} + protected static class NestedProtectedClass {} + static class NestedDefaultClass {} + private static class NestedPrivateClass {} } @@ -94,21 +107,25 @@ public class VisibilityTest { public void classVisibility() { assertThat(Visibility.ofElement(compilation.getElements().getTypeElement("java.util.Map"))) .isEqualTo(PUBLIC); - assertThat(Visibility.ofElement( - compilation.getElements().getTypeElement("java.util.Map.Entry"))) - .isEqualTo(PUBLIC); - assertThat(Visibility.ofElement( - compilation.getElements().getTypeElement(PublicClass.class.getCanonicalName()))) - .isEqualTo(PUBLIC); - assertThat(Visibility.ofElement( - compilation.getElements().getTypeElement(ProtectedClass.class.getCanonicalName()))) - .isEqualTo(PROTECTED); - assertThat(Visibility.ofElement( - compilation.getElements().getTypeElement(DefaultClass.class.getCanonicalName()))) - .isEqualTo(DEFAULT); - assertThat(Visibility.ofElement( - compilation.getElements().getTypeElement(PrivateClass.class.getCanonicalName()))) - .isEqualTo(PRIVATE); + assertThat( + Visibility.ofElement(compilation.getElements().getTypeElement("java.util.Map.Entry"))) + .isEqualTo(PUBLIC); + assertThat( + Visibility.ofElement( + compilation.getElements().getTypeElement(PublicClass.class.getCanonicalName()))) + .isEqualTo(PUBLIC); + assertThat( + Visibility.ofElement( + compilation.getElements().getTypeElement(ProtectedClass.class.getCanonicalName()))) + .isEqualTo(PROTECTED); + assertThat( + Visibility.ofElement( + compilation.getElements().getTypeElement(DefaultClass.class.getCanonicalName()))) + .isEqualTo(DEFAULT); + assertThat( + Visibility.ofElement( + compilation.getElements().getTypeElement(PrivateClass.class.getCanonicalName()))) + .isEqualTo(PRIVATE); } @Test @@ -118,14 +135,11 @@ public class VisibilityTest { assertThat(effectiveVisiblityOfClass(DefaultClass.class)).isEqualTo(DEFAULT); assertThat(effectiveVisiblityOfClass(PrivateClass.class)).isEqualTo(PRIVATE); - assertThat(effectiveVisiblityOfClass(PublicClass.NestedPublicClass.class)) - .isEqualTo(PUBLIC); + assertThat(effectiveVisiblityOfClass(PublicClass.NestedPublicClass.class)).isEqualTo(PUBLIC); assertThat(effectiveVisiblityOfClass(PublicClass.NestedProtectedClass.class)) .isEqualTo(PROTECTED); - assertThat(effectiveVisiblityOfClass(PublicClass.NestedDefaultClass.class)) - .isEqualTo(DEFAULT); - assertThat(effectiveVisiblityOfClass(PublicClass.NestedPrivateClass.class)) - .isEqualTo(PRIVATE); + assertThat(effectiveVisiblityOfClass(PublicClass.NestedDefaultClass.class)).isEqualTo(DEFAULT); + assertThat(effectiveVisiblityOfClass(PublicClass.NestedPrivateClass.class)).isEqualTo(PRIVATE); assertThat(effectiveVisiblityOfClass(ProtectedClass.NestedPublicClass.class)) .isEqualTo(PROTECTED); @@ -136,23 +150,17 @@ public class VisibilityTest { assertThat(effectiveVisiblityOfClass(ProtectedClass.NestedPrivateClass.class)) .isEqualTo(PRIVATE); - assertThat(effectiveVisiblityOfClass(DefaultClass.NestedPublicClass.class)) - .isEqualTo(DEFAULT); + assertThat(effectiveVisiblityOfClass(DefaultClass.NestedPublicClass.class)).isEqualTo(DEFAULT); assertThat(effectiveVisiblityOfClass(DefaultClass.NestedProtectedClass.class)) .isEqualTo(DEFAULT); - assertThat(effectiveVisiblityOfClass(DefaultClass.NestedDefaultClass.class)) - .isEqualTo(DEFAULT); - assertThat(effectiveVisiblityOfClass(DefaultClass.NestedPrivateClass.class)) - .isEqualTo(PRIVATE); + assertThat(effectiveVisiblityOfClass(DefaultClass.NestedDefaultClass.class)).isEqualTo(DEFAULT); + assertThat(effectiveVisiblityOfClass(DefaultClass.NestedPrivateClass.class)).isEqualTo(PRIVATE); - assertThat(effectiveVisiblityOfClass(PrivateClass.NestedPublicClass.class)) - .isEqualTo(PRIVATE); + assertThat(effectiveVisiblityOfClass(PrivateClass.NestedPublicClass.class)).isEqualTo(PRIVATE); assertThat(effectiveVisiblityOfClass(PrivateClass.NestedProtectedClass.class)) .isEqualTo(PRIVATE); - assertThat(effectiveVisiblityOfClass(PrivateClass.NestedDefaultClass.class)) - .isEqualTo(PRIVATE); - assertThat(effectiveVisiblityOfClass(PrivateClass.NestedPrivateClass.class)) - .isEqualTo(PRIVATE); + assertThat(effectiveVisiblityOfClass(PrivateClass.NestedDefaultClass.class)).isEqualTo(PRIVATE); + assertThat(effectiveVisiblityOfClass(PrivateClass.NestedPrivateClass.class)).isEqualTo(PRIVATE); } private Visibility effectiveVisiblityOfClass(Class<?> clazz) { diff --git a/factory/pom.xml b/factory/pom.xml index 48bcfeff..629223a9 100644 --- a/factory/pom.xml +++ b/factory/pom.xml @@ -36,9 +36,11 @@ <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <auto-service.version>1.0</auto-service.version> + <auto-value.version>1.8.1</auto-value.version> <java.version>1.8</java.version> - <guava.version>28.2-jre</guava.version> - <truth.version>1.0.1</truth.version> + <guava.version>30.1.1-jre</guava.version> + <truth.version>1.1.3</truth.version> </properties> <scm> @@ -69,43 +71,25 @@ <dependency> <groupId>com.google.auto</groupId> <artifactId>auto-common</artifactId> - <version>0.10</version> + <version>1.1</version> </dependency> <dependency> <groupId>com.google.auto.value</groupId> <artifactId>auto-value-annotations</artifactId> - <version>1.7</version> - </dependency> - <dependency> - <groupId>com.google.auto.value</groupId> - <artifactId>auto-value</artifactId> - <version>1.7</version> - <scope>provided</scope> + <version>${auto-value.version}</version> </dependency> <dependency> <groupId>com.google.auto.service</groupId> - <artifactId>auto-service</artifactId> - <version>1.0-rc6</version> - <scope>provided</scope> + <artifactId>auto-service-annotations</artifactId> + <version>${auto-service.version}</version> </dependency> <dependency> <groupId>net.ltgt.gradle.incap</groupId> <artifactId>incap</artifactId> - <version>0.2</version> + <version>0.3</version> <scope>provided</scope> </dependency> <dependency> - <groupId>net.ltgt.gradle.incap</groupId> - <artifactId>incap-processor</artifactId> - <version>0.2</version> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>com.google.googlejavaformat</groupId> - <artifactId>google-java-format</artifactId> - <version>1.7</version> - </dependency> - <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>${guava.version}</version> @@ -113,7 +97,7 @@ <dependency> <groupId>com.squareup</groupId> <artifactId>javapoet</artifactId> - <version>1.12.1</version> + <version>1.13.0</version> </dependency> <dependency> <groupId>javax.inject</groupId> @@ -124,13 +108,13 @@ <dependency> <groupId>com.google.testing.compile</groupId> <artifactId>compile-testing</artifactId> - <version>0.18</version> + <version>0.19</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> - <version>4.13</version> + <version>4.13.2</version> <scope>test</scope> </dependency> <dependency> @@ -157,29 +141,54 @@ <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> - <version>3.7.0</version> + <version>3.8.1</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> <compilerArgument>-Xlint:all</compilerArgument> <showWarnings>true</showWarnings> <showDeprecation>true</showDeprecation> + <annotationProcessorPaths> + <path> + <groupId>com.google.auto.service</groupId> + <artifactId>auto-service</artifactId> + <version>${auto-service.version}</version> + </path> + <path> + <groupId>com.google.auto.value</groupId> + <artifactId>auto-value</artifactId> + <version>${auto-value.version}</version> + </path> + <path> + <groupId>net.ltgt.gradle.incap</groupId> + <artifactId>incap-processor</artifactId> + <version>0.3</version> + </path> + </annotationProcessorPaths> </configuration> <dependencies> <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>0.9.4</version> + <version>1.0.7</version> </dependency> </dependencies> </plugin> <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>2.22.2</version> + <configuration> + <argLine>${test.jvm.flags}</argLine> + </configuration> + </plugin> + <plugin> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.2.0</version> </plugin> <plugin> <artifactId>maven-invoker-plugin</artifactId> - <version>3.0.1</version> + <version>3.2.2</version> <configuration> <addTestClassPath>true</addTestClassPath> <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo> @@ -205,4 +214,15 @@ </plugin> </plugins> </build> + <profiles> + <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> + </profile> + </profiles> </project> diff --git a/factory/src/it/functional/pom.xml b/factory/src/it/functional/pom.xml index 56e8f6f2..a7a1853e 100644 --- a/factory/src/it/functional/pom.xml +++ b/factory/src/it/functional/pom.xml @@ -45,7 +45,7 @@ <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> - <version>27.0.1-jre</version> + <version>29.0-jre</version> </dependency> <dependency> <groupId>com.google.inject</groupId> @@ -66,7 +66,7 @@ <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> - <version>4.12</version> + <version>4.13.2</version> <scope>test</scope> </dependency> <dependency> @@ -82,12 +82,12 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.2.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.7.0</version> + <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> @@ -99,7 +99,7 @@ <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>0.9.4</version> + <version>1.0.5</version> </dependency> </dependencies> </plugin> diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java index 4c4a38af..f32e9f8d 100644 --- a/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java +++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java @@ -16,44 +16,44 @@ package com.google.auto.factory; import com.google.auto.factory.otherpackage.OtherPackage; +import dagger.Binds; import dagger.Module; import dagger.Provides; @Module -final class DaggerModule { - @Provides Dependency provideDependency(DependencyImpl impl) { - return impl; - } +abstract class DaggerModule { + private DaggerModule() {} // no instances - @Provides + @Binds + abstract Dependency provideDependency(DependencyImpl impl); + + @Binds @Qualifier - Dependency provideQualifiedDependency(QualifiedDependencyImpl impl) { - return impl; - } + abstract Dependency provideQualifiedDependency(QualifiedDependencyImpl impl); @Provides - int providePrimitive() { + static int providePrimitive() { return 1; } @Provides @Qualifier - int provideQualifiedPrimitive() { + static int provideQualifiedPrimitive() { return 2; } @Provides - Number provideNumber() { + static Number provideNumber() { return 3; } @Provides - ReferencePackage provideReferencePackage(ReferencePackageFactory factory) { + static ReferencePackage provideReferencePackage(ReferencePackageFactory factory) { return factory.create(17); } @Provides - OtherPackage provideOtherPackage() { + static OtherPackage provideOtherPackage() { return new OtherPackage(null, 23); } } diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/DependencyImpl.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/DependencyImpl.java index 4c019ea6..d94a3cf6 100644 --- a/factory/src/it/functional/src/main/java/com/google/auto/factory/DependencyImpl.java +++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/DependencyImpl.java @@ -18,5 +18,6 @@ package com.google.auto.factory; import javax.inject.Inject; public class DependencyImpl implements Dependency { - @Inject DependencyImpl() {} + @Inject + DependencyImpl() {} } diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/GenericFoo.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/GenericFoo.java index f7c13b41..31954a19 100644 --- a/factory/src/it/functional/src/main/java/com/google/auto/factory/GenericFoo.java +++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/GenericFoo.java @@ -28,10 +28,7 @@ public class GenericFoo<A, B extends List<? extends A>, C, E extends Enum<E>> { private final E depE; <D extends IntAccessor & StringAccessor> GenericFoo( - @Provided Provider<A> depA, - B depB, - D depD, - E depE) { + @Provided Provider<A> depA, B depB, D depD, E depE) { this.depA = depA.get(); this.depB = depB; this.depDIntAccessor = depD; diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/GuiceModule.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/GuiceModule.java index 45d4d26e..77346655 100644 --- a/factory/src/it/functional/src/main/java/com/google/auto/factory/GuiceModule.java +++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/GuiceModule.java @@ -18,7 +18,8 @@ package com.google.auto.factory; import com.google.inject.AbstractModule; public class GuiceModule extends AbstractModule { - @Override protected void configure() { + @Override + protected void configure() { bind(Dependency.class).to(DependencyImpl.class); bind(Dependency.class).annotatedWith(Qualifier.class).to(QualifiedDependencyImpl.class); bind(Integer.class).toInstance(1); diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/ReferencePackage.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/ReferencePackage.java index 22aff651..4f67c3b3 100644..100755 --- a/factory/src/it/functional/src/main/java/com/google/auto/factory/ReferencePackage.java +++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/ReferencePackage.java @@ -25,9 +25,7 @@ public class ReferencePackage { private final int random; @Inject - public ReferencePackage( - @Provided OtherPackageFactory otherPackageFactory, - int random) { + ReferencePackage(@Provided OtherPackageFactory otherPackageFactory, int random) { this.otherPackageFactory = otherPackageFactory; this.random = random; } diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/otherpackage/OtherPackage.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/otherpackage/OtherPackage.java index b9925a18..b9925a18 100644..100755 --- a/factory/src/it/functional/src/main/java/com/google/auto/factory/otherpackage/OtherPackage.java +++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/otherpackage/OtherPackage.java diff --git a/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java b/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java index e1412117..3caa5a54 100644 --- a/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java +++ b/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java @@ -19,7 +19,8 @@ public class DependencyInjectionIntegrationTest { private static final IntAndStringAccessor INT_AND_STRING_ACCESSOR = new IntAndStringAccessor() {}; - @Test public void daggerInjectedFactory() { + @Test + public void daggerInjectedFactory() { FooFactory fooFactory = DaggerFactoryComponent.create().factory(); Foo one = fooFactory.create("A"); Foo two = fooFactory.create("B"); @@ -46,8 +47,9 @@ public class DependencyInjectionIntegrationTest { genericFooFactory.create(ImmutableList.of(3L), INT_AND_STRING_ACCESSOR, DepE.VALUE_1); ArrayList<Double> intAndStringAccessorArrayList = new ArrayList<>(); intAndStringAccessorArrayList.add(4.0); - GenericFoo<Number, ArrayList<Double>, Long, DepE> four = genericFooFactory.create( - intAndStringAccessorArrayList, INT_AND_STRING_ACCESSOR, DepE.VALUE_2); + GenericFoo<Number, ArrayList<Double>, Long, DepE> four = + genericFooFactory.create( + intAndStringAccessorArrayList, INT_AND_STRING_ACCESSOR, DepE.VALUE_2); assertThat(three.getDepA()).isEqualTo(3); ImmutableList<Long> unusedLongList = three.getDepB(); assertThat(three.getDepB()).containsExactly(3L); @@ -75,7 +77,8 @@ public class DependencyInjectionIntegrationTest { assertThat(otherPackage.random()).isEqualTo(5); } - @Test public void guiceInjectedFactory() { + @Test + public void guiceInjectedFactory() { FooFactory fooFactory = Guice.createInjector(new GuiceModule()).getInstance(FooFactory.class); Foo one = fooFactory.create("A"); Foo two = fooFactory.create("B"); @@ -103,8 +106,9 @@ public class DependencyInjectionIntegrationTest { genericFooFactory.create(ImmutableList.of(3L), INT_AND_STRING_ACCESSOR, DepE.VALUE_1); ArrayList<Double> intAndStringAccessorArrayList = new ArrayList<>(); intAndStringAccessorArrayList.add(4.0); - GenericFoo<Number, ArrayList<Double>, Long, DepE> four = genericFooFactory.create( - intAndStringAccessorArrayList, INT_AND_STRING_ACCESSOR, DepE.VALUE_2); + GenericFoo<Number, ArrayList<Double>, Long, DepE> four = + genericFooFactory.create( + intAndStringAccessorArrayList, INT_AND_STRING_ACCESSOR, DepE.VALUE_2); assertThat(three.getDepA()).isEqualTo(3); ImmutableList<Long> unusedLongList = three.getDepB(); assertThat(three.getDepB()).containsExactly(3L); @@ -124,8 +128,7 @@ public class DependencyInjectionIntegrationTest { @Test public void guiceInjectedPackageSpanningFactory() { ReferencePackageFactory referencePackageFactory = - Guice.createInjector(new GuiceModule()) - .getInstance(ReferencePackageFactory.class); + Guice.createInjector(new GuiceModule()).getInstance(ReferencePackageFactory.class); ReferencePackage referencePackage = referencePackageFactory.create(5); OtherPackage otherPackage = referencePackage.otherPackage(); assertThat(otherPackage.referencePackageFactory()).isNotSameInstanceAs(referencePackageFactory); 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 2ef84cc3..3b5d90ea 100644 --- a/factory/src/main/java/com/google/auto/factory/AutoFactory.java +++ b/factory/src/main/java/com/google/auto/factory/AutoFactory.java @@ -32,7 +32,7 @@ import java.lang.annotation.Target; * * @author Gregory Kick */ -@Target({ TYPE, CONSTRUCTOR }) +@Target({TYPE, CONSTRUCTOR}) public @interface AutoFactory { /** * The <i>simple</i> name of the generated factory; the factory is always generated in the same @@ -50,7 +50,7 @@ public @interface AutoFactory { /** * A list of interfaces that the generated factory is required to implement. */ - Class<?>[] implementing() default { }; + Class<?>[] implementing() default {}; /** * The type that the generated factory is require to extend. diff --git a/factory/src/main/java/com/google/auto/factory/Provided.java b/factory/src/main/java/com/google/auto/factory/Provided.java index e81e4aa6..226a16f4 100644 --- a/factory/src/main/java/com/google/auto/factory/Provided.java +++ b/factory/src/main/java/com/google/auto/factory/Provided.java @@ -26,4 +26,4 @@ import java.lang.annotation.Target; * @author Gregory Kick */ @Target(PARAMETER) -public @interface Provided { } +public @interface Provided {} diff --git a/factory/src/main/java/com/google/auto/factory/package-info.java b/factory/src/main/java/com/google/auto/factory/package-info.java new file mode 100644 index 00000000..ea1ddd88 --- /dev/null +++ b/factory/src/main/java/com/google/auto/factory/package-info.java @@ -0,0 +1,17 @@ +/* + * Copyright 2021 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; + diff --git a/factory/src/main/java/com/google/auto/factory/processor/AnnotationValues.java b/factory/src/main/java/com/google/auto/factory/processor/AnnotationValues.java index b767c47f..53d38a40 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/AnnotationValues.java +++ b/factory/src/main/java/com/google/auto/factory/processor/AnnotationValues.java @@ -32,24 +32,29 @@ final class AnnotationValues { static boolean asBoolean(AnnotationValue value) { return value.accept( new SimpleAnnotationValueVisitor6<Boolean, Void>() { - @Override protected Boolean defaultAction(Object o, Void p) { + @Override + protected Boolean defaultAction(Object o, Void p) { throw new IllegalArgumentException(); } - @Override public Boolean visitBoolean(boolean b, Void p) { + @Override + public Boolean visitBoolean(boolean b, Void p) { return b; } - }, null); + }, + null); } static TypeElement asType(AnnotationValue value) { return value.accept( new SimpleAnnotationValueVisitor6<TypeElement, Void>() { - @Override protected TypeElement defaultAction(Object o, Void p) { + @Override + protected TypeElement defaultAction(Object o, Void p) { throw new IllegalArgumentException(); } - @Override public TypeElement visitType(TypeMirror t, Void p) { + @Override + public TypeElement visitType(TypeMirror t, Void p) { return t.accept( new SimpleTypeVisitor6<TypeElement, Void>() { @Override @@ -59,12 +64,14 @@ final class AnnotationValues { @Override public TypeElement visitDeclared(DeclaredType t, Void p) { - return Iterables.getOnlyElement(ElementFilter.typesIn( - ImmutableList.of(t.asElement()))); + return Iterables.getOnlyElement( + ElementFilter.typesIn(ImmutableList.of(t.asElement()))); } - }, null); + }, + null); } - }, null); + }, + null); } static ImmutableList<? extends AnnotationValue> asList(AnnotationValue value) { @@ -80,6 +87,7 @@ final class AnnotationValues { List<? extends AnnotationValue> vals, Void p) { return ImmutableList.copyOf(vals); } - }, null); + }, + null); } } 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 e2da6eae..889d8e41 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 @@ -21,13 +21,13 @@ 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.factory.AutoFactory; import com.google.auto.value.AutoValue; -import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; @@ -36,6 +36,7 @@ import com.google.common.collect.ImmutableSet; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Optional; import javax.annotation.processing.Messager; import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; @@ -54,12 +55,19 @@ import javax.lang.model.util.Elements; @AutoValue abstract class AutoFactoryDeclaration { abstract TypeElement targetType(); + abstract Element target(); + abstract Optional<String> className(); + abstract TypeElement extendingType(); + abstract ImmutableSet<TypeElement> implementingTypes(); + abstract boolean allowSubclasses(); + abstract AnnotationMirror mirror(); + abstract ImmutableMap<String, AnnotationValue> valuesMap(); PackageAndClass getFactoryName() { @@ -97,52 +105,70 @@ abstract class AutoFactoryDeclaration { Optional<AutoFactoryDeclaration> createIfValid(Element element) { checkNotNull(element); AnnotationMirror mirror = Mirrors.getAnnotationMirror(element, AutoFactory.class).get(); - checkArgument(Mirrors.getQualifiedName(mirror.getAnnotationType()). - contentEquals(AutoFactory.class.getName())); + checkArgument( + Mirrors.getQualifiedName(mirror.getAnnotationType()) + .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 - AnnotationValue classNameValue = values.get("className"); + // 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. + AnnotationValue classNameValue = requireNonNull(values.get("className")); String className = classNameValue.getValue().toString(); if (!className.isEmpty() && !isValidIdentifier(className)) { - messager.printMessage(ERROR, + messager.printMessage( + ERROR, String.format("\"%s\" is not a valid Java identifier", className), - element, mirror, classNameValue); - return Optional.absent(); + element, + mirror, + classNameValue); + return Optional.empty(); } AnnotationValue extendingValue = checkNotNull(values.get("extending")); TypeElement extendingType = AnnotationValues.asType(extendingValue); if (extendingType == null) { - messager.printMessage(ERROR, "Unable to find the type: " - + extendingValue.getValue().toString(), - element, mirror, extendingValue); - return Optional.absent(); + messager.printMessage( + ERROR, + "Unable to find the type: " + extendingValue.getValue(), + element, + mirror, + extendingValue); + return Optional.empty(); } else if (!isValidSupertypeForClass(extendingType)) { - messager.printMessage(ERROR, - String.format("%s is not a valid supertype for a factory. " - + "Supertypes must be non-final classes.", - extendingType.getQualifiedName()), - element, mirror, extendingValue); - return Optional.absent(); + messager.printMessage( + ERROR, + String.format( + "%s is not a valid supertype for a factory. " + + "Supertypes must be non-final classes.", + extendingType.getQualifiedName()), + element, + mirror, + extendingValue); + return Optional.empty(); } ImmutableList<ExecutableElement> noParameterConstructors = FluentIterable.from(ElementFilter.constructorsIn(extendingType.getEnclosedElements())) - .filter(new Predicate<ExecutableElement>() { - @Override public boolean apply(ExecutableElement constructor) { - return constructor.getParameters().isEmpty(); - } - }) + .filter( + new Predicate<ExecutableElement>() { + @Override + public boolean apply(ExecutableElement constructor) { + return constructor.getParameters().isEmpty(); + } + }) .toList(); - if (noParameterConstructors.size() == 0) { - messager.printMessage(ERROR, - String.format("%s is not a valid supertype for a factory. " - + "Factory supertypes must have a no-arg constructor.", - extendingType.getQualifiedName()), - element, mirror, extendingValue); - return Optional.absent(); + if (noParameterConstructors.isEmpty()) { + messager.printMessage( + ERROR, + String.format( + "%s is not a valid supertype for a factory. " + + "Factory supertypes must have a no-arg constructor.", + extendingType.getQualifiedName()), + element, + mirror, + extendingValue); + return Optional.empty(); } else if (noParameterConstructors.size() > 1) { throw new IllegalStateException("Multiple constructors with no parameters??"); } @@ -161,7 +187,7 @@ abstract class AutoFactoryDeclaration { new AutoValue_AutoFactoryDeclaration( getAnnotatedType(element), element, - className.isEmpty() ? Optional.<String>absent() : Optional.of(className), + className.isEmpty() ? Optional.empty() : Optional.of(className), extendingType, implementingTypes, allowSubclasses, 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 5cc1d94d..82349f21 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 @@ -19,7 +19,6 @@ import com.google.auto.common.MoreTypes; import com.google.auto.factory.AutoFactory; import com.google.auto.factory.Provided; import com.google.auto.service.AutoService; -import com.google.common.base.Optional; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; @@ -30,7 +29,9 @@ import com.google.common.collect.Iterables; import java.io.IOException; import java.util.Arrays; import java.util.Comparator; +import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Messager; @@ -83,8 +84,9 @@ public final class AutoFactoryProcessor extends AbstractProcessor { try { doProcess(roundEnv); } catch (Throwable e) { - messager.printMessage(Kind.ERROR, "Failed to process @AutoFactory annotations:\n" - + Throwables.getStackTraceAsString(e)); + messager.printMessage( + Kind.ERROR, + "Failed to process @AutoFactory annotations:\n" + Throwables.getStackTraceAsString(e)); } return false; } @@ -127,49 +129,58 @@ public final class AutoFactoryProcessor extends AbstractProcessor { simpleNamesToNames(indexedMethods.keySet()); FactoryWriter factoryWriter = new FactoryWriter(processingEnv, factoriesBeingCreated); - indexedMethods.asMap().forEach( - (factoryName, methodDescriptors) -> { - // 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(); - ImmutableSortedSet.Builder<TypeMirror> implementing = newTypeSetBuilder(); - boolean publicType = false; - Boolean allowSubclasses = null; - boolean skipCreation = false; - for (FactoryMethodDescriptor methodDescriptor : methodDescriptors) { - extending.add(methodDescriptor.declaration().extendingType().asType()); - for (TypeElement implementingType : - methodDescriptor.declaration().implementingTypes()) { - implementing.add(implementingType.asType()); - } - publicType |= methodDescriptor.publicMethod(); - if (allowSubclasses == null) { - allowSubclasses = methodDescriptor.declaration().allowSubclasses(); - } else if (!allowSubclasses.equals(methodDescriptor.declaration().allowSubclasses())) { - skipCreation = true; - messager.printMessage(Kind.ERROR, - "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.", - methodDescriptor.declaration().target(), - methodDescriptor.declaration().mirror(), - methodDescriptor.declaration().valuesMap().get("allowSubclasses")); - } - } - if (!skipCreation) { - try { - factoryWriter.writeFactory( - FactoryDescriptor.create( - factoryName, - Iterables.getOnlyElement(extending.build()), - implementing.build(), - publicType, - ImmutableSet.copyOf(methodDescriptors), - implementationMethodDescriptors.get(factoryName), - allowSubclasses)); - } catch (IOException e) { - messager.printMessage(Kind.ERROR, "failed: " + e); - } - } - }); + indexedMethods + .asMap() + .forEach( + (factoryName, methodDescriptors) -> { + if (methodDescriptors.isEmpty()) { + // This shouldn't happen, but check anyway to avoid an exception for + // methodDescriptors.iterator().next() below. + return; + } + // 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(); + ImmutableSortedSet.Builder<TypeMirror> implementing = newTypeSetBuilder(); + boolean publicType = false; + Set<Boolean> allowSubclassesSet = new HashSet<>(); + boolean skipCreation = false; + for (FactoryMethodDescriptor methodDescriptor : methodDescriptors) { + extending.add(methodDescriptor.declaration().extendingType().asType()); + for (TypeElement implementingType : + methodDescriptor.declaration().implementingTypes()) { + implementing.add(implementingType.asType()); + } + publicType |= methodDescriptor.publicMethod(); + allowSubclassesSet.add(methodDescriptor.declaration().allowSubclasses()); + if (allowSubclassesSet.size() > 1) { + skipCreation = true; + messager.printMessage( + Kind.ERROR, + "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.", + methodDescriptor.declaration().target(), + methodDescriptor.declaration().mirror(), + methodDescriptor.declaration().valuesMap().get("allowSubclasses")); + } + } + // The set can't be empty because we eliminated methodDescriptors.isEmpty() above. + boolean allowSubclasses = allowSubclassesSet.iterator().next(); + if (!skipCreation) { + try { + factoryWriter.writeFactory( + FactoryDescriptor.create( + factoryName, + Iterables.getOnlyElement(extending.build()), + implementing.build(), + publicType, + ImmutableSet.copyOf(methodDescriptors), + implementationMethodDescriptors.get(factoryName), + allowSubclasses)); + } catch (IOException e) { + messager.printMessage(Kind.ERROR, "failed: " + e); + } + } + }); } private ImmutableSet<ImplementationMethodDescriptor> implementationMethods( @@ -180,8 +191,7 @@ public final class AutoFactoryProcessor extends AbstractProcessor { ElementFilter.methodsIn(elements.getAllMembers(supertype))) { if (implementationMethod.getModifiers().contains(Modifier.ABSTRACT)) { ExecutableType methodType = - Elements2.getExecutableElementAsMemberOf( - types, implementationMethod, supertype); + Elements2.getExecutableElementAsMemberOf(types, implementationMethod, supertype); ImmutableSet<Parameter> passedParameters = Parameter.forParameterList( implementationMethod.getParameters(), methodType.getParameterTypes(), types); @@ -192,6 +202,7 @@ public final class AutoFactoryProcessor extends AbstractProcessor { .publicMethod() .passedParameters(passedParameters) .isVarArgs(implementationMethod.isVarArgs()) + .exceptions(implementationMethod.getThrownTypes()) .build()); } } diff --git a/factory/src/main/java/com/google/auto/factory/processor/Elements2.java b/factory/src/main/java/com/google/auto/factory/processor/Elements2.java index 30230f7f..3663f370 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/Elements2.java +++ b/factory/src/main/java/com/google/auto/factory/processor/Elements2.java @@ -22,10 +22,10 @@ import static javax.lang.model.element.ElementKind.PACKAGE; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.STATIC; +import com.google.auto.common.MoreTypes; import com.google.common.collect.ImmutableSet; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ExecutableType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; @@ -33,7 +33,7 @@ import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Types; final class Elements2 { - private Elements2() { } + private Elements2() {} static ImmutableSet<ExecutableElement> getConstructors(TypeElement type) { checkNotNull(type); @@ -72,11 +72,11 @@ final class Elements2 { throw new IllegalStateException( "Expected subTypeElement.asType() to return a class/interface type."); } - TypeMirror subExecutableTypeMirror = types.asMemberOf( - (DeclaredType) subTypeMirror, executableElement); + TypeMirror subExecutableTypeMirror = + types.asMemberOf(MoreTypes.asDeclared(subTypeMirror), executableElement); if (!subExecutableTypeMirror.getKind().equals(TypeKind.EXECUTABLE)) { throw new IllegalStateException("Expected subExecutableTypeMirror to be an executable type."); } - return (ExecutableType) subExecutableTypeMirror; + return MoreTypes.asExecutable(subExecutableTypeMirror); } } 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 5ed2307a..019295f6 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 @@ -17,16 +17,15 @@ package com.google.auto.factory.processor; import com.google.auto.value.AutoValue; import com.google.common.base.CharMatcher; -import com.google.common.base.Optional; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableMap; 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 java.util.Collection; +import com.google.common.collect.Streams; import java.util.HashSet; -import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeMirror; @@ -47,20 +46,28 @@ abstract class FactoryDescriptor { }; abstract PackageAndClass name(); + abstract TypeMirror extendingType(); + abstract ImmutableSet<TypeMirror> implementingTypes(); + abstract boolean publicType(); + abstract ImmutableSet<FactoryMethodDescriptor> methodDescriptors(); + abstract ImmutableSet<ImplementationMethodDescriptor> implementationMethodDescriptors(); + abstract boolean allowSubclasses(); + abstract ImmutableMap<Key, ProviderField> providers(); final AutoFactoryDeclaration declaration() { - return Iterables.getFirst(methodDescriptors(), null).declaration(); + // There is always at least one method descriptor. + return methodDescriptors().iterator().next().declaration(); } private static class UniqueNameSet { - private final Set<String> uniqueNames = new HashSet<String>(); + private final Set<String> uniqueNames = new HashSet<>(); /** * Generates a unique name using {@code base}. If {@code base} has not yet been added, it will @@ -92,33 +99,37 @@ abstract class FactoryDescriptor { } ImmutableMap.Builder<Key, ProviderField> providersBuilder = ImmutableMap.builder(); UniqueNameSet uniqueNames = new UniqueNameSet(); - for (Entry<Key, Collection<Parameter>> entry : - parametersForProviders.build().asMap().entrySet()) { - Key key = entry.getKey(); - switch (entry.getValue().size()) { - case 0: - throw new AssertionError(); - case 1: - Parameter parameter = Iterables.getOnlyElement(entry.getValue()); - providersBuilder.put( - key, - ProviderField.create( - uniqueNames.getUniqueName(parameter.name() + "Provider"), - key, - parameter.nullable())); - break; - default: - String providerName = - uniqueNames.getUniqueName( - invalidIdentifierCharacters.replaceFrom(key.toString(), '_') + "Provider"); - Optional<AnnotationMirror> nullable = Optional.absent(); - for (Parameter param : entry.getValue()) { - nullable = nullable.or(param.nullable()); - } - providersBuilder.put(key, ProviderField.create(providerName, key, nullable)); - break; - } - } + parametersForProviders + .build() + .asMap() + .forEach( + (key, parameters) -> { + switch (parameters.size()) { + case 0: + throw new AssertionError(); + case 1: + Parameter parameter = Iterables.getOnlyElement(parameters); + providersBuilder.put( + key, + ProviderField.create( + uniqueNames.getUniqueName(parameter.name() + "Provider"), + key, + parameter.nullable())); + break; + default: + String providerName = + uniqueNames.getUniqueName( + invalidIdentifierCharacters.replaceFrom(key.toString(), '_') + + "Provider"); + Optional<AnnotationMirror> nullable = + parameters.stream() + .map(Parameter::nullable) + .flatMap(Streams::stream) + .findFirst(); + providersBuilder.put(key, ProviderField.create(providerName, key, nullable)); + break; + } + }); ImmutableBiMap<FactoryMethodDescriptor, ImplementationMethodDescriptor> duplicateMethodDescriptors = @@ -129,8 +140,8 @@ abstract class FactoryDescriptor { getDeduplicatedMethodDescriptors(methodDescriptors, duplicateMethodDescriptors); ImmutableSet<ImplementationMethodDescriptor> deduplicatedImplementationMethodDescriptors = - ImmutableSet.copyOf( - Sets.difference(implementationMethodDescriptors, duplicateMethodDescriptors.values())); + Sets.difference(implementationMethodDescriptors, duplicateMethodDescriptors.values()) + .immutableCopy(); return new AutoValue_FactoryDescriptor( name, @@ -191,12 +202,12 @@ abstract class FactoryDescriptor { duplicateMethodDescriptors.get(methodDescriptor); FactoryMethodDescriptor newMethodDescriptor = - (duplicateMethodDescriptor != null) - ? methodDescriptor - .toBuilder() + (duplicateMethodDescriptor != null) + ? methodDescriptor.toBuilder() .overridingMethod(true) .publicMethod(duplicateMethodDescriptor.publicMethod()) .returnType(duplicateMethodDescriptor.returnType()) + .exceptions(duplicateMethodDescriptor.exceptions()) .build() : methodDescriptor; deduplicatedMethodDescriptors.add(newMethodDescriptor); @@ -213,8 +224,7 @@ abstract class FactoryDescriptor { * in the same order. */ private static boolean areDuplicateMethodDescriptors( - FactoryMethodDescriptor factory, - ImplementationMethodDescriptor implementation) { + FactoryMethodDescriptor factory, ImplementationMethodDescriptor implementation) { if (!factory.name().equals(implementation.name())) { return false; diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java index d2331f42..70a21ea2 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java +++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java @@ -18,6 +18,8 @@ package com.google.auto.factory.processor; import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.partitioningBy; import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.tools.Diagnostic.Kind.ERROR; @@ -26,13 +28,11 @@ import com.google.auto.common.MoreElements; import com.google.auto.factory.AutoFactory; import com.google.auto.factory.Provided; import com.google.common.base.Function; -import com.google.common.base.Functions; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimaps; +import java.util.List; +import java.util.Map; +import java.util.Optional; import javax.annotation.processing.Messager; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; @@ -54,9 +54,7 @@ final class FactoryDescriptorGenerator { private final AutoFactoryDeclaration.Factory declarationFactory; FactoryDescriptorGenerator( - Messager messager, - Types types, - AutoFactoryDeclaration.Factory declarationFactory) { + Messager messager, Types types, AutoFactoryDeclaration.Factory declarationFactory) { this.messager = messager; this.types = types; this.declarationFactory = declarationFactory; @@ -68,70 +66,75 @@ final class FactoryDescriptorGenerator { if (!declaration.isPresent()) { return ImmutableSet.of(); } - return element.accept(new ElementKindVisitor6<ImmutableSet<FactoryMethodDescriptor>, Void>() { - @Override - protected ImmutableSet<FactoryMethodDescriptor> defaultAction(Element e, Void p) { - throw new AssertionError("@AutoFactory applied to an impossible element"); - } + return element.accept( + new ElementKindVisitor6<ImmutableSet<FactoryMethodDescriptor>, Void>() { + @Override + protected ImmutableSet<FactoryMethodDescriptor> defaultAction(Element e, Void p) { + throw new AssertionError("@AutoFactory applied to an impossible element"); + } - @Override - public ImmutableSet<FactoryMethodDescriptor> visitTypeAsClass(TypeElement type, Void p) { - if (type.getModifiers().contains(ABSTRACT)) { - // applied to an abstract factory - messager.printMessage(ERROR, - "Auto-factory doesn't support being applied to abstract classes.", type, mirror); - return ImmutableSet.of(); - } else { - // applied to the type to be created - ImmutableSet<ExecutableElement> constructors = Elements2.getConstructors(type); - if (constructors.isEmpty()) { - return generateDescriptorForDefaultConstructor(declaration.get(), type); - } else { - return FluentIterable.from(constructors) - .transform(new Function<ExecutableElement, FactoryMethodDescriptor>() { - @Override public FactoryMethodDescriptor apply(ExecutableElement constructor) { - return generateDescriptorForConstructor(declaration.get(), constructor); - } - }) - .toSet(); + @Override + public ImmutableSet<FactoryMethodDescriptor> visitTypeAsClass(TypeElement type, Void p) { + if (type.getModifiers().contains(ABSTRACT)) { + // applied to an abstract factory + messager.printMessage( + ERROR, + "Auto-factory doesn't support being applied to abstract classes.", + type, + mirror); + return ImmutableSet.of(); + } else { + // applied to the type to be created + ImmutableSet<ExecutableElement> constructors = Elements2.getConstructors(type); + if (constructors.isEmpty()) { + return generateDescriptorForDefaultConstructor(declaration.get(), type); + } else { + return FluentIterable.from(constructors) + .transform( + new Function<ExecutableElement, FactoryMethodDescriptor>() { + @Override + public FactoryMethodDescriptor apply(ExecutableElement constructor) { + return generateDescriptorForConstructor(declaration.get(), constructor); + } + }) + .toSet(); + } + } } - } - } - @Override - public ImmutableSet<FactoryMethodDescriptor> visitTypeAsInterface(TypeElement type, Void p) { - // applied to the factory interface - messager.printMessage(ERROR, - "Auto-factory doesn't support being applied to interfaces.", type, mirror); - return ImmutableSet.of(); - } + @Override + public ImmutableSet<FactoryMethodDescriptor> visitTypeAsInterface( + TypeElement type, Void p) { + // applied to the factory interface + messager.printMessage( + ERROR, "Auto-factory doesn't support being applied to interfaces.", type, mirror); + return ImmutableSet.of(); + } - @Override - public ImmutableSet<FactoryMethodDescriptor> visitExecutableAsConstructor(ExecutableElement e, - Void p) { - // applied to a constructor of a type to be created - return ImmutableSet.of(generateDescriptorForConstructor(declaration.get(), e)); - } - }, null); + @Override + public ImmutableSet<FactoryMethodDescriptor> visitExecutableAsConstructor( + ExecutableElement e, Void p) { + // applied to a constructor of a type to be created + return ImmutableSet.of(generateDescriptorForConstructor(declaration.get(), e)); + } + }, + null); } - FactoryMethodDescriptor generateDescriptorForConstructor(final AutoFactoryDeclaration declaration, - ExecutableElement constructor) { + FactoryMethodDescriptor generateDescriptorForConstructor( + final AutoFactoryDeclaration declaration, ExecutableElement constructor) { checkNotNull(constructor); checkArgument(constructor.getKind() == ElementKind.CONSTRUCTOR); TypeElement classElement = MoreElements.asType(constructor.getEnclosingElement()); - ImmutableListMultimap<Boolean, ? extends VariableElement> parameterMap = - Multimaps.index(constructor.getParameters(), Functions.forPredicate( - new Predicate<VariableElement>() { - @Override - public boolean apply(VariableElement parameter) { - return isAnnotationPresent(parameter, Provided.class); - } - })); + Map<Boolean, List<VariableElement>> parameterMap = + constructor.getParameters().stream() + .collect(partitioningBy(parameter -> isAnnotationPresent(parameter, Provided.class))); + // The map returned by partitioningBy always has entries for both key values but our + // null-checker isn't yet smart enough to know that. ImmutableSet<Parameter> providedParameters = - Parameter.forParameterList(parameterMap.get(true), types); + Parameter.forParameterList(requireNonNull(parameterMap.get(true)), types); ImmutableSet<Parameter> passedParameters = - Parameter.forParameterList(parameterMap.get(false), types); + Parameter.forParameterList(requireNonNull(parameterMap.get(false)), types); return FactoryMethodDescriptor.builder(declaration) .name("create") .returnType(classElement.asType()) @@ -140,6 +143,8 @@ final class FactoryDescriptorGenerator { .passedParameters(passedParameters) .creationParameters(Parameter.forParameterList(constructor.getParameters(), types)) .isVarArgs(constructor.isVarArgs()) + .exceptions(constructor.getThrownTypes()) + .overridingMethod(false) .build(); } @@ -150,9 +155,12 @@ final class FactoryDescriptorGenerator { .name("create") .returnType(type.asType()) .publicMethod(type.getModifiers().contains(PUBLIC)) - .passedParameters(ImmutableSet.<Parameter>of()) - .creationParameters(ImmutableSet.<Parameter>of()) - .providedParameters(ImmutableSet.<Parameter>of()) + .providedParameters(ImmutableSet.of()) + .passedParameters(ImmutableSet.of()) + .creationParameters(ImmutableSet.of()) + .isVarArgs(false) + .exceptions(ImmutableSet.of()) + .overridingMethod(false) .build()); } } 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 43e5097d..45259573 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 @@ -31,45 +31,65 @@ import javax.lang.model.type.TypeMirror; @AutoValue abstract class FactoryMethodDescriptor { abstract AutoFactoryDeclaration declaration(); + abstract String name(); + abstract TypeMirror returnType(); + abstract boolean publicMethod(); + abstract boolean overridingMethod(); + abstract ImmutableSet<Parameter> passedParameters(); + abstract ImmutableSet<Parameter> providedParameters(); + abstract ImmutableSet<Parameter> creationParameters(); - abstract Builder toBuilder(); + abstract boolean isVarArgs(); + abstract ImmutableSet<TypeMirror> exceptions(); + + abstract Builder toBuilder(); + final PackageAndClass factoryName() { return declaration().getFactoryName(); } static Builder builder(AutoFactoryDeclaration declaration) { - return new AutoValue_FactoryMethodDescriptor.Builder() - .declaration(checkNotNull(declaration)) - .publicMethod(false) - .overridingMethod(false) - .isVarArgs(false); + return new AutoValue_FactoryMethodDescriptor.Builder().declaration(checkNotNull(declaration)); } @AutoValue.Builder abstract static class Builder { abstract Builder declaration(AutoFactoryDeclaration declaration); + abstract Builder name(String name); + abstract Builder returnType(TypeMirror returnType); + abstract Builder publicMethod(boolean publicMethod); + abstract Builder overridingMethod(boolean overridingMethod); + abstract Builder passedParameters(Iterable<Parameter> passedParameters); + abstract Builder providedParameters(Iterable<Parameter> providedParameters); + abstract Builder creationParameters(Iterable<Parameter> creationParameters); + abstract Builder isVarArgs(boolean isVarargs); + + abstract Builder exceptions(Iterable<? extends TypeMirror> exceptions); + abstract FactoryMethodDescriptor buildImpl(); FactoryMethodDescriptor build() { FactoryMethodDescriptor descriptor = buildImpl(); - checkState(descriptor.creationParameters().equals( - Sets.union(descriptor.passedParameters(), descriptor.providedParameters()))); + checkState( + descriptor + .creationParameters() + .equals(Sets.union(descriptor.passedParameters(), descriptor.providedParameters()))); return descriptor; } } 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 53b99cb5..b7f9c3e4 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 @@ -19,19 +19,22 @@ import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotatio 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.Objects.requireNonNull; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; import static javax.lang.model.element.Modifier.FINAL; 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.common.base.Function; -import com.google.common.base.Joiner; -import com.google.common.collect.FluentIterable; +import com.google.auto.common.AnnotationMirrors; +import com.google.auto.common.AnnotationValues; 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; @@ -43,13 +46,19 @@ 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.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; @@ -71,14 +80,10 @@ final class FactoryWriter { this.factoriesBeingCreated = factoriesBeingCreated; } - private static final Joiner ARGUMENT_JOINER = Joiner.on(", "); - - void writeFactory(FactoryDescriptor descriptor) - throws IOException { + void writeFactory(FactoryDescriptor descriptor) throws IOException { String factoryName = descriptor.name().className(); TypeSpec.Builder factory = - classBuilder(factoryName) - .addOriginatingElement(descriptor.declaration().targetType()); + classBuilder(factoryName).addOriginatingElement(descriptor.declaration().targetType()); generatedAnnotationSpec( elements, sourceVersion, @@ -145,7 +150,7 @@ final class FactoryWriter { ImmutableSet<TypeVariableName> factoryTypeVariables) { for (FactoryMethodDescriptor methodDescriptor : descriptor.methodDescriptors()) { MethodSpec.Builder method = - MethodSpec.methodBuilder(methodDescriptor.name()) + methodBuilder(methodDescriptor.name()) .addTypeVariables(getMethodTypeVariables(methodDescriptor, factoryTypeVariables)) .returns(TypeName.get(methodDescriptor.returnType())) .varargs(methodDescriptor.isVarArgs()); @@ -155,6 +160,8 @@ final class FactoryWriter { if (methodDescriptor.publicMethod()) { method.addModifiers(PUBLIC); } + method.addExceptions( + methodDescriptor.exceptions().stream().map(TypeName::get).collect(toList())); CodeBlock.Builder args = CodeBlock.builder(); method.addParameters(parameters(methodDescriptor.passedParameters())); Iterator<Parameter> parameters = methodDescriptor.creationParameters().iterator(); @@ -168,7 +175,7 @@ final class FactoryWriter { checkNotNull = false; } } else { - ProviderField provider = descriptor.providers().get(parameter.key()); + ProviderField provider = requireNonNull(descriptor.providers().get(parameter.key())); argument = CodeBlock.of(provider.name()); if (parameter.isProvider()) { // Providers are checked for nullness in the Factory's constructor. @@ -190,8 +197,7 @@ final class FactoryWriter { } } - private void addImplementationMethods( - TypeSpec.Builder factory, FactoryDescriptor descriptor) { + private void addImplementationMethods(TypeSpec.Builder factory, FactoryDescriptor descriptor) { for (ImplementationMethodDescriptor methodDescriptor : descriptor.implementationMethodDescriptors()) { MethodSpec.Builder implementationMethod = @@ -202,18 +208,12 @@ final class FactoryWriter { if (methodDescriptor.publicMethod()) { implementationMethod.addModifiers(PUBLIC); } + implementationMethod.addExceptions( + methodDescriptor.exceptions().stream().map(TypeName::get).collect(toList())); implementationMethod.addParameters(parameters(methodDescriptor.passedParameters())); implementationMethod.addStatement( "return create($L)", - FluentIterable.from(methodDescriptor.passedParameters()) - .transform( - new Function<Parameter, String>() { - @Override - public String apply(Parameter parameter) { - return parameter.name(); - } - }) - .join(ARGUMENT_JOINER)); + methodDescriptor.passedParameters().stream().map(Parameter::name).collect(joining(", "))); factory.addMethod(implementationMethod.build()); } } @@ -225,17 +225,43 @@ final class FactoryWriter { private ImmutableList<ParameterSpec> parameters(Iterable<Parameter> parameters) { ImmutableList.Builder<ParameterSpec> builder = ImmutableList.builder(); for (Parameter parameter : parameters) { - ParameterSpec.Builder parameterBuilder = - ParameterSpec.builder(resolveTypeName(parameter.type().get()), parameter.name()); - for (AnnotationMirror annotation : - Iterables.concat(parameter.nullable().asSet(), parameter.key().qualifier().asSet())) { - parameterBuilder.addAnnotation(AnnotationSpec.get(annotation)); - } - builder.add(parameterBuilder.build()); + 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()); + ParameterSpec parameterSpec = + ParameterSpec.builder(type, parameter.name()).addAnnotations(annotations).build(); + builder.add(parameterSpec); } 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)) { @@ -290,17 +316,20 @@ final class FactoryWriter { * {@code @AutoFactory class Foo} has a constructor parameter of type {@code BarFactory} and * {@code @AutoFactory class Bar} has a constructor parameter of type {@code FooFactory}. We did * in fact find instances of this in Google's source base. + * + * <p>If the type has type annotations then include those in the returned {@link TypeName}. */ private TypeName resolveTypeName(TypeMirror type) { - if (type.getKind() != TypeKind.ERROR) { - return TypeName.get(type); - } - ImmutableSet<PackageAndClass> factoryNames = factoriesBeingCreated.get(type.toString()); - if (factoryNames.size() == 1) { - PackageAndClass packageAndClass = Iterables.getOnlyElement(factoryNames); - return ClassName.get(packageAndClass.packageName(), packageAndClass.className()); + TypeName typeName = TypeName.get(type); + if (type.getKind() == TypeKind.ERROR) { + ImmutableSet<PackageAndClass> factoryNames = factoriesBeingCreated.get(type.toString()); + if (factoryNames.size() == 1) { + PackageAndClass packageAndClass = Iterables.getOnlyElement(factoryNames); + typeName = ClassName.get(packageAndClass.packageName(), packageAndClass.className()); + } } - return TypeName.get(type); + return typeName.annotated( + type.getAnnotationMirrors().stream().map(AnnotationSpec::get).collect(toList())); } private static ImmutableSet<TypeVariableName> getFactoryTypeVariables( diff --git a/factory/src/main/java/com/google/auto/factory/processor/ImplementationMethodDescriptor.java b/factory/src/main/java/com/google/auto/factory/processor/ImplementationMethodDescriptor.java index 9ddc249f..b2705b7e 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/ImplementationMethodDescriptor.java +++ b/factory/src/main/java/com/google/auto/factory/processor/ImplementationMethodDescriptor.java @@ -22,21 +22,25 @@ import javax.lang.model.type.TypeMirror; @AutoValue abstract class ImplementationMethodDescriptor { abstract String name(); + abstract TypeMirror returnType(); + abstract boolean publicMethod(); + abstract ImmutableSet<Parameter> passedParameters(); + abstract boolean isVarArgs(); + abstract ImmutableSet<TypeMirror> exceptions(); + static Builder builder() { - return new AutoValue_ImplementationMethodDescriptor.Builder() - .publicMethod(true) - .isVarArgs(false); + return new AutoValue_ImplementationMethodDescriptor.Builder(); } @AutoValue.Builder - static abstract class Builder { + abstract static class Builder { abstract Builder name(String name); - + abstract Builder returnType(TypeMirror returnTypeElement); abstract Builder publicMethod(boolean publicMethod); @@ -49,6 +53,8 @@ abstract class ImplementationMethodDescriptor { abstract Builder isVarArgs(boolean isVarargs); + abstract Builder exceptions(Iterable<? extends TypeMirror> exceptions); + abstract ImplementationMethodDescriptor build(); } } diff --git a/factory/src/main/java/com/google/auto/factory/processor/Key.java b/factory/src/main/java/com/google/auto/factory/processor/Key.java index 04bd4f37..728149eb 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/Key.java +++ b/factory/src/main/java/com/google/auto/factory/processor/Key.java @@ -24,9 +24,8 @@ import com.google.auto.common.AnnotationMirrors; import com.google.auto.common.MoreTypes; import com.google.auto.value.AutoValue; import com.google.common.base.Equivalence; -import com.google.common.base.Optional; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableSet; +import java.util.Collection; +import java.util.Optional; import javax.inject.Qualifier; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeMirror; @@ -65,17 +64,15 @@ abstract class Key { * <tr><td>{@code int} <td>{@code Integer} * </table> */ - static Key create( - TypeMirror type, Iterable<? extends AnnotationMirror> annotations, Types types) { - ImmutableSet.Builder<AnnotationMirror> qualifiers = ImmutableSet.builder(); - for (AnnotationMirror annotation : annotations) { - if (isAnnotationPresent(annotation.getAnnotationType().asElement(), Qualifier.class)) { - qualifiers.add(annotation); - } - } - + static Key create(TypeMirror type, Collection<AnnotationMirror> annotations, Types types) { // TODO(gak): check for only one qualifier rather than using the first - Optional<AnnotationMirror> qualifier = FluentIterable.from(qualifiers.build()).first(); + Optional<AnnotationMirror> qualifier = + annotations.stream() + .filter( + annotation -> + isAnnotationPresent( + annotation.getAnnotationType().asElement(), Qualifier.class)) + .findFirst(); TypeMirror keyType = isProvider(type) @@ -97,7 +94,7 @@ abstract class Key { } @Override - public String toString() { + public final String toString() { String typeQualifiedName = MoreTypes.asTypeElement(type().get()).toString(); return qualifier().isPresent() ? qualifier().get() + "/" + typeQualifiedName 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 c5b7d8bf..313fc9ee 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 @@ -17,11 +17,11 @@ package com.google.auto.factory.processor; import com.google.auto.common.MoreTypes; import com.google.common.base.Equivalence; -import com.google.common.base.Optional; import com.google.common.collect.ImmutableMap; import java.lang.annotation.Annotation; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import javax.inject.Provider; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; @@ -34,20 +34,23 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleElementVisitor6; final class Mirrors { - private Mirrors() { } + private Mirrors() {} static Name getQualifiedName(DeclaredType type) { - return type.asElement().accept(new SimpleElementVisitor6<Name, Void>() { - @Override - protected Name defaultAction(Element e, Void p) { - throw new AssertionError("DeclaredTypes should be TypeElements"); - } + return type.asElement() + .accept( + new SimpleElementVisitor6<Name, Void>() { + @Override + protected Name defaultAction(Element e, Void p) { + throw new AssertionError("DeclaredTypes should be TypeElements"); + } - @Override - public Name visitType(TypeElement e, Void p) { - return e.getQualifiedName(); - } - }, null); + @Override + public Name visitType(TypeElement e, Void p) { + return e.getQualifiedName(); + } + }, + null); } /** {@code true} if {@code type} is a {@link Provider}. */ @@ -62,8 +65,8 @@ final class Mirrors { static ImmutableMap<String, AnnotationValue> simplifyAnnotationValueMap( Map<? extends ExecutableElement, ? extends AnnotationValue> annotationValueMap) { ImmutableMap.Builder<String, AnnotationValue> builder = ImmutableMap.builder(); - for (Entry<? extends ExecutableElement, ? extends AnnotationValue> entry - : annotationValueMap.entrySet()) { + for (Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : + annotationValueMap.entrySet()) { builder.put(entry.getKey().getSimpleName().toString(), entry.getValue()); } return builder.build(); @@ -73,15 +76,13 @@ final class Mirrors { * Get the {@link AnnotationMirror} for the type {@code annotationType} present on the given * {@link Element} if it exists. */ - static Optional<AnnotationMirror> getAnnotationMirror(Element element, - Class<? extends Annotation> annotationType) { + static Optional<AnnotationMirror> getAnnotationMirror( + Element element, Class<? extends Annotation> annotationType) { String annotationName = annotationType.getName(); - for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { - if (getQualifiedName(annotationMirror.getAnnotationType()).contentEquals(annotationName)) { - return Optional.of(annotationMirror); - } - } - return Optional.absent(); + return element.getAnnotationMirrors().stream() + .filter(a -> getQualifiedName(a.getAnnotationType()).contentEquals(annotationName)) + .<AnnotationMirror>map(x -> x) // get rid of wildcard <? extends AnnotationMirror> + .findFirst(); } /** @@ -91,9 +92,7 @@ final class Mirrors { // TODO(ronshapiro): this is used in AutoFactory and Dagger, consider moving it into auto-common. static <T> Optional<Equivalence.Wrapper<T>> wrapOptionalInEquivalence( Equivalence<T> equivalence, Optional<T> optional) { - return optional.isPresent() - ? Optional.of(equivalence.wrap(optional.get())) - : Optional.<Equivalence.Wrapper<T>>absent(); + return optional.map(equivalence::wrap); } /** @@ -102,8 +101,6 @@ final class Mirrors { */ static <T> Optional<T> unwrapOptionalEquivalence( Optional<Equivalence.Wrapper<T>> wrappedOptional) { - return wrappedOptional.isPresent() - ? Optional.of(wrappedOptional.get().get()) - : Optional.<T>absent(); + return wrappedOptional.map(Equivalence.Wrapper::get); } } 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 781225a2..c5059ece 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 @@ -18,19 +18,20 @@ package com.google.auto.factory.processor; 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.base.Optional; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.stream.Stream; import javax.inject.Provider; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.TypeElement; @@ -63,6 +64,7 @@ abstract class Parameter { abstract String name(); abstract Key key(); + abstract Optional<Equivalence.Wrapper<AnnotationMirror>> nullableWrapper(); Optional<AnnotationMirror> nullable() { @@ -71,15 +73,12 @@ abstract class Parameter { private static Parameter forVariableElement( VariableElement variable, TypeMirror type, Types types) { - Optional<AnnotationMirror> nullable = Optional.absent(); - Iterable<? extends AnnotationMirror> annotations = - Iterables.concat(variable.getAnnotationMirrors(), type.getAnnotationMirrors()); - for (AnnotationMirror annotation : annotations) { - if (isNullable(annotation)) { - nullable = Optional.of(annotation); - break; - } - } + List<AnnotationMirror> annotations = + Stream.of(variable.getAnnotationMirrors(), type.getAnnotationMirrors()) + .flatMap(List::stream) + .collect(toList()); + Optional<AnnotationMirror> nullable = + annotations.stream().filter(Parameter::isNullable).findFirst(); Key key = Key.create(type, annotations, types); return new AutoValue_Parameter( @@ -108,7 +107,7 @@ abstract class Parameter { Set<String> names = Sets.newHashSetWithExpectedSize(variables.size()); for (int i = 0; i < variables.size(); i++) { Parameter parameter = forVariableElement(variables.get(i), variableTypes.get(i), types); - checkArgument(names.add(parameter.name())); + checkArgument(names.add(parameter.name()), "Duplicate parameter name: %s", parameter.name()); builder.add(parameter); } ImmutableSet<Parameter> parameters = builder.build(); diff --git a/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java b/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java index fe4c1fd2..bd88f837 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java +++ b/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java @@ -26,6 +26,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.ElementKindVisitor6; +import org.checkerframework.checker.nullness.qual.Nullable; final class ProvidedChecker { private final Messager messager; @@ -35,41 +36,54 @@ final class ProvidedChecker { } void checkProvidedParameter(Element element) { - checkArgument(isAnnotationPresent(element, Provided.class), "%s not annoated with @Provided", - element); - element.accept(new ElementKindVisitor6<Void, Void>() { - @Override - protected Void defaultAction(Element e, Void p) { - throw new AssertionError("Provided can only be applied to parameters"); - } - - @Override - public Void visitVariableAsParameter(final VariableElement providedParameter, Void p) { - providedParameter.getEnclosingElement().accept(new ElementKindVisitor6<Void, Void>() { + checkArgument( + isAnnotationPresent(element, Provided.class), "%s not annoated with @Provided", element); + element.accept( + new ElementKindVisitor6<@Nullable Void, @Nullable Void>() { @Override - protected Void defaultAction(Element e, Void p) { - raiseError(providedParameter, "@%s may only be applied to constructor parameters"); - return null; + protected @Nullable Void defaultAction(Element e, @Nullable Void p) { + throw new AssertionError("Provided can only be applied to parameters"); } @Override - public Void visitExecutableAsConstructor(ExecutableElement constructor, Void p) { - if (!(annotatedWithAutoFactory(constructor) - || annotatedWithAutoFactory(constructor.getEnclosingElement()))) { - raiseError(providedParameter, - "@%s may only be applied to constructors requesting an auto-factory"); - } + public @Nullable Void visitVariableAsParameter( + VariableElement providedParameter, @Nullable Void p) { + providedParameter + .getEnclosingElement() + .accept( + new ElementKindVisitor6<@Nullable Void, @Nullable Void>() { + @Override + protected @Nullable Void defaultAction(Element e, @Nullable Void p) { + raiseError( + providedParameter, "@%s may only be applied to constructor parameters"); + return null; + } + + @Override + public @Nullable Void visitExecutableAsConstructor( + ExecutableElement constructor, @Nullable Void p) { + if (!(annotatedWithAutoFactory(constructor) + || annotatedWithAutoFactory(constructor.getEnclosingElement()))) { + raiseError( + providedParameter, + "@%s may only be applied to constructors requesting an auto-factory"); + } + return null; + } + }, + p); return null; } - }, p); - return null; - } - }, null); + }, + null); } private void raiseError(VariableElement providedParameter, String messageFormat) { - messager.printMessage(ERROR, String.format(messageFormat, Provided.class.getSimpleName()), - providedParameter, Mirrors.getAnnotationMirror(providedParameter, Provided.class).get()); + messager.printMessage( + ERROR, + String.format(messageFormat, Provided.class.getSimpleName()), + providedParameter, + Mirrors.getAnnotationMirror(providedParameter, Provided.class).get()); } private static boolean annotatedWithAutoFactory(Element e) { diff --git a/factory/src/main/java/com/google/auto/factory/processor/ProviderField.java b/factory/src/main/java/com/google/auto/factory/processor/ProviderField.java index 855c1feb..99847ec0 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/ProviderField.java +++ b/factory/src/main/java/com/google/auto/factory/processor/ProviderField.java @@ -21,13 +21,15 @@ import static com.google.auto.factory.processor.Mirrors.wrapOptionalInEquivalenc import com.google.auto.common.AnnotationMirrors; import com.google.auto.value.AutoValue; import com.google.common.base.Equivalence; -import com.google.common.base.Optional; +import java.util.Optional; import javax.lang.model.element.AnnotationMirror; @AutoValue abstract class ProviderField { abstract String name(); + abstract Key key(); + abstract Optional<Equivalence.Wrapper<AnnotationMirror>> nullableWrapper(); Optional<AnnotationMirror> nullable() { diff --git a/factory/src/main/java/com/google/auto/factory/processor/TypeVariables.java b/factory/src/main/java/com/google/auto/factory/processor/TypeVariables.java index 1f894732..4bd546e8 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/TypeVariables.java +++ b/factory/src/main/java/com/google/auto/factory/processor/TypeVariables.java @@ -38,8 +38,8 @@ final class TypeVariables { return type.accept(ReferencedTypeVariables.INSTANCE, new HashSet<>()); } - private static final class ReferencedTypeVariables extends - SimpleTypeVisitor8<ImmutableSet<TypeVariable>, Set<Element>> { + private static final class ReferencedTypeVariables + extends SimpleTypeVisitor8<ImmutableSet<TypeVariable>, Set<Element>> { private static final ReferencedTypeVariables INSTANCE = new ReferencedTypeVariables(); @@ -53,8 +53,7 @@ final class TypeVariables { } @Override - public ImmutableSet<TypeVariable> visitDeclared( - DeclaredType t, Set<Element> visited) { + public ImmutableSet<TypeVariable> visitDeclared(DeclaredType t, Set<Element> visited) { if (!visited.add(t.asElement())) { return ImmutableSet.of(); } @@ -66,8 +65,7 @@ final class TypeVariables { } @Override - public ImmutableSet<TypeVariable> visitTypeVariable( - TypeVariable t, Set<Element> visited) { + public ImmutableSet<TypeVariable> visitTypeVariable(TypeVariable t, Set<Element> visited) { if (!visited.add(t.asElement())) { return ImmutableSet.of(); } @@ -79,8 +77,7 @@ final class TypeVariables { } @Override - public ImmutableSet<TypeVariable> visitUnion( - UnionType t, Set<Element> visited) { + public ImmutableSet<TypeVariable> visitUnion(UnionType t, Set<Element> visited) { ImmutableSet.Builder<TypeVariable> typeVariables = ImmutableSet.builder(); for (TypeMirror unionType : t.getAlternatives()) { typeVariables.addAll(unionType.accept(this, visited)); @@ -89,8 +86,7 @@ final class TypeVariables { } @Override - public ImmutableSet<TypeVariable> visitIntersection( - IntersectionType t, Set<Element> visited) { + public ImmutableSet<TypeVariable> visitIntersection(IntersectionType t, Set<Element> visited) { ImmutableSet.Builder<TypeVariable> typeVariables = ImmutableSet.builder(); for (TypeMirror intersectionType : t.getBounds()) { typeVariables.addAll(intersectionType.accept(this, visited)); @@ -99,8 +95,7 @@ final class TypeVariables { } @Override - public ImmutableSet<TypeVariable> visitWildcard( - WildcardType t, Set<Element> visited) { + public ImmutableSet<TypeVariable> visitWildcard(WildcardType t, Set<Element> visited) { ImmutableSet.Builder<TypeVariable> typeVariables = ImmutableSet.builder(); TypeMirror extendsBound = t.getExtendsBound(); if (extendsBound != null) { diff --git a/factory/src/main/java/com/google/auto/factory/processor/package-info.java b/factory/src/main/java/com/google/auto/factory/processor/package-info.java index 7339020a..4a7c4b10 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/package-info.java +++ b/factory/src/main/java/com/google/auto/factory/processor/package-info.java @@ -15,4 +15,5 @@ * This package contains the annotation processor that implements the * {@link com.google.auto.factory.AutoFactory} API. */ -package com.google.auto.factory.processor;
\ No newline at end of file +package com.google.auto.factory.processor; + diff --git a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryDeclarationTest.java b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryDeclarationTest.java index 1b23a65d..7bef2334 100644 --- a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryDeclarationTest.java +++ b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryDeclarationTest.java @@ -24,7 +24,8 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class AutoFactoryDeclarationTest { - @Test public void identifiers() { + @Test + public void identifiers() { assertThat(isValidIdentifier("String")).isTrue(); assertThat(isValidIdentifier("9CantStartWithNumber")).isFalse(); assertThat(isValidIdentifier("enum")).isFalse(); 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 3088bb2e..0df4c9ca 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,15 +15,16 @@ */ package com.google.auto.factory.processor; -import static com.google.common.truth.Truth.assertAbout; -import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; -import static com.google.testing.compile.JavaSourcesSubject.assertThat; -import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; +import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; +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 com.google.common.collect.ImmutableSet; import com.google.common.io.Resources; -import com.google.testing.compile.CompilationRule; +import com.google.testing.compile.Compilation; +import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; import java.io.IOException; import java.io.UncheckedIOException; @@ -31,415 +32,489 @@ import java.util.Collections; import java.util.List; import javax.lang.model.SourceVersion; import javax.tools.JavaFileObject; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** - * Functional tests for the {@link AutoFactoryProcessor}. - */ +/** Functional tests for the {@link AutoFactoryProcessor}. */ @RunWith(JUnit4.class) public class AutoFactoryProcessorTest { + private final Compiler javac = Compiler.javac().withProcessors(new AutoFactoryProcessor()); - @Rule public final CompilationRule compilationRule = new CompilationRule(); + @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")); + } - @Test public void simpleClass() { - assertAbout(javaSource()) - .that(JavaFileObjects.forResource("good/SimpleClass.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("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")); } @Test public void nestedClasses() { - assertAbout(javaSource()) - .that(JavaFileObjects.forResource("good/NestedClasses.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources( - loadExpectedFile("expected/NestedClasses_SimpleNestedClassFactory.java"), - loadExpectedFile("expected/NestedClassCustomNamedFactory.java")); - } - - @Test public void simpleClassNonFinal() { - assertAbout(javaSource()) - .that(JavaFileObjects.forResource("good/SimpleClassNonFinal.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/SimpleClassNonFinalFactory.java")); - } - - @Test public void publicClass() { - assertAbout(javaSource()) - .that(JavaFileObjects.forResource("good/PublicClass.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/PublicClassFactory.java")); - } - - @Test public void simpleClassCustomName() { - assertAbout(javaSource()) - .that(JavaFileObjects.forResource("good/SimpleClassCustomName.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/CustomNamedFactory.java")); - } - - @Test public void simpleClassMixedDeps() { - assertAbout(javaSources()) - .that( - ImmutableSet.of( - JavaFileObjects.forResource("good/SimpleClassMixedDeps.java"), - JavaFileObjects.forResource("support/AQualifier.java"))) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/SimpleClassMixedDepsFactory.java")); - } - - @Test public void simpleClassPassedDeps() { - assertAbout(javaSource()) - .that(JavaFileObjects.forResource("good/SimpleClassPassedDeps.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/SimpleClassPassedDepsFactory.java")); - } - - @Test public void simpleClassProvidedDeps() { - assertAbout(javaSources()) - .that( - ImmutableSet.of( - JavaFileObjects.forResource("support/AQualifier.java"), - JavaFileObjects.forResource("support/BQualifier.java"), - JavaFileObjects.forResource("good/SimpleClassProvidedDeps.java"))) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/SimpleClassProvidedDepsFactory.java")); + 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")); + } + + @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")); + } + + @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")); + } + + @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")); + } + + @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")); + } + + @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")); + } + + @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")); } @Test public void simpleClassProvidedProviderDeps() { - assertAbout(javaSources()) - .that( - ImmutableSet.of( - JavaFileObjects.forResource("support/AQualifier.java"), - JavaFileObjects.forResource("support/BQualifier.java"), - JavaFileObjects.forResource("good/SimpleClassProvidedProviderDeps.java"))) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/SimpleClassProvidedProviderDepsFactory.java")); - } - - @Test public void constructorAnnotated() { - assertAbout(javaSource()) - .that(JavaFileObjects.forResource("good/ConstructorAnnotated.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/ConstructorAnnotatedFactory.java")); - } - - @Test public void constructorAnnotatedNonFinal() { - assertAbout(javaSource()) - .that(JavaFileObjects.forResource("good/ConstructorAnnotatedNonFinal.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/ConstructorAnnotatedNonFinalFactory.java")); - } - - @Test public void simpleClassImplementingMarker() { - assertAbout(javaSource()) - .that(JavaFileObjects.forResource("good/SimpleClassImplementingMarker.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/SimpleClassImplementingMarkerFactory.java")); - } - - @Test public void simpleClassImplementingSimpleInterface() { - assertAbout(javaSource()) - .that(JavaFileObjects.forResource("good/SimpleClassImplementingSimpleInterface.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources( + 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")); + } + + @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")); + } + + @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")); + } + + @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")); + } + + @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")); + } + + @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")); } - @Test public void mixedDepsImplementingInterfaces() { - assertAbout(javaSource()) - .that(JavaFileObjects.forResource("good/MixedDepsImplementingInterfaces.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/MixedDepsImplementingInterfacesFactory.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")); } - @Test public void failsWithMixedFinals() { + @Test + public void failsWithMixedFinals() { JavaFileObject file = JavaFileObjects.forResource("bad/MixedFinals.java"); - assertAbout(javaSource()) - .that(file) - .processedWith(new AutoFactoryProcessor()) - .failsToCompile() - .withErrorContaining( + Compilation compilation = javac.compile(file); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.") - .in(file).onLine(24) - .and().withErrorContaining( + .inFile(file) + .onLine(24); + assertThat(compilation) + .hadErrorContaining( "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.") - .in(file).onLine(27); + .inFile(file) + .onLine(27); } - @Test public void providedButNoAutoFactory() { + @Test + public void providedButNoAutoFactory() { JavaFileObject file = JavaFileObjects.forResource("bad/ProvidedButNoAutoFactory.java"); - assertAbout(javaSource()) - .that(file) - .processedWith(new AutoFactoryProcessor()) - .failsToCompile() - .withErrorContaining( + Compilation compilation = javac.compile(file); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( "@Provided may only be applied to constructors requesting an auto-factory") - .in(file).onLine(21).atColumn(38); + .inFile(file) + .onLineContaining("@Provided"); } - @Test public void providedOnMethodParameter() { + @Test + public void providedOnMethodParameter() { JavaFileObject file = JavaFileObjects.forResource("bad/ProvidedOnMethodParameter.java"); - assertAbout(javaSource()) - .that(file) - .processedWith(new AutoFactoryProcessor()) - .failsToCompile() - .withErrorContaining( - "@Provided may only be applied to constructor parameters") - .in(file).onLine(21).atColumn(23); + 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() { + @Test + public void invalidCustomName() { JavaFileObject file = JavaFileObjects.forResource("bad/InvalidCustomName.java"); - assertAbout(javaSource()) - .that(file) - .processedWith(new AutoFactoryProcessor()) - .failsToCompile() - .withErrorContaining("\"SillyFactory!\" is not a valid Java identifier") - .in(file).onLine(20); + 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() { + Compilation compilation = + javac.compile(JavaFileObjects.forResource("good/FactoryExtendingAbstractClass.java")); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(compilation) + .generatedSourceFile("tests.FactoryExtendingAbstractClassFactory") + .hasSourceEquivalentTo( + loadExpectedFile("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")); } - @Test public void factoryExtendingAbstractClass() { - assertAbout(javaSource()) - .that(JavaFileObjects.forResource("good/FactoryExtendingAbstractClass.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/FactoryExtendingAbstractClassFactory.java")); + @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 factoryExtendingAbstractClass_withConstructorParams() { + @Test + public void factoryExtendingAbstractClass_multipleConstructors() { JavaFileObject file = - JavaFileObjects.forResource("good/FactoryExtendingAbstractClassWithConstructorParams.java"); - assertAbout(javaSource()) - .that(file) - .processedWith(new AutoFactoryProcessor()) - .failsToCompile() - .withErrorContaining( - "tests.FactoryExtendingAbstractClassWithConstructorParams.AbstractFactory " - + "is not a valid supertype for a factory. " - + "Factory supertypes must have a no-arg constructor.") - .in(file).onLine(21); - } - - @Test public void factoryExtendingAbstractClass_multipleConstructors() { - JavaFileObject file = JavaFileObjects.forResource( - "good/FactoryExtendingAbstractClassWithMultipleConstructors.java"); - assertAbout(javaSource()) - .that(file) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError(); - } - - @Test public void factoryExtendingInterface() { + JavaFileObjects.forResource( + "good/FactoryExtendingAbstractClassWithMultipleConstructors.java"); + Compilation compilation = javac.compile(file); + assertThat(compilation).succeededWithoutWarnings(); + } + + @Test + public void factoryExtendingInterface() { JavaFileObject file = JavaFileObjects.forResource("bad/InterfaceSupertype.java"); - assertAbout(javaSource()) - .that(file) - .processedWith(new AutoFactoryProcessor()) - .failsToCompile() - .withErrorContaining("java.lang.Runnable is not a valid supertype for a factory. " - + "Supertypes must be non-final classes.") - .in(file).onLine(20); + 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() { + @Test + public void factoryExtendingEnum() { JavaFileObject file = JavaFileObjects.forResource("bad/EnumSupertype.java"); - assertAbout(javaSource()) - .that(file) - .processedWith(new AutoFactoryProcessor()) - .failsToCompile() - .withErrorContaining( - "java.util.concurrent.TimeUnit is not a valid supertype for a factory. " - + "Supertypes must be non-final classes.") - .in(file).onLine(21); + 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() { + @Test + public void factoryExtendingFinalClass() { JavaFileObject file = JavaFileObjects.forResource("bad/FinalSupertype.java"); - assertAbout(javaSource()) - .that(file) - .processedWith(new AutoFactoryProcessor()) - .failsToCompile() - .withErrorContaining("java.lang.Boolean is not a valid supertype for a factory. " - + "Supertypes must be non-final classes.") - .in(file).onLine(20); + 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"); } - @Test public void factoryImplementingGenericInterfaceExtension() { + @Test + public void factoryImplementingGenericInterfaceExtension() { JavaFileObject file = JavaFileObjects.forResource("good/FactoryImplementingGenericInterfaceExtension.java"); - assertAbout(javaSource()) - .that(file) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources( + Compilation compilation = javac.compile(file); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(compilation) + .generatedSourceFile("tests.FactoryImplementingGenericInterfaceExtensionFactory") + .hasSourceEquivalentTo( loadExpectedFile("expected/FactoryImplementingGenericInterfaceExtensionFactory.java")); } - @Test public void multipleFactoriesImpementingInterface() { + @Test + public void multipleFactoriesImpementingInterface() { JavaFileObject file = JavaFileObjects.forResource("good/MultipleFactoriesImplementingInterface.java"); - assertAbout(javaSource()) - .that(file) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources( - loadExpectedFile("expected/MultipleFactoriesImplementingInterface_ClassAFactory.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")); } - @Test public void classUsingQualifierWithArgs() { - assertAbout(javaSources()) - .that( - ImmutableSet.of( - JavaFileObjects.forResource("support/QualifierWithArgs.java"), - JavaFileObjects.forResource("good/ClassUsingQualifierWithArgs.java"))) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/ClassUsingQualifierWithArgsFactory.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")); } - @Test public void factoryImplementingInterfaceWhichRedeclaresCreateMethods() { - JavaFileObject file = - JavaFileObjects.forResource("good/FactoryImplementingCreateMethod.java"); - assertAbout(javaSource()) - .that(file) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources( + @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")); } - @Test public void nullableParams() { - assertAbout(javaSources()) - .that( - ImmutableSet.of( - JavaFileObjects.forResource("good/SimpleClassNullableParameters.java"), - JavaFileObjects.forResource("support/AQualifier.java"), - JavaFileObjects.forResource("support/BQualifier.java"))) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/SimpleClassNullableParametersFactory.java")); - } - - @Test public void customNullableType() { - assertAbout(javaSource()) - .that(JavaFileObjects.forResource("good/CustomNullable.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/CustomNullableFactory.java")); - } - - @Test public void checkerFrameworkNullableType() { - assertAbout(javaSource()) - .that(JavaFileObjects.forResource("good/CheckerFrameworkNullable.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/CheckerFrameworkNullableFactory.java")); - } - - @Test public void multipleProvidedParamsWithSameKey() { - assertAbout(javaSource()) - .that(JavaFileObjects.forResource("good/MultipleProvidedParamsSameKey.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/MultipleProvidedParamsSameKeyFactory.java")); - } - - @Test public void providerArgumentToCreateMethod() { - assertAbout(javaSource()) - .that(JavaFileObjects.forResource("good/ProviderArgumentToCreateMethod.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/ProviderArgumentToCreateMethodFactory.java")); - } - - @Test public void multipleFactoriesConflictingParameterNames() { - assertThat( + @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")); + } + + @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")); + } + + @Test + public void checkerFrameworkNullableType() { + // TYPE_USE annotations are pretty much unusable with annotation processors on Java 8 because + // of bugs that mean they only appear in the javax.lang.model API when the compiler feels like + // 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")); + } + + @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")); + } + + @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")); + } + + @Test + public void multipleFactoriesConflictingParameterNames() { + Compilation compilation = + javac.compile( JavaFileObjects.forResource("good/MultipleFactoriesConflictingParameterNames.java"), - JavaFileObjects.forResource("support/AQualifier.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources( + JavaFileObjects.forResource("support/AQualifier.java")); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(compilation) + .generatedSourceFile("tests.MultipleFactoriesConflictingParameterNamesFactory") + .hasSourceEquivalentTo( loadExpectedFile("expected/MultipleFactoriesConflictingParameterNamesFactory.java")); } - @Test public void factoryVarargs() { - assertThat(JavaFileObjects.forResource("good/SimpleClassVarargs.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/SimpleClassVarargsFactory.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")); } - @Test public void onlyPrimitives() { - assertThat(JavaFileObjects.forResource("good/OnlyPrimitives.java")) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/OnlyPrimitivesFactory.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")); } @Test public void defaultPackage() { JavaFileObject file = JavaFileObjects.forResource("good/DefaultPackage.java"); - assertAbout(javaSource()) - .that(file) - .processedWith(new AutoFactoryProcessor()) - .compilesWithoutError() - .and() - .generatesSources(loadExpectedFile("expected/DefaultPackageFactory.java")); + Compilation compilation = javac.compile(file); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(compilation) + .generatedSourceFile("DefaultPackageFactory") + .hasSourceEquivalentTo(loadExpectedFile("expected/DefaultPackageFactory.java")); } private JavaFileObject loadExpectedFile(String resourceName) { + if (isJavaxAnnotationProcessingGeneratedAvailable()) { + return JavaFileObjects.forResource(resourceName); + } try { List<String> sourceLines = Resources.readLines(Resources.getResource(resourceName), UTF_8); - if (!isJavaxAnnotationProcessingGeneratedAvailable()) { - replaceGeneratedImport(sourceLines); - } + replaceGeneratedImport(sourceLines); return JavaFileObjects.forSourceLines( resourceName.replace('/', '.').replace(".java", ""), sourceLines); } catch (IOException e) { @@ -457,8 +532,8 @@ public class AutoFactoryProcessorTest { int lastImport = -1; for (String line : sourceLines) { if (line.startsWith("import ") && !line.startsWith("import static ")) { - firstImport = Math.min(firstImport, i); - lastImport = Math.max(lastImport, i); + firstImport = min(firstImport, i); + lastImport = max(lastImport, i); } i++; } diff --git a/factory/src/test/resources/good/FactoryExtendingAbstractClassWithConstructorParams.java b/factory/src/test/resources/bad/FactoryExtendingAbstractClassWithConstructorParams.java index 98c5f667..7c7120bf 100644 --- a/factory/src/test/resources/good/FactoryExtendingAbstractClassWithConstructorParams.java +++ b/factory/src/test/resources/bad/FactoryExtendingAbstractClassWithConstructorParams.java @@ -20,9 +20,9 @@ import tests.FactoryExtendingAbstractClassWithConstructorParams.AbstractFactory; @AutoFactory(extending = AbstractFactory.class) final class FactoryExtendingAbstractClassWithConstructorParams { - static abstract class AbstractFactory { + abstract static class AbstractFactory { protected AbstractFactory(Object obj) {} - + abstract FactoryExtendingAbstractClassWithConstructorParams newInstance(); } } diff --git a/factory/src/test/resources/bad/InvalidCustomName.java b/factory/src/test/resources/bad/InvalidCustomName.java index 5734ee7f..6d0a2f91 100644 --- a/factory/src/test/resources/bad/InvalidCustomName.java +++ b/factory/src/test/resources/bad/InvalidCustomName.java @@ -18,4 +18,4 @@ package tests; import com.google.auto.factory.AutoFactory; @AutoFactory(className = "SillyFactory!") -final class InvalidCustomName { } +final class InvalidCustomName {} diff --git a/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java b/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java index 79175c7e..faa83971 100644 --- a/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java +++ b/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java @@ -15,6 +15,7 @@ */ package tests; +import java.util.Map; import javax.annotation.processing.Generated; import javax.inject.Inject; import javax.inject.Provider; @@ -22,26 +23,34 @@ import org.checkerframework.checker.nullness.compatqual.NullableDecl; import org.checkerframework.checker.nullness.compatqual.NullableType; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" -) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class CheckerFrameworkNullableFactory { private final Provider<String> java_lang_StringProvider; + private final Provider<Map.@NullableType Entry<?, ?>> providedNestedNullableTypeProvider; + @Inject CheckerFrameworkNullableFactory( - Provider<String> java_lang_StringProvider) { + Provider<String> java_lang_StringProvider, + Provider<Map.@NullableType Entry<?, ?>> providedNestedNullableTypeProvider) { this.java_lang_StringProvider = checkNotNull(java_lang_StringProvider, 1); + this.providedNestedNullableTypeProvider = checkNotNull(providedNestedNullableTypeProvider, 2); } CheckerFrameworkNullable create( - @NullableDecl String nullableDecl, @NullableType String nullableType) { + @NullableDecl String nullableDecl, + @NullableType String nullableType, + Map.@NullableType Entry<?, ?> nestedNullableType) { return new CheckerFrameworkNullable( nullableDecl, java_lang_StringProvider.get(), nullableType, - java_lang_StringProvider.get()); + java_lang_StringProvider.get(), + nestedNullableType, + providedNestedNullableTypeProvider.get()); } private static <T> T checkNotNull(T reference, int argumentIndex) { diff --git a/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java b/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java index b5bd89c1..8d889eed 100644 --- a/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java +++ b/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java @@ -20,14 +20,15 @@ import javax.inject.Inject; import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class ClassUsingQualifierWithArgsFactory { private final Provider<String> providedDepAProvider; - @Inject ClassUsingQualifierWithArgsFactory( - @QualifierWithArgs(name="Fred", count=3) Provider<String> providedDepAProvider) { + @Inject + ClassUsingQualifierWithArgsFactory( + @QualifierWithArgs(name = "Fred", count = 3) Provider<String> providedDepAProvider) { this.providedDepAProvider = checkNotNull(providedDepAProvider, 1); } diff --git a/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java b/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java index 6e9d242e..22349851 100644 --- a/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java +++ b/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java @@ -20,13 +20,14 @@ import javax.inject.Inject; import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class ConstructorAnnotatedFactory { private final Provider<Object> objProvider; - @Inject ConstructorAnnotatedFactory(Provider<Object> objProvider) { + @Inject + ConstructorAnnotatedFactory(Provider<Object> objProvider) { this.objProvider = checkNotNull(objProvider, 1); } diff --git a/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java b/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java index f662642d..25ec894f 100644 --- a/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java +++ b/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java @@ -20,13 +20,14 @@ import javax.inject.Inject; import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) class ConstructorAnnotatedNonFinalFactory { private final Provider<Object> objProvider; - @Inject ConstructorAnnotatedNonFinalFactory(Provider<Object> objProvider) { + @Inject + ConstructorAnnotatedNonFinalFactory(Provider<Object> objProvider) { this.objProvider = checkNotNull(objProvider, 1); } diff --git a/factory/src/test/resources/expected/ConstructorAnnotatedThrowsFactory.java b/factory/src/test/resources/expected/ConstructorAnnotatedThrowsFactory.java new file mode 100644 index 00000000..05b30fdf --- /dev/null +++ b/factory/src/test/resources/expected/ConstructorAnnotatedThrowsFactory.java @@ -0,0 +1,59 @@ +/* + * Copyright 2020 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 java.io.IOException; +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/master/factory" + ) +final class ConstructorAnnotatedThrowsFactory { + private final Provider<Object> objProvider; + + @Inject + ConstructorAnnotatedThrowsFactory(Provider<Object> objProvider) { + this.objProvider = checkNotNull(objProvider, 1); + } + + ConstructorAnnotatedThrows create() throws IOException, InterruptedException { + return new ConstructorAnnotatedThrows(); + } + + ConstructorAnnotatedThrows create(String s) { + return new ConstructorAnnotatedThrows(checkNotNull(s, 1)); + } + + ConstructorAnnotatedThrows create(int i) throws IOException { + return new ConstructorAnnotatedThrows(checkNotNull(objProvider.get(), 1), i); + } + + ConstructorAnnotatedThrows create(char c) throws InterruptedException { + return new ConstructorAnnotatedThrows(checkNotNull(objProvider.get(), 1), c); + } + + private static <T> T checkNotNull(T reference, int argumentIndex) { + if (reference == null) { + throw new NullPointerException( + "@AutoFactory method argument is null but is not marked @Nullable. Argument index: " + + argumentIndex); + } + return reference; + } +} diff --git a/factory/src/test/resources/expected/CustomNamedFactory.java b/factory/src/test/resources/expected/CustomNamedFactory.java index c388387c..512c244c 100644 --- a/factory/src/test/resources/expected/CustomNamedFactory.java +++ b/factory/src/test/resources/expected/CustomNamedFactory.java @@ -19,11 +19,12 @@ import javax.annotation.processing.Generated; import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class CustomNamedFactory { - @Inject CustomNamedFactory() {} + @Inject + CustomNamedFactory() {} SimpleClassCustomName create() { return new SimpleClassCustomName(); diff --git a/factory/src/test/resources/expected/CustomNullableFactory.java b/factory/src/test/resources/expected/CustomNullableFactory.java index 31bb5bfa..c8f2f286 100644 --- a/factory/src/test/resources/expected/CustomNullableFactory.java +++ b/factory/src/test/resources/expected/CustomNullableFactory.java @@ -20,9 +20,9 @@ import javax.inject.Inject; import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class CustomNullableFactory { private final Provider<Object> objectProvider; diff --git a/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java b/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java index c56afb0a..f35b414e 100644 --- a/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java +++ b/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java @@ -19,18 +19,20 @@ import javax.annotation.processing.Generated; import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class FactoryExtendingAbstractClassFactory extends FactoryExtendingAbstractClass.AbstractFactory { - @Inject FactoryExtendingAbstractClassFactory() {} + @Inject + FactoryExtendingAbstractClassFactory() {} FactoryExtendingAbstractClass create() { return new FactoryExtendingAbstractClass(); } - @Override public FactoryExtendingAbstractClass newInstance() { + @Override + public FactoryExtendingAbstractClass newInstance() { return create(); } } diff --git a/factory/src/test/resources/expected/FactoryExtendingAbstractClassThrowsFactory.java b/factory/src/test/resources/expected/FactoryExtendingAbstractClassThrowsFactory.java new file mode 100644 index 00000000..402f8946 --- /dev/null +++ b/factory/src/test/resources/expected/FactoryExtendingAbstractClassThrowsFactory.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 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 java.io.IOException; +import javax.annotation.processing.Generated; +import javax.inject.Inject; + +@Generated( + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) +final class FactoryExtendingAbstractClassThrowsFactory + extends FactoryExtendingAbstractClassThrows.AbstractFactory { + @Inject + FactoryExtendingAbstractClassThrowsFactory() {} + + FactoryExtendingAbstractClassThrows create() throws IOException, InterruptedException { + return new FactoryExtendingAbstractClassThrows(); + } + + @Override + public FactoryExtendingAbstractClassThrows newInstance() throws Exception { + return create(); + } +} diff --git a/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java b/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java index f23dc9ab..2d8a392d 100644 --- a/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java +++ b/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java @@ -20,9 +20,9 @@ import javax.annotation.processing.Generated; import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class FactoryImplementingCreateMethod_ConcreteClassFactory implements FactoryImplementingCreateMethod.FactoryInterfaceWithCreateMethod { @@ -40,7 +40,8 @@ final class FactoryImplementingCreateMethod_ConcreteClassFactory } @Override - public FactoryImplementingCreateMethod.ConcreteClass create(List<Integer> genericWithDifferentArgumentName) { + public FactoryImplementingCreateMethod.ConcreteClass create( + List<Integer> genericWithDifferentArgumentName) { return new FactoryImplementingCreateMethod.ConcreteClass( checkNotNull(genericWithDifferentArgumentName, 1)); } diff --git a/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java b/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java index 3bfc5262..9f2bf0ae 100644 --- a/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java +++ b/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java @@ -20,20 +20,23 @@ import javax.inject.Inject; import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class FactoryImplementingGenericInterfaceExtensionFactory implements FactoryImplementingGenericInterfaceExtension.MyFactory { private final Provider<String> sProvider; + @Inject FactoryImplementingGenericInterfaceExtensionFactory(Provider<String> sProvider) { this.sProvider = checkNotNull(sProvider, 1); } + FactoryImplementingGenericInterfaceExtension create(Integer i) { return new FactoryImplementingGenericInterfaceExtension( checkNotNull(sProvider.get(), 1), checkNotNull(i, 2)); } + @Override public FactoryImplementingGenericInterfaceExtension make(Integer arg) { return create(arg); diff --git a/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java b/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java index 19f2c138..ec4089b7 100644 --- a/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java +++ b/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java @@ -23,15 +23,18 @@ import javax.inject.Provider; * @author Gregory Kick */ @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" -) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class MixedDepsImplementingInterfacesFactory - implements MixedDepsImplementingInterfaces.FromInt, MixedDepsImplementingInterfaces.FromObject, - MixedDepsImplementingInterfaces.MarkerA, MixedDepsImplementingInterfaces.MarkerB { + implements MixedDepsImplementingInterfaces.FromInt, + MixedDepsImplementingInterfaces.FromObject, + MixedDepsImplementingInterfaces.MarkerA, + MixedDepsImplementingInterfaces.MarkerB { private final Provider<String> sProvider; - @Inject MixedDepsImplementingInterfacesFactory(Provider<String> sProvider) { + @Inject + MixedDepsImplementingInterfacesFactory(Provider<String> sProvider) { this.sProvider = checkNotNull(sProvider, 1); } @@ -43,11 +46,13 @@ final class MixedDepsImplementingInterfacesFactory return new MixedDepsImplementingInterfaces(checkNotNull(o, 1)); } - @Override public MixedDepsImplementingInterfaces fromInt(int i) { + @Override + public MixedDepsImplementingInterfaces fromInt(int i) { return create(i); } - @Override public MixedDepsImplementingInterfaces fromObject(Object o) { + @Override + public MixedDepsImplementingInterfaces fromObject(Object o) { return create(o); } diff --git a/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java b/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java index fac6e13a..3eaf3afa 100644 --- a/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java +++ b/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.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" -) + ) final class MultipleFactoriesConflictingParameterNamesFactory { private final Provider<String> stringProvider; diff --git a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java index 5ee2b2fe..6fcfb036 100644 --- a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java +++ b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java @@ -19,9 +19,9 @@ import javax.annotation.processing.Generated; import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class MultipleFactoriesImplementingInterface_ClassAFactory implements MultipleFactoriesImplementingInterface.Base.Factory { @Inject diff --git a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java index f6540683..56646891 100644 --- a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java +++ b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java @@ -19,9 +19,9 @@ import javax.annotation.processing.Generated; import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class MultipleFactoriesImplementingInterface_ClassBFactory implements MultipleFactoriesImplementingInterface.Base.Factory { @Inject diff --git a/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java b/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java index de7bad72..97cc8ac2 100644 --- a/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java +++ b/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java @@ -20,9 +20,9 @@ import javax.inject.Inject; import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class MultipleProvidedParamsSameKeyFactory { private final Provider<String> java_lang_StringProvider; diff --git a/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java b/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java index bf6a4681..fe7aa1a9 100644 --- a/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java +++ b/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java @@ -19,11 +19,12 @@ import javax.annotation.processing.Generated; import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class NestedClassCustomNamedFactory { - @Inject NestedClassCustomNamedFactory() {} + @Inject + NestedClassCustomNamedFactory() {} NestedClasses.SimpleNestedClassWithCustomFactory create() { return new NestedClasses.SimpleNestedClassWithCustomFactory(); diff --git a/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java b/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java index f982e86f..41ecc52e 100644 --- a/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java +++ b/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java @@ -19,11 +19,12 @@ import javax.annotation.processing.Generated; import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class NestedClasses_SimpleNestedClassFactory { - @Inject NestedClasses_SimpleNestedClassFactory() {} + @Inject + NestedClasses_SimpleNestedClassFactory() {} NestedClasses.SimpleNestedClass create() { return new NestedClasses.SimpleNestedClass(); diff --git a/factory/src/test/resources/expected/OnlyPrimitivesFactory.java b/factory/src/test/resources/expected/OnlyPrimitivesFactory.java index ec60c58e..b931a222 100644 --- a/factory/src/test/resources/expected/OnlyPrimitivesFactory.java +++ b/factory/src/test/resources/expected/OnlyPrimitivesFactory.java @@ -19,11 +19,12 @@ import javax.annotation.processing.Generated; import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class OnlyPrimitivesFactory { - @Inject OnlyPrimitivesFactory() {} + @Inject + OnlyPrimitivesFactory() {} OnlyPrimitives create(int i, long l) { return new OnlyPrimitives(i, l); diff --git a/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java b/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java index 4d1a4cf5..75a6291c 100644 --- a/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java +++ b/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java @@ -22,10 +22,11 @@ import javax.inject.Provider; @Generated( value = "com.google.auto.factory.processor.AutoFactoryProcessor", comments = "https://github.com/google/auto/tree/master/factory" -) + ) final class ProviderArgumentToCreateMethodFactory - implements ProviderArgumentToCreateMethod.CustomCreator{ - @Inject ProviderArgumentToCreateMethodFactory() {} + implements ProviderArgumentToCreateMethod.CustomCreator { + @Inject + ProviderArgumentToCreateMethodFactory() {} ProviderArgumentToCreateMethod create(Provider<String> stringProvider) { return new ProviderArgumentToCreateMethod(checkNotNull(stringProvider, 1)); diff --git a/factory/src/test/resources/expected/PublicClassFactory.java b/factory/src/test/resources/expected/PublicClassFactory.java index 06671dc6..9e5c113d 100644 --- a/factory/src/test/resources/expected/PublicClassFactory.java +++ b/factory/src/test/resources/expected/PublicClassFactory.java @@ -19,11 +19,12 @@ import javax.annotation.processing.Generated; import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) public final class PublicClassFactory { - @Inject public PublicClassFactory() {} + @Inject + public PublicClassFactory() {} public PublicClass create() { return new PublicClass(); diff --git a/factory/src/test/resources/expected/SimpleClassFactory.java b/factory/src/test/resources/expected/SimpleClassFactory.java index 308d2cdc..4741b752 100644 --- a/factory/src/test/resources/expected/SimpleClassFactory.java +++ b/factory/src/test/resources/expected/SimpleClassFactory.java @@ -19,11 +19,12 @@ import javax.annotation.processing.Generated; import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassFactory { - @Inject SimpleClassFactory() {} + @Inject + SimpleClassFactory() {} SimpleClass create() { return new SimpleClass(); diff --git a/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java b/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java index 6c611e99..17701387 100644 --- a/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java +++ b/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java @@ -20,11 +20,12 @@ import javax.annotation.processing.Generated; import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassImplementingMarkerFactory implements RandomAccess { - @Inject SimpleClassImplementingMarkerFactory() {} + @Inject + SimpleClassImplementingMarkerFactory() {} SimpleClassImplementingMarker create() { return new SimpleClassImplementingMarker(); diff --git a/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java b/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java index 720e7d0f..7dd91bea 100644 --- a/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java +++ b/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java @@ -19,18 +19,20 @@ import javax.annotation.processing.Generated; import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassImplementingSimpleInterfaceFactory implements SimpleClassImplementingSimpleInterface.SimpleInterface { - @Inject SimpleClassImplementingSimpleInterfaceFactory() {} + @Inject + SimpleClassImplementingSimpleInterfaceFactory() {} SimpleClassImplementingSimpleInterface create() { return new SimpleClassImplementingSimpleInterface(); } - @Override public SimpleClassImplementingSimpleInterface newInstance() { + @Override + public SimpleClassImplementingSimpleInterface newInstance() { return create(); } } diff --git a/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java b/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java index ccdea61c..b69ea326 100644 --- a/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java +++ b/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java @@ -20,14 +20,14 @@ import javax.inject.Inject; import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassMixedDepsFactory { private final Provider<String> providedDepAProvider; - @Inject SimpleClassMixedDepsFactory( - @AQualifier Provider<String> providedDepAProvider) { + @Inject + SimpleClassMixedDepsFactory(@AQualifier Provider<String> providedDepAProvider) { this.providedDepAProvider = checkNotNull(providedDepAProvider, 1); } diff --git a/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java b/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java index d323812e..5ab90306 100644 --- a/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java +++ b/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java @@ -19,11 +19,12 @@ import javax.annotation.processing.Generated; import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) class SimpleClassNonFinalFactory { - @Inject SimpleClassNonFinalFactory() {} + @Inject + SimpleClassNonFinalFactory() {} SimpleClassNonFinal create() { return new SimpleClassNonFinal(); diff --git a/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java b/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java index e3540386..5b955964 100644 --- a/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java +++ b/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java @@ -21,9 +21,9 @@ import javax.inject.Inject; import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassNullableParametersFactory { private final Provider<String> providedNullableProvider; diff --git a/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java b/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java index 3260c36e..9cc8a166 100644 --- a/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java +++ b/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java @@ -19,11 +19,12 @@ import javax.annotation.processing.Generated; import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassPassedDepsFactory { - @Inject SimpleClassPassedDepsFactory() {} + @Inject + SimpleClassPassedDepsFactory() {} SimpleClassPassedDeps create(String depA, String depB) { return new SimpleClassPassedDeps(checkNotNull(depA, 1), checkNotNull(depB, 2)); diff --git a/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java b/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java index 05d1e5ab..52448aad 100644 --- a/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java +++ b/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java @@ -20,9 +20,9 @@ import javax.inject.Inject; import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassProvidedDepsFactory { private final Provider<Integer> providedPrimitiveAProvider; private final Provider<Integer> providedPrimitiveBProvider; diff --git a/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java b/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java index aafdcec2..7bf2372c 100644 --- a/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java +++ b/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java @@ -20,9 +20,9 @@ import javax.inject.Inject; import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassProvidedProviderDepsFactory { private final Provider<String> providedDepAProvider; private final Provider<String> providedDepBProvider; diff --git a/factory/src/test/resources/expected/SimpleClassThrowsFactory.java b/factory/src/test/resources/expected/SimpleClassThrowsFactory.java new file mode 100644 index 00000000..eda503a4 --- /dev/null +++ b/factory/src/test/resources/expected/SimpleClassThrowsFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright 2020 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 java.io.IOException; +import javax.annotation.processing.Generated; +import javax.inject.Inject; + +@Generated( + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) +final class SimpleClassThrowsFactory { + @Inject + SimpleClassThrowsFactory() {} + + SimpleClassThrows create() throws IOException, InterruptedException { + return new SimpleClassThrows(); + } +} diff --git a/factory/src/test/resources/expected/SimpleClassVarargsFactory.java b/factory/src/test/resources/expected/SimpleClassVarargsFactory.java index 51c7f466..ac7c4bdc 100644 --- a/factory/src/test/resources/expected/SimpleClassVarargsFactory.java +++ b/factory/src/test/resources/expected/SimpleClassVarargsFactory.java @@ -21,9 +21,10 @@ import javax.inject.Inject; @Generated( value = "com.google.auto.factory.processor.AutoFactoryProcessor", comments = "https://github.com/google/auto/tree/master/factory" -) + ) final class SimpleClassVarargsFactory implements SimpleClassVarargs.InterfaceWithVarargs { - @Inject SimpleClassVarargsFactory() {} + @Inject + SimpleClassVarargsFactory() {} SimpleClassVarargs create(String... args) { return new SimpleClassVarargs(checkNotNull(args, 1)); diff --git a/factory/src/test/resources/good/CheckerFrameworkNullable.java b/factory/src/test/resources/good/CheckerFrameworkNullable.java index 7f2a0fee..8b9cbc26 100644 --- a/factory/src/test/resources/good/CheckerFrameworkNullable.java +++ b/factory/src/test/resources/good/CheckerFrameworkNullable.java @@ -17,6 +17,7 @@ package tests; import com.google.auto.factory.AutoFactory; import com.google.auto.factory.Provided; +import java.util.Map; import org.checkerframework.checker.nullness.compatqual.NullableDecl; import org.checkerframework.checker.nullness.compatqual.NullableType; @@ -27,5 +28,7 @@ final class CheckerFrameworkNullable { @NullableDecl String nullableDecl, @Provided @NullableDecl String providedNullableDecl, @NullableType String nullableType, - @Provided @NullableType String providedNullableType) {} + @Provided @NullableType String providedNullableType, + Map.@NullableType Entry<?, ?> nestedNullableType, + @Provided Map.@NullableType Entry<?, ?> providedNestedNullableType) {} } diff --git a/factory/src/test/resources/good/ConstructorAnnotated.java b/factory/src/test/resources/good/ConstructorAnnotated.java index fdc02f35..ddb154f4 100644 --- a/factory/src/test/resources/good/ConstructorAnnotated.java +++ b/factory/src/test/resources/good/ConstructorAnnotated.java @@ -19,9 +19,17 @@ import com.google.auto.factory.AutoFactory; import com.google.auto.factory.Provided; final class ConstructorAnnotated { - @AutoFactory ConstructorAnnotated() {} + @AutoFactory + ConstructorAnnotated() {} + ConstructorAnnotated(Object obj) {} - @AutoFactory ConstructorAnnotated(String s) {} - @AutoFactory ConstructorAnnotated(@Provided Object obj, int i) {} - @AutoFactory ConstructorAnnotated(@Provided Object obj, char c) {} + + @AutoFactory + ConstructorAnnotated(String s) {} + + @AutoFactory + ConstructorAnnotated(@Provided Object obj, int i) {} + + @AutoFactory + ConstructorAnnotated(@Provided Object obj, char c) {} } diff --git a/factory/src/test/resources/good/ConstructorAnnotatedNonFinal.java b/factory/src/test/resources/good/ConstructorAnnotatedNonFinal.java index 5bed1e60..1b10e79e 100644 --- a/factory/src/test/resources/good/ConstructorAnnotatedNonFinal.java +++ b/factory/src/test/resources/good/ConstructorAnnotatedNonFinal.java @@ -19,9 +19,17 @@ import com.google.auto.factory.AutoFactory; import com.google.auto.factory.Provided; final class ConstructorAnnotatedNonFinal { - @AutoFactory(allowSubclasses = true) ConstructorAnnotatedNonFinal() {} + @AutoFactory(allowSubclasses = true) + ConstructorAnnotatedNonFinal() {} + ConstructorAnnotatedNonFinal(Object obj) {} - @AutoFactory(allowSubclasses = true) ConstructorAnnotatedNonFinal(String s) {} - @AutoFactory(allowSubclasses = true) ConstructorAnnotatedNonFinal(@Provided Object obj, int i) {} - @AutoFactory(allowSubclasses = true) ConstructorAnnotatedNonFinal(@Provided Object obj, char c) {} + + @AutoFactory(allowSubclasses = true) + ConstructorAnnotatedNonFinal(String s) {} + + @AutoFactory(allowSubclasses = true) + ConstructorAnnotatedNonFinal(@Provided Object obj, int i) {} + + @AutoFactory(allowSubclasses = true) + ConstructorAnnotatedNonFinal(@Provided Object obj, char c) {} } diff --git a/factory/src/test/resources/good/ConstructorAnnotatedThrows.java b/factory/src/test/resources/good/ConstructorAnnotatedThrows.java new file mode 100644 index 00000000..58a52d0b --- /dev/null +++ b/factory/src/test/resources/good/ConstructorAnnotatedThrows.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 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.auto.factory.Provided; +import java.io.IOException; + +final class ConstructorAnnotatedThrows { + @AutoFactory + ConstructorAnnotatedThrows() throws IOException, InterruptedException {} + + ConstructorAnnotatedThrows(Object obj) {} + + @AutoFactory + ConstructorAnnotatedThrows(String s) {} + + @AutoFactory + ConstructorAnnotatedThrows(@Provided Object obj, int i) throws IOException {} + + @AutoFactory + ConstructorAnnotatedThrows(@Provided Object obj, char c) throws InterruptedException {} +} diff --git a/factory/src/test/resources/good/FactoryExtendingAbstractClass.java b/factory/src/test/resources/good/FactoryExtendingAbstractClass.java index 5511e99e..bd3a4dc7 100644 --- a/factory/src/test/resources/good/FactoryExtendingAbstractClass.java +++ b/factory/src/test/resources/good/FactoryExtendingAbstractClass.java @@ -20,7 +20,7 @@ import tests.FactoryExtendingAbstractClass.AbstractFactory; @AutoFactory(extending = AbstractFactory.class) final class FactoryExtendingAbstractClass { - static abstract class AbstractFactory { + abstract static class AbstractFactory { abstract FactoryExtendingAbstractClass newInstance(); } } diff --git a/factory/src/test/resources/good/FactoryExtendingAbstractClassThrows.java b/factory/src/test/resources/good/FactoryExtendingAbstractClassThrows.java new file mode 100644 index 00000000..2ac43f3f --- /dev/null +++ b/factory/src/test/resources/good/FactoryExtendingAbstractClassThrows.java @@ -0,0 +1,29 @@ +/* + * Copyright 2020 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 java.io.IOException; +import tests.FactoryExtendingAbstractClassThrows.AbstractFactory; + +@AutoFactory(extending = AbstractFactory.class) +final class FactoryExtendingAbstractClassThrows { + FactoryExtendingAbstractClassThrows() throws IOException, InterruptedException {} + + abstract static class AbstractFactory { + abstract FactoryExtendingAbstractClassThrows newInstance() throws Exception; + } +} diff --git a/factory/src/test/resources/good/FactoryExtendingAbstractClassWithMultipleConstructors.java b/factory/src/test/resources/good/FactoryExtendingAbstractClassWithMultipleConstructors.java index 43e94ce1..20e0b838 100644 --- a/factory/src/test/resources/good/FactoryExtendingAbstractClassWithMultipleConstructors.java +++ b/factory/src/test/resources/good/FactoryExtendingAbstractClassWithMultipleConstructors.java @@ -20,10 +20,11 @@ import tests.FactoryExtendingAbstractClassWithMultipleConstructors.AbstractFacto @AutoFactory(extending = AbstractFactory.class) final class FactoryExtendingAbstractClassWithMultipleConstructors { - static abstract class AbstractFactory { + abstract static class AbstractFactory { protected AbstractFactory(Object obj) {} + protected AbstractFactory() {} - + abstract FactoryExtendingAbstractClassWithMultipleConstructors newInstance(); } } diff --git a/factory/src/test/resources/good/FactoryImplementingCreateMethod.java b/factory/src/test/resources/good/FactoryImplementingCreateMethod.java index db15eefe..d2659667 100644 --- a/factory/src/test/resources/good/FactoryImplementingCreateMethod.java +++ b/factory/src/test/resources/good/FactoryImplementingCreateMethod.java @@ -26,7 +26,7 @@ final class FactoryImplementingCreateMethod { Interface create(); Interface create(int a); - + Interface create(List<Integer> generic); } diff --git a/factory/src/test/resources/good/MixedDepsImplementingInterfaces.java b/factory/src/test/resources/good/MixedDepsImplementingInterfaces.java index c7435edd..05ee0df9 100644 --- a/factory/src/test/resources/good/MixedDepsImplementingInterfaces.java +++ b/factory/src/test/resources/good/MixedDepsImplementingInterfaces.java @@ -24,18 +24,18 @@ import com.google.auto.factory.Provided; final class MixedDepsImplementingInterfaces { @AutoFactory(implementing = {FromInt.class, MarkerA.class}) MixedDepsImplementingInterfaces(@Provided String s, int i) {} - + @AutoFactory(implementing = {FromObject.class, MarkerB.class}) MixedDepsImplementingInterfaces(Object o) {} interface FromInt { MixedDepsImplementingInterfaces fromInt(int i); } - + interface FromObject { MixedDepsImplementingInterfaces fromObject(Object o); } - + interface MarkerA {} interface MarkerB {} diff --git a/factory/src/test/resources/good/MultipleFactoriesImplementingInterface.java b/factory/src/test/resources/good/MultipleFactoriesImplementingInterface.java index 2eecf1ae..f7709ec0 100644 --- a/factory/src/test/resources/good/MultipleFactoriesImplementingInterface.java +++ b/factory/src/test/resources/good/MultipleFactoriesImplementingInterface.java @@ -17,7 +17,7 @@ package tests; import com.google.auto.factory.AutoFactory; -class MultipleFactoriesImplementingInterface { +class MultipleFactoriesImplementingInterface { static interface Base { static interface Factory { public abstract Base abstractNonDefaultCreate(); @@ -25,8 +25,8 @@ class MultipleFactoriesImplementingInterface { } @AutoFactory(implementing = Base.Factory.class) - static class ClassA implements Base { } + static class ClassA implements Base {} @AutoFactory(implementing = Base.Factory.class) static class ClassB implements Base {} -} +} diff --git a/factory/src/test/resources/good/SimpleClassImplementingMarker.java b/factory/src/test/resources/good/SimpleClassImplementingMarker.java index 24e3abc0..52a1fd5a 100644 --- a/factory/src/test/resources/good/SimpleClassImplementingMarker.java +++ b/factory/src/test/resources/good/SimpleClassImplementingMarker.java @@ -1,11 +1,11 @@ /* * 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 @@ -17,5 +17,4 @@ import com.google.auto.factory.AutoFactory; import java.util.RandomAccess; @AutoFactory(implementing = RandomAccess.class) -class SimpleClassImplementingMarker { -} +class SimpleClassImplementingMarker {} diff --git a/factory/src/test/resources/good/SimpleClassProvidedDeps.java b/factory/src/test/resources/good/SimpleClassProvidedDeps.java index ffcefd2a..fa9e0c41 100644 --- a/factory/src/test/resources/good/SimpleClassProvidedDeps.java +++ b/factory/src/test/resources/good/SimpleClassProvidedDeps.java @@ -1,11 +1,11 @@ /* * 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 diff --git a/factory/src/test/resources/good/SimpleClassThrows.java b/factory/src/test/resources/good/SimpleClassThrows.java new file mode 100644 index 00000000..67155952 --- /dev/null +++ b/factory/src/test/resources/good/SimpleClassThrows.java @@ -0,0 +1,24 @@ +/* + * Copyright 2020 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 java.io.IOException; + +@AutoFactory +final class SimpleClassThrows { + SimpleClassThrows() throws IOException, InterruptedException {} +} diff --git a/factory/src/test/resources/support/QualifierWithArgs.java b/factory/src/test/resources/support/QualifierWithArgs.java index 81e3f84b..89f54eb6 100644 --- a/factory/src/test/resources/support/QualifierWithArgs.java +++ b/factory/src/test/resources/support/QualifierWithArgs.java @@ -29,5 +29,6 @@ import javax.inject.Qualifier; @Retention(RUNTIME) @interface QualifierWithArgs { String name(); + int count(); } diff --git a/service/pom.xml b/service/pom.xml index a64ca050..e29bdc37 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -37,8 +37,8 @@ <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> - <guava.version>27.0.1-jre</guava.version> - <truth.version>1.0.1</truth.version> + <guava.version>30.1.1-jre</guava.version> + <truth.version>1.1.3</truth.version> </properties> <scm> @@ -90,7 +90,7 @@ <dependency> <groupId>com.google.testing.compile</groupId> <artifactId>compile-testing</artifactId> - <version>0.18</version> + <version>0.19</version> </dependency> <dependency> <groupId>com.google.truth</groupId> @@ -100,7 +100,7 @@ <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> - <version>4.12</version> + <version>4.13.2</version> </dependency> </dependencies> </dependencyManagement> @@ -111,7 +111,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.7.0</version> + <version>3.8.1</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>0.9.4</version> + <version>1.0.7</version> </dependency> </dependencies> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.2.0</version> </plugin> </plugins> </pluginManagement> diff --git a/service/processor/pom.xml b/service/processor/pom.xml index 22fc20e9..262493a9 100644 --- a/service/processor/pom.xml +++ b/service/processor/pom.xml @@ -49,7 +49,7 @@ <dependency> <groupId>com.google.auto</groupId> <artifactId>auto-common</artifactId> - <version>0.10</version> + <version>1.1</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 3bf42d91..f12299a5 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 @@ -17,8 +17,10 @@ package com.google.auto.service.processor; import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; import static com.google.auto.common.MoreElements.getAnnotationMirror; -import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.auto.common.MoreStreams.toImmutableSet; +import static com.google.common.base.Throwables.getStackTraceAsString; +import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.auto.service.AutoService; import com.google.common.annotations.VisibleForTesting; @@ -28,8 +30,7 @@ import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import java.io.IOException; import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.StringWriter; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -57,10 +58,11 @@ import javax.tools.StandardLocation; * configuration files described in {@link java.util.ServiceLoader}. * <p> * Processor Options:<ul> - * <li>debug - turns on debug statements</li> + * <li>{@code -Adebug} - turns on debug statements</li> + * <li>{@code -Averify=true} - turns on extra verification</li> * </ul> */ -@SupportedOptions({ "debug", "verify" }) +@SupportedOptions({"debug", "verify"}) public class AutoServiceProcessor extends AbstractProcessor { @VisibleForTesting @@ -74,7 +76,7 @@ public class AutoServiceProcessor extends AbstractProcessor { * {@code "com.google.apphosting.LocalRpcService" -> * "com.google.apphosting.datastore.LocalDatastoreService"} */ - private Multimap<String, String> providers = HashMultimap.create(); + private final Multimap<String, String> providers = HashMultimap.create(); @Override public ImmutableSet<String> getSupportedAnnotationTypes() { @@ -104,28 +106,24 @@ public class AutoServiceProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { try { - return processImpl(annotations, roundEnv); - } catch (Exception e) { + processImpl(annotations, roundEnv); + } catch (RuntimeException e) { // We don't allow exceptions of any kind to propagate to the compiler - StringWriter writer = new StringWriter(); - e.printStackTrace(new PrintWriter(writer)); - fatalError(writer.toString()); - return true; + fatalError(getStackTraceAsString(e)); } + return false; } - private boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + private void processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver()) { generateConfigFiles(); } else { processAnnotations(annotations, roundEnv); } - - return true; } - private void processAnnotations(Set<? extends TypeElement> annotations, - RoundEnvironment roundEnv) { + private void processAnnotations( + Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class); @@ -134,7 +132,7 @@ public class AutoServiceProcessor extends AbstractProcessor { for (Element e : elements) { // TODO(gak): check for error trees? - TypeElement providerImplementer = (TypeElement) e; + TypeElement providerImplementer = MoreElements.asType(e); AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class).get(); Set<DeclaredType> providerInterfaces = getValueFieldOfClasses(annotationMirror); if (providerInterfaces.isEmpty()) { @@ -147,12 +145,14 @@ public class AutoServiceProcessor extends AbstractProcessor { log("provider interface: " + providerType.getQualifiedName()); log("provider implementer: " + providerImplementer.getQualifiedName()); - if (checkImplementer(providerImplementer, providerType)) { + if (checkImplementer(providerImplementer, providerType, annotationMirror)) { providers.put(getBinaryName(providerType), getBinaryName(providerImplementer)); } else { - String message = "ServiceProviders must implement their service provider interface. " - + providerImplementer.getQualifiedName() + " does not implement " - + providerType.getQualifiedName(); + String message = + "ServiceProviders must implement their service provider interface. " + + providerImplementer.getQualifiedName() + + " does not implement " + + providerType.getQualifiedName(); error(message, e, annotationMirror); } } @@ -172,8 +172,8 @@ public class AutoServiceProcessor extends AbstractProcessor { // before we attempt to get the resource in case the behavior // of filer.getResource does change to match the spec, but there's // no good way to resolve CLASS_OUTPUT without first getting a resource. - FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", - resourceFile); + FileObject existingFile = + filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile); log("Looking for existing resource file at " + existingFile.toUri()); Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream()); log("Existing service entries: " + oldServices); @@ -187,19 +187,18 @@ public class AutoServiceProcessor extends AbstractProcessor { log("Resource file did not already exist."); } - Set<String> newServices = new HashSet<String>(providers.get(providerInterface)); - if (allServices.containsAll(newServices)) { + Set<String> newServices = new HashSet<>(providers.get(providerInterface)); + if (!allServices.addAll(newServices)) { log("No new service entries being added."); - return; + continue; } - allServices.addAll(newServices); log("New service file contents: " + allServices); - FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "", - resourceFile); - OutputStream out = fileObject.openOutputStream(); - ServicesFiles.writeServiceFile(allServices, out); - out.close(); + FileObject fileObject = + filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceFile); + try (OutputStream out = fileObject.openOutputStream()) { + ServicesFiles.writeServiceFile(allServices, out); + } log("Wrote to: " + fileObject.toUri()); } catch (IOException e) { fatalError("Unable to create " + resourceFile + ", " + e); @@ -209,14 +208,17 @@ public class AutoServiceProcessor extends AbstractProcessor { } /** - * Verifies {@link ServiceProvider} constraints on the concrete provider class. - * Note that these constraints are enforced at runtime via the ServiceLoader, - * we're just checking them at compile time to be extra nice to our users. + * Verifies {@link ServiceProvider} constraints on the concrete provider class. Note that these + * constraints are enforced at runtime via the ServiceLoader, we're just checking them at compile + * time to be extra nice to our users. */ - private boolean checkImplementer(TypeElement providerImplementer, TypeElement providerType) { + private boolean checkImplementer( + TypeElement providerImplementer, + TypeElement providerType, + AnnotationMirror annotationMirror) { String verify = processingEnv.getOptions().get("verify"); - if (verify == null || !Boolean.valueOf(verify)) { + if (verify == null || !Boolean.parseBoolean(verify)) { return true; } @@ -225,7 +227,37 @@ public class AutoServiceProcessor extends AbstractProcessor { Types types = processingEnv.getTypeUtils(); - return types.isSubtype(providerImplementer.asType(), providerType.asType()); + if (types.isSubtype(providerImplementer.asType(), providerType.asType())) { + return true; + } + + // 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)) { + warning( + "Service provider " + + providerType + + " is generic, so it can't be named exactly by @AutoService." + + " If this is OK, add @SuppressWarnings(\"rawtypes\").", + providerImplementer, + annotationMirror); + } + return true; + } + + return false; + } + + private static boolean rawTypesSuppressed(Element element) { + for (; element != null; element = element.getEnclosingElement()) { + SuppressWarnings suppress = element.getAnnotation(SuppressWarnings.class); + if (suppress != null && Arrays.asList(suppress.value()).contains("rawtypes")) { + return true; + } + } + return false; } /** @@ -241,19 +273,20 @@ public class AutoServiceProcessor extends AbstractProcessor { Element enclosingElement = element.getEnclosingElement(); if (enclosingElement instanceof PackageElement) { - PackageElement pkg = (PackageElement) enclosingElement; + PackageElement pkg = MoreElements.asPackage(enclosingElement); if (pkg.isUnnamed()) { return className; } return pkg.getQualifiedName() + "." + className; } - TypeElement typeElement = (TypeElement) enclosingElement; + TypeElement typeElement = MoreElements.asType(enclosingElement); return getBinaryNameImpl(typeElement, typeElement.getSimpleName() + "$" + className); } /** - * Returns the contents of a {@code Class[]}-typed "value" field in a given {@code annotationMirror}. + * Returns the contents of a {@code Class[]}-typed "value" field in a given {@code + * annotationMirror}. */ private ImmutableSet<DeclaredType> getValueFieldOfClasses(AnnotationMirror annotationMirror) { return getAnnotationValue(annotationMirror, "value") @@ -261,16 +294,15 @@ public class AutoServiceProcessor extends AbstractProcessor { new SimpleAnnotationValueVisitor8<ImmutableSet<DeclaredType>, Void>() { @Override public ImmutableSet<DeclaredType> visitType(TypeMirror typeMirror, Void v) { - // TODO(ronshapiro): class literals may not always be declared types, i.e. int.class, - // int[].class + // TODO(ronshapiro): class literals may not always be declared types, i.e. + // int.class, int[].class return ImmutableSet.of(MoreTypes.asDeclared(typeMirror)); } @Override public ImmutableSet<DeclaredType> visitArray( List<? extends AnnotationValue> values, Void v) { - return values - .stream() + return values.stream() .flatMap(value -> value.accept(this, null).stream()) .collect(toImmutableSet()); } @@ -284,6 +316,10 @@ public class AutoServiceProcessor extends AbstractProcessor { } } + private void warning(String msg, Element element, AnnotationMirror annotation) { + processingEnv.getMessager().printMessage(Kind.WARNING, msg, element, annotation); + } + private void error(String msg, Element element, AnnotationMirror annotation) { processingEnv.getMessager().printMessage(Kind.ERROR, msg, element, annotation); } 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 c61b3ba2..75d6cca7 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 @@ -35,7 +35,7 @@ import java.util.Set; final class ServicesFiles { public static final String SERVICES_PATH = "META-INF/services"; - private ServicesFiles() { } + private ServicesFiles() {} /** * Returns an absolute path to a service file given the class @@ -96,4 +96,4 @@ final class ServicesFiles { } writer.flush(); } -}
\ No newline at end of file +} diff --git a/service/processor/src/main/java/com/google/auto/service/processor/package-info.java b/service/processor/src/main/java/com/google/auto/service/processor/package-info.java index a9f0adb5..453c95e8 100644 --- a/service/processor/src/main/java/com/google/auto/service/processor/package-info.java +++ b/service/processor/src/main/java/com/google/auto/service/processor/package-info.java @@ -15,4 +15,4 @@ * This package contains the annotation processor that implements the * {@link com.google.auto.service.AutoService} API. */ -package com.google.auto.service.processor;
\ No newline at end of file +package com.google.auto.service.processor; 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 d3e00a73..35615689 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,52 +16,132 @@ package com.google.auto.service.processor; import static com.google.auto.service.processor.AutoServiceProcessor.MISSING_SERVICES_ERROR; -import static com.google.testing.compile.JavaSourcesSubject.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.StandardLocation; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** - * Tests the {@link AutoServiceProcessor}. - */ +/** Tests the {@link AutoServiceProcessor}. */ @RunWith(JUnit4.class) public class AutoServiceProcessorTest { @Test public void autoService() { - assertThat( - 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")) - .processedWith(new AutoServiceProcessor()) - .compilesWithoutError() - .and().generatesFiles( - JavaFileObjects.forResource("META-INF/services/test.SomeService"), - JavaFileObjects.forResource("META-INF/services/test.AnotherService")); + 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")); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(compilation) + .generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/services/test.SomeService") + .hasContents( + Resources.asByteSource(Resources.getResource("META-INF/services/test.SomeService"))); + assertThat(compilation) + .generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/services/test.AnotherService") + .hasContents( + Resources.asByteSource(Resources.getResource("META-INF/services/test.AnotherService"))); } @Test public void multiService() { - assertThat( - JavaFileObjects.forResource("test/SomeService.java"), - JavaFileObjects.forResource("test/AnotherService.java"), - JavaFileObjects.forResource("test/MultiServiceProvider.java")) - .processedWith(new AutoServiceProcessor()) - .compilesWithoutError() - .and().generatesFiles( - JavaFileObjects.forResource("META-INF/services/test.SomeServiceMulti"), - JavaFileObjects.forResource("META-INF/services/test.AnotherServiceMulti")); + Compilation compilation = + Compiler.javac() + .withProcessors(new AutoServiceProcessor()) + .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 + // and likewise META-INF/services/test.AnotherService. + assertThat(compilation) + .generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/services/test.SomeService") + .contentsAsUtf8String() + .isEqualTo("test.MultiServiceProvider\n"); + assertThat(compilation) + .generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/services/test.AnotherService") + .contentsAsUtf8String() + .isEqualTo("test.MultiServiceProvider\n"); } @Test public void badMultiService() { - assertThat(JavaFileObjects.forResource("test/NoServices.java")) - .processedWith(new AutoServiceProcessor()) - .failsToCompile() - .withErrorContaining(MISSING_SERVICES_ERROR); + Compilation compilation = + Compiler.javac() + .withProcessors(new AutoServiceProcessor()) + .compile(JavaFileObjects.forResource("test/NoServices.java")); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorContaining(MISSING_SERVICES_ERROR); + } + + @Test + public void generic() { + Compilation compilation = + Compiler.javac() + .withProcessors(new AutoServiceProcessor()) + .compile( + JavaFileObjects.forResource("test/GenericService.java"), + JavaFileObjects.forResource("test/GenericServiceProvider.java")); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(compilation) + .generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/services/test.GenericService") + .contentsAsUtf8String() + .isEqualTo("test.GenericServiceProvider\n"); + } + + @Test + public void genericWithVerifyOption() { + Compilation compilation = + Compiler.javac() + .withProcessors(new AutoServiceProcessor()) + .withOptions("-Averify=true") + .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 genericWithVerifyOptionAndSuppressWarings() { + Compilation compilation = + Compiler.javac() + .withProcessors(new AutoServiceProcessor()) + .withOptions("-Averify=true") + .compile( + JavaFileObjects.forResource("test/GenericService.java"), + JavaFileObjects.forResource("test/GenericServiceProviderSuppressWarnings.java")); + assertThat(compilation).succeededWithoutWarnings(); + } + + @Test + public void nestedGenericWithVerifyOptionAndSuppressWarnings() { + Compilation compilation = + Compiler.javac() + .withProcessors(new AutoServiceProcessor()) + .withOptions("-Averify=true") + .compile( + JavaFileObjects.forResource("test/GenericService.java"), + JavaFileObjects.forResource("test/EnclosingGeneric.java")); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(compilation) + .generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/services/test.GenericService") + .contentsAsUtf8String() + .isEqualTo("test.EnclosingGeneric$GenericServiceProvider\n"); } } diff --git a/service/processor/src/test/resources/META-INF/services/test.AnotherServiceMulti b/service/processor/src/test/resources/META-INF/services/test.AnotherServiceMulti deleted file mode 100644 index f6ef36ac..00000000 --- a/service/processor/src/test/resources/META-INF/services/test.AnotherServiceMulti +++ /dev/null @@ -1 +0,0 @@ -test.MultiServiceProvider diff --git a/service/processor/src/test/resources/META-INF/services/test.SomeServiceMulti b/service/processor/src/test/resources/META-INF/services/test.SomeServiceMulti deleted file mode 100644 index f6ef36ac..00000000 --- a/service/processor/src/test/resources/META-INF/services/test.SomeServiceMulti +++ /dev/null @@ -1 +0,0 @@ -test.MultiServiceProvider diff --git a/service/processor/src/test/resources/test/AnotherService.java b/service/processor/src/test/resources/test/AnotherService.java index c096c223..de80f06d 100644 --- a/service/processor/src/test/resources/test/AnotherService.java +++ b/service/processor/src/test/resources/test/AnotherService.java @@ -15,4 +15,4 @@ */ package test; -interface AnotherService { }
\ No newline at end of file +interface AnotherService {} diff --git a/service/processor/src/test/resources/test/AnotherServiceProvider.java b/service/processor/src/test/resources/test/AnotherServiceProvider.java index c5e5c117..2a023e43 100644 --- a/service/processor/src/test/resources/test/AnotherServiceProvider.java +++ b/service/processor/src/test/resources/test/AnotherServiceProvider.java @@ -18,4 +18,4 @@ package test; import com.google.auto.service.AutoService; @AutoService(AnotherService.class) -public class AnotherServiceProvider implements AnotherService { }
\ No newline at end of file +public class AnotherServiceProvider implements AnotherService {} diff --git a/service/processor/src/test/resources/test/Enclosing.java b/service/processor/src/test/resources/test/Enclosing.java index 26dd5852..24202a59 100644 --- a/service/processor/src/test/resources/test/Enclosing.java +++ b/service/processor/src/test/resources/test/Enclosing.java @@ -19,5 +19,5 @@ import com.google.auto.service.AutoService; public class Enclosing { @AutoService(SomeService.class) - public static class NestedSomeServiceProvider implements SomeService { } -}
\ No newline at end of file + public static class NestedSomeServiceProvider implements SomeService {} +} diff --git a/service/processor/src/test/resources/test/EnclosingGeneric.java b/service/processor/src/test/resources/test/EnclosingGeneric.java new file mode 100644 index 00000000..cddddacb --- /dev/null +++ b/service/processor/src/test/resources/test/EnclosingGeneric.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020 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; + +/** Test for suppressing warnings about raw types on nested {@code @AutoService} classes. */ +@SuppressWarnings("rawtypes") +public final class EnclosingGeneric { + /** + * This is technically a raw class reference, but should be suppressed by the + * {@code @SuppressWarnings} on the enclosing class. + */ + @AutoService(GenericService.class) + public class GenericServiceProvider<T> implements GenericService<T> {} + + private EnclosingGeneric() {} +} diff --git a/service/processor/src/test/resources/test/GenericService.java b/service/processor/src/test/resources/test/GenericService.java new file mode 100644 index 00000000..5ed13ffc --- /dev/null +++ b/service/processor/src/test/resources/test/GenericService.java @@ -0,0 +1,22 @@ +/* + * Copyright 2020 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; + +/** + * An interface with a type parameter, which by default will produce a warning with + * {@code @AutoService} if you compile with {@code -Averify=true}. + */ +public interface GenericService<T> {} diff --git a/service/processor/src/test/resources/test/GenericServiceProvider.java b/service/processor/src/test/resources/test/GenericServiceProvider.java new file mode 100644 index 00000000..84c5cba0 --- /dev/null +++ b/service/processor/src/test/resources/test/GenericServiceProvider.java @@ -0,0 +1,25 @@ +/* + * Copyright 2020 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; + +/** + * An implementation of a service with a type parameter, which by default will produce a warning + * if you compile with {@code -Averify=true}. + */ +@AutoService(GenericService.class) +public class GenericServiceProvider<T> implements GenericService<T> {} diff --git a/service/processor/src/test/resources/test/GenericServiceProviderSuppressWarnings.java b/service/processor/src/test/resources/test/GenericServiceProviderSuppressWarnings.java new file mode 100644 index 00000000..eb285dad --- /dev/null +++ b/service/processor/src/test/resources/test/GenericServiceProviderSuppressWarnings.java @@ -0,0 +1,26 @@ +/* + * Copyright 2020 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; + +/** + * An implementation of a service with a type parameter, which will not produce a warning even if + * compiled with {@code -Averify=true}, because of the {@code @SuppressWarnings}. + */ +@AutoService(GenericService.class) +@SuppressWarnings("rawtypes") +public class GenericServiceProviderSuppressWarnings<T> implements GenericService<T> {} diff --git a/service/processor/src/test/resources/test/SomeService.java b/service/processor/src/test/resources/test/SomeService.java index d29c4097..d81cac40 100644 --- a/service/processor/src/test/resources/test/SomeService.java +++ b/service/processor/src/test/resources/test/SomeService.java @@ -15,4 +15,4 @@ */ package test; -interface SomeService { }
\ No newline at end of file +interface SomeService {} diff --git a/service/processor/src/test/resources/test/SomeServiceProvider1.java b/service/processor/src/test/resources/test/SomeServiceProvider1.java index 008136be..fc2c843c 100644 --- a/service/processor/src/test/resources/test/SomeServiceProvider1.java +++ b/service/processor/src/test/resources/test/SomeServiceProvider1.java @@ -18,4 +18,4 @@ package test; import com.google.auto.service.AutoService; @AutoService(SomeService.class) -public class SomeServiceProvider1 implements SomeService { }
\ No newline at end of file +public class SomeServiceProvider1 implements SomeService {} diff --git a/service/processor/src/test/resources/test/SomeServiceProvider2.java b/service/processor/src/test/resources/test/SomeServiceProvider2.java index 5444996b..b7097d41 100644 --- a/service/processor/src/test/resources/test/SomeServiceProvider2.java +++ b/service/processor/src/test/resources/test/SomeServiceProvider2.java @@ -18,4 +18,4 @@ package test; import com.google.auto.service.AutoService; @AutoService(SomeService.class) -public class SomeServiceProvider2 implements SomeService { }
\ No newline at end of file +public class SomeServiceProvider2 implements SomeService {} diff --git a/util/generate-latest-docs.sh b/util/generate-latest-docs.sh index 8617ba41..88c8e7fb 100755 --- a/util/generate-latest-docs.sh +++ b/util/generate-latest-docs.sh @@ -1,25 +1,25 @@ -# see http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/ for details +#!/bin/bash -if [ "$TRAVIS_REPO_SLUG" == "google/auto" ] && \ - [ "$TRAVIS_JDK_VERSION" == "oraclejdk7" ] && \ - [ "$TRAVIS_PULL_REQUEST" == "false" ] && \ - [ "$TRAVIS_BRANCH" == "master" ]; then - echo -e "Publishing javadoc...\n" - - mvn -f build-pom.xml javadoc:aggregate - TARGET="$(pwd)/target" +# Run by GitHub Actions (see .github/workflows/ci.yml) - cd $HOME - git clone --quiet --branch=gh-pages https://${GH_TOKEN}@github.com/google/auto gh-pages > /dev/null - - cd gh-pages - git config --global user.email "travis@travis-ci.org" - git config --global user.name "travis-ci" - git rm -rf api/latest - mv ${TARGET}/site/apidocs api/latest - git add -A -f api/latest - git commit -m "Latest javadoc on successful travis build $TRAVIS_BUILD_NUMBER auto-pushed to gh-pages" - git push -fq origin gh-pages > /dev/null +set -e - echo -e "Published Javadoc to gh-pages.\n" -fi +echo -e "Publishing javadoc...\n" + +mvn -f build-pom.xml javadoc:aggregate +TARGET="$(pwd)/target" + +cd $HOME +git clone --quiet --branch=gh-pages "https://x-access-token:${GITHUB_TOKEN}@github.com/google/auto" gh-pages > /dev/null + +cd gh-pages +git config --global user.name "$GITHUB_ACTOR" +git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com" +git rm -rf api/latest +mkdir -p api # Just to make mv work if the directory is missing +mv ${TARGET}/site/apidocs api/latest +git add -A -f api/latest +git commit -m "Latest javadoc on successful CI build auto-pushed to gh-pages" +git push -fq origin gh-pages > /dev/null + +echo -e "Published Javadoc to gh-pages.\n" diff --git a/util/publish-snapshot-on-commit.sh b/util/publish-snapshot-on-commit.sh index 0b74c6bb..0166b059 100755 --- a/util/publish-snapshot-on-commit.sh +++ b/util/publish-snapshot-on-commit.sh @@ -1,12 +1,6 @@ -# see https://coderwall.com/p/9b_lfq +#!/bin/bash -if [ "$TRAVIS_REPO_SLUG" == "google/auto" ] && \ - [ "$TRAVIS_JDK_VERSION" == "oraclejdk7" ] && \ - [ "$TRAVIS_PULL_REQUEST" == "false" ] && \ - [ "$TRAVIS_BRANCH" == "master" ]; then - echo -e "Publishing maven snapshot...\n" +set -e - mvn -f build-pom.xml clean source:jar deploy --settings="util/settings.xml" -DskipTests=true -Dmaven.javadoc.skip=true - - echo -e "Published maven snapshot" -fi +mvn -B dependency:go-offline test clean -U --quiet --fail-never -DskipTests=true -f build-pom.xml +mvn -B -U source:jar deploy -DskipTests=true -f build-pom.xml diff --git a/util/settings.xml b/util/settings.xml deleted file mode 100644 index 91f444b2..00000000 --- a/util/settings.xml +++ /dev/null @@ -1,9 +0,0 @@ -<settings> - <servers> - <server> - <id>sonatype-nexus-snapshots</id> - <username>${env.CI_DEPLOY_USERNAME}</username> - <password>${env.CI_DEPLOY_PASSWORD}</password> - </server> - </servers> -</settings> diff --git a/value/Android.bp b/value/Android.bp index 73a72154..2f554aaa 100644 --- a/value/Android.bp +++ b/value/Android.bp @@ -72,6 +72,23 @@ java_plugin { } java_plugin { + name: "auto_oneof_plugin", + static_libs: [ + "libauto_value_plugin", + + "auto_android_annotation_stubs", + "auto_common", + "auto_service_plugin", + "auto_value_extension", + "escapevelocity", + "guava", + "javapoet", + ], + processor_class: "com.google.auto.value.processor.AutoOneOfProcessor", + visibility: ["//visibility:public"], +} + +java_plugin { name: "auto_annotation_plugin", static_libs: [ "libauto_value_plugin", diff --git a/value/annotations/pom.xml b/value/annotations/pom.xml index d6c03de5..8bc63ac9 100644 --- a/value/annotations/pom.xml +++ b/value/annotations/pom.xml @@ -54,6 +54,7 @@ <include>com/google/auto/value/*</include> <include>com/google/auto/value/extension/memoized/*</include> <include>com/google/auto/value/extension/serializable/*</include> + <include>com/google/auto/value/extension/toprettystring/*</include> </includes> </configuration> </plugin> diff --git a/value/pom.xml b/value/pom.xml index dcf2cb1d..5405c7cd 100644 --- a/value/pom.xml +++ b/value/pom.xml @@ -37,8 +37,8 @@ <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> - <guava.version>28.1-jre</guava.version> - <truth.version>1.0.1</truth.version> + <guava.version>30.1.1-jre</guava.version> + <truth.version>1.1.3</truth.version> </properties> <scm> @@ -89,7 +89,7 @@ <dependency> <groupId>com.squareup</groupId> <artifactId>javapoet</artifactId> - <version>1.11.1</version> + <version>1.13.0</version> </dependency> <!-- test dependencies --> @@ -107,7 +107,7 @@ <dependency> <groupId>com.google.testing.compile</groupId> <artifactId>compile-testing</artifactId> - <version>0.18</version> + <version>0.19</version> </dependency> <dependency> <groupId>com.google.truth</groupId> @@ -122,7 +122,7 @@ <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> - <version>4.13</version> + <version>4.13.2</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> @@ -138,7 +138,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.7.0</version> + <version>3.8.1</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> @@ -150,19 +150,19 @@ <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>0.9.4</version> + <version>1.0.7</version> </dependency> </dependencies> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.2.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-invoker-plugin</artifactId> - <version>3.0.1</version> + <version>3.2.2</version> <configuration> <addTestClassPath>true</addTestClassPath> <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo> diff --git a/value/processor/pom.xml b/value/processor/pom.xml index 5968352c..8892c4f6 100644 --- a/value/processor/pom.xml +++ b/value/processor/pom.xml @@ -40,23 +40,26 @@ <tag>HEAD</tag> </scm> + <properties> + <auto-service.version>1.0</auto-service.version> + <errorprone.version>2.7.1</errorprone.version> + </properties> + <dependencies> <dependency> <groupId>com.google.auto</groupId> <artifactId>auto-common</artifactId> - <version>0.10</version> + <version>1.1</version> </dependency> <dependency> <groupId>com.google.auto.service</groupId> - <artifactId>auto-service</artifactId> - <version>1.0-rc6</version> - <scope>provided</scope> + <artifactId>auto-service-annotations</artifactId> + <version>${auto-service.version}</version> </dependency> <dependency> <groupId>com.google.errorprone</groupId> <artifactId>error_prone_annotations</artifactId> - <version>2.3.3</version> - <scope>provided</scope> + <version>${errorprone.version}</version> </dependency> <dependency> <groupId>com.google.escapevelocity</groupId> @@ -66,13 +69,7 @@ <dependency> <groupId>net.ltgt.gradle.incap</groupId> <artifactId>incap</artifactId> - <version>0.2</version> - </dependency> - <dependency> - <groupId>net.ltgt.gradle.incap</groupId> - <artifactId>incap-processor</artifactId> - <version>0.2</version> - <scope>provided</scope> + <version>0.3</version> </dependency> <dependency> <groupId>com.google.guava</groupId> @@ -90,6 +87,12 @@ <scope>test</scope> </dependency> <dependency> + <groupId>com.google.errorprone</groupId> + <artifactId>error_prone_type_annotations</artifactId> + <version>${errorprone.version}</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <scope>test</scope> @@ -122,7 +125,7 @@ <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> - <version>3.1.0</version> + <version>3.11.2</version> <scope>test</scope> </dependency> </dependencies> @@ -149,7 +152,55 @@ <include>com/google/auto/value/extension/memoized/processor/**/*.java</include> <include>com/google/auto/value/extension/serializable/processor/**/*.java</include> <include>com/google/auto/value/extension/serializable/serializer/**/*.java</include> + <include>com/google/auto/value/extension/toprettystring/processor/**/*.java</include> </includes> + <compilerArgs> + <!-- This is something of a hack to allow tests to pass. Ideally we would build + TestStringSerializerFactory as a separate artifact, to avoid a problem when it + is built at the same time as @AutoValue classes that might end up finding it. + But by allowing a missing class to be ignored, we avoid crashing if there is a + META-INF/services entry for a class that the compiler has not yet generated. --> + <arg>-AallowedMissingSerializableExtensionClasses=.*TestStringSerializerFactory</arg> + </compilerArgs> + <annotationProcessorPaths> + <path> + <groupId>com.google.auto.service</groupId> + <artifactId>auto-service</artifactId> + <version>${auto-service.version}</version> + </path> + <path> + <groupId>net.ltgt.gradle.incap</groupId> + <artifactId>incap-processor</artifactId> + <version>0.3</version> + </path> + </annotationProcessorPaths> + </configuration> + <executions> + <execution> + <id>default-testCompile</id> + <configuration> + <annotationProcessorPaths> + <path> + <groupId>com.google.auto.value</groupId> + <artifactId>auto-value</artifactId> + <version>${project.version}</version> + </path> + <path> + <groupId>com.google.auto.service</groupId> + <artifactId>auto-service</artifactId> + <version>${auto-service.version}</version> + </path> + </annotationProcessorPaths> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>2.22.2</version> + <configuration> + <argLine>${test.jvm.flags}</argLine> </configuration> </plugin> <plugin> @@ -217,5 +268,14 @@ <additionalparam>-Xdoclint:none</additionalparam> </properties> </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> + </profile> </profiles> </project> diff --git a/value/src/it/functional/pom.xml b/value/src/it/functional/pom.xml index 750b9c43..d4ae1386 100644 --- a/value/src/it/functional/pom.xml +++ b/value/src/it/functional/pom.xml @@ -32,6 +32,7 @@ <version>1.7.4</version> <name>Auto-Value Functional Integration Test</name> <properties> + <kotlin.version>1.5.21</kotlin.version> <exclude.tests>this-matches-nothing</exclude.tests> </properties> <dependencies> @@ -48,7 +49,7 @@ <dependency> <groupId>com.google.auto.service</groupId> <artifactId>auto-service</artifactId> - <version>1.0-rc6</version> + <version>1.0</version> </dependency> <dependency> <groupId>com.google.guava</groupId> @@ -62,7 +63,7 @@ <dependency> <groupId>com.google.gwt</groupId> <artifactId>gwt-user</artifactId> - <version>2.8.2</version> + <version>2.9.0</version> </dependency> <dependency> <groupId>junit</groupId> @@ -90,21 +91,32 @@ <scope>test</scope> </dependency> <dependency> + <groupId>dev.gradleplugins</groupId> + <artifactId>gradle-test-kit</artifactId> + <version>6.8.3</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> - <version>3.1.0</version> + <version>3.11.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.eclipse.jdt</groupId> <artifactId>ecj</artifactId> - <version>3.20.0</version> + <version>3.25.0</version> </dependency> <dependency> <groupId>com.google.escapevelocity</groupId> <artifactId>escapevelocity</artifactId> <version>0.9.1</version> </dependency> + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-stdlib</artifactId> + <version>${kotlin.version}</version> + </dependency> </dependencies> <build> @@ -112,17 +124,49 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.2.0</version> + </plugin> + <plugin> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-maven-plugin</artifactId> + <version>${kotlin.version}</version> + <executions> + <!-- + The Kotlin configuration here is a bit unusual. JetBrains recommends + <https://kotlinlang.org/docs/maven.html#compile-kotlin-and-java-sources> + a fairly invasive reconfiguration of the maven-compiler-plugin (which compiles Java) + in order to ensure that the Kotlin compiler can run first. In our case, we have just + one Kotlin file that a test in Java accesses. So, even though it is in src/test/java, + we compile it in the `compile` goal, which means it is available to the Java test sources + when they are compiled in the `default-testCompile` goal. + + Currently if you want to use JDK ≥ 16 then you must set + JAVA_TOOL_OPTIONS=__illegal-access=permit + except the two underscores should be dashes (which XML comments won't allow us to write). + This is a bug that will presumably be fixed in a forthcoming version. + --> + <execution> + <id>compile</id> + <goals> + <goal>compile</goal> + </goals> + <configuration> + <sourceDirs> + <sourceDir>${project.basedir}/src/test/java</sourceDir> + </sourceDirs> + </configuration> + </execution> + </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.7.0</version> + <version>3.8.1</version> <dependencies> <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>0.9.4</version> + <version>1.0.7</version> </dependency> </dependencies> <configuration> @@ -132,6 +176,7 @@ <arg>-Xlint:all</arg> <arg>-encoding</arg> <arg>utf8</arg> + <arg>-Acom.google.auto.value.AutoBuilderIsUnstable</arg> </compilerArgs> <showWarnings>true</showWarnings> <showDeprecation>true</showDeprecation> @@ -143,12 +188,23 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> - <version>2.7</version> + <version>2.8.2</version> <configuration> <!-- Build/test, but don't deploy --> <skip>true</skip> </configuration> </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>2.22.2</version> + <configuration> + <argLine>${test.jvm.flags}</argLine> + <systemPropertyVariables> + <autoValueVersion>${project.version}</autoValueVersion> + </systemPropertyVariables> + </configuration> + </plugin> </plugins> </build> @@ -160,12 +216,12 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.7.0</version> + <version>3.8.1</version> <dependencies> <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>0.9.4</version> + <version>1.0.7</version> </dependency> </dependencies> <configuration> @@ -195,5 +251,27 @@ <exclude.tests>**/AutoValueJava8Test.java</exclude.tests> </properties> </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> + </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 + obviously has to be compiled separately from the Java test that uses it, + AutoBuilderKotlinTest doesn't pass on earlier JDK versions. --> + <id>exclude-separate-compilation-parameter-names</id> + <activation> + <jdk>(,11)</jdk> + </activation> + <properties> + <exclude.tests>**/AutoBuilderKotlinTest.java</exclude.tests> + </properties> + </profile> </profiles> </project> 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 ca1ef6b9..a04d41f3 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 @@ -15,20 +15,24 @@ */ package com.google.auto.value; -import static org.junit.Assert.assertEquals; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.TruthJUnit.assume; import com.google.auto.value.annotations.Empty; import com.google.auto.value.annotations.GwtArrays; import com.google.auto.value.annotations.StringValues; +import com.google.common.base.StandardSystemProperty; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; -import com.google.common.primitives.Ints; import com.google.common.testing.EqualsTester; +import com.google.common.testing.SerializableTester; +import java.io.ObjectStreamClass; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -38,6 +42,7 @@ import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; +import org.junit.AssumptionViolatedException; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -66,13 +71,41 @@ public class AutoAnnotationTest { } @Test + public void testEqualsParameterAnnotation() throws ReflectiveOperationException { + assume() + .that(Double.parseDouble(StandardSystemProperty.JAVA_SPECIFICATION_VERSION.value())) + .isAtLeast(8.0); + Class<? extends Annotation> jspecifyNullable; + try { + // We write this using .concat in order to hide it from rewriting rules. + jspecifyNullable = + Class.forName("org".concat(".jspecify.nullness.Nullable")).asSubclass(Annotation.class); + } catch (ClassNotFoundException e) { + throw new AssumptionViolatedException("No JSpecify @Nullable available", e); + } + @SuppressWarnings("GetClassOnAnnotation") // yes, I really want the implementation class + Class<? extends StringValues> autoAnnotationImpl = newStringValues(new String[0]).getClass(); + Method equals = autoAnnotationImpl.getDeclaredMethod("equals", Object.class); + // The remaining faffing around with reflection is there because we have a Google-internal test + // that runs this code with -source 7 -target 7. We're really just doing this: + // assertThat(equals.getAnnotatedParameterTypes()[0].isAnnotationPresent(jspecifyNullable)) + // .isTrue(); + Method getAnnotatedParameterTypes = Method.class.getMethod("getAnnotatedParameterTypes"); + Object[] annotatedParameterTypes = (Object[]) getAnnotatedParameterTypes.invoke(equals); + Method isAnnotationPresent = + annotatedParameterTypes[0].getClass().getMethod("isAnnotationPresent", Class.class); + assertThat(isAnnotationPresent.invoke(annotatedParameterTypes[0], jspecifyNullable)) + .isEqualTo(true); + } + + @Test public void testArraysAreCloned() { String[] array = {"Jekyll"}; StringValues stringValues = newStringValues(array); array[0] = "Hyde"; - assertEquals("Jekyll", stringValues.value()[0]); + assertThat(stringValues.value()).asList().containsExactly("Jekyll"); stringValues.value()[0] = "Hyde"; - assertEquals("Jekyll", stringValues.value()[0]); + assertThat(stringValues.value()[0]).isEqualTo("Jekyll"); } @Test @@ -80,12 +113,12 @@ public class AutoAnnotationTest { String[] strings = {"Jekyll"}; int[] ints = {2, 3, 5}; GwtArrays arrays = newGwtArrays(strings, ints); - assertEquals(ImmutableList.of("Jekyll"), ImmutableList.copyOf(arrays.strings())); - assertEquals(ImmutableList.of(2, 3, 5), Ints.asList(arrays.ints())); + assertThat(arrays.strings()).asList().containsExactly("Jekyll"); + assertThat(arrays.ints()).asList().containsExactly(2, 3, 5).inOrder(); strings[0] = "Hyde"; ints[0] = -1; - assertEquals(ImmutableList.of("Jekyll"), ImmutableList.copyOf(arrays.strings())); - assertEquals(ImmutableList.of(2, 3, 5), Ints.asList(arrays.ints())); + assertThat(arrays.strings()).asList().containsExactly("Jekyll"); + assertThat(arrays.ints()).asList().containsExactly(2, 3, 5).inOrder(); } @AutoAnnotation @@ -406,7 +439,39 @@ public class AutoAnnotationTest { .testEquals(); } + @Test + public void testSerialization() { + Annotation[] instances = {EVERYTHING_FROM_AUTO, EVERYTHING_FROM_AUTO_COLLECTIONS}; + for (Annotation instance : instances) { + SerializableTester.reserializeAndAssert(instance); + } + } + + @Test + @SuppressWarnings("GetClassOnAnnotation") // yes, we really do want the implementation classes + public void testSerialVersionUid() { + Class<? extends Everything> everythingImpl = EVERYTHING_FROM_AUTO.getClass(); + Class<? extends Everything> everythingFromCollectionsImpl = + EVERYTHING_FROM_AUTO_COLLECTIONS.getClass(); + assertThat(everythingImpl).isNotEqualTo(everythingFromCollectionsImpl); + long everythingUid = ObjectStreamClass.lookup(everythingImpl).getSerialVersionUID(); + long everythingFromCollectionsUid = + ObjectStreamClass.lookup(everythingFromCollectionsImpl).getSerialVersionUID(); + // Two different implementations of the same annotation with the same members being provided + // (not defaulted) should have the same serialVersionUID. They won't be serial-compatible, of + // course, because their classes are different. So we're really just checking that the + // serialVersionUID depends only on the names and types of those members. + assertThat(everythingFromCollectionsUid).isEqualTo(everythingUid); + Class<? extends StringValues> stringValuesImpl = newStringValues(new String[0]).getClass(); + long stringValuesUid = ObjectStreamClass.lookup(stringValuesImpl).getSerialVersionUID(); + // The previous assertion would be vacuously true if every implementation had the same + // serialVersionUID, so check that that's not true. + assertThat(stringValuesUid).isNotEqualTo(everythingUid); + } + public static class IntList extends ArrayList<Integer> { + private static final long serialVersionUID = 1L; + IntList(Collection<Integer> c) { super(c); } @@ -440,7 +505,7 @@ public class AutoAnnotationTest { IntList intList = new IntList(ImmutableList.of(1, 2, 3)); IntArray actual = newIntArray(intList); IntArray expected = AnnotatedWithIntArray.class.getAnnotation(IntArray.class); - assertEquals(expected, actual); + assertThat(actual).isEqualTo(expected); } @Test @@ -461,8 +526,8 @@ public class AutoAnnotationTest { + "@com.google.auto.value.annotations.StringValues([\"foo\", \"bar\"])" + "]" + ")"; - assertEquals(expected, EVERYTHING_FROM_AUTO.toString()); - assertEquals(expected, EVERYTHING_FROM_AUTO_COLLECTIONS.toString()); + assertThat(EVERYTHING_FROM_AUTO.toString()).isEqualTo(expected); + assertThat(EVERYTHING_FROM_AUTO_COLLECTIONS.toString()).isEqualTo(expected); } @Test @@ -475,7 +540,7 @@ public class AutoAnnotationTest { String expected = "@com.google.auto.value.annotations.StringValues(" + "[\"\", \"\\r\\n\", \"hello, world\", \"Éamonn\", \"\\007\\uffef\"])"; - assertEquals(expected, instance.toString()); + assertThat(instance.toString()).isEqualTo(expected); } @Retention(RetentionPolicy.RUNTIME) @@ -498,7 +563,7 @@ public class AutoAnnotationTest { ImmutableList.<Class<? extends Annotation>>of(AnnotationsAnnotation.class)); AnnotationsAnnotation fromReflect = AnnotatedWithAnnotationsAnnotation.class.getAnnotation(AnnotationsAnnotation.class); - assertEquals(fromReflect, generated); + assertThat(generated).isEqualTo(fromReflect); } @Retention(RetentionPolicy.RUNTIME) @@ -520,7 +585,7 @@ public class AutoAnnotationTest { newClassesAnnotation(Arrays.<Class<?>>asList(AnnotationsAnnotation.class)); ClassesAnnotation fromReflect = AnnotatedWithClassesAnnotation.class.getAnnotation(ClassesAnnotation.class); - assertEquals(fromReflect, generated); + assertThat(generated).isEqualTo(fromReflect); } @Retention(RetentionPolicy.RUNTIME) 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 new file mode 100644 index 00000000..1dc346c9 --- /dev/null +++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderKotlinTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2021 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; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class AutoBuilderKotlinTest { + @AutoBuilder(ofClass = KotlinData.class) + abstract static class KotlinDataBuilder { + static KotlinDataBuilder builder() { + return new AutoBuilder_AutoBuilderKotlinTest_KotlinDataBuilder(); + } + + abstract KotlinDataBuilder setInt(int x); + + abstract KotlinDataBuilder setString(String x); + + abstract KotlinData build(); + } + + @Test + public void simpleKotlin() { + KotlinData x = KotlinDataBuilder.builder().setInt(23).setString("skidoo").build(); + assertThat(x.getInt()).isEqualTo(23); + assertThat(x.getString()).isEqualTo("skidoo"); + } + + @AutoBuilder(ofClass = KotlinDataWithNullable.class) + abstract static class KotlinDataWithNullableBuilder { + static KotlinDataWithNullableBuilder builder() { + return new AutoBuilder_AutoBuilderKotlinTest_KotlinDataWithNullableBuilder(); + } + + abstract KotlinDataWithNullableBuilder setAnInt(int x); + + abstract KotlinDataWithNullableBuilder setAString(String x); + + abstract KotlinDataWithNullable build(); + } + + @Test + public void kotlinWithNullable() { + KotlinDataWithNullable empty = KotlinDataWithNullableBuilder.builder().build(); + assertThat(empty.getAnInt()).isNull(); + assertThat(empty.getAString()).isNull(); + + KotlinDataWithNullable notEmpty = + KotlinDataWithNullableBuilder.builder().setAString("answer").setAnInt(42).build(); + assertThat(notEmpty.getAString()).isEqualTo("answer"); + assertThat(notEmpty.getAnInt()).isEqualTo(42); + } + + @AutoBuilder(ofClass = KotlinDataWithDefaults.class) + abstract static class KotlinDataWithDefaultsBuilder { + static KotlinDataWithDefaultsBuilder builder() { + return new AutoBuilder_AutoBuilderKotlinTest_KotlinDataWithDefaultsBuilder(); + } + + abstract KotlinDataWithDefaultsBuilder setAnInt(int x); + + abstract KotlinDataWithDefaultsBuilder setAString(String x); + + 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(); + assertThat(x.getAString()).isEqualTo("answer"); + assertThat(x.getAnInt()).isEqualTo(42); + } +} 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 new file mode 100644 index 00000000..952edaac --- /dev/null +++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java @@ -0,0 +1,580 @@ +/* + * Copyright 2021 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; + +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 org.junit.Assert.fail; + +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.math.BigInteger; +import java.time.LocalTime; +import java.util.AbstractSet; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class AutoBuilderTest { + static class Simple { + private final int anInt; + private final String aString; + + Simple(int anInt, String aString) { + this.anInt = anInt; + this.aString = aString; + } + + static Simple of(int anInt, String aString) { + return new Simple(anInt, aString); + } + + @Override + public boolean equals(Object x) { + if (x instanceof Simple) { + Simple that = (Simple) x; + return this.anInt == that.anInt && Objects.equals(this.aString, that.aString); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(anInt, aString); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("anInt", anInt) + .add("aString", aString) + .toString(); + } + + static Builder builder() { + return new AutoBuilder_AutoBuilderTest_Simple_Builder(); + } + + @AutoBuilder + abstract static class Builder { + abstract Builder setAnInt(int x); + + abstract Builder setAString(String x); + + abstract Simple build(); + } + } + + @Test + public void simple() { + Simple x = Simple.builder().setAnInt(23).setAString("skidoo").build(); + assertThat(x).isEqualTo(new Simple(23, "skidoo")); + } + + @AutoValue + abstract static class SimpleAuto { + abstract int getFoo(); + + abstract String getBar(); + + static Builder builder() { + return new AutoBuilder_AutoBuilderTest_SimpleAuto_Builder(); + } + + // There's no particular reason to do this since @AutoValue.Builder works just as well, but + // let's check anyway. + @AutoBuilder(ofClass = AutoValue_AutoBuilderTest_SimpleAuto.class) + abstract static class Builder { + abstract Builder setFoo(int x); + + abstract Builder setBar(String x); + + abstract AutoValue_AutoBuilderTest_SimpleAuto build(); + } + } + + @Test + public void simpleAuto() { + SimpleAuto x = SimpleAuto.builder().setFoo(23).setBar("skidoo").build(); + assertThat(x.getFoo()).isEqualTo(23); + assertThat(x.getBar()).isEqualTo("skidoo"); + } + + enum Truthiness { + FALSY, + TRUTHY + } + + @interface MyAnnotation { + String value(); + + int DEFAULT_ID = -1; + + int id() default DEFAULT_ID; + + Truthiness DEFAULT_TRUTHINESS = Truthiness.FALSY; + + Truthiness truthiness() default Truthiness.FALSY; + } + + // This method has a parameter for `truthiness`, even though that has a default, but it has no + // parameter for `id`, which also has a default. + @AutoAnnotation + static MyAnnotation myAnnotation(String value, Truthiness truthiness) { + return new AutoAnnotation_AutoBuilderTest_myAnnotation(value, truthiness); + } + + @AutoBuilder(callMethod = "myAnnotation") + interface MyAnnotationBuilder { + MyAnnotationBuilder value(String x); + + MyAnnotationBuilder truthiness(Truthiness x); + + MyAnnotation build(); + } + + static MyAnnotationBuilder myAnnotationBuilder() { + return new AutoBuilder_AutoBuilderTest_MyAnnotationBuilder() + .truthiness(MyAnnotation.DEFAULT_TRUTHINESS); + } + + @Test + public void simpleAutoAnnotation() { + MyAnnotation annotation1 = myAnnotationBuilder().value("foo").build(); + assertThat(annotation1.value()).isEqualTo("foo"); + assertThat(annotation1.id()).isEqualTo(MyAnnotation.DEFAULT_ID); + assertThat(annotation1.truthiness()).isEqualTo(MyAnnotation.DEFAULT_TRUTHINESS); + MyAnnotation annotation2 = + myAnnotationBuilder().value("bar").truthiness(Truthiness.TRUTHY).build(); + assertThat(annotation2.value()).isEqualTo("bar"); + assertThat(annotation2.id()).isEqualTo(MyAnnotation.DEFAULT_ID); + assertThat(annotation2.truthiness()).isEqualTo(Truthiness.TRUTHY); + } + + static class Overload { + final int anInt; + final String aString; + final BigInteger aBigInteger; + + Overload(int anInt, String aString) { + this(anInt, aString, BigInteger.ZERO); + } + + Overload(int anInt, String aString, BigInteger aBigInteger) { + this.anInt = anInt; + this.aString = aString; + this.aBigInteger = aBigInteger; + } + + @Override + public boolean equals(Object x) { + if (x instanceof Overload) { + Overload that = (Overload) x; + return this.anInt == that.anInt + && Objects.equals(this.aString, that.aString) + && Objects.equals(this.aBigInteger, that.aBigInteger); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(anInt, aString, aBigInteger); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("anInt", anInt) + .add("aString", aString) + .add("aBigInteger", aBigInteger) + .toString(); + } + + static Builder1 builder1() { + return new AutoBuilder_AutoBuilderTest_Overload_Builder1(); + } + + static Builder2 builder2() { + return new AutoBuilder_AutoBuilderTest_Overload_Builder2(); + } + + @AutoBuilder + interface Builder1 { + Builder1 setAnInt(int x); + + Builder1 setAString(String x); + + Overload build(); + } + + @AutoBuilder + interface Builder2 { + Builder2 setAnInt(int x); + + Builder2 setAString(String x); + + Builder2 setABigInteger(BigInteger x); + + Overload build(); + } + } + + @Test + public void overloadedConstructor() { + Overload actual1 = Overload.builder1().setAnInt(23).setAString("skidoo").build(); + Overload expected1 = new Overload(23, "skidoo"); + assertThat(actual1).isEqualTo(expected1); + + BigInteger big17 = BigInteger.valueOf(17); + Overload actual2 = + Overload.builder2().setAnInt(17).setAString("17").setABigInteger(big17).build(); + Overload expected2 = new Overload(17, "17", big17); + assertThat(actual2).isEqualTo(expected2); + } + + @AutoBuilder(callMethod = "of", ofClass = Simple.class) + interface SimpleStaticBuilder { + SimpleStaticBuilder anInt(int x); + + SimpleStaticBuilder aString(String x); + + Simple build(); + } + + static SimpleStaticBuilder simpleStaticBuilder() { + return new AutoBuilder_AutoBuilderTest_SimpleStaticBuilder(); + } + + @Test + public void staticMethod() { + Simple actual = simpleStaticBuilder().anInt(17).aString("17").build(); + Simple expected = new Simple(17, "17"); + assertThat(actual).isEqualTo(expected); + } + + // We can't be sure that the java.time package has parameter names, so we use this intermediary. + // Otherwise we could just write @AutoBuilder(callMethod = "of", ofClass = LocalTime.class). + // It's still interesting to test this as a realistic example. + static LocalTime localTimeOf(int hour, int minute, int second, int nanoOfSecond) { + return LocalTime.of(hour, minute, second, nanoOfSecond); + } + + static LocalTimeBuilder localTimeBuilder() { + return new AutoBuilder_AutoBuilderTest_LocalTimeBuilder().nanoOfSecond(0); + } + + @AutoBuilder(callMethod = "localTimeOf") + interface LocalTimeBuilder { + LocalTimeBuilder hour(int hour); + + LocalTimeBuilder minute(int minute); + + LocalTimeBuilder second(int second); + + LocalTimeBuilder nanoOfSecond(int nanoOfSecond); + + LocalTime build(); + } + + @Test + public void staticMethodOfContainingClass() { + LocalTime actual = localTimeBuilder().hour(12).minute(34).second(56).build(); + LocalTime expected = LocalTime.of(12, 34, 56); + assertThat(actual).isEqualTo(expected); + } + + @Test + public void missingRequiredProperty() { + // This test is compiled at source level 7 by CompileWithEclipseTest, so we can't use + // assertThrows with a lambda. + try { + localTimeBuilder().hour(12).minute(34).build(); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessageThat().isEqualTo("Missing required properties: second"); + } + } + + static void throwException() throws IOException { + throw new IOException("oops"); + } + + static ThrowExceptionBuilder throwExceptionBuilder() { + return new AutoBuilder_AutoBuilderTest_ThrowExceptionBuilder(); + } + + @AutoBuilder(callMethod = "throwException") + interface ThrowExceptionBuilder { + void build() throws IOException; + } + + @Test + public void emptyBuilderThrowsException() { + try { + throwExceptionBuilder().build(); + fail(); + } catch (IOException expected) { + assertThat(expected).hasMessageThat().isEqualTo("oops"); + } + } + + static class ListContainer { + private final ImmutableList<String> list; + + ListContainer(ImmutableList<String> list) { + this.list = checkNotNull(list); + } + + @Override + public boolean equals(Object o) { + return o instanceof ListContainer && list.equals(((ListContainer) o).list); + } + + @Override + public int hashCode() { + return list.hashCode(); + } + + @Override + public String toString() { + return list.toString(); + } + + static Builder builder() { + return new AutoBuilder_AutoBuilderTest_ListContainer_Builder(); + } + + @AutoBuilder + interface Builder { + Builder setList(Iterable<String> list); + + ImmutableList.Builder<String> listBuilder(); + + ListContainer build(); + } + } + + @Test + public void propertyBuilder() { + ListContainer expected = new ListContainer(ImmutableList.of("one", "two", "three")); + ListContainer actual1 = + ListContainer.builder().setList(ImmutableList.of("one", "two", "three")).build(); + assertThat(actual1).isEqualTo(expected); + + ListContainer.Builder builder2 = ListContainer.builder(); + builder2.listBuilder().add("one", "two", "three"); + assertThat(builder2.build()).isEqualTo(expected); + + ListContainer.Builder builder3 = ListContainer.builder().setList(ImmutableList.of("one")); + builder3.listBuilder().add("two", "three"); + assertThat(builder3.build()).isEqualTo(expected); + + ListContainer.Builder builder4 = ListContainer.builder(); + builder4.listBuilder(); + try { + builder4.setList(ImmutableList.of("one", "two", "three")); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessageThat().isEqualTo("Cannot set list after calling listBuilder()"); + } + } + + static <T> String concatList(ImmutableList<T> list) { + // We're avoiding streams for now so we compile this in Java 7 mode in CompileWithEclipseTest. + StringBuilder sb = new StringBuilder(); + for (T element : list) { + sb.append(element); + } + return sb.toString(); + } + + @AutoBuilder(callMethod = "concatList") + interface ConcatListCaller<T> { + ImmutableList.Builder<T> listBuilder(); + + String call(); + } + + @Test + public void propertyBuilderWithoutSetter() { + ConcatListCaller<Integer> caller = new AutoBuilder_AutoBuilderTest_ConcatListCaller<>(); + caller.listBuilder().add(1, 1, 2, 3, 5, 8); + String s = caller.call(); + assertThat(s).isEqualTo("112358"); + } + + static <K, V extends Number> Map<K, V> singletonMap(K key, V value) { + return Collections.singletonMap(key, value); + } + + static <K, V extends Number> SingletonMapBuilder<K, V> singletonMapBuilder() { + return new AutoBuilder_AutoBuilderTest_SingletonMapBuilder<>(); + } + + @AutoBuilder(callMethod = "singletonMap") + interface SingletonMapBuilder<K, V extends Number> { + SingletonMapBuilder<K, V> key(K key); + + SingletonMapBuilder<K, V> value(V value); + + Map<K, V> build(); + } + + @Test + public void genericStaticMethod() { + ImmutableMap<String, Integer> expected = ImmutableMap.of("17", 17); + SingletonMapBuilder<String, Integer> builder = singletonMapBuilder(); + Map<String, Integer> actual = builder.key("17").value(17).build(); + assertThat(actual).isEqualTo(expected); + } + + static class SingletonSet<E> extends AbstractSet<E> { + private final E element; + + SingletonSet(E element) { + this.element = element; + } + + @Override + public int size() { + return 1; + } + + @Override + public Iterator<E> iterator() { + return new Iterator<E>() { + private boolean first = true; + + @Override + public boolean hasNext() { + return first; + } + + @Override + public E next() { + if (!first) { + throw new NoSuchElementException(); + } + first = false; + return element; + } + }; + } + } + + @AutoBuilder(ofClass = SingletonSet.class) + interface SingletonSetBuilder<E> { + SingletonSetBuilder<E> setElement(E element); + + SingletonSet<E> build(); + } + + static <E> SingletonSetBuilder<E> singletonSetBuilder() { + return new AutoBuilder_AutoBuilderTest_SingletonSetBuilder<>(); + } + + @Test + public void genericClass() { + ImmutableSet<String> expected = ImmutableSet.of("foo"); + SingletonSetBuilder<String> builder = singletonSetBuilder(); + Set<String> actual = builder.setElement("foo").build(); + assertThat(actual).isEqualTo(expected); + } + + static class TypedSingletonSet<E> extends SingletonSet<E> { + private final Class<?> type; + + <T extends E> TypedSingletonSet(T element, Class<T> type) { + super(element); + this.type = type; + } + + @Override + public String toString() { + return type.getName() + super.toString(); + } + } + + @AutoBuilder(ofClass = TypedSingletonSet.class) + interface TypedSingletonSetBuilder<E, T extends E> { + TypedSingletonSetBuilder<E, T> setElement(T element); + + TypedSingletonSetBuilder<E, T> setType(Class<T> type); + + TypedSingletonSet<E> build(); + } + + static <E, T extends E> TypedSingletonSetBuilder<E, T> typedSingletonSetBuilder() { + return new AutoBuilder_AutoBuilderTest_TypedSingletonSetBuilder<>(); + } + + @Test + public void genericClassWithGenericConstructor() { + TypedSingletonSetBuilder<CharSequence, String> builder = typedSingletonSetBuilder(); + TypedSingletonSet<CharSequence> set = builder.setElement("foo").setType(String.class).build(); + assertThat(set.toString()).isEqualTo("java.lang.String[foo]"); + } + + static <T> ImmutableList<T> pair(T first, T second) { + return ImmutableList.of(first, second); + } + + @AutoBuilder(callMethod = "pair") + interface PairBuilder<T> { + PairBuilder<T> setFirst(T x); + + T getFirst(); + + PairBuilder<T> setSecond(T x); + + Optional<T> getSecond(); + + ImmutableList<T> build(); + } + + static <T> PairBuilder<T> pairBuilder() { + return new AutoBuilder_AutoBuilderTest_PairBuilder<>(); + } + + @Test + public void genericGetters() { + PairBuilder<Number> builder = pairBuilder(); + assertThat(builder.getSecond()).isEmpty(); + builder.setSecond(2); + assertThat(builder.getSecond()).hasValue(2); + try { + builder.getFirst(); + fail(); + } catch (IllegalStateException expected) { + } + builder.setFirst(1.0); + assertThat(builder.getFirst()).isEqualTo(1.0); + assertThat(builder.build()).containsExactly(1.0, 2).inOrder(); + } +} diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfTest.java index 1b587282..ee337409 100644 --- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfTest.java +++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfTest.java @@ -499,10 +499,7 @@ public class AutoOneOfTest { ArrayValue string = ArrayValue.ofString("foo"); ArrayValue ints1 = ArrayValue.ofInts(new int[] {17, 23}); ArrayValue ints2 = ArrayValue.ofInts(new int[] {17, 23}); - new EqualsTester() - .addEqualityGroup(string) - .addEqualityGroup(ints1, ints2) - .testEquals(); + new EqualsTester().addEqualityGroup(string).addEqualityGroup(ints1, ints2).testEquals(); } @Retention(RetentionPolicy.RUNTIME) @@ -560,8 +557,11 @@ public class AutoOneOfTest { @AutoOneOf(MaybeEmpty.Kind.class) public abstract static class MaybeEmpty implements Serializable { + private static final long serialVersionUID = 1L; + public enum Kind { - EMPTY, STRING, + EMPTY, + STRING, } public abstract Kind getKind(); 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 10812f8d..3f4e9bf5 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 @@ -15,9 +15,11 @@ */ package com.google.auto.value; +import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static com.google.common.truth.Truth8.assertThat; +import static com.google.common.truth.TruthJUnit.assume; import static com.google.testing.compile.CompilationSubject.assertThat; import static org.junit.Assert.assertThrows; import static org.junit.Assume.assumeTrue; @@ -53,6 +55,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.util.ElementFilter; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; +import org.junit.AssumptionViolatedException; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -153,6 +156,19 @@ public class AutoValueJava8Test { .isEqualTo("NullableProperties{nullableString=null, randomInt=23}"); } + @Test + public void testEqualsParameterIsAnnotated() throws NoSuchMethodException { + // 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. + double javaVersion = Double.parseDouble(JAVA_SPECIFICATION_VERSION.value()); + assume().that(javaVersion).isAtLeast(9.0); + Method equals = + NullableProperties.create(null, 23).getClass().getMethod("equals", Object.class); + AnnotatedType[] parameterTypes = equals.getAnnotatedParameterTypes(); + assertThat(parameterTypes).hasLength(1); + assertThat(parameterTypes[0].getAnnotation(Nullable.class)).isNotNull(); + } + @AutoAnnotation static Nullable nullable() { return new AutoAnnotation_AutoValueJava8Test_nullable(); @@ -162,9 +178,7 @@ public class AutoValueJava8Test { public void testNullablePropertyImplementationIsNullable() throws NoSuchMethodException { Method method = AutoValue_AutoValueJava8Test_NullableProperties.class.getDeclaredMethod("nullableString"); - assertThat(method.getAnnotatedReturnType().getAnnotations()) - .asList() - .contains(nullable()); + assertThat(method.getAnnotatedReturnType().getAnnotations()).asList().contains(nullable()); } @Test @@ -198,8 +212,9 @@ public class AutoValueJava8Test { @Test public void testExcludedNullablePropertyImplementation() throws NoSuchMethodException { - Method method = AutoValue_AutoValueJava8Test_NullablePropertiesNotCopied.class - .getDeclaredMethod("nullableString"); + Method method = + AutoValue_AutoValueJava8Test_NullablePropertiesNotCopied.class.getDeclaredMethod( + "nullableString"); assertThat(method.getAnnotatedReturnType().getAnnotations()) .asList() .doesNotContain(nullable()); @@ -536,6 +551,35 @@ public class AutoValueJava8Test { } } + @AutoValue + abstract static class NoNullableRef { + abstract String foo(); + + static NoNullableRef of(String foo) { + return new AutoValue_AutoValueJava8Test_NoNullableRef(foo); + } + } + + // Tests that we generate equals(@Nullable x) using JSpecify @Nullable if that annotation is + // available and there is no other @Nullable type annotation mentioned in the @AutoValue class. + // If there *are* other @Nullable type annotations, other test methods here will check that they + // are used instead. + @Test + public void testDefaultToJSpecifyNullable() throws ReflectiveOperationException { + Class<? extends Annotation> jspecifyNullable; + try { + // We write this using .concat in order to hide it from rewriting rules. + jspecifyNullable = + Class.forName("org".concat(".jspecify.nullness.Nullable")).asSubclass(Annotation.class); + } catch (ClassNotFoundException e) { + throw new AssumptionViolatedException("No JSpecify @Nullable available", e); + } + Class<? extends NoNullableRef> autoValueImpl = NoNullableRef.of("foo").getClass(); + Method equals = autoValueImpl.getDeclaredMethod("equals", Object.class); + assertThat(equals.getAnnotatedParameterTypes()[0].isAnnotationPresent(jspecifyNullable)) + .isTrue(); + } + @Test public void testBuilderWithUnprefixedGetter() { assumeTrue(javacHandlesTypeAnnotationsCorrectly); @@ -571,7 +615,7 @@ public class AutoValueJava8Test { public abstract static class BuilderWithPrefixedGetters<T extends Comparable<T>> { public abstract ImmutableList<T> getList(); - public abstract T getT(); + public abstract @Nullable T getT(); @SuppressWarnings("mutable") public abstract int @Nullable [] getInts(); @@ -586,7 +630,7 @@ public class AutoValueJava8Test { public abstract static class Builder<T extends Comparable<T>> { public abstract Builder<T> setList(ImmutableList<T> list); - public abstract Builder<T> setT(T t); + public abstract Builder<T> setT(@Nullable T t); public abstract Builder<T> setInts(int[] ints); @@ -761,6 +805,7 @@ public class AutoValueJava8Test { @AutoValue.Builder abstract static class Builder { abstract Builder maybeJustMaybe(Optional<String> maybe); + abstract OptionalOptional build(); } } @@ -795,6 +840,7 @@ public class AutoValueJava8Test { @AutoValue.Builder abstract static class Builder { abstract Builder setPredicate(Predicate<? super Integer> predicate); + abstract OptionalExtends build(); } } 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 346bc53a..3a7e7bc4 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 @@ -34,6 +34,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.ImmutableTable; +import com.google.common.collect.Ordering; import com.google.common.testing.EqualsTester; import com.google.common.testing.SerializableTester; import java.io.ObjectStreamClass; @@ -52,6 +53,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -60,6 +62,7 @@ import java.util.NavigableMap; import java.util.NavigableSet; import java.util.NoSuchElementException; import java.util.SortedMap; +import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import javax.annotation.Nullable; @@ -70,6 +73,7 @@ import org.junit.runners.JUnit4; /** @author emcmanus@google.com (Éamonn McManus) */ @RunWith(JUnit4.class) +@SuppressWarnings({"AutoValueImmutableFields", "AutoValueFinalMethods", "TypeNameShadowing"}) public class AutoValueTest { private static boolean omitIdentifiers; @@ -180,12 +184,15 @@ public class AutoValueTest { @AutoValue abstract static class StrangeGetters { abstract int get1st(); + abstract int get_1st(); // by default we'll use _1st where identifiers are needed, so foil that. @AutoValue.Builder abstract static class Builder { abstract Builder set1st(int x); + abstract Builder set_1st(int x); + abstract StrangeGetters build(); } @@ -284,6 +291,8 @@ public class AutoValueTest { @AutoValue public abstract static class Serialize implements Serializable { + private static final long serialVersionUID = 1L; + public abstract int integer(); public abstract String string(); @@ -845,6 +854,8 @@ public class AutoValueTest { @Test public void testGenericClassWithHairyBounds() throws Exception { class ComparableList<E> extends ArrayList<E> implements Comparable<ComparableList<E>> { + private static final long serialVersionUID = 1L; + @Override public int compareTo(ComparableList<E> list) { throw new UnsupportedOperationException(); @@ -1262,7 +1273,7 @@ public class AutoValueTest { } @AutoValue - public abstract static class ComplexInheritance extends AbstractBase implements A, B { + public abstract static class ComplexInheritance extends AbstractBase implements IntfA, IntfB { public static ComplexInheritance create(String name) { return new AutoValue_AutoValueTest_ComplexInheritance(name); } @@ -1277,9 +1288,9 @@ public class AutoValueTest { } } - interface A extends Base {} + interface IntfA extends Base {} - interface B extends Base {} + interface IntfB extends Base {} interface Base { int answer(); @@ -1352,7 +1363,7 @@ public class AutoValueTest { } @AutoValue - public abstract static class InheritTwice implements A, B { + public abstract static class InheritTwice implements IntfA, IntfB { public static InheritTwice create(int answer) { return new AutoValue_AutoValueTest_InheritTwice(answer); } @@ -1664,6 +1675,13 @@ public class AutoValueTest { .build(); assertThat(suppliedDirectly.optionalString()).hasValue("foo"); assertThat(suppliedDirectly.optionalInteger()).hasValue(23); + + try { + // The parameter is not marked @Nullable so this should fail. + OptionalPropertiesWithBuilder.builder().setOptionalString((String) null); + fail(); + } catch (NullPointerException expected) { + } } @AutoValue @@ -1874,6 +1892,40 @@ public class AutoValueTest { assertEquals((Integer) 17, instance3.u()); } + public interface ToBuilder<BuilderT> { + BuilderT toBuilder(); + } + + @AutoValue + public abstract static class InheritedToBuilder<T, U> + implements ToBuilder<InheritedToBuilder.Builder<T, U>> { + + public abstract T t(); + + public abstract U u(); + + public static <T, U> Builder<T, U> builder() { + return new AutoValue_AutoValueTest_InheritedToBuilder.Builder<T, U>(); + } + + @AutoValue.Builder + public abstract static class Builder<T, U> { + public abstract Builder<T, U> setT(T t); + + public abstract Builder<T, U> setU(U u); + + public abstract InheritedToBuilder<T, U> build(); + } + } + + @Test + public void testInheritedToBuilder() { + InheritedToBuilder<Integer, String> x = + InheritedToBuilder.<Integer, String>builder().setT(17).setU("wibble").build(); + InheritedToBuilder<Integer, String> y = x.toBuilder().setT(23).build(); + assertThat(y.u()).isEqualTo("wibble"); + } + @AutoValue public abstract static class BuilderWithSet<T extends Comparable<T>> { public abstract List<T> list(); @@ -2115,6 +2167,37 @@ public class AutoValueTest { } @AutoValue + public abstract static class BuilderWithPrefixedGettersAndUnprefixedSetters { + public abstract String getOAuth(); + + public abstract String getOBrien(); + + public static Builder builder() { + return new AutoValue_AutoValueTest_BuilderWithPrefixedGettersAndUnprefixedSetters.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder oAuth(String x); + + public abstract Builder OBrien(String x); + + public abstract BuilderWithPrefixedGettersAndUnprefixedSetters build(); + } + } + + @Test + public void testBuilderWithPrefixedGetterAndUnprefixedSetter() { + BuilderWithPrefixedGettersAndUnprefixedSetters x = + BuilderWithPrefixedGettersAndUnprefixedSetters.builder() + .oAuth("OAuth") + .OBrien("Flann") + .build(); + assertThat(x.getOAuth()).isEqualTo("OAuth"); + assertThat(x.getOBrien()).isEqualTo("Flann"); + } + + @AutoValue public abstract static class BuilderWithPropertyBuilders<FooT extends Comparable<FooT>> { public abstract ImmutableList<FooT> getFoos(); @@ -2223,6 +2306,7 @@ public class AutoValueTest { @AutoValue.Builder abstract static class Builder { abstract ImmutableList.Builder<String> listBuilder(); + abstract PropertyBuilderInheritsType build(); } } @@ -2820,6 +2904,8 @@ public class AutoValueTest { } public static class MyMap<K, V> extends HashMap<K, V> { + private static final long serialVersionUID = 1L; + public MyMap() {} public MyMap(Map<K, V> map) { @@ -2828,6 +2914,8 @@ public class AutoValueTest { } public static class MyMapBuilder<K, V> extends LinkedHashMap<K, V> { + private static final long serialVersionUID = 1L; + public MyMapBuilder() {} public MyMapBuilder(Map<K, V> map) { @@ -2873,6 +2961,8 @@ public class AutoValueTest { } public static class MyStringMap<V> extends MyMap<String, V> { + private static final long serialVersionUID = 1L; + public MyStringMap() {} public MyStringMap(Map<String, V> map) { @@ -2885,6 +2975,8 @@ public class AutoValueTest { } public static class MyStringMapBuilder<V> extends MyMapBuilder<String, V> { + private static final long serialVersionUID = 1L; + public MyStringMapBuilder() {} public MyStringMapBuilder(Map<String, V> map) { @@ -3258,6 +3350,7 @@ public class AutoValueTest { @AutoValue.Builder abstract static class Builder { abstract Builder setMetrics(ImmutableSet<? extends Number> metrics); + abstract GenericExtends build(); } } @@ -3282,6 +3375,7 @@ public class AutoValueTest { @AutoValue.Builder abstract static class Builder { abstract Builder setList(List<String> list); + abstract Child build(); } } @@ -3325,14 +3419,18 @@ public class AutoValueTest { @SuppressWarnings("ClassCanBeStatic") static class OuterWithTypeParam<T extends Number> { class InnerWithTypeParam<U> {} + class InnerWithoutTypeParam {} + static class Nested {} } @AutoValue abstract static class Nesty { abstract OuterWithTypeParam<Double>.InnerWithTypeParam<String> innerWithTypeParam(); + abstract OuterWithTypeParam<Double>.InnerWithoutTypeParam innerWithoutTypeParam(); + abstract OuterWithTypeParam.Nested nested(); static Builder builder() { @@ -3343,8 +3441,11 @@ public class AutoValueTest { abstract static class Builder { abstract Builder setInnerWithTypeParam( OuterWithTypeParam<Double>.InnerWithTypeParam<String> x); + abstract Builder setInnerWithoutTypeParam(OuterWithTypeParam<Double>.InnerWithoutTypeParam x); + abstract Builder setNested(OuterWithTypeParam.Nested x); + abstract Nesty build(); } } @@ -3353,11 +3454,12 @@ public class AutoValueTest { public void outerWithTypeParam() throws ReflectiveOperationException { @SuppressWarnings("UseDiamond") // Currently we compile this with -source 6 in the Eclipse test. OuterWithTypeParam<Double> outer = new OuterWithTypeParam<Double>(); - Nesty nesty = Nesty.builder() - .setInnerWithTypeParam(outer.new InnerWithTypeParam<String>()) - .setInnerWithoutTypeParam(outer.new InnerWithoutTypeParam()) - .setNested(new OuterWithTypeParam.Nested()) - .build(); + Nesty nesty = + Nesty.builder() + .setInnerWithTypeParam(outer.new InnerWithTypeParam<String>()) + .setInnerWithoutTypeParam(outer.new InnerWithoutTypeParam()) + .setNested(new OuterWithTypeParam.Nested()) + .build(); Type originalReturnType = Nesty.class.getDeclaredMethod("innerWithTypeParam").getGenericReturnType(); Type generatedReturnType = @@ -3383,6 +3485,7 @@ public class AutoValueTest { @MyAnnotation("thing") abstract static class Builder { abstract Builder setFoo(String x); + abstract BuilderAnnotationsNotCopied build(); } } @@ -3407,6 +3510,7 @@ public class AutoValueTest { @MyAnnotation("thing") abstract static class Builder { abstract Builder setFoo(String x); + abstract BuilderAnnotationsCopied build(); } } @@ -3417,4 +3521,106 @@ public class AutoValueTest { assertThat(builder.getClass().getAnnotations()).asList().containsExactly(myAnnotation("thing")); assertThat(builder.setFoo("foo").build().foo()).isEqualTo("foo"); } + + @AutoValue + @AutoValue.CopyAnnotations + @SuppressWarnings({"rawtypes", "unchecked"}) // deliberately checking handling of raw types + abstract static class DataWithSortedCollectionBuilders<K, V> { + abstract ImmutableSortedMap<K, V> anImmutableSortedMap(); + + abstract ImmutableSortedSet<V> anImmutableSortedSet(); + + abstract ImmutableSortedMap<Integer, V> nonGenericImmutableSortedMap(); + + abstract ImmutableSortedSet rawImmutableSortedSet(); + + abstract DataWithSortedCollectionBuilders.Builder<K, V> toBuilder(); + + static <K, V> DataWithSortedCollectionBuilders.Builder<K, V> builder() { + return new AutoValue_AutoValueTest_DataWithSortedCollectionBuilders.Builder<K, V>(); + } + + @AutoValue.Builder + abstract static class Builder<K, V> { + abstract DataWithSortedCollectionBuilders.Builder<K, V> anImmutableSortedMap( + SortedMap<K, V> anImmutableSortedMap); + + abstract ImmutableSortedMap.Builder<K, V> anImmutableSortedMapBuilder( + Comparator<K> keyComparator); + + abstract DataWithSortedCollectionBuilders.Builder<K, V> anImmutableSortedSet( + SortedSet<V> anImmutableSortedSet); + + abstract ImmutableSortedSet.Builder<V> anImmutableSortedSetBuilder(Comparator<V> comparator); + + abstract ImmutableSortedMap.Builder<Integer, V> nonGenericImmutableSortedMapBuilder( + Comparator<Integer> keyComparator); + + abstract ImmutableSortedSet.Builder rawImmutableSortedSetBuilder(Comparator comparator); + + abstract DataWithSortedCollectionBuilders<K, V> build(); + } + } + + @Test + @SuppressWarnings({"rawtypes", "unchecked"}) // deliberately checking handling of raw types + public void shouldGenerateBuildersWithComparators() { + Comparator<String> stringComparator = + new Comparator<String>() { + @Override + public int compare(String left, String right) { + return left.compareTo(right); + } + }; + + Comparator<Integer> intComparator = + new Comparator<Integer>() { + @Override + public int compare(Integer o1, Integer o2) { + return o1 - o2; + } + }; + + Comparator comparator = + new Comparator() { + @Override + public int compare(Object left, Object right) { + return String.valueOf(left).compareTo(String.valueOf(right)); + } + }; + + AutoValueTest.DataWithSortedCollectionBuilders.Builder<String, Integer> builder = + AutoValueTest.DataWithSortedCollectionBuilders.builder(); + + builder + .anImmutableSortedMapBuilder(stringComparator) + .put("Charlie", 1) + .put("Alfa", 2) + .put("Bravo", 3); + builder.anImmutableSortedSetBuilder(intComparator).add(1, 5, 9, 3); + builder.nonGenericImmutableSortedMapBuilder(intComparator).put(9, 99).put(1, 11).put(3, 33); + builder.rawImmutableSortedSetBuilder(comparator).add("Bravo", "Charlie", "Alfa"); + + AutoValueTest.DataWithSortedCollectionBuilders<String, Integer> data = builder.build(); + + AutoValueTest.DataWithSortedCollectionBuilders.Builder<String, Integer> copiedBuilder = + data.toBuilder(); + AutoValueTest.DataWithSortedCollectionBuilders<String, Integer> copiedData = + copiedBuilder.build(); + + assertThat(data.anImmutableSortedMap().keySet()) + .containsExactly("Alfa", "Bravo", "Charlie") + .inOrder(); + assertThat(data.anImmutableSortedSet()).containsExactly(1, 3, 5, 9).inOrder(); + assertThat(data.nonGenericImmutableSortedMap().keySet()).containsExactly(1, 3, 9).inOrder(); + assertThat(data.rawImmutableSortedSet()).containsExactly("Alfa", "Bravo", "Charlie").inOrder(); + + assertThat(copiedData).isEqualTo(data); + + try { + builder.anImmutableSortedMapBuilder(Ordering.from(stringComparator).reverse()); + fail("Calling property builder method a second time should have failed"); + } catch (IllegalStateException expected) { + } + } } 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 15188274..ca10fb45 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 @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import com.google.auto.value.processor.AutoAnnotationProcessor; +import com.google.auto.value.processor.AutoBuilderProcessor; import com.google.auto.value.processor.AutoOneOfProcessor; import com.google.auto.value.processor.AutoValueProcessor; import com.google.common.collect.ImmutableList; @@ -58,25 +59,28 @@ public class CompileWithEclipseTest { @BeforeClass public static void setSourceRoot() { assertWithMessage("basedir property must be set - test must be run from Maven") - .that(SOURCE_ROOT).isNotNull(); + .that(SOURCE_ROOT) + .isNotNull(); } public @Rule TemporaryFolder tmp = new TemporaryFolder(); private static final ImmutableSet<String> IGNORED_TEST_FILES = - ImmutableSet.of("AutoValueNotEclipseTest.java", "CompileWithEclipseTest.java"); + ImmutableSet.of( + "AutoValueNotEclipseTest.java", "CompileWithEclipseTest.java", "GradleTest.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"); + f -> + f.getName().equals("AutoValueJava8Test.java") + || f.getName().equals("AutoOneOfJava8Test.java") + || f.getName().equals("EmptyExtension.java"); @Test - public void compileWithEclipseJava6() throws Exception { - compileWithEclipse("6", JAVA_FILE.and(JAVA8_TEST.negate())); + public void compileWithEclipseJava7() throws Exception { + compileWithEclipse("7", JAVA_FILE.and(JAVA8_TEST.negate())); } @Test @@ -103,17 +107,28 @@ public class CompileWithEclipseTest { // fileManager.getLocation(SYSTEM_MODULES). File rtJar = new File(JAVA_HOME.value() + "/lib/rt.jar"); if (rtJar.exists()) { - List<File> bootClassPath = ImmutableList.<File>builder() - .add(rtJar) - .addAll(fileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH)) - .build(); + List<File> bootClassPath = + ImmutableList.<File>builder() + .add(rtJar) + .addAll(fileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH)) + .build(); fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, bootClassPath); } Iterable<? extends JavaFileObject> sourceFileObjects = fileManager.getJavaFileObjectsFromFiles(sources); String outputDir = tmp.getRoot().toString(); ImmutableList<String> options = - ImmutableList.of("-d", outputDir, "-s", outputDir, "-source", version, "-target", version); + ImmutableList.of( + "-d", + outputDir, + "-s", + outputDir, + "-source", + version, + "-target", + version, + "-warn:-warningToken,-intfAnnotation", + "-Acom.google.auto.value.AutoBuilderIsUnstable"); JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, options, null, sourceFileObjects); // Explicitly supply an empty list of extensions for AutoValueProcessor, because otherwise this @@ -121,7 +136,10 @@ public class CompileWithEclipseTest { AutoValueProcessor autoValueProcessor = new AutoValueProcessor(ImmutableList.of()); ImmutableList<? extends Processor> processors = ImmutableList.of( - autoValueProcessor, new AutoOneOfProcessor(), new AutoAnnotationProcessor()); + autoValueProcessor, + new AutoOneOfProcessor(), + new AutoAnnotationProcessor(), + new AutoBuilderProcessor()); task.setProcessors(processors); assertWithMessage("Compilation should succeed").that(task.call()).isTrue(); } 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/GradleTest.java new file mode 100644 index 00000000..f4eb5388 --- /dev/null +++ b/value/src/it/functional/src/test/java/com/google/auto/value/GradleTest.java @@ -0,0 +1,187 @@ +/* + * Copyright 2021 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; + +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.collect.ImmutableList; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.gradle.util.GradleVersion; +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 class GradleTest { + @Rule public TemporaryFolder fakeProject = new TemporaryFolder(); + + private static final String BUILD_GRADLE_TEXT = + String.join( + "\n", + "plugins {", + " id 'java-library'", + "}", + "repositories {", + " maven { url = uri('${localRepository}') }", + "}", + "dependencies {", + " compileOnlyApi " + + " 'com.google.auto.value:auto-value-annotations:${autoValueVersion}'", + " annotationProcessor 'com.google.auto.value:auto-value:${autoValueVersion}'", + "}"); + + private static final String FOO_TEXT = + String.join( + "\n", + "package com.example;", + "", + "import com.google.auto.value.AutoValue;", + "", + "@AutoValue", + "abstract class Foo {", + " abstract String bar();", + "", + " static Foo of(String bar) {", + " return new AutoValue_Foo(bar);", + " }", + "}"); + + private static final Optional<File> GRADLE_INSTALLATION = getGradleInstallation(); + + @Test + public void basic() throws IOException { + String autoValueVersion = System.getProperty("autoValueVersion"); + assertThat(autoValueVersion).isNotNull(); + String localRepository = System.getProperty("localRepository"); + assertThat(localRepository).isNotNull(); + + // Set up the fake Gradle project. + String buildGradleText = expandSystemProperties(BUILD_GRADLE_TEXT); + writeFile(fakeProject.newFile("build.gradle").toPath(), buildGradleText); + Path srcDir = fakeProject.newFolder("src", "main", "java", "com", "example").toPath(); + writeFile(srcDir.resolve("Foo.java"), FOO_TEXT); + + // Build it the first time. + BuildResult result1 = buildFakeProject(); + assertThat(result1.getOutput()) + .contains( + "Full recompilation is required because no incremental change information is" + + " available"); + Path output = + fakeProject + .getRoot() + .toPath() + .resolve("build/classes/java/main/com/example/AutoValue_Foo.class"); + assertThat(Files.exists(output)).isTrue(); + + // Add a source file to the project. + String barText = FOO_TEXT.replace("Foo", "Bar"); + Path barFile = srcDir.resolve("Bar.java"); + writeFile(barFile, barText); + + // Build it a second time. + BuildResult result2 = buildFakeProject(); + assertThat(result2.getOutput()).doesNotContain("Full recompilation is required"); + + // Remove the second source file and build a third time. If incremental annotation processing + // is not working, this will produce a message like this: + // Full recompilation is required because com.google.auto.value.processor.AutoValueProcessor + // is not incremental + Files.delete(barFile); + BuildResult result3 = buildFakeProject(); + assertThat(result3.getOutput()).doesNotContain("Full recompilation is required"); + } + + private BuildResult buildFakeProject() throws IOException { + GradleRunner runner = + GradleRunner.create() + .withProjectDir(fakeProject.getRoot()) + .withArguments("--info", "compileJava"); + if (GRADLE_INSTALLATION.isPresent()) { + runner.withGradleInstallation(GRADLE_INSTALLATION.get()); + } else { + runner.withGradleVersion(GradleVersion.current().getVersion()); + } + return runner.build(); + } + + private static Optional<File> getGradleInstallation() { + String gradleHome = System.getenv("GRADLE_HOME"); + if (gradleHome != null) { + File gradleHomeFile = new File(gradleHome); + if (gradleHomeFile.isDirectory()) { + return Optional.of(new File(gradleHome)); + } + } + try { + Path gradleExecutable = Paths.get("/usr/bin/gradle"); + Path gradleLink = gradleExecutable.resolveSibling(Files.readSymbolicLink(gradleExecutable)); + if (!gradleLink.endsWith("bin/gradle")) { + return Optional.empty(); + } + Path installationPath = gradleLink.getParent().getParent(); + if (!Files.isDirectory(installationPath)) { + return Optional.empty(); + } + Optional<Path> coreJar; + Pattern corePattern = Pattern.compile("gradle-core-([0-9]+)\\..*\\.jar"); + try (Stream<Path> files = Files.walk(installationPath.resolve("lib"))) { + coreJar = + files + .filter( + p -> { + Matcher matcher = corePattern.matcher(p.getFileName().toString()); + if (matcher.matches()) { + int version = Integer.parseInt(matcher.group(1)); + if (version >= 5) { + return true; + } + } + return false; + }) + .findFirst(); + } + return coreJar.map(unused -> installationPath.toFile()); + } catch (IOException e) { + return Optional.empty(); + } + } + + private static String expandSystemProperties(String s) { + for (String name : System.getProperties().stringPropertyNames()) { + String value = System.getProperty(name); + s = s.replace("${" + name + "}", value); + } + return s; + } + + private static void writeFile(Path file, String text) throws IOException { + Files.write(file, ImmutableList.of(text), UTF_8); + } +} 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 new file mode 100644 index 00000000..f3318890 --- /dev/null +++ b/value/src/it/functional/src/test/java/com/google/auto/value/KotlinData.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2021 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 + +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") diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/EmptyExtension.java b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/EmptyExtension.java index 0476906c..e6f7abf7 100644 --- a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/EmptyExtension.java +++ b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/EmptyExtension.java @@ -99,8 +99,7 @@ public class EmptyExtension extends AutoValueExtension { if (typeParameters.isEmpty()) { return ""; } - return typeParameters - .stream() + return typeParameters.stream() .map(e -> e.getSimpleName().toString()) .collect(joining(", ", "<", ">")); } diff --git a/value/src/it/gwtserializer/pom.xml b/value/src/it/gwtserializer/pom.xml index eea89528..42cc2fe2 100644 --- a/value/src/it/gwtserializer/pom.xml +++ b/value/src/it/gwtserializer/pom.xml @@ -36,7 +36,7 @@ <dependency> <groupId>com.google.gwt</groupId> <artifactId>gwt</artifactId> - <version>2.8.2</version> + <version>2.9.0</version> <type>pom</type> <scope>import</scope> </dependency> @@ -94,17 +94,17 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.7.0</version> + <version>3.8.1</version> <dependencies> <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>0.9.4</version> + <version>1.0.7</version> </dependency> </dependencies> <configuration> - <source>1.7</source> - <target>1.7</target> + <source>1.8</source> + <target>1.8</target> <compilerArgument>-Xlint:all</compilerArgument> <showWarnings>true</showWarnings> <showDeprecation>true</showDeprecation> @@ -120,7 +120,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> - <version>3.0.2</version> + <version>3.2.0</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-rc-6</version> + <version>1.0.0</version> <executions> <execution> <goals> @@ -144,7 +144,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> - <version>2.7</version> + <version>2.8.2</version> <configuration> <!-- Build/test, but don't deploy --> <skip>true</skip> diff --git a/value/src/main/java/com/google/auto/value/AutoBuilder.java b/value/src/main/java/com/google/auto/value/AutoBuilder.java new file mode 100644 index 00000000..b9709005 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/AutoBuilder.java @@ -0,0 +1,64 @@ +/* + * Copyright 2021 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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +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: + * + * <pre> + * + * {@code @}AutoBuilder(ofClass = Person.class) + * abstract class PersonBuilder { + * static PersonBuilder builder() { + * return new AutoBuilder_PersonBuilder(); + * } + * + * abstract PersonBuilder setName(String name); + * abstract PersonBuilder setId(int id); + * abstract Person build(); + * }</pre> + * + * @see <a + * href="https://github.com/google/auto/blob/master/value/userguide/autobuilder.md">AutoBuilder + * User's Guide</a> + */ +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +public @interface AutoBuilder { + /** + * The static method from {@link #ofClass} to call when the build-method of the builder is called. + * By default this is empty, meaning that a constructor rather than a static method should be + * called. There can be more than one method with the given name, or more than one constructor, in + * which case the one to call is the one whose parameter names and types correspond to the + * abstract methods of the class or interface with the {@code @AutoBuilder} annotation. + */ + String callMethod() default ""; + + /** + * The class or interface containing the constructor or static method that the generated builder + * will eventually call. By default this is the class or interface that <i>contains</i> the class + * or interface with the {@code @AutoBuilder} annotation. + */ + Class<?> ofClass() default Void.class; +} 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 45a677c9..d7541f65 100644 --- a/value/src/main/java/com/google/auto/value/AutoValue.java +++ b/value/src/main/java/com/google/auto/value/AutoValue.java @@ -98,7 +98,7 @@ public @interface AutoValue { * <p>If you want to copy annotations from your {@literal @}AutoValue-annotated class's methods to * the generated fields in the AutoValue_... implementation, annotate your method * with {@literal @}AutoValue.CopyAnnotations. For example, if Example.java is:<pre> - + * * {@code @}Immutable * {@code @}AutoValue * abstract class Example { 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 2c8a3fb0..343645ae 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 @@ -121,12 +121,10 @@ public abstract class AutoValueExtension { * example, if you have... * * <pre> - * interface Parent<T> { + * {@code interface Parent<T>} { * T bar(); * } - * - * {@code @AutoValue abstract class Foo implements Parent<String> {...}} - * </pre> + * {@code @AutoValue abstract class Foo implements Parent<String> {...}}</pre> * * ...then the type of the {@code bar} property in {@code Foo} is actually {@code String}, but * the {@code ExecutableElement} will be the the method in {@code Parent}, whose return type is @@ -418,11 +416,11 @@ public abstract class AutoValueExtension { * Context#abstractMethods()}. * * <p>For example, Android's {@code Parcelable} interface includes a <a - * href="http://developer.android.com/reference/android/os/Parcelable.html#writeToParcel(android.os.Parcel, - * int)">method</a> {@code void writeToParcel(Parcel, int)}. Normally AutoValue would not know - * what to do with that abstract method. But an {@code AutoValueExtension} that understands {@code - * Parcelable} can provide a useful implementation and return the {@code writeToParcel} method - * here. That will prevent a warning about the method from AutoValue. + * href="http://developer.android.com/reference/android/os/Parcelable.html#writeToParcel(android.os.Parcel,int)">method</a> + * {@code void writeToParcel(Parcel, int)}. Normally AutoValue would not know what to do with that + * abstract method. But an {@code AutoValueExtension} that understands {@code Parcelable} can + * provide a useful implementation and return the {@code writeToParcel} method here. That will + * prevent a warning about the method from AutoValue. * * @param context the Context of the code generation for this class. */ 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 0ca46bde..acbe1c03 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 @@ -19,18 +19,19 @@ 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.memoized.processor.ClassNames.MEMOIZED_NAME; import static com.google.auto.value.extension.memoized.processor.MemoizedValidator.getAnnotationMirror; import static com.google.common.base.Predicates.equalTo; import static com.google.common.base.Predicates.not; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableSet.toImmutableSet; 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; @@ -50,7 +51,6 @@ 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.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.FormatMethod; @@ -65,9 +65,7 @@ import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; import java.lang.annotation.Inherited; -import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; import javax.annotation.processing.Messager; @@ -80,7 +78,6 @@ 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.element.TypeParameterElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; @@ -126,13 +123,9 @@ public final class MemoizeExtension extends AutoValueExtension { } private static ImmutableSet<ExecutableElement> memoizedMethods(Context context) { - ImmutableSet.Builder<ExecutableElement> memoizedMethods = ImmutableSet.builder(); - for (ExecutableElement method : methodsIn(context.autoValueClass().getEnclosedElements())) { - if (getAnnotationMirror(method, MEMOIZED_NAME).isPresent()) { - memoizedMethods.add(method); - } - } - return memoizedMethods.build(); + return methodsIn(context.autoValueClass().getEnclosedElements()).stream() + .filter(m -> getAnnotationMirror(m, MEMOIZED_NAME).isPresent()) + .collect(toImmutableSet()); } static final class Generator { @@ -164,7 +157,7 @@ public final class MemoizeExtension extends AutoValueExtension { classBuilder(className) .superclass(superType()) .addAnnotations(copiedClassAnnotations(context.autoValueClass())) - .addTypeVariables(typeVariableNames()) + .addTypeVariables(annotatedTypeVariableNames()) .addModifiers(isFinal ? FINAL : ABSTRACT) .addMethod(constructor()); generatedAnnotationSpec(elements, sourceVersion, MemoizeExtension.class) @@ -183,6 +176,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(); @@ -193,23 +187,31 @@ public final class MemoizeExtension extends AutoValueExtension { } private ImmutableList<TypeVariableName> typeVariableNames() { - ImmutableList.Builder<TypeVariableName> typeVariableNamesBuilder = ImmutableList.builder(); - for (TypeParameterElement typeParameter : context.autoValueClass().getTypeParameters()) { - typeVariableNamesBuilder.add(TypeVariableName.get(typeParameter)); - } - return typeVariableNamesBuilder.build(); + return context.autoValueClass().getTypeParameters().stream() + .map(TypeVariableName::get) + .collect(toImmutableList()); + } + + private ImmutableList<TypeVariableName> annotatedTypeVariableNames() { + return context.autoValueClass().getTypeParameters().stream() + .map( + p -> + TypeVariableName.get(p) + .annotated( + p.getAnnotationMirrors().stream() + .map(AnnotationSpec::get) + .collect(toImmutableList()))) + .collect(toImmutableList()); } private MethodSpec constructor() { MethodSpec.Builder constructor = constructorBuilder(); - for (Map.Entry<String, TypeMirror> property : context.propertyTypes().entrySet()) { - constructor.addParameter(annotatedType(property.getValue()), property.getKey() + "$"); - } - List<String> namesWithDollars = new ArrayList<String>(); - for (String property : context.properties().keySet()) { - namesWithDollars.add(property + "$"); - } - constructor.addStatement("super($L)", Joiner.on(", ").join(namesWithDollars)); + context + .propertyTypes() + .forEach((name, type) -> constructor.addParameter(annotatedType(type), name + "$")); + String superParams = + context.properties().keySet().stream().map(n -> n + "$").collect(joining(", ")); + constructor.addStatement("super($L)", superParams); return constructor.build(); } @@ -250,6 +252,7 @@ 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 @@ -478,15 +481,15 @@ public final class MemoizeExtension extends AutoValueExtension { return elements.overrides(method, objectMethod(methodName), context.autoValueClass()); } - private ExecutableElement objectMethod(final String methodName) { + private ExecutableElement objectMethod(String methodName) { TypeElement object = elements.getTypeElement(Object.class.getName()); - for (ExecutableElement method : methodsIn(object.getEnclosedElements())) { - if (method.getSimpleName().contentEquals(methodName)) { - return method; - } - } - throw new IllegalArgumentException( - String.format("No method in Object named \"%s\"", methodName)); + return methodsIn(object.getEnclosedElements()).stream() + .filter(m -> m.getSimpleName().contentEquals(methodName)) + .findFirst() + .orElseThrow( + () -> + new IllegalArgumentException( + String.format("No method in Object named \"%s\"", methodName))); } private boolean pullDownMethodAnnotation(AnnotationMirror annotation) { @@ -594,9 +597,7 @@ public final class MemoizeExtension extends AutoValueExtension { /** 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()); + 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/memoized/processor/MemoizedValidator.java b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizedValidator.java index 5a770500..d250fbc6 100644 --- a/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizedValidator.java +++ b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizedValidator.java @@ -37,8 +37,8 @@ import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; /** - * An annotation {@link Processor} that reports errors for {@link Memoized @Memoized} methods that - * are not inside {@code AutoValue}-annotated classes. + * An annotation {@link Processor} that reports errors for {@code @Memoized} methods that are not + * inside {@code AutoValue}-annotated classes. */ @AutoService(Processor.class) @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING) @@ -66,9 +66,7 @@ public final class MemoizedValidator extends AbstractProcessor { } private static boolean isAutoValue(Element element) { - return element - .getAnnotationMirrors() - .stream() + return element.getAnnotationMirrors().stream() .map(annotation -> MoreTypes.asTypeElement(annotation.getAnnotationType())) .anyMatch(type -> type.getQualifiedName().contentEquals("com.google.auto.value.AutoValue")); } diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/userguide/index.md b/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/index.md index e7661839..0282404b 100644 --- a/value/src/main/java/com/google/auto/value/extension/serializable/userguide/index.md +++ b/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/index.md @@ -4,7 +4,6 @@ An [`AutoValue`] extension that enables `@AutoValue` classes with un-serializable properties to be serializable. - ## Usage To use the [`SerializableAutoValueExtension`] with your `AutoValue` class, the @@ -100,4 +99,3 @@ un-serializable types with [SerializerExtensions]. [`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 - diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/userguide/serializer-extension.md b/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/serializer-extension.md index edfa3ca3..aaec7c02 100644 --- a/value/src/main/java/com/google/auto/value/extension/serializable/userguide/serializer-extension.md +++ b/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/serializer-extension.md @@ -243,4 +243,3 @@ as-is. [`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 - 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 d3265d02..5143d8bf 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 @@ -15,9 +15,9 @@ */ package com.google.auto.value.extension.serializable.processor; +import static com.google.auto.common.MoreStreams.toImmutableList; +import static com.google.auto.common.MoreStreams.toImmutableMap; import static com.google.auto.value.extension.serializable.processor.ClassNames.SERIALIZABLE_AUTO_VALUE_NAME; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableMap.toImmutableMap; import static java.util.stream.Collectors.joining; import com.google.auto.common.GeneratedAnnotationSpecs; @@ -59,6 +59,7 @@ import javax.lang.model.type.TypeMirror; * <li>The AutoValue class must implement {@link Serializable}. * <li>Unserializable fields in the AutoValue class must be supported by a {@link * com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension}. + * </ul> */ @AutoService(AutoValueExtension.class) public final class SerializableAutoValueExtension extends AutoValueExtension { diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoader.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoader.java index 12984b05..e81b5867 100644 --- a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoader.java +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoader.java @@ -21,6 +21,8 @@ import com.google.auto.value.extension.serializable.serializer.interfaces.Serial import com.google.auto.value.processor.SimpleServiceLoader; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; +import java.util.Optional; +import java.util.regex.Pattern; import javax.annotation.processing.ProcessingEnvironment; import javax.tools.Diagnostic; @@ -40,10 +42,18 @@ public final class SerializerFactoryLoader { private static ImmutableList<SerializerExtension> loadExtensions( ProcessingEnvironment processingEnv) { + // The below is a workaround for a test-building bug. We don't expect to support it indefinitely + // so don't depend on it. + String allowedMissingClasses = + processingEnv.getOptions().get("allowedMissingSerializableExtensionClasses"); + Optional<Pattern> allowedMissingClassesPattern = + Optional.ofNullable(allowedMissingClasses).map(Pattern::compile); try { return ImmutableList.copyOf( SimpleServiceLoader.load( - SerializerExtension.class, SerializerFactoryLoader.class.getClassLoader())); + SerializerExtension.class, + SerializerFactoryLoader.class.getClassLoader(), + allowedMissingClassesPattern)); } catch (Throwable t) { processingEnv .getMessager() diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java index 7ff4f19d..a390adde 100644 --- a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java @@ -55,17 +55,22 @@ public final class ImmutableListSerializerExtension implements SerializerExtensi return Optional.empty(); } - return Optional.of(new ImmutableListSerializer(containedTypeSerializer, processingEnv)); + return Optional.of( + new ImmutableListSerializer(containedTypeSerializer, factory, processingEnv)); } private static class ImmutableListSerializer implements Serializer { private final Serializer containedTypeSerializer; + private final SerializerFactory factory; private final ProcessingEnvironment processingEnv; ImmutableListSerializer( - Serializer containedTypeSerializer, ProcessingEnvironment processingEnv) { + Serializer containedTypeSerializer, + SerializerFactory factory, + ProcessingEnvironment processingEnv) { this.containedTypeSerializer = containedTypeSerializer; + this.factory = factory; this.processingEnv = processingEnv; } @@ -81,7 +86,7 @@ public final class ImmutableListSerializerExtension implements SerializerExtensi @Override public CodeBlock toProxy(CodeBlock expression) { - CodeBlock element = CodeBlock.of("value$$"); + CodeBlock element = factory.newIdentifier("value"); return CodeBlock.of( "$L.stream().map($T.wrapper($L -> $L)).collect($T.toImmutableList())", expression, @@ -93,7 +98,7 @@ public final class ImmutableListSerializerExtension implements SerializerExtensi @Override public CodeBlock fromProxy(CodeBlock expression) { - CodeBlock element = CodeBlock.of("value$$"); + CodeBlock element = factory.newIdentifier("value"); return CodeBlock.of( "$L.stream().map($T.wrapper($L -> $L)).collect($T.toImmutableList())", expression, diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java index 9d571e3b..8d67e103 100644 --- a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java @@ -60,7 +60,7 @@ public final class ImmutableMapSerializerExtension implements SerializerExtensio return Optional.of( new ImmutableMapSerializer( - keyType, valueType, keyTypeSerializer, valueTypeSerializer, processingEnv)); + keyType, valueType, keyTypeSerializer, valueTypeSerializer, factory, processingEnv)); } private static class ImmutableMapSerializer implements Serializer { @@ -71,6 +71,7 @@ public final class ImmutableMapSerializerExtension implements SerializerExtensio private final TypeMirror valueProxyType; private final Serializer keyTypeSerializer; private final Serializer valueTypeSerializer; + private final SerializerFactory factory; private final ProcessingEnvironment processingEnv; ImmutableMapSerializer( @@ -78,6 +79,7 @@ public final class ImmutableMapSerializerExtension implements SerializerExtensio TypeMirror valueType, Serializer keyTypeSerializer, Serializer valueTypeSerializer, + SerializerFactory factory, ProcessingEnvironment processingEnv) { this.keyType = keyType; this.valueType = valueType; @@ -85,6 +87,7 @@ public final class ImmutableMapSerializerExtension implements SerializerExtensio this.valueProxyType = valueTypeSerializer.proxyFieldType(); this.keyTypeSerializer = keyTypeSerializer; this.valueTypeSerializer = valueTypeSerializer; + this.factory = factory; this.processingEnv = processingEnv; } @@ -117,13 +120,15 @@ public final class ImmutableMapSerializerExtension implements SerializerExtensio generateValueMapFunction(valueProxyType, valueType, valueTypeSerializer::fromProxy)); } - private static CodeBlock generateKeyMapFunction( + private CodeBlock generateKeyMapFunction( TypeMirror originalType, TypeMirror transformedType, Function<CodeBlock, CodeBlock> proxyMap) { - CodeBlock element = CodeBlock.of("element$$"); + CodeBlock element = factory.newIdentifier("element"); + CodeBlock value = factory.newIdentifier("value"); return CodeBlock.of( - "value$$ -> $T.<$T, $T>wrapper($L -> $L).apply(value$$.getKey())", + "$1L -> $2T.<$3T, $4T>wrapper($5L -> $6L).apply($1L.getKey())", + value, FunctionWithExceptions.class, originalType, transformedType, @@ -131,13 +136,15 @@ public final class ImmutableMapSerializerExtension implements SerializerExtensio proxyMap.apply(element)); } - private static CodeBlock generateValueMapFunction( + private CodeBlock generateValueMapFunction( TypeMirror originalType, TypeMirror transformedType, Function<CodeBlock, CodeBlock> proxyMap) { - CodeBlock element = CodeBlock.of("element$$"); + CodeBlock element = factory.newIdentifier("element"); + CodeBlock value = factory.newIdentifier("value"); return CodeBlock.of( - "value$$ -> $T.<$T, $T>wrapper($L -> $L).apply(value$$.getValue())", + "$1L -> $2T.<$3T, $4T>wrapper($5L -> $6L).apply($1L.getValue())", + value, FunctionWithExceptions.class, originalType, transformedType, diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java index 57741f91..7c55289d 100644 --- a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java @@ -19,7 +19,9 @@ import com.google.auto.value.extension.serializable.serializer.interfaces.Serial import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension; import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory; import com.google.common.collect.ImmutableList; +import com.squareup.javapoet.CodeBlock; import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.type.TypeMirror; @@ -28,6 +30,7 @@ public final class SerializerFactoryImpl implements SerializerFactory { private final ImmutableList<SerializerExtension> extensions; private final ProcessingEnvironment env; + private final AtomicInteger idCount = new AtomicInteger(); public SerializerFactoryImpl( ImmutableList<SerializerExtension> extensions, ProcessingEnvironment env) { @@ -45,4 +48,9 @@ public final class SerializerFactoryImpl implements SerializerFactory { } return IdentitySerializerFactory.getSerializer(typeMirror); } + + @Override + public CodeBlock newIdentifier(String prefix) { + return CodeBlock.of("$L$$$L", prefix, idCount.incrementAndGet()); + } } diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java index d05c88b2..2b542251 100644 --- a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java @@ -15,6 +15,7 @@ */ package com.google.auto.value.extension.serializable.serializer.interfaces; +import com.squareup.javapoet.CodeBlock; import javax.lang.model.type.TypeMirror; /** @@ -26,4 +27,16 @@ public interface SerializerFactory { /** Returns a {@link Serializer} for the given {@link TypeMirror}. */ Serializer getSerializer(TypeMirror type); + + /** + * Returns an identifier beginning with the given prefix and that is distinct from any identifier + * returned by another call to this method. The returned identifier will contain a {@code $}, + * which should also mean it is distinct from identifiers in user code that are in scope. + * + * <p>The default implementation of this method throws {@link UnsupportedOperationException} for + * compatibility reasons. + */ + default CodeBlock newIdentifier(String prefix) { + throw new UnsupportedOperationException(); + } } diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/ToPrettyString.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/ToPrettyString.java new file mode 100644 index 00000000..cd2762a2 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/ToPrettyString.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021 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.extension.toprettystring; + +import static java.lang.annotation.ElementType.METHOD; + +import java.lang.annotation.Documented; +import java.lang.annotation.Target; +import java.util.Collection; + +/** + * Annotates instance methods that return an easy-to-read {@link String} representing the instance. + * When the method is {@code abstract} and enclosed in an {@link com.google.auto.value.AutoValue} + * class, an implementation of the method will be automatically generated. + * + * <p>When generating an implementation of an {@code @ToPrettyString} method, each property of the + * {@code @AutoValue} type is individually printed in an easy-to-read format. If the type of the + * property itself has a {@code @ToPrettyString} method, that method will be called in assistance of + * computing the pretty string. Non-{@code @AutoValue} classes can contribute a pretty string + * representation by annotating a method with {@code @ToPrettyString}. + * + * <p>{@link Collection} and {@link Collection}-like types have special representations in generated + * pretty strings. + * + * <p>If no {@code @ToPrettyString} method is found on a type and the type is not one with a built + * in rendering, the {@link Object#toString()} value will be used instead. + * + * <p>{@code @ToPrettyString} is valid on overridden {@code toString()} and other methods alike. + * + * <h3>Example</h3> + * + * <pre> + * {@code @AutoValue} + * abstract class Pretty { + * abstract {@code List<String>} property(); + * + * {@code @ToPrettyString} + * abstract String toPrettyString(); + * } + * + * System.out.println(new AutoValue_Pretty(List.of("abc", "def", "has\nnewline)).toPrettyString()) + * // Pretty{ + * // property = [ + * // abc, + * // def, + * // has + * // newline, + * // ] + * // } + * }</pre> + */ +@Documented +@Target(METHOD) +public @interface ToPrettyString {} diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/Annotations.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/Annotations.java new file mode 100644 index 00000000..255d6c9c --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/Annotations.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021 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.extension.toprettystring.processor; + +import static com.google.auto.value.extension.toprettystring.processor.ClassNames.TO_PRETTY_STRING_NAME; + +import com.google.auto.common.MoreTypes; +import java.util.Optional; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; + +/** Extension methods for working with {@link AnnotationMirror}. */ +final class Annotations { + static Optional<AnnotationMirror> getAnnotationMirror(Element element, String annotationName) { + for (AnnotationMirror annotation : element.getAnnotationMirrors()) { + TypeElement annotationElement = MoreTypes.asTypeElement(annotation.getAnnotationType()); + if (annotationElement.getQualifiedName().contentEquals(annotationName)) { + return Optional.of(annotation); + } + } + return Optional.empty(); + } + + static Optional<AnnotationMirror> toPrettyStringAnnotation(Element element) { + return getAnnotationMirror(element, TO_PRETTY_STRING_NAME); + } + + private Annotations() {} +} diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ClassNames.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ClassNames.java new file mode 100644 index 00000000..0cfd5772 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ClassNames.java @@ -0,0 +1,25 @@ +/* + * Copyright 2021 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.extension.toprettystring.processor; + +/** Names of classes that are referenced in the processor/extension. */ +final class ClassNames { + static final String TO_PRETTY_STRING_NAME = + "com.google.auto.value.extension.toprettystring.ToPrettyString"; + + private ClassNames() {} +} 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 new file mode 100644 index 00000000..e2381f7e --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java @@ -0,0 +1,297 @@ +/* + * Copyright 2021 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.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.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.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.MethodSpec; +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. + * + * <p>This is copied from {@link + * com.google.auto.value.extension.memoized.processor.MemoizeExtension} until we find a better + * 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; + + private ExtensionClassTypeSpecBuilder( + Context context, String className, String classToExtend, boolean isFinal) { + this.context = context; + this.className = className; + this.classToExtend = classToExtend; + this.isFinal = isFinal; + this.types = context.processingEnvironment().getTypeUtils(); + this.elements = context.processingEnvironment().getElementUtils(); + this.sourceVersion = context.processingEnvironment().getSourceVersion(); + } + + static TypeSpec.Builder extensionClassTypeSpecBuilder( + Context context, String className, String classToExtend, boolean isFinal) { + return new ExtensionClassTypeSpecBuilder(context, className, classToExtend, isFinal) + .extensionClassBuilder(); + } + + TypeSpec.Builder extensionClassBuilder() { + TypeSpec.Builder builder = + classBuilder(className) + .superclass(superType()) + .addAnnotations(copiedClassAnnotations(context.autoValueClass())) + .addTypeVariables(annotatedTypeVariableNames()) + .addModifiers(isFinal ? FINAL : ABSTRACT) + .addMethod(constructor()); + generatedAnnotationSpec(elements, sourceVersion, ToPrettyStringExtension.class) + .ifPresent(builder::addAnnotation); + return builder; + } + + private TypeName superType() { + ClassName superType = ClassName.get(context.packageName(), classToExtend); + ImmutableList<TypeVariableName> typeVariableNames = typeVariableNames(); + + return typeVariableNames.isEmpty() + ? superType + : ParameterizedTypeName.get(superType, typeVariableNames.toArray(new TypeName[] {})); + } + + private ImmutableList<TypeVariableName> typeVariableNames() { + return context.autoValueClass().getTypeParameters().stream() + .map(TypeVariableName::get) + .collect(toImmutableList()); + } + + private ImmutableList<TypeVariableName> annotatedTypeVariableNames() { + return context.autoValueClass().getTypeParameters().stream() + .map( + p -> + TypeVariableName.get(p) + .annotated( + p.getAnnotationMirrors().stream() + .map(AnnotationSpec::get) + .collect(toImmutableList()))) + .collect(toImmutableList()); + } + + private MethodSpec constructor() { + MethodSpec.Builder constructor = constructorBuilder(); + context + .propertyTypes() + .forEach((name, type) -> constructor.addParameter(annotatedType(type), name + "$")); + String superParams = + context.properties().keySet().stream().map(n -> n + "$").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; + } + } + + /** 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(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. */ + 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/toprettystring/processor/ToPrettyStringExtension.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringExtension.java new file mode 100644 index 00000000..134fcac4 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringExtension.java @@ -0,0 +1,562 @@ +/* + * Copyright 2021 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.extension.toprettystring.processor; + +import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods; +import static com.google.auto.common.MoreStreams.toImmutableList; +import static com.google.auto.common.MoreTypes.asTypeElement; +import static com.google.auto.value.extension.toprettystring.processor.ExtensionClassTypeSpecBuilder.extensionClassTypeSpecBuilder; +import static com.google.auto.value.extension.toprettystring.processor.ToPrettyStringMethods.toPrettyStringMethod; +import static com.google.auto.value.extension.toprettystring.processor.ToPrettyStringMethods.toPrettyStringMethods; +import static com.google.common.collect.Iterables.getLast; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.collect.Sets.intersection; +import static com.squareup.javapoet.MethodSpec.methodBuilder; +import static javax.lang.model.element.Modifier.FINAL; +import static javax.lang.model.element.Modifier.PRIVATE; +import static javax.lang.model.element.Modifier.PROTECTED; +import static javax.lang.model.element.Modifier.PUBLIC; +import static javax.lang.model.element.Modifier.STATIC; + +import com.google.auto.common.MoreTypes; +import com.google.auto.service.AutoService; +import com.google.auto.value.extension.AutoValueExtension; +import com.google.auto.value.extension.toprettystring.processor.ToPrettyStringExtension.PrettyPrintableKind.KindVisitor; +import com.google.common.base.Equivalence; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.SimpleTypeVisitor8; +import javax.lang.model.util.Types; + +/** + * Generates implementations of {@link + * com.google.auto.value.extension.toprettystring.ToPrettyString} annotated methods in {@link + * com.google.auto.value.AutoValue} types. + */ +@AutoService(AutoValueExtension.class) +public final class ToPrettyStringExtension extends AutoValueExtension { + private static final ImmutableSet<Modifier> INHERITED_VISIBILITY_MODIFIERS = + ImmutableSet.of(PUBLIC, PROTECTED); + private static final String INDENT = " "; + private static final String INDENT_METHOD_NAME = "$indent"; + private static final CodeBlock KEY_VALUE_SEPARATOR = CodeBlock.of("$S", ": "); + + @Override + public String generateClass( + Context context, String className, String classToExtend, boolean isFinal) { + TypeSpec type = + extensionClassTypeSpecBuilder(context, className, classToExtend, isFinal) + .addMethods(toPrettyStringMethodSpecs(context)) + .build(); + return JavaFile.builder(context.packageName(), type) + .skipJavaLangImports(true) + .build() + .toString(); + } + + private ImmutableList<MethodSpec> toPrettyStringMethodSpecs(Context context) { + ExecutableElement toPrettyStringMethod = getOnlyElement(toPrettyStringMethods(context)); + MethodSpec.Builder method = + methodBuilder(toPrettyStringMethod.getSimpleName().toString()) + .addAnnotation(Override.class) + .returns(ClassName.get(String.class)) + .addModifiers(FINAL) + .addModifiers( + intersection(toPrettyStringMethod.getModifiers(), INHERITED_VISIBILITY_MODIFIERS)); + + method.addCode("return $S", context.autoValueClass().getSimpleName() + " {"); + ToPrettyStringImplementation implementation = ToPrettyStringImplementation.create(context); + method.addCode(implementation.toStringCodeBlock.build()); + + if (!context.properties().isEmpty()) { + method.addCode(" + $S", "\n"); + } + method.addCode(" + $S;\n", "}"); + + return ImmutableList.<MethodSpec>builder() + .add(method.build()) + .addAll(implementation.delegateMethods.values()) + .add(indentMethod()) + .build(); + } + + private static MethodSpec indentMethod() { + return methodBuilder(INDENT_METHOD_NAME) + .addModifiers(PRIVATE, STATIC) + .returns(ClassName.get(String.class)) + .addParameter(TypeName.INT, "level") + .addStatement("$1T builder = new $1T()", StringBuilder.class) + .beginControlFlow("for (int i = 0; i < level; i++)") + .addStatement("builder.append($S)", INDENT) + .endControlFlow() + .addStatement("return builder.toString()") + .build(); + } + + private static class ToPrettyStringImplementation { + private final Types types; + private final Elements elements; + + private final CodeBlock.Builder toStringCodeBlock = CodeBlock.builder(); + private final Map<Equivalence.Wrapper<TypeMirror>, MethodSpec> delegateMethods = + new LinkedHashMap<>(); + private final Set<String> methodNames = new HashSet<>(); + + private ToPrettyStringImplementation(Context context) { + this.types = context.processingEnvironment().getTypeUtils(); + this.elements = context.processingEnvironment().getElementUtils(); + // do not submit: what about "inherited" static methods? + getLocalAndInheritedMethods(context.autoValueClass(), types, elements) + .forEach(method -> methodNames.add(method.getSimpleName().toString())); + } + + static ToPrettyStringImplementation create(Context context) { + ToPrettyStringImplementation implemention = new ToPrettyStringImplementation(context); + context + .propertyTypes() + .forEach( + (propertyName, type) -> { + String methodName = + context.properties().get(propertyName).getSimpleName().toString(); + implemention.toStringCodeBlock.add( + "\n + $S + $L + $S", + String.format("\n%s%s = ", INDENT, propertyName), + implemention.format(CodeBlock.of("$N()", methodName), CodeBlock.of("1"), type), + ","); + }); + return implemention; + } + + /** + * Returns {@code propertyAccess} formatted for use within the {@link + * com.google.auto.value.extension.toprettystring.ToPrettyString} implementation. + * + * <p>If a helper method is necessary for formatting, a {@link MethodSpec} will be added to + * {@link #delegateMethods}. + * + * @param propertyAccess a reference to the variable that should be formatted. + * @param indentAccess a reference to an {@code int} representing how many indent levels should + * be used for this property. + * @param type the type of the {@code propertyAccess}. + */ + private CodeBlock format(CodeBlock propertyAccess, CodeBlock indentAccess, TypeMirror type) { + PrettyPrintableKind printableKind = type.accept(new KindVisitor(types, elements), null); + DelegateMethod delegateMethod = new DelegateMethod(propertyAccess, indentAccess); + switch (printableKind) { + case PRIMITIVE: + return propertyAccess; + case REGULAR_OBJECT: + return delegateMethod + .methodName("format") + .invocation( + elements.getTypeElement("java.lang.Object").asType(), () -> reindent("toString")); + case HAS_TO_PRETTY_STRING_METHOD: + ExecutableElement method = + toPrettyStringMethod(asTypeElement(type), types, elements).get(); + return delegateMethod.invocation(type, () -> reindent(method.getSimpleName())); + case ARRAY: + TypeMirror componentType = MoreTypes.asArray(type).getComponentType(); + return delegateMethod.invocation(type, () -> forEachLoopMethodBody(componentType)); + case COLLECTION: + TypeMirror elementType = + getOnlyElement(resolvedTypeParameters(type, "java.util.Collection")); + return delegateMethod.invocation( + collectionOf(elementType), () -> forEachLoopMethodBody(elementType)); + case IMMUTABLE_PRIMITIVE_ARRAY: + return delegateMethod.invocation(type, this::forLoopMethodBody); + case OPTIONAL: + case GUAVA_OPTIONAL: + TypeMirror optionalType = getOnlyElement(MoreTypes.asDeclared(type).getTypeArguments()); + return delegateMethod.invocation( + type, () -> optionalMethodBody(optionalType, printableKind)); + case MAP: + return formatMap(type, delegateMethod); + case MULTIMAP: + return formatMultimap(type, delegateMethod); + } + throw new AssertionError(printableKind); + } + + private CodeBlock formatMap(TypeMirror type, DelegateMethod delegateMethod) { + ImmutableList<TypeMirror> typeParameters = resolvedTypeParameters(type, "java.util.Map"); + TypeMirror keyType = typeParameters.get(0); + TypeMirror valueType = typeParameters.get(1); + return delegateMethod.invocation( + mapOf(keyType, valueType), () -> mapMethodBody(keyType, valueType)); + } + + private CodeBlock formatMultimap(TypeMirror type, DelegateMethod delegateMethod) { + ImmutableList<TypeMirror> typeParameters = + resolvedTypeParameters(type, "com.google.common.collect.Multimap"); + TypeMirror keyType = typeParameters.get(0); + TypeMirror valueType = typeParameters.get(1); + return delegateMethod.invocation( + multimapOf(keyType, valueType), + () -> multimapMethodBody(keyType, collectionOf(valueType))); + } + + /** + * Parameter object to simplify the branches of {@link #format(CodeBlock, CodeBlock, + * TypeMirror)} that call a delegate method. + */ + private class DelegateMethod { + + private final CodeBlock propertyAccess; + private final CodeBlock indentAccess; + private Optional<String> methodName = Optional.empty(); + + DelegateMethod(CodeBlock propertyAccess, CodeBlock indentAccess) { + this.propertyAccess = propertyAccess; + this.indentAccess = indentAccess; + } + + DelegateMethod methodName(String methodName) { + this.methodName = Optional.of(methodName); + return this; + } + + CodeBlock invocation(TypeMirror parameterType, Supplier<CodeBlock> methodBody) { + Equivalence.Wrapper<TypeMirror> key = MoreTypes.equivalence().wrap(parameterType); + // This doesn't use putIfAbsent because the methodBody supplier could recursively create + // new delegate methods. Map.putIfAbsent doesn't support reentrant calls. + if (!delegateMethods.containsKey(key)) { + delegateMethods.put( + key, + createMethod( + methodName.orElseGet(() -> newDelegateMethodName(parameterType)), + parameterType, + methodBody)); + } + return CodeBlock.of( + "$N($L, $L)", delegateMethods.get(key).name, propertyAccess, indentAccess); + } + + private String newDelegateMethodName(TypeMirror type) { + String prefix = "format" + nameForType(type); + String methodName = prefix; + for (int i = 2; !methodNames.add(methodName); i++) { + methodName = prefix + i; + } + return methodName; + } + + private MethodSpec createMethod( + String methodName, TypeMirror type, Supplier<CodeBlock> methodBody) { + return methodBuilder(methodName) + .addModifiers(PRIVATE, STATIC) + .returns(ClassName.get(String.class)) + .addParameter(TypeName.get(type), "value") + .addParameter(TypeName.INT, "indentLevel") + .beginControlFlow("if (value == null)") + .addStatement("return $S", "null") + .endControlFlow() + .addCode(methodBody.get()) + .build(); + } + } + + private CodeBlock reindent(CharSequence methodName) { + return CodeBlock.builder() + .addStatement( + "return value.$1N().replace($2S, $2S + $3N(indentLevel))", + methodName, + "\n", + INDENT_METHOD_NAME) + .build(); + } + + private CodeBlock forEachLoopMethodBody(TypeMirror elementType) { + return loopMethodBody( + "[", + "]", + CodeBlock.of("for ($T element : value)", elementType), + format(CodeBlock.of("element"), CodeBlock.of("indentLevel + 1"), elementType)); + } + + private CodeBlock forLoopMethodBody() { + return loopMethodBody( + "[", + "]", + CodeBlock.of("for (int i = 0; i < value.length(); i++)"), + CodeBlock.of("value.get(i)")); + } + + private CodeBlock mapMethodBody(TypeMirror keyType, TypeMirror valueType) { + return forEachMapEntryMethodBody(keyType, valueType, "value"); + } + + private CodeBlock multimapMethodBody(TypeMirror keyType, TypeMirror valueType) { + return forEachMapEntryMethodBody(keyType, valueType, "value.asMap()"); + } + + private CodeBlock forEachMapEntryMethodBody( + TypeMirror keyType, TypeMirror valueType, String propertyAccess) { + CodeBlock entryType = CodeBlock.of("$T<$T, $T>", Map.Entry.class, keyType, valueType); + return loopMethodBody( + "{", + "}", + CodeBlock.of("for ($L entry : $L.entrySet())", entryType, propertyAccess), + format(CodeBlock.of("entry.getKey()"), CodeBlock.of("indentLevel + 1"), keyType), + KEY_VALUE_SEPARATOR, + format(CodeBlock.of("entry.getValue()"), CodeBlock.of("indentLevel + 1"), valueType)); + } + + private CodeBlock loopMethodBody( + String openSymbol, + String closeSymbol, + CodeBlock loopDeclaration, + CodeBlock... appendedValues) { + ImmutableList<CodeBlock> allAppendedValues = + ImmutableList.<CodeBlock>builder() + .add(CodeBlock.of("$S", "\n")) + .add(CodeBlock.of("$N(indentLevel + 1)", INDENT_METHOD_NAME)) + .add(appendedValues) + .add(CodeBlock.of("$S", ",")) + .build(); + return CodeBlock.builder() + .addStatement("$1T builder = new $1T().append($2S)", StringBuilder.class, openSymbol) + .addStatement("boolean hasElements = false") + .beginControlFlow("$L", loopDeclaration) + .addStatement( + "builder$L", + allAppendedValues.stream() + .map(value -> CodeBlock.of(".append($L)", value)) + .collect(CodeBlock.joining(""))) + .addStatement("hasElements = true") + .endControlFlow() + .beginControlFlow("if (hasElements)") + .addStatement("builder.append($S).append($N(indentLevel))", "\n", INDENT_METHOD_NAME) + .endControlFlow() + .addStatement("return builder.append($S).toString()", closeSymbol) + .build(); + } + + private CodeBlock optionalMethodBody( + TypeMirror optionalType, PrettyPrintableKind printableKind) { + return CodeBlock.builder() + .addStatement( + "return (value.isPresent() ? $L : $S)", + format(CodeBlock.of("value.get()"), CodeBlock.of("indentLevel"), optionalType), + printableKind.equals(PrettyPrintableKind.OPTIONAL) ? "<empty>" : "<absent>") + .build(); + } + + private ImmutableList<TypeMirror> resolvedTypeParameters( + TypeMirror propertyType, String interfaceName) { + return elements.getTypeElement(interfaceName).getTypeParameters().stream() + .map(p -> types.asMemberOf(MoreTypes.asDeclared(propertyType), p)) + .collect(toImmutableList()); + } + + private DeclaredType collectionOf(TypeMirror elementType) { + return types.getDeclaredType(elements.getTypeElement("java.util.Collection"), elementType); + } + + private DeclaredType mapOf(TypeMirror keyType, TypeMirror valueType) { + return types.getDeclaredType(elements.getTypeElement("java.util.Map"), keyType, valueType); + } + + private DeclaredType multimapOf(TypeMirror keyType, TypeMirror valueType) { + return types.getDeclaredType( + elements.getTypeElement("com.google.common.collect.Multimap"), keyType, valueType); + } + + /** Returns a valid Java identifier for method or variable of type {@code type}. */ + private String nameForType(TypeMirror type) { + return type.accept( + new SimpleTypeVisitor8<String, Void>() { + @Override + public String visitDeclared(DeclaredType type, Void v) { + String simpleName = simpleNameForType(type); + if (type.getTypeArguments().isEmpty()) { + return simpleName; + } + ImmutableList<String> typeArgumentNames = + type.getTypeArguments().stream() + .map(t -> simpleNameForType(t)) + .collect(toImmutableList()); + if (isMapOrMultimap(type) && typeArgumentNames.size() == 2) { + return String.format( + "%sOf%sTo%s", simpleName, typeArgumentNames.get(0), typeArgumentNames.get(1)); + } + + List<String> parts = new ArrayList<>(); + parts.add(simpleName); + parts.add("Of"); + parts.addAll(typeArgumentNames.subList(0, typeArgumentNames.size() - 1)); + if (typeArgumentNames.size() > 1) { + parts.add("And"); + } + parts.add(getLast(typeArgumentNames)); + return String.join("", parts); + } + + @Override + protected String defaultAction(TypeMirror type, Void v) { + return simpleNameForType(type); + } + }, + null); + } + + boolean isMapOrMultimap(TypeMirror type) { + TypeMirror mapType = elements.getTypeElement("java.util.Map").asType(); + if (types.isAssignable(type, types.erasure(mapType))) { + return true; + } + TypeElement multimapElement = elements.getTypeElement("com.google.common.collect.Multimap"); + return multimapElement != null + && types.isAssignable(type, types.erasure(multimapElement.asType())); + } + + private String simpleNameForType(TypeMirror type) { + return type.accept( + new SimpleTypeVisitor8<String, Void>() { + @Override + public String visitPrimitive(PrimitiveType primitiveType, Void v) { + return types.boxedClass(primitiveType).getSimpleName().toString(); + } + + @Override + public String visitArray(ArrayType arrayType, Void v) { + return arrayType.getComponentType().accept(this, null) + "Array"; + } + + @Override + public String visitDeclared(DeclaredType declaredType, Void v) { + return declaredType.asElement().getSimpleName().toString(); + } + + @Override + protected String defaultAction(TypeMirror typeMirror, Void v) { + throw new AssertionError(typeMirror); + } + }, + null); + } + } + + enum PrettyPrintableKind { + HAS_TO_PRETTY_STRING_METHOD, + REGULAR_OBJECT, + PRIMITIVE, + COLLECTION, + ARRAY, + IMMUTABLE_PRIMITIVE_ARRAY, + OPTIONAL, + GUAVA_OPTIONAL, + MAP, + MULTIMAP, + ; + + private static final ImmutableMap<String, PrettyPrintableKind> KINDS_BY_EXACT_TYPE = + ImmutableMap.of( + "java.util.Optional", OPTIONAL, + "com.google.common.base.Optional", GUAVA_OPTIONAL, + "com.google.common.primitives.ImmutableIntArray", IMMUTABLE_PRIMITIVE_ARRAY, + "com.google.common.primitives.ImmutableLongArray", IMMUTABLE_PRIMITIVE_ARRAY, + "com.google.common.primitives.ImmutableDoubleArray", IMMUTABLE_PRIMITIVE_ARRAY); + + private static final ImmutableMap<String, PrettyPrintableKind> KINDS_BY_SUPERTYPE = + ImmutableMap.of( + "java.util.Collection", COLLECTION, + "java.util.Map", MAP, + "com.google.common.collect.Multimap", MULTIMAP); + + static class KindVisitor extends SimpleTypeVisitor8<PrettyPrintableKind, Void> { + private final Elements elements; + private final Types types; + + KindVisitor(Types types, Elements elements) { + this.types = types; + this.elements = elements; + } + + @Override + public PrettyPrintableKind visitPrimitive(PrimitiveType primitiveType, Void v) { + return PRIMITIVE; + } + + @Override + public PrettyPrintableKind visitArray(ArrayType arrayType, Void v) { + return ARRAY; + } + + @Override + public PrettyPrintableKind visitDeclared(DeclaredType declaredType, Void v) { + TypeElement typeElement = asTypeElement(declaredType); + if (toPrettyStringMethod(typeElement, types, elements).isPresent()) { + return HAS_TO_PRETTY_STRING_METHOD; + } + PrettyPrintableKind byExactType = + KINDS_BY_EXACT_TYPE.get(typeElement.getQualifiedName().toString()); + if (byExactType != null) { + return byExactType; + } + + for (Map.Entry<String, PrettyPrintableKind> entry : KINDS_BY_SUPERTYPE.entrySet()) { + TypeElement supertypeElement = elements.getTypeElement(entry.getKey()); + if (supertypeElement != null + && types.isAssignable(declaredType, types.erasure(supertypeElement.asType()))) { + return entry.getValue(); + } + } + + return REGULAR_OBJECT; + } + } + } + + @Override + public boolean applicable(Context context) { + return toPrettyStringMethods(context).size() == 1; + } + + @Override + public ImmutableSet<ExecutableElement> consumeMethods(Context context) { + return toPrettyStringMethods(context); + } + + @Override + public IncrementalExtensionType incrementalType(ProcessingEnvironment processingEnvironment) { + return IncrementalExtensionType.ISOLATING; + } +} diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringMethods.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringMethods.java new file mode 100644 index 00000000..041a16d6 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringMethods.java @@ -0,0 +1,66 @@ +/* + * Copyright 2021 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.extension.toprettystring.processor; + +import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods; +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.toPrettyStringAnnotation; +import static com.google.common.collect.MoreCollectors.toOptional; + +import com.google.auto.value.extension.AutoValueExtension.Context; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import java.util.Optional; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +final class ToPrettyStringMethods { + /** + * Returns the {@link com.google.auto.value.extension.toprettystring.ToPrettyString} annotated + * methods for an {@code @AutoValue} type. + */ + static ImmutableSet<ExecutableElement> toPrettyStringMethods(Context context) { + return context.abstractMethods().stream() + .filter(method -> toPrettyStringAnnotation(method).isPresent()) + .collect(toImmutableSet()); + } + + /** + * Returns the {@link com.google.auto.value.extension.toprettystring.ToPrettyString} annotated + * method for a type. + */ + static ImmutableList<ExecutableElement> toPrettyStringMethods( + TypeElement element, Types types, Elements elements) { + return getLocalAndInheritedMethods(element, types, elements).stream() + .filter(method -> toPrettyStringAnnotation(method).isPresent()) + .collect(toImmutableList()); + } + + /** + * Returns the {@link com.google.auto.value.extension.toprettystring.ToPrettyString} annotated + * method for a type. + */ + static Optional<ExecutableElement> toPrettyStringMethod( + TypeElement element, Types types, Elements elements) { + return toPrettyStringMethods(element, types, elements).stream().collect(toOptional()); + } + + private ToPrettyStringMethods() {} +} diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringValidator.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringValidator.java new file mode 100644 index 00000000..b77a54d6 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringValidator.java @@ -0,0 +1,142 @@ +/* + * Copyright 2021 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.extension.toprettystring.processor; + +import static com.google.auto.value.extension.toprettystring.processor.ClassNames.TO_PRETTY_STRING_NAME; +import static com.google.auto.value.extension.toprettystring.processor.ToPrettyStringMethods.toPrettyStringMethods; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toCollection; +import static javax.lang.model.element.Modifier.STATIC; +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.service.AutoService; +import com.google.common.collect.ImmutableList; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Messager; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +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 javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; +import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; + +/** + * An annotation processor that validates {@link + * com.google.auto.value.extension.toprettystring.ToPrettyString} usage. + */ +@AutoService(Processor.class) +@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING) +@SupportedAnnotationTypes(TO_PRETTY_STRING_NAME) +public final class ToPrettyStringValidator extends AbstractProcessor { + @Override + public boolean process( + Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) { + Types types = processingEnv.getTypeUtils(); + Elements elements = processingEnv.getElementUtils(); + TypeElement toPrettyString = elements.getTypeElement(TO_PRETTY_STRING_NAME); + + Set<ExecutableElement> annotatedMethods = + methodsIn(roundEnvironment.getElementsAnnotatedWith(toPrettyString)); + for (ExecutableElement method : annotatedMethods) { + validateMethod(method, elements); + } + + validateSingleToPrettyStringMethod(annotatedMethods, types, elements); + + return false; + } + + private void validateMethod(ExecutableElement method, Elements elements) { + ErrorReporter errorReporter = new ErrorReporter(method, processingEnv.getMessager()); + if (method.getModifiers().contains(STATIC)) { + errorReporter.reportError("@ToPrettyString methods must be instance methods"); + } + + TypeMirror stringType = elements.getTypeElement("java.lang.String").asType(); + if (!MoreTypes.equivalence().equivalent(method.getReturnType(), stringType)) { + errorReporter.reportError("@ToPrettyString methods must return String"); + } + + if (!method.getParameters().isEmpty()) { + errorReporter.reportError("@ToPrettyString methods cannot have parameters"); + } + } + + private void validateSingleToPrettyStringMethod( + Set<ExecutableElement> annotatedMethods, Types types, Elements elements) { + Set<TypeElement> enclosingTypes = + annotatedMethods.stream() + .map(Element::getEnclosingElement) + .map(MoreElements::asType) + .collect(toCollection(LinkedHashSet::new)); + for (TypeElement enclosingType : enclosingTypes) { + ImmutableList<ExecutableElement> methods = + toPrettyStringMethods(enclosingType, types, elements); + if (methods.size() > 1) { + processingEnv + .getMessager() + .printMessage( + ERROR, + String.format( + "%s has multiple @ToPrettyString methods:%s", + enclosingType.getQualifiedName(), formatMethodList(methods)), + enclosingType); + } + } + } + + private String formatMethodList(ImmutableList<ExecutableElement> methods) { + return methods.stream().map(this::formatMethodInList).collect(joining()); + } + + private String formatMethodInList(ExecutableElement method) { + return String.format( + "\n - %s.%s()", + MoreElements.asType(method.getEnclosingElement()).getQualifiedName(), + method.getSimpleName()); + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + private static final class ErrorReporter { + private final ExecutableElement method; + private final Messager messager; + + ErrorReporter(ExecutableElement method, Messager messager) { + this.method = method; + this.messager = messager; + } + + void reportError(String error) { + messager.printMessage(ERROR, error, method); + } + } +} 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 d3cd9bd5..3acf9332 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 @@ -17,14 +17,20 @@ package com.google.auto.value.processor; import static com.google.auto.common.GeneratedAnnotations.generatedAnnotation; import static com.google.auto.value.processor.ClassNames.AUTO_ANNOTATION_NAME; +import static com.google.common.collect.Maps.immutableEntry; +import static java.util.Comparator.comparing; +import static java.util.stream.Collectors.joining; import com.google.auto.common.MoreElements; +import com.google.auto.common.MoreTypes; import com.google.auto.common.SuperficialValidation; import com.google.auto.service.AutoService; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.hash.Hashing; import com.google.common.primitives.Primitives; import com.google.errorprone.annotations.FormatMethod; import java.io.IOException; @@ -41,6 +47,7 @@ import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; 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.ElementKind; @@ -74,11 +81,30 @@ import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; public class AutoAnnotationProcessor extends AbstractProcessor { public AutoAnnotationProcessor() {} + private Elements elementUtils; + private Types typeUtils; + private Nullables nullables; + private TypeMirror javaLangObject; + @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } + @Override + public ImmutableSet<String> getSupportedOptions() { + return ImmutableSet.of(Nullables.NULLABLE_OPTION); + } + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + this.elementUtils = processingEnv.getElementUtils(); + this.typeUtils = processingEnv.getTypeUtils(); + this.nullables = new Nullables(processingEnv); + this.javaLangObject = elementUtils.getTypeElement("java.lang.Object").asType(); + } + /** * Issue a compilation error. This method does not throw an exception, since we want to continue * processing and perhaps report other errors. @@ -99,26 +125,10 @@ public class AutoAnnotationProcessor extends AbstractProcessor { return new AbortProcessingException(); } - private Elements elementUtils; - private Types typeUtils; - @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { - elementUtils = processingEnv.getElementUtils(); - typeUtils = processingEnv.getTypeUtils(); - boolean claimed = - (annotations.size() == 1 - && annotations - .iterator() - .next() - .getQualifiedName() - .contentEquals(AUTO_ANNOTATION_NAME)); - if (claimed) { - process(roundEnv); - return true; - } else { - return false; - } + process(roundEnv); + return false; } private void process(RoundEnvironment roundEnv) { @@ -152,7 +162,7 @@ public class AutoAnnotationProcessor extends AbstractProcessor { Set<Class<?>> wrapperTypesUsedInCollections = wrapperTypesUsedInCollections(method); ImmutableMap<String, ExecutableElement> memberMethods = getMemberMethods(annotationElement); - TypeElement methodClass = (TypeElement) method.getEnclosingElement(); + TypeElement methodClass = MoreElements.asType(method.getEnclosingElement()); String pkg = TypeSimplifier.packageNameOf(methodClass); ImmutableMap<String, AnnotationValue> defaultValues = getDefaultValues(annotationElement); @@ -169,9 +179,11 @@ public class AutoAnnotationProcessor extends AbstractProcessor { vars.generated = getGeneratedTypeName(); vars.members = members; vars.params = parameters; + vars.equalsParameterType = equalsParameterType(); vars.pkg = pkg; vars.wrapperTypesUsedInCollections = wrapperTypesUsedInCollections; vars.gwtCompatible = isGwtCompatible(annotationElement); + vars.serialVersionUID = computeSerialVersionUid(members, parameters); ImmutableMap<String, Integer> invariableHashes = invariableHashes(members, parameters.keySet()); vars.invariableHashSum = 0; for (int h : invariableHashes.values()) { @@ -191,6 +203,18 @@ public class AutoAnnotationProcessor extends AbstractProcessor { .orElse(""); } + private String equalsParameterType() { + // 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()); + } + /** * Returns the hashCode of the given AnnotationValue, if that hashCode is guaranteed to be always * the same. The hashCode of a String or primitive type never changes. The hashCode of a Class or @@ -246,7 +270,7 @@ public class AutoAnnotationProcessor extends AbstractProcessor { private boolean methodsAreOverloaded(List<ExecutableElement> methods) { boolean overloaded = false; - Set<String> classNames = new HashSet<String>(); + Set<String> classNames = new HashSet<>(); for (ExecutableElement method : methods) { String qualifiedClassName = fullyQualifiedName( @@ -261,10 +285,10 @@ public class AutoAnnotationProcessor extends AbstractProcessor { } private String generatedClassName(ExecutableElement method) { - TypeElement type = (TypeElement) method.getEnclosingElement(); + TypeElement type = MoreElements.asType(method.getEnclosingElement()); String name = type.getSimpleName().toString(); while (type.getEnclosingElement() instanceof TypeElement) { - type = (TypeElement) type.getEnclosingElement(); + type = MoreElements.asType(type.getEnclosingElement()); name = type.getSimpleName() + "_" + name; } return "AutoAnnotation_" + name + "_" + method.getSimpleName(); @@ -275,7 +299,7 @@ public class AutoAnnotationProcessor extends AbstractProcessor { if (returnTypeMirror.getKind() == TypeKind.DECLARED) { Element returnTypeElement = typeUtils.asElement(method.getReturnType()); if (returnTypeElement.getKind() == ElementKind.ANNOTATION_TYPE) { - return (TypeElement) returnTypeElement; + return MoreElements.asType(returnTypeElement); } } throw abortWithError( @@ -399,7 +423,7 @@ public class AutoAnnotationProcessor extends AbstractProcessor { if (memberType.getKind() != TypeKind.ARRAY) { return false; } - TypeMirror arrayElementType = ((ArrayType) memberType).getComponentType(); + TypeMirror arrayElementType = MoreTypes.asArray(memberType).getComponentType(); TypeMirror wrappedArrayElementType = arrayElementType.getKind().isPrimitive() ? typeUtils.boxedClass((PrimitiveType) arrayElementType).asType() @@ -416,7 +440,7 @@ public class AutoAnnotationProcessor extends AbstractProcessor { * like {@code List<Integer>}. This is needed because we will emit a helper method for each such * type, for example to convert {@code Collection<Integer>} into {@code int[]}. */ - private Set<Class<?>> wrapperTypesUsedInCollections(ExecutableElement method) { + private ImmutableSet<Class<?>> wrapperTypesUsedInCollections(ExecutableElement method) { TypeElement javaUtilCollection = elementUtils.getTypeElement(Collection.class.getName()); ImmutableSet.Builder<Class<?>> usedInCollections = ImmutableSet.builder(); for (Class<?> wrapper : Primitives.allWrapperTypes()) { @@ -437,9 +461,7 @@ public class AutoAnnotationProcessor extends AbstractProcessor { } private static boolean isGwtCompatible(TypeElement annotationElement) { - return annotationElement - .getAnnotationMirrors() - .stream() + return annotationElement.getAnnotationMirrors().stream() .map(mirror -> mirror.getAnnotationType().asElement()) .anyMatch(element -> element.getSimpleName().contentEquals("GwtCompatible")); } @@ -448,6 +470,43 @@ public class AutoAnnotationProcessor extends AbstractProcessor { return pkg.isEmpty() ? cls : pkg + "." + cls; } + /** + * We compute a {@code serialVersionUID} for the generated class based on the names and types of + * the annotation members that the {@code @AutoAnnotation} method defines. These are exactly the + * names and types of the instance fields in the generated class. So in the common case where the + * annotation acquires a new member with a default value, if the {@code @AutoAnnotation} method is + * not changed then the generated class will acquire an implementation of the new member method + * which just returns the default value. The {@code serialVersionUID} will not change, which makes + * sense because the instance fields haven't changed, and instances that were serialized before + * the new member was added should deserialize fine. On the other hand, if you then add a + * parameter to the {@code @AutoAnnotation} method for the new member, the implementation class + * will acquire a new instance field, and we will compute a different {@code serialVersionUID}. + * That's because an instance serialized before that change would not have a value for the new + * instance field, which would end up zero or null. Users don't expect annotation methods to + * return null so that would be bad. + * + * <p>We could instead add a {@code readObject(ObjectInputStream)} method that would check that + * all of the instance fields are really present in the deserialized instance, and perhaps + * replace them with their default values from the annotation if not. That seems a lot more + * complicated than is justified, though, especially since the instance fields are final and + * would have to be set in the deserialized object through reflection. + */ + private static long computeSerialVersionUid( + ImmutableMap<String, Member> members, ImmutableMap<String, Parameter> parameters) { + // TypeMirror.toString() isn't fully specified so it could potentially differ between + // implementations. Our member.getType() string comes from TypeEncoder and is predictable, but + // it includes `...` markers around fully-qualified type names, which are used to handle + // imports. So we remove those markers below. + String namesAndTypesString = + members.entrySet().stream() + .filter(e -> parameters.containsKey(e.getKey())) + .map(e -> immutableEntry(e.getKey(), e.getValue().getType().replace("`", ""))) + .sorted(comparing(Map.Entry::getKey)) + .map(e -> e.getKey() + ":" + e.getValue()) + .collect(joining(";")); + return Hashing.murmur3_128().hashUnencodedChars(namesAndTypesString).asLong(); + } + private void writeSourceFile(String className, String text, TypeElement originatingType) { try { JavaFileObject sourceFile = @@ -491,7 +550,7 @@ public class AutoAnnotationProcessor extends AbstractProcessor { public String getComponentType() { Preconditions.checkState(getTypeMirror().getKind() == TypeKind.ARRAY); - ArrayType arrayType = (ArrayType) getTypeMirror(); + ArrayType arrayType = MoreTypes.asArray(getTypeMirror()); return TypeEncoder.encode(arrayType.getComponentType()); } @@ -513,12 +572,12 @@ public class AutoAnnotationProcessor extends AbstractProcessor { if (getTypeMirror().getKind() != TypeKind.ARRAY) { return false; } - TypeMirror componentType = ((ArrayType) getTypeMirror()).getComponentType(); + TypeMirror componentType = MoreTypes.asArray(getTypeMirror()).getComponentType(); if (componentType.getKind() != TypeKind.DECLARED) { return false; } - DeclaredType declared = (DeclaredType) componentType; - if (!((TypeElement) processingEnv.getTypeUtils().asElement(componentType)) + DeclaredType declared = MoreTypes.asDeclared(componentType); + if (!MoreElements.asType(processingEnv.getTypeUtils().asElement(componentType)) .getQualifiedName() .contentEquals("java.lang.Class")) { return false; @@ -530,7 +589,7 @@ public class AutoAnnotationProcessor extends AbstractProcessor { if (parameter.getKind() != TypeKind.WILDCARD) { return true; // for Class<Foo> } - WildcardType wildcard = (WildcardType) parameter; + WildcardType wildcard = MoreTypes.asWildcard(parameter); // In theory, we should check if getExtendsBound() != Object, since '?' is equivalent to // '? extends Object', but, experimentally, neither javac or ecj will sets getExtendsBound() // to 'Object', so there isn't a point in checking. diff --git a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java index 4db59c78..11bc896c 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java @@ -35,6 +35,12 @@ class AutoAnnotationTemplateVars extends TemplateVars { */ Map<String, AutoAnnotationProcessor.Parameter> params; + /** + * A string representing the parameter type declaration of the equals(Object) method, including + * any annotations. + */ + String equalsParameterType; + /** The encoded form of the {@code Generated} class, or empty if it is not available. */ String generated; @@ -74,6 +80,12 @@ class AutoAnnotationTemplateVars extends TemplateVars { /** The sum of the hash code contributions from the members in {@link #invariableHashes}. */ Integer invariableHashSum; + /** + * A computed {@code serialVersionUID} based on the names and types of the {@code @AutoAnnotation} + * method parameters. + */ + Long serialVersionUID; + private static final Template TEMPLATE = parsedTemplateForResource("autoannotation.vm"); @Override 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 new file mode 100644 index 00000000..b6a578fc --- /dev/null +++ b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java @@ -0,0 +1,417 @@ +/* + * Copyright 2021 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.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.toImmutableSet; +import static com.google.auto.value.processor.AutoValueProcessor.OMIT_IDENTIFIERS_OPTION; +import static com.google.auto.value.processor.ClassNames.AUTO_BUILDER_NAME; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toMap; +import static javax.lang.model.util.ElementFilter.constructorsIn; +import static javax.lang.model.util.ElementFilter.methodsIn; + +import com.google.auto.common.AnnotationMirrors; +import com.google.auto.common.AnnotationValues; +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.ImmutableSet; +import com.google.common.collect.Maps; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Stream; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.Processor; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.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.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +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> + * @author Éamonn McManus + */ +@AutoService(Processor.class) +@SupportedAnnotationTypes(AUTO_BUILDER_NAME) +@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING) +public class AutoBuilderProcessor extends AutoValueishProcessor { + private static final String ALLOW_OPTION = "com.google.auto.value.AutoBuilderIsUnstable"; + + public AutoBuilderProcessor() { + super(AUTO_BUILDER_NAME); + } + + @Override + public Set<String> getSupportedOptions() { + return ImmutableSet.of(OMIT_IDENTIFIERS_OPTION, ALLOW_OPTION); + } + + private TypeMirror javaLangVoid; + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + javaLangVoid = elementUtils().getTypeElement("java.lang.Void").asType(); + } + + @Override + void processType(TypeElement autoBuilderType) { + if (!processingEnv.getOptions().containsKey(ALLOW_OPTION)) { + errorReporter() + .abortWithError( + autoBuilderType, + "Compile with -A%s to enable this UNSUPPORTED AND UNSTABLE prototype", + ALLOW_OPTION); + } + if (autoBuilderType.getKind() != ElementKind.CLASS + && autoBuilderType.getKind() != ElementKind.INTERFACE) { + errorReporter() + .abortWithError( + autoBuilderType, + "[AutoBuilderWrongType] @AutoBuilder only applies to classes and interfaces"); + } + checkModifiersIfNested(autoBuilderType); + // The annotation is guaranteed to be present by the contract of Processor#process + AnnotationMirror autoBuilderAnnotation = + getAnnotationMirror(autoBuilderType, AUTO_BUILDER_NAME).get(); + TypeElement ofClass = getOfClass(autoBuilderType, autoBuilderAnnotation); + checkModifiersIfNested(ofClass, autoBuilderType, "AutoBuilder ofClass"); + String callMethod = findCallMethodValue(autoBuilderAnnotation); + ImmutableSet<ExecutableElement> methods = + abstractMethodsIn( + getLocalAndInheritedMethods(autoBuilderType, typeUtils(), elementUtils())); + ExecutableElement executable = findExecutable(ofClass, callMethod, autoBuilderType, methods); + BuilderSpec builderSpec = new BuilderSpec(ofClass, processingEnv, errorReporter()); + BuilderSpec.Builder builder = builderSpec.new Builder(autoBuilderType); + TypeMirror builtType = builtType(executable); + Optional<BuilderMethodClassifier<VariableElement>> maybeClassifier = + BuilderMethodClassifierForAutoBuilder.classify( + methods, errorReporter(), processingEnv, executable, builtType, autoBuilderType); + if (!maybeClassifier.isPresent()) { + // 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); + AutoBuilderTemplateVars vars = new AutoBuilderTemplateVars(); + vars.props = propertySet(executable, propertyToGetterName); + 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.toBuilderMethods = ImmutableList.of(); + defineSharedVarsForType(autoBuilderType, ImmutableSet.of(), vars); + String text = vars.toText(); + text = TypeEncoder.decode(text, processingEnv, vars.pkg, autoBuilderType.asType()); + text = Reformatter.fixup(text); + writeSourceFile(generatedClassName, text, autoBuilderType); + } + + private ImmutableSet<Property> propertySet( + ExecutableElement executable, Map<String, String> propertyToGetterName) { + // 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() + .collect(toMap(v -> v, v -> v.getSimpleName().toString())); + fixReservedIdentifiers(identifiers); + return executable.getParameters().stream() + .map( + v -> + newProperty( + v, identifiers.get(v), propertyToGetterName.get(v.getSimpleName().toString()))) + .collect(toImmutableSet()); + } + + private Property newProperty(VariableElement var, String identifier, String getterName) { + String name = var.getSimpleName().toString(); + TypeMirror type = var.asType(); + Optional<String> nullableAnnotation = nullableAnnotationFor(var, var.asType()); + return new Property( + name, identifier, TypeEncoder.encode(type), type, nullableAnnotation, getterName); + } + + private ExecutableElement findExecutable( + TypeElement ofClass, + String callMethod, + TypeElement autoBuilderType, + ImmutableSet<ExecutableElement> methods) { + List<ExecutableElement> executables = + findRelevantExecutables(ofClass, callMethod, autoBuilderType); + String description = + callMethod.isEmpty() ? "constructor" : "static method named \"" + callMethod + "\""; + switch (executables.size()) { + case 0: + throw errorReporter() + .abortWithError( + autoBuilderType, + "[AutoBuilderNoVisible] No visible %s for %s", + description, + ofClass); + case 1: + return executables.get(0); + default: + return matchingExecutable(autoBuilderType, executables, methods, description); + } + } + + private ImmutableList<ExecutableElement> findRelevantExecutables( + TypeElement ofClass, String callMethod, TypeElement autoBuilderType) { + List<? extends Element> elements = ofClass.getEnclosedElements(); + Stream<ExecutableElement> relevantExecutables = + callMethod.isEmpty() + ? constructorsIn(elements).stream() + : methodsIn(elements).stream() + .filter(m -> m.getSimpleName().contentEquals(callMethod)) + .filter(m -> m.getModifiers().contains(Modifier.STATIC)); + return relevantExecutables + .filter(c -> visibleFrom(c, getPackage(autoBuilderType))) + .collect(toImmutableList()); + } + + private ExecutableElement matchingExecutable( + TypeElement autoBuilderType, + List<ExecutableElement> executables, + ImmutableSet<ExecutableElement> methods, + 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 + // basically just looking for an executable where all the parameter names correspond to setters + // or property builders in the interface. We might find out after choosing one that it is wrong + // for whatever reason (types don't match, spurious methods, etc). But it is likely that if the + // names are all accounted for in the methods, and if there's no other matching executable with + // 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()); + switch (matches.size()) { + case 0: + throw errorReporter() + .abortWithError( + autoBuilderType, + "[AutoBuilderNoMatch] Property names do not correspond to the parameter names of" + + " any %s:\n%s", + description, + executableListString(executables)); + case 1: + return matches.get(0); + 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()); + if (maxMatches.size() > 1) { + throw errorReporter() + .abortWithError( + autoBuilderType, + "[AutoBuilderAmbiguous] Property names correspond to more than one %s:\n%s", + description, + executableListString(maxMatches)); + } + return maxMatches.get(0); + } + + private String executableListString(List<ExecutableElement> executables) { + return executables.stream() + .map(AutoBuilderProcessor::executableString) + .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) { + // 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. + // A parameter named foo could be matched by methods like this: + // X foo(Y) + // X setFoo(Y) + // X fooBuilder() + // X fooBuilder(Y) + // 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) { + String name = method.getSimpleName().toString(); + if (name.endsWith("Builder")) { + String property = name.substring(0, name.length() - "Builder".length()); + parameterNames.remove(property); + } + if (method.getParameters().size() == 1) { + parameterNames.remove(name); + if (name.startsWith("set")) { + parameterNames.remove(name.substring(3)); + } + } + if (parameterNames.isEmpty()) { + return true; + } + } + return false; + } + + private boolean visibleFrom(Element element, PackageElement fromPackage) { + Visibility visibility = Visibility.effectiveVisibilityOfElement(element); + switch (visibility) { + case PUBLIC: + return true; + case PROTECTED: + // We care about whether the constructor is visible from the generated class. The generated + // class is never going to be a subclass of the class containing the constructor, so + // protected and default access are equivalent. + case DEFAULT: + return getPackage(element).equals(fromPackage); + default: + return false; + } + } + + 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 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()); + } + } + + private static final ElementKind ELEMENT_KIND_RECORD = elementKindRecord(); + + private static ElementKind elementKindRecord() { + try { + Field record = ElementKind.class.getField("RECORD"); + return (ElementKind) record.get(null); + } catch (ReflectiveOperationException e) { + // OK: we must be on a JDK version that predates this. + return null; + } + } + + private TypeElement getOfClass( + TypeElement autoBuilderType, AnnotationMirror autoBuilderAnnotation) { + TypeElement ofClassValue = findOfClassValue(autoBuilderAnnotation); + boolean isDefault = typeUtils().isSameType(ofClassValue.asType(), javaLangVoid); + if (!isDefault) { + return ofClassValue; + } + Element enclosing = autoBuilderType.getEnclosingElement(); + ElementKind enclosingKind = enclosing.getKind(); + if (enclosing.getKind() != ElementKind.CLASS && enclosingKind != ELEMENT_KIND_RECORD) { + errorReporter() + .abortWithError( + autoBuilderType, + "[AutoBuilderEnclosing] @AutoBuilder must specify ofClass=Something.class or it" + + " must be nested inside the class to be built; actually nested inside %s %s.", + Ascii.toLowerCase(enclosingKind.name()), + enclosing); + } + return MoreElements.asType(enclosing); + } + + private TypeElement findOfClassValue(AnnotationMirror autoBuilderAnnotation) { + AnnotationValue ofClassValue = + AnnotationMirrors.getAnnotationValue(autoBuilderAnnotation, "ofClass"); + Object value = ofClassValue.getValue(); + if (value instanceof TypeMirror) { + TypeMirror ofClassType = (TypeMirror) value; + switch (ofClassType.getKind()) { + case DECLARED: + return MoreTypes.asTypeElement(ofClassType); + case ERROR: + throw new MissingTypeException(MoreTypes.asError(ofClassType)); + default: + break; + } + } + throw new MissingTypeException(null); + } + + private String findCallMethodValue(AnnotationMirror autoBuilderAnnotation) { + AnnotationValue callMethodValue = + AnnotationMirrors.getAnnotationValue(autoBuilderAnnotation, "callMethod"); + return AnnotationValues.getString(callMethodValue); + } + + @Override + Optional<String> nullableAnnotationForMethod(ExecutableElement propertyMethod) { + // TODO(b/183005059): implement + return Optional.empty(); + } +} diff --git a/value/src/main/java/com/google/auto/value/processor/AutoBuilderTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoBuilderTemplateVars.java new file mode 100644 index 00000000..8c08a481 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/processor/AutoBuilderTemplateVars.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021 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.escapevelocity.Template; + +class AutoBuilderTemplateVars extends AutoValueOrBuilderTemplateVars { + private static final Template TEMPLATE = parsedTemplateForResource("autobuilder.vm"); + + @Override + Template parsedTemplate() { + return TEMPLATE; + } +} 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 d73c1c29..711b138c 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 @@ -58,7 +58,7 @@ import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; @AutoService(Processor.class) @SupportedAnnotationTypes(AUTO_ONE_OF_NAME) @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING) -public class AutoOneOfProcessor extends AutoValueOrOneOfProcessor { +public class AutoOneOfProcessor extends AutoValueishProcessor { public AutoOneOfProcessor() { super(AUTO_ONE_OF_NAME); } @@ -69,6 +69,11 @@ public class AutoOneOfProcessor extends AutoValueOrOneOfProcessor { } @Override + public ImmutableSet<String> getSupportedOptions() { + return ImmutableSet.of(Nullables.NULLABLE_OPTION); + } + + @Override void processType(TypeElement autoOneOfType) { if (autoOneOfType.getKind() != ElementKind.CLASS) { errorReporter() @@ -123,19 +128,9 @@ public class AutoOneOfProcessor extends AutoValueOrOneOfProcessor { } private DeclaredType mirrorForKindType(TypeElement autoOneOfType) { - Optional<AnnotationMirror> oneOfAnnotation = - getAnnotationMirror(autoOneOfType, AUTO_ONE_OF_NAME); - if (!oneOfAnnotation.isPresent()) { - // This shouldn't happen unless the compilation environment is buggy, - // but it has happened in the past and can crash the compiler. - errorReporter() - .abortWithError( - autoOneOfType, - "[AutoOneOfCompilerBug] annotation processor for @AutoOneOf was invoked with a type" - + " that does not have that annotation; this is probably a compiler bug"); - } - AnnotationValue kindValue = - AnnotationMirrors.getAnnotationValue(oneOfAnnotation.get(), "value"); + // The annotation is guaranteed to be present by the contract of Processor#process + AnnotationMirror oneOfAnnotation = getAnnotationMirror(autoOneOfType, AUTO_ONE_OF_NAME).get(); + AnnotationValue kindValue = AnnotationMirrors.getAnnotationValue(oneOfAnnotation, "value"); Object value = kindValue.getValue(); if (value instanceof TypeMirror) { TypeMirror kindType = (TypeMirror) value; @@ -162,9 +157,7 @@ public class AutoOneOfProcessor extends AutoValueOrOneOfProcessor { Map<String, String> transformedPropertyNames = propertyNames.stream().collect(toMap(this::transformName, s -> s)); Map<String, Element> transformedEnumConstants = - kindElement - .getEnclosedElements() - .stream() + kindElement.getEnclosedElements().stream() .filter(e -> e.getKind().equals(ElementKind.ENUM_CONSTANT)) .collect(toMap(e -> transformName(e.getSimpleName().toString()), e -> e)); @@ -215,8 +208,7 @@ public class AutoOneOfProcessor extends AutoValueOrOneOfProcessor { TypeMirror kindMirror, ImmutableSet<ExecutableElement> abstractMethods) { Set<ExecutableElement> kindGetters = - abstractMethods - .stream() + abstractMethods.stream() .filter(e -> sameType(kindMirror, e.getReturnType())) .filter(e -> e.getParameters().isEmpty()) .collect(toSet()); @@ -273,14 +265,15 @@ public class AutoOneOfProcessor extends AutoValueOrOneOfProcessor { AutoOneOfTemplateVars vars, ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes, ExecutableElement kindGetter) { - vars.props = propertySet( - propertyMethodsAndTypes, ImmutableListMultimap.of(), ImmutableListMultimap.of()); + vars.props = + propertySet( + propertyMethodsAndTypes, ImmutableListMultimap.of(), ImmutableListMultimap.of()); vars.kindGetter = kindGetter.getSimpleName().toString(); vars.kindType = TypeEncoder.encode(kindGetter.getReturnType()); TypeElement javaIoSerializable = elementUtils().getTypeElement("java.io.Serializable"); vars.serializable = - javaIoSerializable != null // just in case - && typeUtils().isAssignable(type.asType(), javaIoSerializable.asType()); + javaIoSerializable != null // just in case + && typeUtils().isAssignable(type.asType(), javaIoSerializable.asType()); } @Override diff --git a/value/src/main/java/com/google/auto/value/processor/AutoOneOfTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoOneOfTemplateVars.java index 1e15771f..06b24e2c 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoOneOfTemplateVars.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoOneOfTemplateVars.java @@ -25,7 +25,7 @@ import java.util.Map; * @author emcmanus@google.com (Éamonn McManus) */ @SuppressWarnings("unused") // the fields in this class are only read via reflection -class AutoOneOfTemplateVars extends AutoValueOrOneOfTemplateVars { +class AutoOneOfTemplateVars extends AutoValueishTemplateVars { /** * The properties defined by the parent class's abstract methods. The elements of this set are in * the same order as the original abstract method declarations in the AutoOneOf class. diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueBuilderProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueBuilderProcessor.java index 62dfeb35..ac6c8ecb 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoValueBuilderProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueBuilderProcessor.java @@ -15,7 +15,7 @@ */ package com.google.auto.value.processor; -import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.hasAnnotationMirror; +import static com.google.auto.value.processor.AutoValueishProcessor.hasAnnotationMirror; import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_BUILDER_NAME; import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_NAME; 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 new file mode 100644 index 00000000..86cf4974 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java @@ -0,0 +1,155 @@ +/* + * Copyright 2012 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.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder; +import com.google.common.collect.ImmutableList; +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. + * + * @author emcmanus@google.com (Éamonn McManus) + */ +@SuppressWarnings("unused") // the fields in this class are only read via reflection +abstract class AutoValueOrBuilderTemplateVars extends AutoValueishTemplateVars { + /** + * The properties defined by the parent class's abstract methods. The elements of this set are in + * the same order as the original abstract method declarations in the AutoValue class. + */ + ImmutableSet<Property> props; + + /** + * The simple name of the generated builder, or empty if there is no builder. This is just + * {@code Builder} for AutoValue, since it is nested inside the {@code AutoValue_Foo} class. But + * it is {@code AutoBuilder_Foo} for AutoBuilder. + */ + String builderName = ""; + + /** + * The name of the builder type as it should appear in source code, or empty if there is no + * builder type. If class {@code Address} contains {@code @AutoValue.Builder} class Builder then + * this will typically be {@code "Address.Builder"}. + */ + String builderTypeName = ""; + + /** + * The formal generic signature of the {@code AutoValue.Builder} class. This is empty, or contains + * type variables with optional bounds, for example {@code <K, V extends K>}. + */ + String builderFormalTypes = ""; + + /** + * The generic signature used by the generated builder subclass for its superclass reference. This + * is empty, or contains only type variables with no bounds, for example {@code <K, V>}. + */ + String builderActualTypes = ""; + + /** True if the builder being implemented is an interface, false if it is an abstract class. */ + Boolean builderIsInterface = false; + + /** + * The full spelling of any annotations to add to the generated builder subclass, or an empty list + * if there are none. A non-empty value might look something like {@code + * @`java.lang.SuppressWarnings`("Immutable")}. The {@code ``} marks are explained in + * {@link TypeEncoder}. + */ + ImmutableList<String> builderAnnotations = ImmutableList.of(); + + /** The builder's build method, often {@code "build"}. */ + Optional<SimpleMethod> buildMethod = Optional.empty(); + + /** The type that will be built by the {@code build()} method of a builder. */ + String builtType; + + /** + * The constructor or method invocation that the {@code build()} method of a builder should use, + * without any parameters. This might be {@code "new Foo"} or {@code "Foo.someMethod"}. + */ + String build; + + /** + * A multimap from property names (like foo) to the corresponding setters. The same property may + * be set by more than one setter. For example, an ImmutableList might be set by {@code + * setFoo(ImmutableList<String>)} and {@code setFoo(String[])}. + */ + ImmutableMultimap<String, BuilderSpec.PropertySetter> builderSetters = ImmutableMultimap.of(); + + /** + * A map from property names to information about the associated property builder. A property + * called foo (defined by a method foo() or getFoo()) can have a property builder called + * fooBuilder(). The type of foo must be a type that has an associated builder following certain + * conventions. Guava immutable types such as ImmutableList follow those conventions, as do many + * {@code @AutoValue} types. + */ + ImmutableMap<String, PropertyBuilder> builderPropertyBuilders = ImmutableMap.of(); + + /** + * Properties that are required to be set. A property must be set explicitly except in the + * following cases: + * + * <ul> + * <li>it is {@code @Nullable} (in which case it defaults to null); + * <li>it is {@code Optional} (in which case it defaults to empty); + * <li>it has a property-builder method (in which case it defaults to empty). + * </ul> + */ + ImmutableSet<Property> builderRequiredProperties = ImmutableSet.of(); + + /** + * A map from property names to information about the associated property getter. A property + * called foo (defined by a method foo() or getFoo()) can have a property getter method with the + * same name (foo() or getFoo()) and either the same return type or an Optional (or OptionalInt, + * etc) wrapping it. + */ + ImmutableMap<String, BuilderSpec.PropertyGetter> builderGetters = ImmutableMap.of(); + + /** + * 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 + * 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. + */ + ImmutableList<SimpleMethod> toBuilderMethods; + + /** + * Whether to include identifiers in strings in the generated code. If false, exception messages + * will not mention properties by name, and {@code toString()} will include neither property names + * nor the name of the {@code @AutoValue} class. + */ + Boolean identifiers; + + /** + * True if the generated class should be final (there are no extensions that will generate + * 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 aafffd7c..ab7da924 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 @@ -16,11 +16,12 @@ package com.google.auto.value.processor; import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods; +import static com.google.auto.common.MoreStreams.toImmutableList; import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_NAME; import static com.google.common.collect.Sets.difference; import static com.google.common.collect.Sets.intersection; +import static java.util.Comparator.naturalOrder; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; import com.google.auto.service.AutoService; import com.google.auto.value.extension.AutoValueExtension; @@ -32,11 +33,9 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -64,8 +63,8 @@ import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; @AutoService(Processor.class) @SupportedAnnotationTypes(AUTO_VALUE_NAME) @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC) -public class AutoValueProcessor extends AutoValueOrOneOfProcessor { - private static final String OMIT_IDENTIFIERS_OPTION = "com.google.auto.value.OmitIdentifiers"; +public class AutoValueProcessor extends AutoValueishProcessor { + static final String OMIT_IDENTIFIERS_OPTION = "com.google.auto.value.OmitIdentifiers"; // We moved MemoizeExtension to a different package, which had an unexpected effect: // now if an old version of AutoValue is in the class path, ServiceLoader can pick up both the @@ -100,10 +99,9 @@ public class AutoValueProcessor extends AutoValueOrOneOfProcessor { @VisibleForTesting static ImmutableList<AutoValueExtension> extensionsFromLoader(ClassLoader loader) { - return ImmutableList.copyOf( - Iterables.filter( - SimpleServiceLoader.load(AutoValueExtension.class, loader), - ext -> !ext.getClass().getName().equals(OLD_MEMOIZE_EXTENSION))); + return SimpleServiceLoader.load(AutoValueExtension.class, loader).stream() + .filter(ext -> !ext.getClass().getName().equals(OLD_MEMOIZE_EXTENSION)) + .collect(toImmutableList()); } @Override @@ -131,14 +129,17 @@ public class AutoValueProcessor extends AutoValueOrOneOfProcessor { } @Override - public Set<String> getSupportedOptions() { + public ImmutableSet<String> getSupportedOptions() { ImmutableSet.Builder<String> builder = ImmutableSet.builder(); AutoValueExtension.IncrementalExtensionType incrementalType = extensions.stream() .map(e -> e.incrementalType(processingEnv)) - .min(Comparator.naturalOrder()) + .min(naturalOrder()) .orElse(AutoValueExtension.IncrementalExtensionType.ISOLATING); - builder.add(OMIT_IDENTIFIERS_OPTION).addAll(optionsFor(incrementalType)); + builder + .add(OMIT_IDENTIFIERS_OPTION) + .add(Nullables.NULLABLE_OPTION) + .addAll(optionsFor(incrementalType)); for (AutoValueExtension extension : extensions) { builder.addAll(extension.getSupportedOptions()); } @@ -164,15 +165,6 @@ public class AutoValueProcessor extends AutoValueOrOneOfProcessor { @Override void processType(TypeElement type) { - if (!hasAnnotationMirror(type, AUTO_VALUE_NAME)) { - // This shouldn't happen unless the compilation environment is buggy, - // but it has happened in the past and can crash the compiler. - errorReporter() - .abortWithError( - type, - "[AutoValueCompilerBug] annotation processor for @AutoValue was invoked with a type" - + " that does not have that annotation; this is probably a compiler bug"); - } if (type.getKind() != ElementKind.CLASS) { errorReporter() .abortWithError(type, "[AutoValueNotClass] @AutoValue only applies to classes"); @@ -216,7 +208,7 @@ public class AutoValueProcessor extends AutoValueOrOneOfProcessor { Optional<BuilderSpec.Builder> builder = builderSpec.getBuilder(); ImmutableSet<ExecutableElement> toBuilderMethods; if (builder.isPresent()) { - toBuilderMethods = builder.get().toBuilderMethods(typeUtils(), abstractMethods); + toBuilderMethods = builder.get().toBuilderMethods(typeUtils(), type, abstractMethods); } else { toBuilderMethods = ImmutableSet.of(); } @@ -226,8 +218,9 @@ public class AutoValueProcessor extends AutoValueOrOneOfProcessor { ImmutableMap<String, ExecutableElement> properties = propertyNameToMethodMap(propertyMethodsAndTypes.keySet()); - ExtensionContext context = new ExtensionContext( - processingEnv, type, properties, propertyMethodsAndTypes, abstractMethods); + ExtensionContext context = + new ExtensionContext( + processingEnv, type, properties, propertyMethodsAndTypes, abstractMethods); ImmutableList<AutoValueExtension> applicableExtensions = applicableExtensions(type, context); ImmutableSet<ExecutableElement> consumedMethods = methodsConsumedByExtensions( @@ -240,21 +233,23 @@ public class AutoValueProcessor extends AutoValueOrOneOfProcessor { propertyMethodsAndTypes = propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods), type); properties = propertyNameToMethodMap(propertyMethodsAndTypes.keySet()); - context = new ExtensionContext( - processingEnv, type, properties, propertyMethodsAndTypes, allAbstractMethods); + context = + new ExtensionContext( + processingEnv, type, properties, propertyMethodsAndTypes, allAbstractMethods); } ImmutableSet<ExecutableElement> propertyMethods = propertyMethodsAndTypes.keySet(); boolean extensionsPresent = !applicableExtensions.isEmpty(); validateMethods(type, abstractMethods, toBuilderMethods, propertyMethods, extensionsPresent); - String finalSubclass = generatedSubclassName(type, 0); + String finalSubclass = TypeSimplifier.simpleNameOf(generatedSubclassName(type, 0)); AutoValueTemplateVars vars = new AutoValueTemplateVars(); - vars.finalSubclass = TypeSimplifier.simpleNameOf(finalSubclass); vars.types = processingEnv.getTypeUtils(); vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION); defineSharedVarsForType(type, methods, vars); defineVarsForType(type, vars, toBuilderMethods, propertyMethodsAndTypes, builder); + vars.builtType = vars.origClass + vars.actualTypes; + vars.build = "new " + finalSubclass + vars.actualTypes; // If we've encountered problems then we might end up invoking extensions with inconsistent // state. Anyway we probably don't want to generate code which is likely to provoke further @@ -276,7 +271,7 @@ public class AutoValueProcessor extends AutoValueOrOneOfProcessor { text = Reformatter.fixup(text); writeSourceFile(subclass, text, type); GwtSerialization gwtSerialization = new GwtSerialization(gwtCompatibility, processingEnv, type); - gwtSerialization.maybeWriteGwtSerializer(vars); + gwtSerialization.maybeWriteGwtSerializer(vars, finalSubclass); } // Invokes each of the given extensions to generate its subclass, and returns the number of @@ -431,22 +426,22 @@ public class AutoValueProcessor extends AutoValueOrOneOfProcessor { ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes, Optional<BuilderSpec.Builder> maybeBuilder) { ImmutableSet<ExecutableElement> propertyMethods = propertyMethodsAndTypes.keySet(); - // We can't use ImmutableList.toImmutableList() for obscure Google-internal reasons. vars.toBuilderMethods = - ImmutableList.copyOf(toBuilderMethods.stream().map(SimpleMethod::new).collect(toList())); + toBuilderMethods.stream().map(SimpleMethod::new).collect(toImmutableList()); + vars.toBuilderConstructor = !vars.toBuilderMethods.isEmpty(); ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyFields = propertyFieldAnnotationMap(type, propertyMethods); ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyMethods = propertyMethodAnnotationMap(type, propertyMethods); vars.props = propertySet(propertyMethodsAndTypes, annotatedPropertyFields, annotatedPropertyMethods); - vars.serialVersionUID = getSerialVersionUID(type); // Check for @AutoValue.Builder and add appropriate variables if it is present. maybeBuilder.ifPresent( builder -> { ImmutableBiMap<ExecutableElement, String> methodToPropertyName = propertyNameToMethodMap(propertyMethods).inverse(); - builder.defineVars(vars, methodToPropertyName); + builder.defineVarsForAutoValue(vars, methodToPropertyName); + vars.builderName = "Builder"; vars.builderAnnotations = copiedClassAnnotations(builder.builderType()); }); } diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java index 8f855bd0..e53b92e9 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java @@ -15,15 +15,7 @@ */ package com.google.auto.value.processor; -import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.ImmutableSet; import com.google.escapevelocity.Template; -import java.util.Optional; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.util.Types; /** * The variables to substitute into the autovalue.vm template. @@ -31,22 +23,7 @@ import javax.lang.model.util.Types; * @author emcmanus@google.com (Éamonn McManus) */ @SuppressWarnings("unused") // the fields in this class are only read via reflection -class AutoValueTemplateVars extends AutoValueOrOneOfTemplateVars { - /** - * The properties defined by the parent class's abstract methods. The elements of this set are in - * the same order as the original abstract method declarations in the AutoValue class. - */ - ImmutableSet<AutoValueOrOneOfProcessor.Property> props; - - /** - * Whether to include identifiers in strings in the generated code. If false, exception messages - * will not mention properties by name, and {@code toString()} will include neither property names - * nor the name of the {@code @AutoValue} class. - */ - Boolean identifiers; - - /** The type utilities returned by {@link ProcessingEnvironment#getTypeUtils()}. */ - Types types; +class AutoValueTemplateVars extends AutoValueOrBuilderTemplateVars { /** * The encoding of the {@code @GwtCompatible} annotation to add to this class, or an empty string @@ -56,100 +33,14 @@ class AutoValueTemplateVars extends AutoValueOrOneOfTemplateVars { */ String gwtCompatibleAnnotation; - /** The text of the serialVersionUID constant, or empty if there is none. */ - String serialVersionUID; - /** The simple name of the generated subclass. */ String subclass; /** - * The simple name of the final generated subclass. For {@code @AutoValue public static class Foo - * {}} this should always be "AutoValue_Foo". - */ - String finalSubclass; - - /** - * True if the generated class should be final (there are no extensions that will generate - * subclasses) - */ - Boolean isFinal = false; - - /** * The modifiers (for example {@code final} or {@code abstract}) for the generated subclass, * followed by a space if they are not empty. */ String modifiers; - /** - * The name of the builder type as it should appear in source code, or empty if there is no - * builder type. If class {@code Address} contains {@code @AutoValue.Builder} class Builder then - * this will typically be {@code "Address.Builder"}. - */ - String builderTypeName = ""; - - /** - * The formal generic signature of the {@code AutoValue.Builder} class. This is empty, or contains - * type variables with optional bounds, for example {@code <K, V extends K>}. - */ - String builderFormalTypes = ""; - /** - * The generic signature used by the generated builder subclass for its superclass reference. This - * is empty, or contains only type variables with no bounds, for example {@code <K, V>}. - */ - String builderActualTypes = ""; - - /** True if the builder being implemented is an interface, false if it is an abstract class. */ - Boolean builderIsInterface = false; - - /** - * The full spelling of any annotations to add to the generated builder subclass, or an empty list - * if there are none. A non-empty value might look something like {@code - * @`java.lang.SuppressWarnings`("Immutable")}. The {@code ``} marks are explained in - * {@link TypeEncoder}. - */ - ImmutableList<String> builderAnnotations = ImmutableList.of(); - - /** The builder's build method, often {@code "build"}. */ - Optional<SimpleMethod> buildMethod = Optional.empty(); - - /** - * A multimap from property names (like foo) to the corresponding setters. The same property may - * be set by more than one setter. For example, an ImmutableList might be set by {@code - * setFoo(ImmutableList<String>)} and {@code setFoo(String[])}. - */ - ImmutableMultimap<String, BuilderSpec.PropertySetter> builderSetters = ImmutableMultimap.of(); - - /** - * A map from property names to information about the associated property builder. A property - * called foo (defined by a method foo() or getFoo()) can have a property builder called - * fooBuilder(). The type of foo must be a type that has an associated builder following certain - * conventions. Guava immutable types such as ImmutableList follow those conventions, as do many - * {@code @AutoValue} types. - */ - ImmutableMap<String, PropertyBuilder> builderPropertyBuilders = ImmutableMap.of(); - - /** - * Properties that are required to be set. A property must be set explicitly except in the - * following cases: - * - * <ul> - * <li>it is {@code @Nullable} (in which case it defaults to null); - * <li>it is {@code Optional} (in which case it defaults to empty); - * <li>it has a property-builder method (in which case it defaults to empty). - * </ul> - */ - ImmutableSet<AutoValueProcessor.Property> builderRequiredProperties = ImmutableSet.of(); - - /** - * A map from property names to information about the associated property getter. A property - * called foo (defined by a method foo() or getFoo()) can have a property getter method with the - * same name (foo() or getFoo()) and either the same return type or an Optional (or OptionalInt, - * etc) wrapping it. - */ - ImmutableMap<String, BuilderSpec.PropertyGetter> builderGetters = ImmutableMap.of(); - - /** Any {@code toBuilder()} methods, that is methods that return the builder type. */ - ImmutableList<SimpleMethod> toBuilderMethods; - private static final Template TEMPLATE = parsedTemplateForResource("autovalue.vm"); @Override diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java index 36a82bd4..93f2f79e 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java @@ -19,6 +19,8 @@ import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; 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.toImmutableSet; import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_PACKAGE_NAME; import static com.google.auto.value.processor.ClassNames.COPY_ANNOTATIONS_NAME; import static com.google.common.collect.Iterables.getOnlyElement; @@ -56,6 +58,7 @@ import java.util.Optional; import java.util.OptionalInt; import java.util.Set; import java.util.function.Predicate; +import java.util.stream.IntStream; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; @@ -82,11 +85,12 @@ import javax.tools.Diagnostic; import javax.tools.JavaFileObject; /** - * Shared code between AutoValueProcessor and AutoOneOfProcessor. + * Shared code between {@link AutoValueProcessor}, {@link AutoOneOfProcessor}, and {@link + * AutoBuilderProcessor}. * * @author emcmanus@google.com (Éamonn McManus) */ -abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { +abstract class AutoValueishProcessor extends AbstractProcessor { private final String annotationClassName; /** @@ -96,7 +100,7 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { */ private final List<String> deferredTypeNames = new ArrayList<>(); - AutoValueOrOneOfProcessor(String annotationClassName) { + AutoValueishProcessor(String annotationClassName) { this.annotationClassName = annotationClassName; } @@ -106,11 +110,13 @@ abstract class AutoValueOrOneOfProcessor 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); } final ErrorReporter errorReporter() { @@ -149,30 +155,26 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { public static class Property { private final String name; private final String identifier; - private final ExecutableElement method; private final String type; - private final ImmutableList<String> fieldAnnotations; - private final ImmutableList<String> methodAnnotations; + private final TypeMirror typeMirror; private final Optional<String> nullableAnnotation; private final Optionalish optional; + private final String getter; Property( String name, String identifier, - ExecutableElement method, String type, - ImmutableList<String> fieldAnnotations, - ImmutableList<String> methodAnnotations, - Optional<String> nullableAnnotation) { + TypeMirror typeMirror, + Optional<String> nullableAnnotation, + String getter) { this.name = name; this.identifier = identifier; - this.method = method; this.type = type; - this.fieldAnnotations = fieldAnnotations; - this.methodAnnotations = methodAnnotations; + this.typeMirror = typeMirror; this.nullableAnnotation = nullableAnnotation; - TypeMirror propertyType = method.getReturnType(); - this.optional = Optionalish.createIfOptional(propertyType); + this.optional = Optionalish.createIfOptional(typeMirror); + this.getter = getter; } /** @@ -196,16 +198,8 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { return name; } - /** - * Returns the name of the getter method for this property as defined by the {@code @AutoValue} - * class. For property {@code foo}, this will be {@code foo} or {@code getFoo} or {@code isFoo}. - */ - public String getGetter() { - return method.getSimpleName().toString(); - } - public TypeMirror getTypeMirror() { - return method.getReturnType(); + return typeMirror; } public String getType() { @@ -213,23 +207,7 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { } public TypeKind getKind() { - return method.getReturnType().getKind(); - } - - /** - * Returns the annotations (in string form) that should be applied to the property's field - * declaration. - */ - public List<String> getFieldAnnotations() { - return fieldAnnotations; - } - - /** - * Returns the annotations (in string form) that should be applied to the property's method - * implementation. - */ - public List<String> getMethodAnnotations() { - return methodAnnotations; + return typeMirror.getKind(); } /** @@ -257,13 +235,66 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { return nullableAnnotation.isPresent(); } + /** + * 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. + */ + public String getGetter() { + return getter; + } + } + + /** A {@link Property} that corresponds to an abstract getter method in the source. */ + public static class GetterProperty extends Property { + private final ExecutableElement method; + private final ImmutableList<String> fieldAnnotations; + private final ImmutableList<String> methodAnnotations; + + GetterProperty( + String name, + String identifier, + ExecutableElement method, + String type, + ImmutableList<String> fieldAnnotations, + ImmutableList<String> methodAnnotations, + Optional<String> nullableAnnotation) { + super( + name, + identifier, + type, + method.getReturnType(), + nullableAnnotation, + method.getSimpleName().toString()); + this.method = method; + this.fieldAnnotations = fieldAnnotations; + this.methodAnnotations = methodAnnotations; + } + + /** + * Returns the annotations (in string form) that should be applied to the property's field + * declaration. + */ + public List<String> getFieldAnnotations() { + return fieldAnnotations; + } + + /** + * Returns the annotations (in string form) that should be applied to the property's method + * implementation. + */ + public List<String> getMethodAnnotations() { + return methodAnnotations; + } + public String getAccess() { return SimpleMethod.access(method); } @Override public boolean equals(Object obj) { - return obj instanceof Property && ((Property) obj).method.equals(method); + return obj instanceof GetterProperty && ((GetterProperty) obj).method.equals(method); } @Override @@ -289,8 +320,7 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { } simpleAnnotationName = annotationType.getSimpleName().toString(); List<TypeElement> deferredTypes = - deferredTypeNames - .stream() + deferredTypeNames.stream() .map(name -> elementUtils().getTypeElement(name)) .collect(toList()); if (roundEnv.processingOver()) { @@ -383,7 +413,7 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { (propertyMethod, returnType) -> { String propertyType = TypeEncoder.encodeWithAnnotations( - returnType, getExcludedAnnotationTypes(propertyMethod)); + returnType, ImmutableList.of(), getExcludedAnnotationTypes(propertyMethod)); String propertyName = methodToPropertyName.get(propertyMethod); String identifier = methodToIdentifier.get(propertyMethod); ImmutableList<String> fieldAnnotations = @@ -393,7 +423,7 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { ImmutableList<String> methodAnnotations = annotationStrings(methodAnnotationMirrors); Optional<String> nullableAnnotation = nullableAnnotationForMethod(propertyMethod); Property p = - new Property( + new GetterProperty( propertyName, identifier, propertyMethod, @@ -411,11 +441,9 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { return props.build(); } - /** Defines the template variables that are shared by AutoValue and AutoOneOf. */ + /** Defines the template variables that are shared by AutoValue, AutoOneOf, and AutoBuilder. */ final void defineSharedVarsForType( - TypeElement type, - ImmutableSet<ExecutableElement> methods, - AutoValueOrOneOfTemplateVars vars) { + TypeElement type, ImmutableSet<ExecutableElement> methods, AutoValueishTemplateVars vars) { vars.pkg = TypeSimplifier.packageNameOf(type); vars.origClass = TypeSimplifier.classNameOf(type); vars.simpleClassName = TypeSimplifier.simpleNameOf(vars.origClass); @@ -423,7 +451,7 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { generatedAnnotation(elementUtils(), processingEnv.getSourceVersion()) .map(annotation -> TypeEncoder.encode(annotation.asType())) .orElse(""); - vars.formalTypes = TypeEncoder.formalTypeParametersString(type); + vars.formalTypes = TypeEncoder.typeParametersString(type.getTypeParameters()); vars.actualTypes = TypeSimplifier.actualTypeParametersString(type); vars.wildcardTypes = wildcardTypeParametersString(type); vars.annotations = copiedClassAnnotations(type); @@ -432,14 +460,17 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { vars.toString = methodsToGenerate.containsKey(ObjectMethod.TO_STRING); vars.equals = methodsToGenerate.containsKey(ObjectMethod.EQUALS); vars.hashCode = methodsToGenerate.containsKey(ObjectMethod.HASH_CODE); - vars.equalsParameterType = equalsParameterType(methodsToGenerate); + Optional<AnnotationMirror> nullable = nullables.appropriateNullableGivenMethods(methods); + vars.equalsParameterType = equalsParameterType(methodsToGenerate, nullable); + vars.serialVersionUID = getSerialVersionUID(type); } /** Returns the spelling to be used in the generated code for the given list of annotations. */ static ImmutableList<String> annotationStrings(List<? extends AnnotationMirror> annotations) { // TODO(b/68008628): use ImmutableList.toImmutableList() when that works. - return ImmutableList.copyOf( - annotations.stream().map(AnnotationOutput::sourceFormForAnnotation).collect(toList())); + return annotations.stream() + .map(AnnotationOutput::sourceFormForAnnotation) + .collect(toImmutableList()); } /** @@ -454,7 +485,7 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { static String generatedClassName(TypeElement type, String prefix) { String name = type.getSimpleName().toString(); while (type.getEnclosingElement() instanceof TypeElement) { - type = (TypeElement) type.getEnclosingElement(); + type = MoreElements.asType(type.getEnclosingElement()); name = type.getSimpleName() + "_" + name; } String pkg = TypeSimplifier.packageNameOf(type); @@ -566,12 +597,9 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { } private static OptionalInt nullableAnnotationIndex(List<? extends AnnotationMirror> annotations) { - for (int i = 0; i < annotations.size(); i++) { - if (isNullable(annotations.get(i))) { - return OptionalInt.of(i); - } - } - return OptionalInt.empty(); + return IntStream.range(0, annotations.size()) + .filter(i -> isNullable(annotations.get(i))) + .findFirst(); } private static boolean isNullable(AnnotationMirror annotation) { @@ -583,21 +611,19 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { * includes {@code isFoo} methods if they return {@code boolean}. This corresponds to JavaBeans * conventions. */ - static ImmutableSet<ExecutableElement> prefixedGettersIn(Iterable<ExecutableElement> methods) { - ImmutableSet.Builder<ExecutableElement> getters = ImmutableSet.builder(); - for (ExecutableElement method : methods) { - String name = method.getSimpleName().toString(); - // Note that getfoo() (without a capital) is still a getter. - boolean get = name.startsWith("get") && !name.equals("get"); - boolean is = - name.startsWith("is") - && !name.equals("is") - && method.getReturnType().getKind() == TypeKind.BOOLEAN; - if (get || is) { - getters.add(method); - } - } - return getters.build(); + static ImmutableSet<ExecutableElement> prefixedGettersIn(Collection<ExecutableElement> methods) { + return methods.stream() + .filter(AutoValueishProcessor::isPrefixedGetter) + .collect(toImmutableSet()); + } + + static boolean isPrefixedGetter(ExecutableElement method) { + String name = method.getSimpleName().toString(); + // Note that getfoo() (without a capital) is still a getter. + return (name.startsWith("get") && !name.equals("get")) + || (name.startsWith("is") + && !name.equals("is") + && method.getReturnType().getKind() == TypeKind.BOOLEAN); } /** @@ -611,7 +637,7 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { * anyway, so the special behaviour is not useful, and of course it behaves poorly with examples * like {@code OAuth}. */ - private static String nameWithoutPrefix(String name) { + static String nameWithoutPrefix(String name) { if (name.startsWith("get")) { name = name.substring(3); } else { @@ -622,27 +648,33 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { } /** - * Checks that, if the given {@code @AutoValue} or {@code @AutoOneOf} class is nested, it is - * static and not private. This check is not necessary for correctness, since the generated code - * would not compile if the check fails, but it produces better error messages for the user. + * Checks that, if the given {@code @AutoValue}, {@code @AutoOneOf}, or {@code @AutoBuilder} class + * is nested, it is static and not private. This check is not necessary for correctness, since the + * generated code would not compile if the check fails, but it produces better error messages for + * the user. */ final void checkModifiersIfNested(TypeElement type) { + checkModifiersIfNested(type, type, simpleAnnotationName); + } + + final void checkModifiersIfNested(TypeElement type, TypeElement reportedType, String what) { ElementKind enclosingKind = type.getEnclosingElement().getKind(); if (enclosingKind.isClass() || enclosingKind.isInterface()) { if (type.getModifiers().contains(Modifier.PRIVATE)) { errorReporter.abortWithError( - type, "[AutoValuePrivate] @%s class must not be private", simpleAnnotationName); + reportedType, "[%sPrivate] @%s class must not be private", simpleAnnotationName, what); } else if (Visibility.effectiveVisibilityOfElement(type).equals(Visibility.PRIVATE)) { // The previous case, where the class itself is private, is much commoner so it deserves // its own error message, even though it would be caught by the test here too. errorReporter.abortWithError( - type, - "[AutoValueInPrivate] @%s class must not be nested in a private class", - simpleAnnotationName); + reportedType, + "[%sInPrivate] @%s class must not be nested in a private class", + simpleAnnotationName, + what); } if (!type.getModifiers().contains(Modifier.STATIC)) { errorReporter.abortWithError( - type, "[AutoValueInner] Nested @%s class must be static", simpleAnnotationName); + reportedType, "[%sInner] Nested @%s class must be static", simpleAnnotationName, what); } } // In principle type.getEnclosingElement() could be an ExecutableElement (for a class @@ -693,7 +725,7 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { ObjectMethod override = objectMethodToOverride(method); boolean canGenerate = method.getModifiers().contains(Modifier.ABSTRACT) - || isJavaLangObject((TypeElement) method.getEnclosingElement()); + || isJavaLangObject(MoreElements.asType(method.getEnclosingElement())); if (!override.equals(ObjectMethod.NONE) && canGenerate) { methodsToGenerate.put(override, method); } @@ -705,14 +737,25 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { * Returns the encoded parameter type of the {@code equals(Object)} method that is to be * generated, or an empty string if the method is not being generated. The parameter type includes * any type annotations, for example {@code @Nullable}. + * + * @param methodsToGenerate the Object methods that are being generated + * @param nullable the type of a {@code @Nullable} type annotation that we have found, if any */ - static String equalsParameterType(Map<ObjectMethod, ExecutableElement> methodsToGenerate) { + static String equalsParameterType( + Map<ObjectMethod, ExecutableElement> methodsToGenerate, Optional<AnnotationMirror> nullable) { ExecutableElement equals = methodsToGenerate.get(ObjectMethod.EQUALS); if (equals == null) { return ""; // this will not be referenced because no equals method will be generated } TypeMirror parameterType = equals.getParameters().get(0).asType(); - return TypeEncoder.encodeWithAnnotations(parameterType); + // Add @Nullable if we know one and the parameter doesn't already have one. + // 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()); } /** @@ -751,8 +794,7 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { * {@code toString()}. */ ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsIn( - Set<ExecutableElement> abstractMethods, - TypeElement autoValueOrOneOfType) { + Set<ExecutableElement> abstractMethods, TypeElement autoValueOrOneOfType) { DeclaredType declaredType = MoreTypes.asDeclared(autoValueOrOneOfType.asType()); ImmutableSet.Builder<ExecutableElement> properties = ImmutableSet.builder(); for (ExecutableElement method : abstractMethods) { @@ -778,7 +820,7 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { TypeMirror type = getter.getReturnType(); if (type.getKind() == TypeKind.ARRAY) { TypeMirror componentType = MoreTypes.asArray(type).getComponentType(); - if (componentType.getKind().isPrimitive()) { + if (componentType.getKind().isPrimitive()) { warnAboutPrimitiveArrays(autoValueClass, getter); } else { errorReporter.reportError( @@ -829,12 +871,13 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { @Override public Boolean visitArray(List<? extends AnnotationValue> list, Void p) { return list.stream().map(AnnotationValue::getValue).anyMatch("mutable"::equals); - } + } } /** - * Returns a string like {@code "1234L"} if {@code type instanceof Serializable} and defines - * {@code serialVersionUID = 1234L}; otherwise {@code ""}. + * Returns a string like {@code "private static final long serialVersionUID = 1234L"} if {@code + * type instanceof Serializable} and defines {@code serialVersionUID = 1234L}; otherwise {@code + * ""}. */ final String getSerialVersionUID(TypeElement type) { TypeMirror serializable = elementUtils().getTypeElement(Serializable.class.getName()).asType(); @@ -846,7 +889,7 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { if (field.getModifiers().containsAll(Arrays.asList(Modifier.STATIC, Modifier.FINAL)) && field.asType().getKind() == TypeKind.LONG && value != null) { - return value + "L"; + return "private static final long serialVersionUID = " + value + "L;"; } else { errorReporter.reportError( field, "serialVersionUID must be a static final long compile-time constant"); @@ -889,7 +932,25 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { // 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)); + ImmutableSet.<String>builder() + .addAll(getExcludedAnnotationClassNames(type)) + .addAll(getAnnotationsMarkedWithInherited(type)) + // + // 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 copyAnnotations(type, type, excludedAnnotations); } else { @@ -919,8 +980,7 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { @SuppressWarnings("unchecked") List<AnnotationValue> excludedClasses = (List<AnnotationValue>) getAnnotationValue(maybeAnnotation.get(), "exclude").getValue(); - return excludedClasses - .stream() + return excludedClasses.stream() .map(annotationValue -> (DeclaredType) annotationValue.getValue()) .collect(toCollection(TypeMirrorSet::new)); } @@ -930,17 +990,14 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { * strings that are fully-qualified class names. */ private Set<String> getExcludedAnnotationClassNames(Element element) { - return getExcludedAnnotationTypes(element) - .stream() + return getExcludedAnnotationTypes(element).stream() .map(MoreTypes::asTypeElement) .map(typeElement -> typeElement.getQualifiedName().toString()) .collect(toSet()); } private static Set<String> getAnnotationsMarkedWithInherited(Element element) { - return element - .getAnnotationMirrors() - .stream() + return element.getAnnotationMirrors().stream() .filter(a -> isAnnotationPresent(a.getAnnotationType().asElement(), Inherited.class)) .map(a -> getAnnotationFqName(a)) .collect(toSet()); @@ -1007,9 +1064,7 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { Set<String> returnTypeAnnotations = getReturnTypeAnnotations(method, this::annotationAppliesToFields); Set<String> nonFieldAnnotations = - method - .getAnnotationMirrors() - .stream() + method.getAnnotationMirrors().stream() .map(a -> a.getAnnotationType().asElement()) .map(MoreElements::asType) .filter(a -> !annotationAppliesToFields(a)) @@ -1027,10 +1082,7 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor { private Set<String> getReturnTypeAnnotations( ExecutableElement method, Predicate<TypeElement> typeFilter) { - return method - .getReturnType() - .getAnnotationMirrors() - .stream() + return method.getReturnType().getAnnotationMirrors().stream() .map(a -> a.getAnnotationType().asElement()) .map(MoreElements::asType) .filter(typeFilter) diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoValueishTemplateVars.java index 9fdbea4f..9ff2891a 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfTemplateVars.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueishTemplateVars.java @@ -18,12 +18,12 @@ package com.google.auto.value.processor; import com.google.common.collect.ImmutableList; /** - * The variables to substitute into the autovalue.vm or autooneof.vm template. + * The variables to substitute into the autovalue.vm, autooneof.vm, or builder.vm templates. * * @author emcmanus@google.com (Éamonn McManus) */ @SuppressWarnings("unused") // the fields in this class are only read via reflection -abstract class AutoValueOrOneOfTemplateVars extends TemplateVars { +abstract class AutoValueishTemplateVars extends TemplateVars { /** Whether to generate an equals(Object) method. */ Boolean equals; @@ -81,4 +81,11 @@ abstract class AutoValueOrOneOfTemplateVars extends TemplateVars { * wildcard, for example {@code <?, ?>}. */ String wildcardTypes; + + /** + * The text of the complete serialVersionUID declaration, or empty if there is none. When + * non-empty, it will be something like {@code private static final long serialVersionUID = + * 123L;}. + */ + String serialVersionUID; } 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 81751e30..51773e6f 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 @@ -15,8 +15,7 @@ */ package com.google.auto.value.processor; -import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.nullableAnnotationFor; -import static com.google.common.collect.Sets.difference; +import static com.google.auto.value.processor.AutoValueishProcessor.nullableAnnotationFor; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; @@ -38,7 +37,9 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; +import java.util.stream.Stream; import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -54,19 +55,35 @@ import javax.lang.model.util.Types; /** * Classifies methods inside builder types, based on their names and parameter and return types. * + * @param <E> the kind of {@link Element} that the corresponding properties are defined by. This is + * {@link ExecutableElement} for AutoValue, where properties are defined by abstract methods, + * and {@link VariableElement} for AutoBuilder, where they are defined by constructor or method + * parameters. * @author Éamonn McManus */ -class BuilderMethodClassifier { +abstract class BuilderMethodClassifier<E extends Element> { private static final Equivalence<TypeMirror> TYPE_EQUIVALENCE = MoreTypes.equivalence(); private final ErrorReporter errorReporter; private final Types typeUtils; private final Elements elementUtils; - private final TypeElement autoValueClass; + private final TypeMirror builtType; private final TypeElement builderType; - private final ImmutableBiMap<ExecutableElement, String> getterToPropertyName; - private final ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType; - private final ImmutableMap<String, ExecutableElement> getterNameToGetter; + + /** + * Property types, rewritten to refer to type variables in the builder. For example, suppose you + * have {@code @AutoValue abstract class Foo<T>} with a getter {@code abstract T bar()} and a + * builder {@code @AutoValue.Builder interface Builder<T>} with a setter {@code abstract + * Builder<T> setBar(T t)}. Then the {@code T} of {@code Foo<T>} and the {@code T} of {@code + * Foo.Builder<T>} are two separate variables. Originally {@code bar()} returned the {@code T} of + * {@code Foo<T>}, but in this map we have rewritten it to be the {@code T} of {@code + * Foo.Builder<T>}. + * + * <p>Importantly, this rewrite <b>loses type annotations</b>, so when those are important we must + * be careful to look at the original type as reported by the {@link #originalPropertyType} + * method. + */ + private final ImmutableMap<String, TypeMirror> rewrittenPropertyTypes; private final Set<ExecutableElement> buildMethods = new LinkedHashSet<>(); private final Map<String, BuilderSpec.PropertyGetter> builderGetters = new LinkedHashMap<>(); @@ -79,76 +96,27 @@ class BuilderMethodClassifier { private boolean settersPrefixed; - private BuilderMethodClassifier( + BuilderMethodClassifier( ErrorReporter errorReporter, ProcessingEnvironment processingEnv, - TypeElement autoValueClass, + TypeMirror builtType, TypeElement builderType, - ImmutableBiMap<ExecutableElement, String> getterToPropertyName, - ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType) { + ImmutableMap<String, TypeMirror> rewrittenPropertyTypes) { this.errorReporter = errorReporter; this.typeUtils = processingEnv.getTypeUtils(); this.elementUtils = processingEnv.getElementUtils(); - this.autoValueClass = autoValueClass; + this.builtType = builtType; this.builderType = builderType; - this.getterToPropertyName = getterToPropertyName; - this.getterToPropertyType = getterToPropertyType; - ImmutableMap.Builder<String, ExecutableElement> getterToPropertyNameBuilder = - ImmutableMap.builder(); - for (ExecutableElement getter : getterToPropertyName.keySet()) { - getterToPropertyNameBuilder.put(getter.getSimpleName().toString(), getter); - } - this.getterNameToGetter = getterToPropertyNameBuilder.build(); + this.rewrittenPropertyTypes = rewrittenPropertyTypes; this.eclipseHack = new EclipseHack(processingEnv); } /** - * Classifies the given methods from a builder type and its ancestors. - * - * @param methods the abstract methods in {@code builderType} and its ancestors. - * @param errorReporter where to report errors. - * @param processingEnv the ProcessingEnvironment for annotation processing. - * @param autoValueClass the {@code AutoValue} class containing the builder. - * @param builderType the builder class or interface within {@code autoValueClass}. - * @param getterToPropertyName a map from getter methods to the properties they get. - * @param getterToPropertyType a map from getter methods to their return types. The return types - * here use type parameters from the builder class (if any) rather than from the {@code - * AutoValue} class, even though the getter methods are in the latter. - * @param autoValueHasToBuilder true if the containing {@code @AutoValue} class has a {@code - * toBuilder()} method. - * @return an {@code Optional} that contains the results of the classification if it was - * successful or nothing if it was not. - */ - static Optional<BuilderMethodClassifier> classify( - Iterable<ExecutableElement> methods, - ErrorReporter errorReporter, - ProcessingEnvironment processingEnv, - TypeElement autoValueClass, - TypeElement builderType, - ImmutableBiMap<ExecutableElement, String> getterToPropertyName, - ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType, - boolean autoValueHasToBuilder) { - BuilderMethodClassifier classifier = - new BuilderMethodClassifier( - errorReporter, - processingEnv, - autoValueClass, - builderType, - getterToPropertyName, - getterToPropertyType); - if (classifier.classifyMethods(methods, autoValueHasToBuilder)) { - return Optional.of(classifier); - } else { - return Optional.empty(); - } - } - - /** * Returns a multimap from the name of a property to the methods that set it. If the property is * defined by an abstract method in the {@code @AutoValue} class called {@code foo()} or {@code * getFoo()} then the name of the property is {@code foo} and there will be an entry in the map - * where the key is {@code "foo"} and the value describes a method in the builder called - * {@code foo} or {@code setFoo}. + * where the key is {@code "foo"} and the value describes a method in the builder called {@code + * foo} or {@code setFoo}. */ ImmutableMultimap<String, PropertySetter> propertyNameToSetters() { return ImmutableMultimap.copyOf( @@ -179,8 +147,7 @@ class BuilderMethodClassifier { } /** Classifies the given methods and sets the state of this object based on what is found. */ - private boolean classifyMethods( - Iterable<ExecutableElement> methods, boolean autoValueHasToBuilder) { + boolean classifyMethods(Iterable<ExecutableElement> methods, boolean autoValueHasToBuilder) { int startErrorCount = errorReporter.errorCount(); for (ExecutableElement method : methods) { classifyMethod(method); @@ -198,49 +165,48 @@ class BuilderMethodClassifier { } else { errorReporter.reportError( propertyNameToUnprefixedSetters.values().iterator().next().getSetter(), - "[AutoValueSetNotSet] If any setter methods use the setFoo convention then all must"); + "[%sSetNotSet] If any setter methods use the setFoo convention then all must", + autoWhat()); return false; } - getterToPropertyName.forEach( - (getter, property) -> { - TypeMirror propertyType = getterToPropertyType.get(getter); - boolean hasSetter = propertyNameToSetter.containsKey(property); - PropertyBuilder propertyBuilder = propertyNameToPropertyBuilder.get(property); - boolean hasBuilder = propertyBuilder != null; - if (hasBuilder) { - // If property bar of type Bar has a barBuilder() that returns BarBuilder, then it must - // be possible to make a BarBuilder from a Bar if either (1) the @AutoValue class has a - // toBuilder() or (2) there is also a setBar(Bar). Making BarBuilder from Bar is - // possible if Bar either has a toBuilder() method or BarBuilder has an addAll or putAll - // method that accepts a Bar argument. - boolean canMakeBarBuilder = - (propertyBuilder.getBuiltToBuilder() != null - || propertyBuilder.getCopyAll() != null); - boolean needToMakeBarBuilder = (autoValueHasToBuilder || hasSetter); - if (needToMakeBarBuilder && !canMakeBarBuilder) { - errorReporter.reportError( - propertyBuilder.getPropertyBuilderMethod(), - "[AutoValueCantMakeBuilder] Property builder method returns %1$s but there is no" - + " way to make that type from %2$s: %2$s does not have a non-static" - + " toBuilder() method that returns %1$s, and %1$s does not have a method" - + " addAll or putAll that accepts an argument of type %2$s", - propertyBuilder.getBuilderTypeMirror(), - propertyType); - } - } else if (!hasSetter) { - // We have neither barBuilder() nor setBar(Bar), so we should complain. - String setterName = settersPrefixed ? prefixWithSet(property) : property; - errorReporter.reportError( - builderType, - "[AutoValueBuilderMissingMethod] Expected a method with this signature: %s%s" - + " %s(%s), or a %sBuilder() method", - builderType, - typeParamsString(), - setterName, - propertyType, - property); - } - }); + for (String property : rewrittenPropertyTypes.keySet()) { + TypeMirror propertyType = rewrittenPropertyTypes.get(property); + boolean hasSetter = propertyNameToSetter.containsKey(property); + PropertyBuilder propertyBuilder = propertyNameToPropertyBuilder.get(property); + boolean hasBuilder = propertyBuilder != null; + if (hasBuilder) { + // If property bar of type Bar has a barBuilder() that returns BarBuilder, then it must + // be possible to make a BarBuilder from a Bar if either (1) the @AutoValue class has a + // toBuilder() or (2) there is also a setBar(Bar). Making BarBuilder from Bar is + // possible if Bar either has a toBuilder() method or BarBuilder has an addAll or putAll + // method that accepts a Bar argument. + boolean canMakeBarBuilder = + (propertyBuilder.getBuiltToBuilder() != null || propertyBuilder.getCopyAll() != null); + boolean needToMakeBarBuilder = (autoValueHasToBuilder || hasSetter); + if (needToMakeBarBuilder && !canMakeBarBuilder) { + errorReporter.reportError( + propertyBuilder.getPropertyBuilderMethod(), + "[AutoValueCantMakeBuilder] Property builder method returns %1$s but there is no" + + " way to make that type from %2$s: %2$s does not have a non-static" + + " toBuilder() method that returns %1$s, and %1$s does not have a method" + + " addAll or putAll that accepts an argument of type %2$s", + propertyBuilder.getBuilderTypeMirror(), + propertyType); + } + } else if (!hasSetter) { + // We have neither barBuilder() nor setBar(Bar), so we should complain. + String setterName = settersPrefixed ? prefixWithSet(property) : property; + errorReporter.reportError( + builderType, + "[%sBuilderMissingMethod] Expected a method with this signature: %s" + + " %s(%s), or a %sBuilder() method", + autoWhat(), + builderType.asType(), + setterName, + propertyType, + property); + } + } return errorReporter.errorCount() == startErrorCount; } @@ -255,7 +221,7 @@ class BuilderMethodClassifier { break; default: errorReporter.reportError( - method, "[AutoValueBuilderArgs] Builder methods must have 0 or 1 parameters"); + method, "[%sBuilderArgs] Builder methods must have 0 or 1 parameters", autoWhat()); } } @@ -268,26 +234,26 @@ class BuilderMethodClassifier { * ImmutableList<String> foos()} or {@code getFoos()}. */ private void classifyMethodNoArgs(ExecutableElement method) { - String methodName = method.getSimpleName().toString(); - TypeMirror returnType = builderMethodReturnType(method); - - ExecutableElement getter = getterNameToGetter.get(methodName); - if (getter != null) { - classifyGetter(method, getter); + Optional<String> getterProperty = propertyForBuilderGetter(method); + if (getterProperty.isPresent()) { + classifyGetter(method, getterProperty.get()); return; } + String methodName = method.getSimpleName().toString(); + TypeMirror returnType = builderMethodReturnType(method); + if (methodName.endsWith("Builder")) { String property = methodName.substring(0, methodName.length() - "Builder".length()); - if (getterToPropertyName.containsValue(property)) { + if (rewrittenPropertyTypes.containsKey(property)) { PropertyBuilderClassifier propertyBuilderClassifier = new PropertyBuilderClassifier( errorReporter, typeUtils, elementUtils, this, - getterToPropertyName, - getterToPropertyType, + this::propertyIsNullable, + rewrittenPropertyTypes, eclipseHack); Optional<PropertyBuilder> propertyBuilder = propertyBuilderClassifier.makePropertyBuilder(method, property); @@ -298,22 +264,24 @@ class BuilderMethodClassifier { } } - if (TYPE_EQUIVALENCE.equivalent(returnType, autoValueClass.asType())) { + if (TYPE_EQUIVALENCE.equivalent(returnType, builtType)) { buildMethods.add(method); } else { errorReporter.reportError( method, - "[AutoValueBuilderNoArg] Method without arguments should be a build method returning" - + " %1$s%2$s, or a getter method with the same name and type as a getter method of" - + " %1$s, or fooBuilder() where foo() or getFoo() is a getter method of %1$s", - autoValueClass, - typeParamsString()); + "[%1$sBuilderNoArg] Method without arguments should be a build method returning" + + " %2$s, or a getter method with the same name and type as %3$s," + + " or fooBuilder() where %4$s is %3$s", + // "where foo() or getFoo() is a method in..." or "where foo is a parameter of..." + autoWhat(), + builtType, + getterMustMatch(), + fooBuilderMustMatch()); } } - private void classifyGetter(ExecutableElement builderGetter, ExecutableElement originalGetter) { - String propertyName = getterToPropertyName.get(originalGetter); - TypeMirror originalGetterType = getterToPropertyType.get(originalGetter); + private void classifyGetter(ExecutableElement builderGetter, String propertyName) { + TypeMirror originalGetterType = rewrittenPropertyTypes.get(propertyName); TypeMirror builderGetterType = builderMethodReturnType(builderGetter); String builderGetterTypeString = TypeEncoder.encodeWithAnnotations(builderGetterType); if (TYPE_EQUIVALENCE.equivalent(builderGetterType, originalGetterType)) { @@ -344,51 +312,76 @@ class BuilderMethodClassifier { builderGetter, "[AutoValueBuilderReturnType] Method matches a property of %1$s but has return type %2$s" + " instead of %3$s or an Optional wrapping of %3$s", - autoValueClass, + builtType, builderGetterType, originalGetterType); } /** - * Classifies a method given that it has one argument. Currently, a method with one argument can - * only be a setter, meaning that it must look like {@code foo(T)} or {@code setFoo(T)}, where the - * {@code AutoValue} class has a property called {@code foo} of type {@code T}. + * Classifies a method given that it has one argument. A method with one argument can be: + * + * <ul> + * <li>a setter, meaning that it looks like {@code foo(T)} or {@code setFoo(T)}, where the + * {@code AutoValue} class has a property called {@code foo} of type {@code T}; + * <li>a property builder with one argument, meaning it looks like {@code + * ImmutableSortedSet.Builder<V> foosBuilder(Comparator<V>)}, where the {@code AutoValue} + * class has a property called {@code foos} with a type whose builder can be made with an + * argument of the given type. + * </ul> */ private void classifyMethodOneArg(ExecutableElement method) { + if (classifyPropertyBuilderOneArg(method)) { + return; + } String methodName = method.getSimpleName().toString(); - Map<String, ExecutableElement> propertyNameToGetter = getterToPropertyName.inverse(); + ImmutableMap<String, E> propertyElements = propertyElements(); String propertyName = null; - ExecutableElement valueGetter = propertyNameToGetter.get(methodName); + E propertyElement = propertyElements.get(methodName); Multimap<String, PropertySetter> propertyNameToSetters = null; - if (valueGetter != null) { + if (propertyElement != null) { propertyNameToSetters = propertyNameToUnprefixedSetters; propertyName = methodName; - } else if (valueGetter == null && methodName.startsWith("set") && methodName.length() > 3) { + } else if (methodName.startsWith("set") && methodName.length() > 3) { propertyNameToSetters = propertyNameToPrefixedSetters; propertyName = PropertyNames.decapitalizeLikeJavaBeans(methodName.substring(3)); - valueGetter = propertyNameToGetter.get(propertyName); - if (valueGetter == null) { + propertyElement = propertyElements.get(propertyName); + if (propertyElement == null) { // If our property is defined by a getter called getOAuth() then it is called "OAuth" - // because of Introspector.decapitalize. Therefore we want Introspector.decapitalize to - // be used for the setter too, so that you can write setOAuth(x). Meanwhile if the property - // is defined by a getter called oAuth() then it is called "oAuth", but you would still - // expect to be able to set it using setOAuth(x). Hence the second try using a decapitalize - // method without the quirky two-leading-capitals rule. + // because of JavaBeans rules. Therefore we want JavaBeans rules to be used for the setter + // too, so that you can write setOAuth(x). Meanwhile if the property is defined by a getter + // called oAuth() then it is called "oAuth", but you would still expect to be able to set it + // using setOAuth(x). Hence the second try using a decapitalize method without the quirky + // two-leading-capitals rule. propertyName = PropertyNames.decapitalizeNormally(methodName.substring(3)); - valueGetter = propertyNameToGetter.get(propertyName); + propertyElement = propertyElements.get(propertyName); + } + } else { + // We might also have an unprefixed setter, so the getter is called OAuth() or getOAuth() and + // the setter is called oAuth(x), where again JavaBeans rules imply that it should be called + // OAuth(x). Iterating over the properties here is a bit clunky but this case should be + // unusual. + propertyNameToSetters = propertyNameToUnprefixedSetters; + for (Map.Entry<String, E> entry : propertyElements.entrySet()) { + if (methodName.equals(PropertyNames.decapitalizeNormally(entry.getKey()))) { + propertyName = entry.getKey(); + propertyElement = entry.getValue(); + break; + } } } - if (valueGetter == null || propertyNameToSetters == null) { + if (propertyElement == null || propertyNameToSetters == null) { // The second disjunct isn't needed but convinces control-flow checkers that // propertyNameToSetters can't be null when we call put on it below. errorReporter.reportError( method, - "[AutoValueBuilderWhatProp] Method does not correspond to a property of %s", - autoValueClass); + "[%sBuilderWhatProp] Method %s does not correspond to %s", + autoWhat(), + methodName, + getterMustMatch()); checkForFailedJavaBean(method); return; } - Optional<Copier> function = getSetterFunction(valueGetter, method); + Optional<Copier> function = getSetterFunction(propertyElement, method); if (function.isPresent()) { DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType()); ExecutableType methodMirror = @@ -399,28 +392,44 @@ class BuilderMethodClassifier { propertyName, new PropertySetter(method, parameterType, function.get())); } else { errorReporter.reportError( - method, "Setter methods must return %s%s", builderType, typeParamsString()); + method, + "[%sBuilderRet] Setter methods must return %s", + autoWhat(), + builderType.asType()); } } } - // A frequent source of problems is where the JavaBeans conventions have been followed for - // most but not all getters. Then AutoValue considers that they haven't been followed at all, - // so you might have a property called getFoo where you thought it was called just foo, and - // you might not understand why your setter called setFoo is rejected (it would have to be called - // setGetFoo). - private void checkForFailedJavaBean(ExecutableElement rejectedSetter) { - ImmutableSet<ExecutableElement> allGetters = getterToPropertyName.keySet(); - ImmutableSet<ExecutableElement> prefixedGetters = - AutoValueProcessor.prefixedGettersIn(allGetters); - if (prefixedGetters.size() < allGetters.size() - && prefixedGetters.size() >= allGetters.size() / 2) { - errorReporter.reportNote( - rejectedSetter, - "This might be because you are using the getFoo() convention" - + " for some but not all methods. These methods don't follow the convention: %s", - difference(allGetters, prefixedGetters)); + /** + * Classifies a method given that it has one argument and is a property builder with a parameter, + * like {@code ImmutableSortedSet.Builder<String> foosBuilder(Comparator<String>)}. + * + * @param method A method to classify + * @return true if method has been classified successfully + */ + private boolean classifyPropertyBuilderOneArg(ExecutableElement method) { + String methodName = method.getSimpleName().toString(); + if (!methodName.endsWith("Builder")) { + return false; } + String property = methodName.substring(0, methodName.length() - "Builder".length()); + if (!rewrittenPropertyTypes.containsKey(property)) { + return false; + } + PropertyBuilderClassifier propertyBuilderClassifier = + new PropertyBuilderClassifier( + errorReporter, + typeUtils, + elementUtils, + this, + this::propertyIsNullable, + rewrittenPropertyTypes, + eclipseHack); + Optional<PropertyBuilder> maybePropertyBuilder = + propertyBuilderClassifier.makePropertyBuilder(method, property); + maybePropertyBuilder.ifPresent( + propertyBuilder -> propertyNameToPropertyBuilder.put(property, propertyBuilder)); + return maybePropertyBuilder.isPresent(); } /** @@ -431,12 +440,12 @@ class BuilderMethodClassifier { * using a method like {@code ImmutableList.copyOf} or {@code Optional.of}, when the returned * function will be something like {@code s -> "Optional.of(" + s + ")"}. */ - private Optional<Copier> getSetterFunction( - ExecutableElement valueGetter, ExecutableElement setter) { + private Optional<Copier> getSetterFunction(E propertyElement, ExecutableElement setter) { VariableElement parameterElement = Iterables.getOnlyElement(setter.getParameters()); boolean nullableParameter = nullableAnnotationFor(parameterElement, parameterElement.asType()).isPresent(); - TypeMirror targetType = getterToPropertyType.get(valueGetter); + String property = propertyElements().inverse().get(propertyElement); + TypeMirror targetType = rewrittenPropertyTypes.get(property); ExecutableType finalSetter = MoreTypes.asExecutable( typeUtils.asMemberOf(MoreTypes.asDeclared(builderType.asType()), setter)); @@ -448,14 +457,14 @@ class BuilderMethodClassifier { && typeUtils.isAssignable(targetType, parameterType)) { if (nullableParameter) { boolean nullableProperty = - nullableAnnotationFor(valueGetter, valueGetter.getReturnType()).isPresent(); + nullableAnnotationFor(propertyElement, originalPropertyType(propertyElement)) + .isPresent(); if (!nullableProperty) { errorReporter.reportError( setter, - "[AutoValueNullNotNull] Parameter of setter method is @Nullable but property method" - + " %s.%s() is not", - autoValueClass, - valueGetter.getSimpleName()); + "[%sNullNotNull] Parameter of setter method is @Nullable but %s is not", + autoWhat(), + propertyString(propertyElement)); return Optional.empty(); } } @@ -465,12 +474,15 @@ class BuilderMethodClassifier { // Parameter type is not equal to property type, but might be convertible with copyOf. ImmutableList<ExecutableElement> copyOfMethods = copyOfMethods(targetType, nullableParameter); if (!copyOfMethods.isEmpty()) { - return getConvertingSetterFunction(copyOfMethods, valueGetter, setter, parameterType); + return getConvertingSetterFunction(copyOfMethods, propertyElement, setter, parameterType); } errorReporter.reportError( setter, - "[AutoValueGetVsSet] Parameter type %s of setter method should be %s to match getter %s.%s", - parameterType, targetType, autoValueClass, valueGetter.getSimpleName()); + "[%sGetVsSet] Parameter type %s of setter method should be %s to match %s", + autoWhat(), + parameterType, + targetType, + propertyString(propertyElement)); return Optional.empty(); } @@ -481,10 +493,11 @@ class BuilderMethodClassifier { */ private Optional<Copier> getConvertingSetterFunction( ImmutableList<ExecutableElement> copyOfMethods, - ExecutableElement valueGetter, + E propertyElement, ExecutableElement setter, TypeMirror parameterType) { - DeclaredType targetType = MoreTypes.asDeclared(getterToPropertyType.get(valueGetter)); + String property = propertyElements().inverse().get(propertyElement); + DeclaredType targetType = MoreTypes.asDeclared(rewrittenPropertyTypes.get(property)); for (ExecutableElement copyOfMethod : copyOfMethods) { Optional<Copier> function = getConvertingSetterFunction(copyOfMethod, targetType, parameterType); @@ -495,12 +508,12 @@ class BuilderMethodClassifier { String targetTypeSimpleName = targetType.asElement().getSimpleName().toString(); errorReporter.reportError( setter, - "[AutoValueGetVsSetOrConvert] Parameter type %s of setter method should be %s to match" - + " getter %s.%s, or it should be a type that can be passed to %s.%s to produce %s", + "[%sGetVsSetOrConvert] Parameter type %s of setter method should be %s to match %s, or it" + + " should be a type that can be passed to %s.%s to produce %s", + autoWhat(), parameterType, targetType, - autoValueClass, - valueGetter.getSimpleName(), + propertyString(propertyElement), targetTypeSimpleName, copyOfMethods.get(0).getSimpleName(), targetType); @@ -635,7 +648,76 @@ class BuilderMethodClassifier { return "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); } - private String typeParamsString() { - return TypeSimplifier.actualTypeParametersString(autoValueClass); + /** + * True if the given property is nullable, either because its type has a {@code @Nullable} type + * annotation, or because its getter method has a {@code @Nullable} method annotation. + */ + private boolean propertyIsNullable(String property) { + E propertyElement = propertyElements().get(property); + return Stream.of(propertyElement, originalPropertyType(propertyElement)) + .flatMap(ac -> ac.getAnnotationMirrors().stream()) + .map(a -> a.getAnnotationType().asElement().getSimpleName()) + .anyMatch(n -> n.contentEquals("Nullable")); } + + /** + * Returns a map from property names to the corresponding source program elements. For AutoValue, + * these elements are the abstract getter methods in the {@code @AutoValue} class. For + * AutoBuilder, they are the parameters of the constructor or method that the generated builder + * will call. + */ + abstract ImmutableBiMap<String, E> propertyElements(); + + /** + * Returns the property type as it appears on the original source program element. This can be + * different from the type stored in {@link #rewrittenPropertyTypes} since that one will refer to + * type variables in the builder rather than in the original class. Also, {@link + * #rewrittenPropertyTypes} will not have type annotations even if they were present on the + * original element, so {@code originalPropertyType} is the right thing to use for those. + */ + abstract TypeMirror originalPropertyType(E propertyElement); + + /** + * A string identifying the given property element, which is a method for AutoValue or a parameter + * for AutoBuilder. + */ + abstract String propertyString(E propertyElement); + + /** + * Returns the name of the property that the given no-arg builder method queries, if + * any. For example, if your {@code @AutoValue} class has a method {@code abstract String + * getBar()} then an abstract method in its builder with the same signature will query the {@code + * bar} property. + */ + abstract Optional<String> propertyForBuilderGetter(ExecutableElement method); + + /** + * Checks for failed JavaBean usage when a method that looks like a setter doesn't actually match + * anything, and emits a compiler Note if detected. A frequent source of problems is where the + * JavaBeans conventions have been followed for most but not all getters. Then AutoValue considers + * that they haven't been followed at all, so you might have a property called getFoo where you + * thought it was called just foo, and you might not understand why your setter called setFoo is + * rejected (it would have to be called setGetFoo). + * + * <p>This is not relevant for AutoBuilder, which uses parameter names rather than getters. The + * parameter names are unambiguously the same as the property names. + */ + abstract void checkForFailedJavaBean(ExecutableElement rejectedSetter); + + /** + * A string describing what sort of Auto this is, {@code "AutoValue"} or {@code "AutoBuilder"}. + */ + abstract String autoWhat(); + + /** + * A string describing what a builder getter must match: a property method for AutoValue, a + * parameter for AutoBuilder. + */ + abstract String getterMustMatch(); + + /** + * A string describing what a property builder for property {@code foo} must match, {@code foo() + * or getFoo()} for AutoValue, {@code foo} for AutoBuilder. + */ + abstract String fooBuilderMustMatch(); } 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 new file mode 100644 index 00000000..55a31983 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoBuilder.java @@ -0,0 +1,247 @@ +/* + * Copyright 2021 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.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 java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +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; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.util.Types; + +class BuilderMethodClassifierForAutoBuilder extends BuilderMethodClassifier<VariableElement> { + private final ExecutableElement executable; + private final ImmutableBiMap<VariableElement, String> paramToPropertyName; + + private BuilderMethodClassifierForAutoBuilder( + ErrorReporter errorReporter, + ProcessingEnvironment processingEnv, + ExecutableElement executable, + TypeMirror builtType, + TypeElement builderType, + ImmutableBiMap<VariableElement, String> paramToPropertyName, + ImmutableMap<String, TypeMirror> rewrittenPropertyTypes) { + super(errorReporter, processingEnv, builtType, builderType, rewrittenPropertyTypes); + this.executable = executable; + this.paramToPropertyName = paramToPropertyName; + } + + /** + * Classifies the given methods from a builder type and its ancestors. + * + * @param methods the abstract methods in {@code builderType} and its ancestors. + * @param errorReporter where to report errors. + * @param processingEnv the ProcessingEnvironment for annotation processing. + * @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}. + * @return an {@code Optional} that contains the results of the classification if it was + * successful or nothing if it was not. + */ + static Optional<BuilderMethodClassifier<VariableElement>> classify( + Iterable<ExecutableElement> methods, + ErrorReporter errorReporter, + ProcessingEnvironment processingEnv, + ExecutableElement executable, + TypeMirror builtType, + TypeElement builderType) { + ImmutableBiMap<VariableElement, String> paramToPropertyName = + executable.getParameters().stream() + .collect(toImmutableBiMap(v -> v, v -> v.getSimpleName().toString())); + ImmutableMap<String, TypeMirror> rewrittenPropertyTypes = + rewriteParameterTypes(executable, builderType, errorReporter, processingEnv.getTypeUtils()); + BuilderMethodClassifier<VariableElement> classifier = + new BuilderMethodClassifierForAutoBuilder( + errorReporter, + processingEnv, + executable, + builtType, + builderType, + paramToPropertyName, + rewrittenPropertyTypes); + if (classifier.classifyMethods(methods, false)) { + return Optional.of(classifier); + } else { + return Optional.empty(); + } + } + + // Rewrites the parameter types of the executable so they use the type variables of the builder + // where appropriate. + // + // Suppose we have something like this: + // + // static <E> Set<E> singletonSet(E elem) {...} + // + // @AutoBuilder(callMethod = "singletonSet") + // interface SingletonSetBuilder<E> { + // SingletonSetBuilder<E> setElem(E elem); + // Set<E> build(); + // } + // + // We want to check that the type of the setter `setElem` matches the type of the + // parameter it is setting. But in fact it doesn't: the type of the setter is + // E-of-SingletonSetBuilder while the type of the parameter is E-of-singletonSet. So we + // need to rewrite any type variables mentioned in parameters so that they use the corresponding + // types from the builder. We want to return a map where "elem" is mapped to + // E-of-SingletonSetBuilder, even though the `elem` that we get from the parameters of + // singletonSet is going to be E-of-singletonSet. And we also want that to work if the parameter + // is something more complicated, like List<? extends E>. + // + // For the corresponding situation with AutoValue, we have a way of dodging the problem somewhat. + // For an @AutoValue class Foo<E> with a builder Builder<E>, we can craft a DeclaredType + // Foo<E> where the E comes from Builder<E>, and we can use Types.asMemberOf to determine the + // return types of methods (which are what we want to rewrite in that case). But that doesn't + // work here because singletonSet is static and Types.asMemberOf would have no effect on it. + // + // So instead we take the type of each parameter and feed it through a TypeVisitor that rewrites + // type variables, rewriting from E-of-singletonSet to E-of-SingletonSetBuilder. Then we can use + // Types.isSameType or Types.isAssignable and it will work as we expect. + // + // In principle a similar situation arises with the return type Set<E> of singletonSet versus + // the return type Set<E> of SingletonSetBuilder.build(). But in fact we only use + // 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, + TypeElement builderType, + ErrorReporter errorReporter, + Types typeUtils) { + ImmutableList<TypeParameterElement> executableTypeParams = executableTypeParams(executable); + List<? extends TypeParameterElement> builderTypeParams = builderType.getTypeParameters(); + if (!BuilderSpec.sameTypeParameters(executableTypeParams, builderTypeParams)) { + errorReporter.abortWithError( + builderType, + "[AutoBuilderTypeParams] Builder type parameters %s must match type parameters %s of %s", + TypeEncoder.typeParametersString(builderTypeParams), + TypeEncoder.typeParametersString(executableTypeParams), + AutoBuilderProcessor.executableString(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() + .collect(toImmutableMap(v -> v.getSimpleName().toString(), Element::asType)); + } + Map<Equivalence.Wrapper<TypeVariable>, TypeMirror> typeVariables = new LinkedHashMap<>(); + for (int i = 0; i < executableTypeParams.size(); i++) { + TypeVariable from = MoreTypes.asTypeVariable(executableTypeParams.get(i).asType()); + TypeVariable to = MoreTypes.asTypeVariable(builderTypeParams.get(i).asType()); + typeVariables.put(MoreTypes.equivalence().wrap(from), to); + } + Function<TypeVariable, TypeMirror> substitute = + v -> typeVariables.get(MoreTypes.equivalence().wrap(v)); + return executable.getParameters().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(); + if (paramToPropertyName.containsValue(methodName)) { + return Optional.of(methodName); + } + if (AutoValueishProcessor.isPrefixedGetter(method)) { + int prefixLength = methodName.startsWith("get") ? 3 : 2; // "get" or "is" + String unprefixed = methodName.substring(prefixLength); + String propertyName = PropertyNames.decapitalizeLikeJavaBeans(unprefixed); + if (paramToPropertyName.containsValue(propertyName)) { + return Optional.of(propertyName); + } + propertyName = PropertyNames.decapitalizeNormally(unprefixed); + if (paramToPropertyName.containsValue(propertyName)) { + return Optional.of(propertyName); + } + } + return Optional.empty(); + } + + @Override + void checkForFailedJavaBean(ExecutableElement rejectedSetter) {} + + @Override + ImmutableBiMap<String, VariableElement> propertyElements() { + return paramToPropertyName.inverse(); + } + + @Override + TypeMirror originalPropertyType(VariableElement propertyElement) { + return propertyElement.asType(); + } + + @Override + String propertyString(VariableElement propertyElement) { + return "parameter \"" + + propertyElement.getSimpleName() + + "\" of " + + AutoBuilderProcessor.executableString(executable); + } + + @Override + String autoWhat() { + return "AutoBuilder"; + } + + @Override + String getterMustMatch() { + return "a parameter of " + AutoBuilderProcessor.executableString(executable); + } + + @Override + String fooBuilderMustMatch() { + return "foo"; + } +} 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 new file mode 100644 index 00000000..dde449bb --- /dev/null +++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java @@ -0,0 +1,148 @@ +/* + * Copyright 2021 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.Sets.difference; + +import com.google.auto.common.MoreElements; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import java.util.Optional; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +class BuilderMethodClassifierForAutoValue extends BuilderMethodClassifier<ExecutableElement> { + private final ErrorReporter errorReporter; + private final ImmutableBiMap<ExecutableElement, String> getterToPropertyName; + private final ImmutableMap<String, ExecutableElement> getterNameToGetter; + private final TypeMirror builtType; + + private BuilderMethodClassifierForAutoValue( + ErrorReporter errorReporter, + ProcessingEnvironment processingEnv, + TypeMirror builtType, + TypeElement builderType, + ImmutableBiMap<ExecutableElement, String> getterToPropertyName, + ImmutableMap<String, TypeMirror> rewrittenPropertyTypes) { + super(errorReporter, processingEnv, builtType, builderType, rewrittenPropertyTypes); + this.errorReporter = errorReporter; + this.getterToPropertyName = getterToPropertyName; + this.getterNameToGetter = + Maps.uniqueIndex(getterToPropertyName.keySet(), m -> m.getSimpleName().toString()); + this.builtType = builtType; + } + + /** + * Classifies the given methods from a builder type and its ancestors. + * + * @param methods the abstract methods in {@code builderType} and its ancestors. + * @param errorReporter where to report errors. + * @param processingEnv the {@link ProcessingEnvironment} for annotation processing. + * @param autoValueClass the {@code AutoValue} class containing the builder. + * @param builderType the builder class or interface within {@code autoValueClass}. + * @param getterToPropertyName a map from getter methods to the properties they get. + * @param rewrittenPropertyTypes a map from property names to types. The types here use type + * parameters from the builder class (if any) rather than from the {@code AutoValue} class, + * even though the getter methods are in the latter. + * @param autoValueHasToBuilder true if the containing {@code @AutoValue} class has a {@code + * toBuilder()} method. + * @return an {@code Optional} that contains the results of the classification if it was + * successful or nothing if it was not. + */ + static Optional<BuilderMethodClassifier<ExecutableElement>> classify( + Iterable<ExecutableElement> methods, + ErrorReporter errorReporter, + ProcessingEnvironment processingEnv, + TypeElement autoValueClass, + TypeElement builderType, + ImmutableBiMap<ExecutableElement, String> getterToPropertyName, + ImmutableMap<String, TypeMirror> rewrittenPropertyTypes, + boolean autoValueHasToBuilder) { + BuilderMethodClassifier<ExecutableElement> classifier = + new BuilderMethodClassifierForAutoValue( + errorReporter, + processingEnv, + autoValueClass.asType(), + builderType, + getterToPropertyName, + rewrittenPropertyTypes); + if (classifier.classifyMethods(methods, autoValueHasToBuilder)) { + return Optional.of(classifier); + } else { + return Optional.empty(); + } + } + + @Override + TypeMirror originalPropertyType(ExecutableElement propertyElement) { + return propertyElement.getReturnType(); + } + + @Override + String propertyString(ExecutableElement propertyElement) { + TypeElement type = MoreElements.asType(propertyElement.getEnclosingElement()); + return "property method " + + type.getQualifiedName() + + "." + + propertyElement.getSimpleName() + + "()"; + } + + @Override + ImmutableBiMap<String, ExecutableElement> propertyElements() { + return getterToPropertyName.inverse(); + } + + @Override + Optional<String> propertyForBuilderGetter(ExecutableElement method) { + String methodName = method.getSimpleName().toString(); + return Optional.ofNullable(getterNameToGetter.get(methodName)).map(getterToPropertyName::get); + } + + @Override + void checkForFailedJavaBean(ExecutableElement rejectedSetter) { + ImmutableSet<ExecutableElement> allGetters = getterToPropertyName.keySet(); + ImmutableSet<ExecutableElement> prefixedGetters = + AutoValueProcessor.prefixedGettersIn(allGetters); + if (prefixedGetters.size() < allGetters.size() + && prefixedGetters.size() >= allGetters.size() / 2) { + errorReporter.reportNote( + rejectedSetter, + "This might be because you are using the getFoo() convention" + + " for some but not all methods. These methods don't follow the convention: %s", + difference(allGetters, prefixedGetters)); + } + } + + @Override + String autoWhat() { + return "AutoValue"; + } + + @Override + String getterMustMatch() { + return "a property method of " + builtType; + } + + @Override + String fooBuilderMustMatch() { + return "foo() or getFoo()"; + } +} 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 7e5b17c9..9f45d172 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 @@ -16,8 +16,9 @@ package com.google.auto.value.processor; import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods; -import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.hasAnnotationMirror; -import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.nullableAnnotationFor; +import static com.google.auto.common.MoreStreams.toImmutableSet; +import static com.google.auto.value.processor.AutoValueishProcessor.hasAnnotationMirror; +import static com.google.auto.value.processor.AutoValueishProcessor.nullableAnnotationFor; import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_BUILDER_NAME; import static com.google.common.collect.Sets.immutableEnumSet; import static java.util.stream.Collectors.toList; @@ -25,16 +26,16 @@ import static java.util.stream.Collectors.toSet; import static javax.lang.model.util.ElementFilter.methodsIn; import static javax.lang.model.util.ElementFilter.typesIn; +import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.auto.value.extension.AutoValueExtension; -import com.google.auto.value.processor.AutoValueOrOneOfProcessor.Property; +import com.google.auto.value.processor.AutoValueishProcessor.Property; import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -49,6 +50,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; @@ -117,7 +119,7 @@ class BuilderSpec { private final TypeElement builderTypeElement; private ImmutableSet<ExecutableElement> toBuilderMethods; private ExecutableElement buildMethod; - private BuilderMethodClassifier classifier; + private BuilderMethodClassifier<?> classifier; Builder(TypeElement builderTypeElement) { this.builderTypeElement = builderTypeElement; @@ -142,14 +144,23 @@ class BuilderSpec { @Override public Optional<ExecutableElement> buildMethod() { - return methodsIn(builderTypeElement.getEnclosedElements()).stream() + Types typeUtils = processingEnv.getTypeUtils(); + DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderTypeElement.asType()); + return MoreElements.getLocalAndInheritedMethods( + builderTypeElement, typeUtils, processingEnv.getElementUtils()) + .stream() .filter( m -> m.getSimpleName().contentEquals("build") && !m.getModifiers().contains(Modifier.PRIVATE) && !m.getModifiers().contains(Modifier.STATIC) - && m.getParameters().isEmpty() - && erasedTypeIs(m.getReturnType(), autoValueClass)) + && m.getParameters().isEmpty()) + .filter( + m -> { + ExecutableType methodMirror = + MoreTypes.asExecutable(typeUtils.asMemberOf(builderTypeMirror, m)); + return erasedTypeIs(methodMirror.getReturnType(), autoValueClass); + }) .findFirst(); } @@ -186,7 +197,8 @@ class BuilderSpec { * Finds any methods in the set that return the builder type. If the builder has type parameters * {@code <A, B>}, then the return type of the method must be {@code Builder<A, B>} with the * same parameter names. We enforce elsewhere that the names and bounds of the builder - * parameters must be the same as those of the @AutoValue class. Here's a correct example: + * parameters must be the same as those of the {@code @AutoValue} class. Here's a correct + * example: * * <pre> * {@code @AutoValue abstract class Foo<A extends Number, B> { @@ -201,18 +213,25 @@ class BuilderSpec { * <p>We currently impose that there cannot be more than one such method. */ ImmutableSet<ExecutableElement> toBuilderMethods( - Types typeUtils, Set<ExecutableElement> abstractMethods) { + Types typeUtils, TypeElement autoValueType, Set<ExecutableElement> abstractMethods) { List<String> builderTypeParamNames = builderTypeElement.getTypeParameters().stream() .map(e -> e.getSimpleName().toString()) .collect(toList()); + DeclaredType autoValueTypeMirror = MoreTypes.asDeclared(autoValueType.asType()); ImmutableSet.Builder<ExecutableElement> methods = ImmutableSet.builder(); for (ExecutableElement method : abstractMethods) { - if (builderTypeElement.equals(typeUtils.asElement(method.getReturnType()))) { + if (!method.getParameters().isEmpty()) { + continue; + } + ExecutableType methodMirror = + MoreTypes.asExecutable(typeUtils.asMemberOf(autoValueTypeMirror, method)); + TypeMirror returnTypeMirror = methodMirror.getReturnType(); + if (builderTypeElement.equals(typeUtils.asElement(returnTypeMirror))) { methods.add(method); - DeclaredType returnType = MoreTypes.asDeclared(method.getReturnType()); + DeclaredType returnType = MoreTypes.asDeclared(returnTypeMirror); List<String> typeArguments = returnType.getTypeArguments().stream() .filter(t -> t.getKind().equals(TypeKind.TYPEVAR)) @@ -237,11 +256,12 @@ class BuilderSpec { return builderMethods; } - void defineVars( - AutoValueTemplateVars vars, + void defineVarsForAutoValue( + AutoValueOrBuilderTemplateVars vars, ImmutableBiMap<ExecutableElement, String> getterToPropertyName) { - Iterable<ExecutableElement> builderMethods = abstractMethods(builderTypeElement); - boolean autoValueHasToBuilder = !toBuilderMethods.isEmpty(); + Iterable<ExecutableElement> builderMethods = + abstractMethods(builderTypeElement, processingEnv); + boolean autoValueHasToBuilder = toBuilderMethods != null && !toBuilderMethods.isEmpty(); ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType = TypeVariables.rewriteReturnTypes( processingEnv.getElementUtils(), @@ -249,32 +269,40 @@ class BuilderSpec { getterToPropertyName.keySet(), autoValueClass, builderTypeElement); - Optional<BuilderMethodClassifier> optionalClassifier = - BuilderMethodClassifier.classify( + ImmutableMap.Builder<String, TypeMirror> rewrittenPropertyTypes = ImmutableMap.builder(); + getterToPropertyType.forEach( + (getter, type) -> rewrittenPropertyTypes.put(getterToPropertyName.get(getter), type)); + Optional<BuilderMethodClassifier<ExecutableElement>> optionalClassifier = + BuilderMethodClassifierForAutoValue.classify( builderMethods, errorReporter, processingEnv, autoValueClass, builderTypeElement, getterToPropertyName, - getterToPropertyType, + rewrittenPropertyTypes.build(), autoValueHasToBuilder); if (!optionalClassifier.isPresent()) { return; } for (ExecutableElement method : methodsIn(builderTypeElement.getEnclosedElements())) { if (method.getSimpleName().contentEquals("builder") - && method.getModifiers().contains(Modifier.STATIC) - && method.getAnnotationMirrors().isEmpty()) { - // For now we ignore methods with annotations, because for example we do want to allow - // Jackson's @JsonCreator. + && method.getModifiers().contains(Modifier.STATIC) + && method.getAnnotationMirrors().isEmpty() + && !(vars instanceof AutoBuilderTemplateVars)) { + // For now we don't warn for methods with annotations, because for example we do want to + // allow Jackson's @JsonCreator. We also don't warn if this is an @AutoBuilder. errorReporter.reportWarning( method, "[AutoValueBuilderInBuilder] Static builder() method should be in the containing" + " class"); } } - this.classifier = optionalClassifier.get(); + defineVars(vars, optionalClassifier.get()); + } + + void defineVars(AutoValueOrBuilderTemplateVars vars, BuilderMethodClassifier<?> classifier) { + this.classifier = classifier; Set<ExecutableElement> buildMethods = classifier.buildMethods(); if (buildMethods.size() != 1) { Set<? extends Element> errorElements = @@ -282,8 +310,8 @@ class BuilderSpec { for (Element buildMethod : errorElements) { errorReporter.reportError( buildMethod, - "[AutoValueBuilderBuild] Builder must have a single no-argument method returning" - + " %s%s", + "[AutoValueBuilderBuild] Builder must have a single no-argument method, typically" + + " called build(), that returns %s%s", autoValueClass, typeParamsString()); } @@ -292,7 +320,8 @@ class BuilderSpec { this.buildMethod = Iterables.getOnlyElement(buildMethods); vars.builderIsInterface = builderTypeElement.getKind() == ElementKind.INTERFACE; vars.builderTypeName = TypeSimplifier.classNameOf(builderTypeElement); - vars.builderFormalTypes = TypeEncoder.formalTypeParametersString(builderTypeElement); + vars.builderFormalTypes = + TypeEncoder.typeParametersString(builderTypeElement.getTypeParameters()); vars.builderActualTypes = TypeSimplifier.actualTypeParametersString(builderTypeElement); vars.buildMethod = Optional.of(new SimpleMethod(buildMethod)); vars.builderGetters = classifier.builderGetters(); @@ -301,15 +330,12 @@ class BuilderSpec { vars.builderPropertyBuilders = ImmutableMap.copyOf(classifier.propertyNameToPropertyBuilder()); - Set<Property> required = new LinkedHashSet<>(vars.props); - for (Property property : vars.props) { - if (property.isNullable() - || property.getOptional() != null - || vars.builderPropertyBuilders.containsKey(property.getName())) { - required.remove(property); - } - } - vars.builderRequiredProperties = ImmutableSet.copyOf(required); + vars.builderRequiredProperties = + vars.props.stream() + .filter(p -> !p.isNullable()) + .filter(p -> p.getOptional() == null) + .filter(p -> !vars.builderPropertyBuilders.containsKey(p.getName())) + .collect(toImmutableSet()); } } @@ -325,6 +351,7 @@ class BuilderSpec { * five) then {@code Optional<T>} can be the corresponding boxed type. */ public static class PropertyGetter { + private final String name; private final String access; private final String type; private final Optionalish optional; @@ -342,11 +369,17 @@ class BuilderSpec { * {@code Optional<T>} rather than {@code T}, as explained above. */ PropertyGetter(ExecutableElement method, String type, Optionalish optional) { + this.name = method.getSimpleName().toString(); this.access = SimpleMethod.access(method); this.type = type; this.optional = optional; } + // Not accessed from templates so doesn't have to be public. + String getName() { + return name; + } + public String getAccess() { return access; } @@ -361,8 +394,8 @@ class BuilderSpec { } /** - * Specifies how to copy a parameter value into the target type. This might be the identity, or - * it might be something like {@code ImmutableList.of(...)} or {@code Optional.ofNullable(...)}. + * Specifies how to copy a parameter value into the target type. This might be the identity, or it + * might be something like {@code ImmutableList.of(...)} or {@code Optional.ofNullable(...)}. */ static class Copier { static final Copier IDENTITY = acceptingNull(x -> x); @@ -452,7 +485,7 @@ class BuilderSpec { return nullableAnnotation; } - public String copy(AutoValueProcessor.Property property) { + public String copy(Property property) { String copy = copier.copy.apply(property.toString()); if (property.isNullable() && !copier.acceptsNull) { copy = String.format("(%s == null ? null : %s)", property, copy); @@ -487,19 +520,24 @@ class BuilderSpec { } private static boolean sameTypeParameters(TypeElement a, TypeElement b) { - int nTypeParameters = a.getTypeParameters().size(); - if (nTypeParameters != b.getTypeParameters().size()) { + return sameTypeParameters(a.getTypeParameters(), b.getTypeParameters()); + } + + static boolean sameTypeParameters( + List<? extends TypeParameterElement> aParams, List<? extends TypeParameterElement> bParams) { + int nTypeParameters = aParams.size(); + if (nTypeParameters != bParams.size()) { return false; } for (int i = 0; i < nTypeParameters; i++) { - TypeParameterElement aParam = a.getTypeParameters().get(i); - TypeParameterElement bParam = b.getTypeParameters().get(i); + TypeParameterElement aParam = aParams.get(i); + TypeParameterElement bParam = bParams.get(i); if (!aParam.getSimpleName().equals(bParam.getSimpleName())) { return false; } - Set<TypeMirror> autoValueBounds = new TypeMirrorSet(aParam.getBounds()); - Set<TypeMirror> builderBounds = new TypeMirrorSet(bParam.getBounds()); - if (!autoValueBounds.equals(builderBounds)) { + Set<TypeMirror> aBounds = new TypeMirrorSet(aParam.getBounds()); + Set<TypeMirror> bBounds = new TypeMirrorSet(bParam.getBounds()); + if (!aBounds.equals(bBounds)) { return false; } } @@ -512,7 +550,8 @@ class BuilderSpec { * then this method will throw an exception that will cause us to defer processing of the current * class until a later annotation-processing round. */ - private ImmutableSet<ExecutableElement> abstractMethods(TypeElement typeElement) { + static ImmutableSet<ExecutableElement> abstractMethods( + TypeElement typeElement, ProcessingEnvironment processingEnv) { Set<ExecutableElement> methods = getLocalAndInheritedMethods( typeElement, processingEnv.getTypeUtils(), processingEnv.getElementUtils()); 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 d23cfd28..76c50710 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 @@ -28,5 +28,7 @@ final class ClassNames { static final String AUTO_ONE_OF_NAME = AUTO_VALUE_PACKAGE_NAME + "AutoOneOf"; static final String AUTO_VALUE_NAME = AUTO_VALUE_PACKAGE_NAME + "AutoValue"; 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"; } diff --git a/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java b/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java index e2a3d839..3ec9a0ef 100644 --- a/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java +++ b/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java @@ -81,9 +81,12 @@ class ErrorReporter { * @param e the element to which it pertains * @param format the format string for the text of the error * @param args arguments for the format string + * @return This method does not return, but is declared with an exception return type so you + * can write {@code throw abortWithError(...)} to tell the compiler that. + * @throws AbortProcessingException always */ @FormatMethod - void abortWithError(Element e, String format, Object... args) { + AbortProcessingException abortWithError(Element e, String format, Object... args) { reportError(e, format, args); throw new AbortProcessingException(); } diff --git a/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java b/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java index 72207541..fae4e092 100644 --- a/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java +++ b/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java @@ -62,9 +62,7 @@ class GwtCompatibility { annotationArguments = ""; } else { annotationArguments = - getElementValues(annotation) - .entrySet() - .stream() + getElementValues(annotation).entrySet().stream() .map(e -> e.getKey().getSimpleName() + " = " + e.getValue()) .collect(joining(", ", "(", ")")); } diff --git a/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java b/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java index cf928d5c..30ad0926 100644 --- a/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java +++ b/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java @@ -18,6 +18,7 @@ package com.google.auto.value.processor; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.stream.Collectors.toList; +import com.google.auto.value.processor.AutoValueishProcessor.GetterProperty; import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multimap; @@ -80,12 +81,14 @@ class GwtSerialization { * com.example.AutoValue_Foo_CustomFieldSerializer. * * @param autoVars the template variables defined for this type. + * @param finalSubclass the simple name of the AutoValue class being generated, AutoValue_Foo + * in the example. */ - void maybeWriteGwtSerializer(AutoValueTemplateVars autoVars) { + void maybeWriteGwtSerializer(AutoValueTemplateVars autoVars, String finalSubclass) { if (shouldWriteGwtSerializer()) { GwtTemplateVars vars = new GwtTemplateVars(); vars.pkg = autoVars.pkg; - vars.subclass = autoVars.finalSubclass; + vars.subclass = finalSubclass; vars.formalTypes = autoVars.formalTypes; vars.actualTypes = autoVars.actualTypes; vars.useBuilder = !autoVars.builderTypeName.isEmpty(); @@ -95,7 +98,8 @@ class GwtSerialization { String className = (vars.pkg.isEmpty() ? "" : vars.pkg + ".") + vars.subclass + "_CustomFieldSerializer"; vars.serializerClass = TypeSimplifier.simpleNameOf(className); - vars.props = autoVars.props.stream().map(Property::new).collect(toList()); + vars.props = + autoVars.props.stream().map(p -> new Property((GetterProperty) p)).collect(toList()); vars.classHashString = computeClassHash(autoVars.props, vars.pkg); String text = vars.toText(); text = TypeEncoder.decode(text, processingEnv, vars.pkg, type.asType()); @@ -104,10 +108,10 @@ class GwtSerialization { } public static class Property { - private final AutoValueProcessor.Property property; + private final GetterProperty property; private final boolean isCastingUnchecked; - Property(AutoValueProcessor.Property property) { + Property(GetterProperty property) { this.property = property; this.isCastingUnchecked = TypeSimplifier.isCastingUnchecked(property.getTypeMirror()); } @@ -242,14 +246,14 @@ class GwtSerialization { .printMessage( Diagnostic.Kind.WARNING, "Could not write generated class " + className + ": " + e); // A warning rather than an error for the reason explained in - // AutoValueOrOneOfProcessor.writeSourceFile. + // AutoValueishProcessor.writeSourceFile. } } // Compute a hash that is guaranteed to change if the names, types, or order of the fields // change. We use TypeEncoder so that we can get a defined string for types, since // TypeMirror.toString() isn't guaranteed to remain the same. - private String computeClassHash(Iterable<AutoValueProcessor.Property> props, String pkg) { + private String computeClassHash(Iterable<AutoValueishProcessor.Property> props, String pkg) { CRC32 crc = new CRC32(); String encodedType = TypeEncoder.encode(type.asType()) + ":"; String decodedType = TypeEncoder.decode(encodedType, processingEnv, "", null); @@ -259,7 +263,7 @@ class GwtSerialization { decodedType = pkg + "." + decodedType; } crc.update(decodedType.getBytes(UTF_8)); - for (AutoValueProcessor.Property prop : props) { + for (AutoValueishProcessor.Property prop : props) { String encodedProp = prop + ":" + TypeEncoder.encode(prop.getTypeMirror()) + ";"; String decodedProp = TypeEncoder.decode(encodedProp, processingEnv, pkg, null); crc.update(decodedProp.getBytes(UTF_8)); 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 new file mode 100644 index 00000000..c07656f1 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/processor/Nullables.java @@ -0,0 +1,192 @@ +/* + * Copyright 2021 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 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; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; +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.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.IntersectionType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; +import javax.lang.model.util.SimpleTypeVisitor8; + +class Nullables { + /** + * If set to a non-empty string, defines which {@code @Nullable} type annotation should be used by + * default. If set to an empty string, does not insert {@code @Nullable} unless it is referenced + * in the {@code @AutoValue} methods. If unset, defaults to {@value #DEFAULT_NULLABLE}. + */ + static final String NULLABLE_OPTION = "com.google.auto.value.NullableTypeAnnotation"; + + // 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; + + Nullables(ProcessingEnvironment processingEnv) { + // -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(); + } + + private static AnnotationMirror annotationMirrorOf(DeclaredType annotationType) { + return new AnnotationMirror() { + @Override + public DeclaredType getAnnotationType() { + return annotationType; + } + + @Override + public ImmutableMap<? extends ExecutableElement, ? extends AnnotationValue> + getElementValues() { + return ImmutableMap.of(); + } + }; + } + + /** + * 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); + } + + private static Optional<AnnotationMirror> nullableIn( + List<? extends AnnotationMirror> annotations) { + return annotations.stream() + .filter(a -> a.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable")) + .map(a -> (AnnotationMirror) a) // get rid of the pesky wildcard + .findFirst(); + } + + private static class NullableFinder extends SimpleTypeVisitor8<Optional<AnnotationMirror>, Void> { + private final TypeMirrorSet visiting = new TypeMirrorSet(); + + NullableFinder() { + super(Optional.empty()); + } + + // Primitives can't be @Nullable so we don't check that. + + @Override + public Optional<AnnotationMirror> visitDeclared(DeclaredType t, Void unused) { + if (!visiting.add(t)) { + return Optional.empty(); + } + return nullableIn(t.getAnnotationMirrors()) + .map(Optional::of) + .orElseGet(() -> visitAll(t.getTypeArguments())); + } + + @Override + public Optional<AnnotationMirror> visitTypeVariable(TypeVariable t, Void unused) { + if (!visiting.add(t)) { + return Optional.empty(); + } + return nullableIn(t.getAnnotationMirrors()) + .map(Optional::of) + .orElseGet(() -> visitAll(ImmutableList.of(t.getUpperBound(), t.getLowerBound()))); + } + + @Override + public Optional<AnnotationMirror> visitArray(ArrayType t, Void unused) { + return nullableIn(t.getAnnotationMirrors()) + .map(Optional::of) + .orElseGet(() -> visit(t.getComponentType())); + } + + @Override + public Optional<AnnotationMirror> visitWildcard(WildcardType t, Void unused) { + return nullableIn(t.getAnnotationMirrors()) + .map(Optional::of) + .orElseGet( + () -> + visitAll( + Stream.of(t.getExtendsBound(), t.getSuperBound()) + .filter(Objects::nonNull) + .collect(toList()))); + } + + @Override + public Optional<AnnotationMirror> visitIntersection(IntersectionType t, Void unused) { + return nullableIn(t.getAnnotationMirrors()) + .map(Optional::of) + .orElseGet(() -> visitAll(t.getBounds())); + } + + private Optional<AnnotationMirror> visitAll(List<? extends TypeMirror> types) { + return types.stream() + .map(this::visit) + .filter(Optional::isPresent) + .findFirst() + .orElse(Optional.empty()); + } + } +} 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 2565cddc..5d0168e4 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 @@ -15,17 +15,19 @@ */ package com.google.auto.value.processor; +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toMap; + import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; -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; +import java.util.Objects; import java.util.Optional; -import javax.lang.model.element.AnnotationMirror; +import java.util.function.Function; +import java.util.function.Predicate; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; @@ -51,25 +53,25 @@ class PropertyBuilderClassifier { private final ErrorReporter errorReporter; private final Types typeUtils; private final Elements elementUtils; - private final BuilderMethodClassifier builderMethodClassifier; - private final ImmutableBiMap<ExecutableElement, String> getterToPropertyName; - private final ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType; + private final BuilderMethodClassifier<?> builderMethodClassifier; + private final Predicate<String> propertyIsNullable; + private final ImmutableMap<String, TypeMirror> propertyTypes; private final EclipseHack eclipseHack; PropertyBuilderClassifier( ErrorReporter errorReporter, Types typeUtils, Elements elementUtils, - BuilderMethodClassifier builderMethodClassifier, - ImmutableBiMap<ExecutableElement, String> getterToPropertyName, - ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType, + BuilderMethodClassifier<?> builderMethodClassifier, + Predicate<String> propertyIsNullable, + ImmutableMap<String, TypeMirror> propertyTypes, EclipseHack eclipseHack) { this.errorReporter = errorReporter; this.typeUtils = typeUtils; this.elementUtils = elementUtils; this.builderMethodClassifier = builderMethodClassifier; - this.getterToPropertyName = getterToPropertyName; - this.getterToPropertyType = getterToPropertyType; + this.propertyIsNullable = propertyIsNullable; + this.propertyTypes = propertyTypes; this.eclipseHack = eclipseHack; } @@ -115,6 +117,14 @@ class PropertyBuilderClassifier { return propertyBuilderMethod; } + /** The property builder method parameters, for example {@code Comparator<T> comparator} */ + public String getPropertyBuilderMethodParameters() { + return propertyBuilderMethod.getParameters().stream() + .map( + parameter -> TypeEncoder.encode(parameter.asType()) + " " + parameter.getSimpleName()) + .collect(joining(", ")); + } + public String getAccess() { return SimpleMethod.access(propertyBuilderMethod); } @@ -206,8 +216,7 @@ class PropertyBuilderClassifier { TypeElement barBuilderTypeElement = MoreTypes.asTypeElement(barBuilderTypeMirror); Map<String, ExecutableElement> barBuilderNoArgMethods = noArgMethodsOf(barBuilderTypeElement); - ExecutableElement barGetter = getterToPropertyName.inverse().get(property); - TypeMirror barTypeMirror = getterToPropertyType.get(barGetter); + TypeMirror barTypeMirror = propertyTypes.get(property); if (barTypeMirror.getKind() != TypeKind.DECLARED) { errorReporter.reportError( method, @@ -216,10 +225,10 @@ class PropertyBuilderClassifier { property); return Optional.empty(); } - if (isNullable(barGetter)) { + if (propertyIsNullable.test(property)) { errorReporter.reportError( - barGetter, - "[AutoValueNullBuilder] Property %s has a property builder so it cannot be @Nullable", + method, + "[AutoValueNullBuilder] Property %s is @Nullable so it cannot have a property builder", property); } TypeElement barTypeElement = MoreTypes.asTypeElement(barTypeMirror); @@ -251,8 +260,13 @@ class PropertyBuilderClassifier { return Optional.empty(); } - Optional<ExecutableElement> maybeBuilderMaker = - builderMaker(barNoArgMethods, barBuilderTypeElement); + Optional<ExecutableElement> maybeBuilderMaker; + if (method.getParameters().isEmpty()) { + maybeBuilderMaker = noArgBuilderMaker(barNoArgMethods, barBuilderTypeElement); + } else { + Map<String, ExecutableElement> barOneArgMethods = oneArgumentMethodsOf(barTypeElement); + maybeBuilderMaker = oneArgBuilderMaker(barOneArgMethods, barBuilderTypeElement); + } if (!maybeBuilderMaker.isPresent()) { errorReporter.reportError( method, @@ -268,10 +282,14 @@ class PropertyBuilderClassifier { String barBuilderType = TypeEncoder.encodeWithAnnotations(barBuilderTypeMirror); String rawBarType = TypeEncoder.encodeRaw(barTypeMirror); + String arguments = + method.getParameters().isEmpty() + ? "()" + : "(" + method.getParameters().get(0).getSimpleName() + ")"; String initializer = (builderMaker.getKind() == ElementKind.CONSTRUCTOR) - ? "new " + barBuilderType + "()" - : rawBarType + "." + builderMaker.getSimpleName() + "()"; + ? "new " + barBuilderType + arguments + : rawBarType + "." + builderMaker.getSimpleName() + arguments; String builtToBuilder = null; String copyAll = null; ExecutableElement toBuilder = barNoArgMethods.get("toBuilder"); @@ -324,41 +342,75 @@ class PropertyBuilderClassifier { private static final ImmutableSet<String> BUILDER_METHOD_NAMES = ImmutableSet.of("naturalOrder", "builder", "newBuilder"); - // (2) `BarBuilder must have a public no-arg constructor, or `Bar` must have a visible static - // method `naturalOrder(), `builder()`, or `newBuilder()` that returns `BarBuilder`. - private Optional<ExecutableElement> builderMaker( + // (2) `BarBuilder` must have a public no-arg constructor, or `Bar` must have a visible static + // method `naturalOrder(), `builder()`, or `newBuilder()` that returns `BarBuilder`; or, + // if we have a foosBuilder(T) method, then `BarBuilder` must have a public constructor with + // a single parameter assignable from T, or a visible static `builder(T)` method. + private Optional<ExecutableElement> noArgBuilderMaker( Map<String, ExecutableElement> barNoArgMethods, TypeElement barBuilderTypeElement) { - for (String builderMethodName : BUILDER_METHOD_NAMES) { - ExecutableElement method = barNoArgMethods.get(builderMethodName); - if (method != null - && method.getModifiers().contains(Modifier.STATIC) - && typeUtils.isSameType( - typeUtils.erasure(method.getReturnType()), - typeUtils.erasure(barBuilderTypeElement.asType()))) { - // TODO(emcmanus): check visibility. We don't want to require public for @AutoValue - // builders. By not checking visibility we risk accepting something as a builder maker - // and then failing when the generated code tries to call Bar.builder(). But the risk - // seems small. - return Optional.of(method); - } + return builderMaker(BUILDER_METHOD_NAMES, barNoArgMethods, barBuilderTypeElement, 0); + } + + private static final ImmutableSet<String> ONE_ARGUMENT_BUILDER_METHOD_NAMES = + ImmutableSet.of("builder"); + + private Optional<ExecutableElement> oneArgBuilderMaker( + Map<String, ExecutableElement> barOneArgMethods, TypeElement barBuilderTypeElement) { + + return builderMaker( + ONE_ARGUMENT_BUILDER_METHOD_NAMES, barOneArgMethods, barBuilderTypeElement, 1); + } + + private Optional<ExecutableElement> builderMaker( + ImmutableSet<String> methodNamesToCheck, + Map<String, ExecutableElement> methods, + TypeElement barBuilderTypeElement, + int argumentCount) { + Optional<ExecutableElement> maybeMethod = + methodNamesToCheck.stream() + .map(methods::get) + .filter(Objects::nonNull) + .filter(method -> method.getModifiers().contains(Modifier.STATIC)) + .filter( + method -> + typeUtils.isSameType( + typeUtils.erasure(method.getReturnType()), + typeUtils.erasure(barBuilderTypeElement.asType()))) + .findFirst(); + + if (maybeMethod.isPresent()) { + // TODO(emcmanus): check visibility. We don't want to require public for @AutoValue + // builders. By not checking visibility we risk accepting something as a builder maker + // and then failing when the generated code tries to call Bar.builder(). But the risk + // seems small. + return maybeMethod; } - return ElementFilter.constructorsIn(barBuilderTypeElement.getEnclosedElements()) - .stream() - .filter(c -> c.getParameters().isEmpty()) + + return ElementFilter.constructorsIn(barBuilderTypeElement.getEnclosedElements()).stream() + .filter(c -> c.getParameters().size() == argumentCount) .filter(c -> c.getModifiers().contains(Modifier.PUBLIC)) .findFirst(); } private Map<String, ExecutableElement> noArgMethodsOf(TypeElement type) { - // Can't easily use ImmutableMap here because getAllMembers could return more than one method - // with the same name. - Map<String, ExecutableElement> methods = new LinkedHashMap<>(); - for (ExecutableElement method : ElementFilter.methodsIn(elementUtils.getAllMembers(type))) { - if (method.getParameters().isEmpty() && !isStaticInterfaceMethodNotIn(method, type)) { - methods.put(method.getSimpleName().toString(), method); - } - } - return methods; + return methodsOf(type, 0); + } + + private ImmutableMap<String, ExecutableElement> oneArgumentMethodsOf(TypeElement type) { + return methodsOf(type, 1); + } + + private ImmutableMap<String, ExecutableElement> methodsOf(TypeElement type, int argumentCount) { + return ElementFilter.methodsIn(elementUtils.getAllMembers(type)).stream() + .filter(method -> method.getParameters().size() == argumentCount) + .filter(method -> !isStaticInterfaceMethodNotIn(method, type)) + .collect( + collectingAndThen( + toMap( + method -> method.getSimpleName().toString(), + Function.identity(), + (method1, method2) -> method1), + ImmutableMap::copyOf)); } // Work around an Eclipse compiler bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=547185 @@ -399,18 +451,4 @@ class PropertyBuilderClassifier { }) .findFirst(); } - - private static boolean isNullable(ExecutableElement getter) { - List<List<? extends AnnotationMirror>> annotationLists = - ImmutableList.of( - getter.getAnnotationMirrors(), getter.getReturnType().getAnnotationMirrors()); - for (List<? extends AnnotationMirror> annotations : annotationLists) { - for (AnnotationMirror annotation : annotations) { - if (annotation.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable")) { - return true; - } - } - } - return false; - } } diff --git a/value/src/main/java/com/google/auto/value/processor/Reformatter.java b/value/src/main/java/com/google/auto/value/processor/Reformatter.java index 69265174..89b6d0d1 100644 --- a/value/src/main/java/com/google/auto/value/processor/Reformatter.java +++ b/value/src/main/java/com/google/auto/value/processor/Reformatter.java @@ -15,6 +15,8 @@ */ package com.google.auto.value.processor; +import com.google.common.base.CharMatcher; + /** * Postprocessor that runs over the output of the template engine in order to make it look nicer. * Mostly, this involves removing surplus horizontal and vertical space. @@ -22,6 +24,14 @@ package com.google.auto.value.processor; * @author emcmanus@google.com (Éamonn McManus) */ class Reformatter { + /** + * Characters that might start a continuation line. Since Google Style requires splitting before + * operators, we expect that a continuation line will begin with one of these (or be inside + * parentheses). We've omitted {@code /} from this list for now since none of our templates splits + * before one, and otherwise we'd have to handle {@code //} and {@code /*}. + */ + private static final CharMatcher OPERATORS = CharMatcher.anyOf("+-*%&|^<>=?:.").precomputed(); + static String fixup(String s) { StringBuilder out = new StringBuilder(); JavaScanner scanner = new JavaScanner(s); @@ -49,27 +59,43 @@ class Reformatter { case ' ': // This token is a string of consecutive spaces that is not at the start of a line. // Consecutive spaces at the start of a line are attached to the previous newline, and - // we don't expect the first line to start with spaces. So we are going to compress this + // we delete spaces at the start of the first line. So we are going to compress this // into just one space, and we are going to delete it entirely if it follows '(' or // precedes a newline or one of the punctuation characters here. - if (s.charAt(previous) != '(' && "\n.,;)".indexOf(s.charAt(end)) < 0) { + if (start > 0 && s.charAt(previous) != '(' && "\n.,;)".indexOf(s.charAt(end)) < 0) { out.append(' '); } continue; case '\n': // This token is a newline plus any following spaces (the indentation of the next line). - // If it is followed by something other than a newline then we will output it. Otherwise, - // it is part of a sequence of newlines but it is not the last one. If this is a context - // where we delete blank lines, or if this is not the first new line in the sequence, or - // if we are at the start of the file, we will delete this one. Otherwise we will output a - // single newline with no following indentation. Contexts where we delete blank lines are - // inside parentheses or inside more than one set of braces. + // If it is followed by something other than a newline then we will output the + // newline, and replace the following spaces by our computed indentation. Otherwise, the + // token is part of a sequence of newlines but it is not the last one. If this is a + // context where we delete blank lines, or if this is not the first new line in the + // sequence, or if we are at the start of the file, we will delete this one. Otherwise we + // will output a single newline with no following indentation. Contexts where we delete + // blank lines are inside parentheses or inside more than one set of braces. if (end < len && s.charAt(end) != '\n') { - if (out.length() == 0) { - // Omit newlines at the very start of the file. - start++; + // Omit newlines at the very start of the file. Also delete newline+indent between + // ( and ), since that shows up in some places where we output one parameter per line, + // when there are no parameters. + char prev = s.charAt(previous); + char next = s.charAt(end); + if (out.length() > 0 && (prev != '(' || next != ')')) { + out.append('\n'); + // Replace any space after the newline with our computed indentation. The algorithm + // here is simplistic but works OK for our current templates. + int indent = braces * 2; + if (parens > 0 || OPERATORS.matches(next)) { + indent += 4; + } else if (next == '}') { + indent -= 2; + } + for (int i = 0; i < indent; i++) { + out.append(' '); + } } - break; // Output the newline and its following indentation. + continue; } if (parens == 0 && braces < 2 && s.charAt(previous) != '\n' && out.length() > 0) { out.append('\n'); @@ -82,4 +108,6 @@ class Reformatter { } return out.toString(); } + + private Reformatter() {} } diff --git a/value/src/main/java/com/google/auto/value/processor/SimpleMethod.java b/value/src/main/java/com/google/auto/value/processor/SimpleMethod.java index 03510231..c144bc04 100644 --- a/value/src/main/java/com/google/auto/value/processor/SimpleMethod.java +++ b/value/src/main/java/com/google/auto/value/processor/SimpleMethod.java @@ -15,6 +15,8 @@ */ package com.google.auto.value.processor; +import static java.util.stream.Collectors.joining; + import java.util.Set; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; @@ -31,10 +33,12 @@ import javax.lang.model.element.Modifier; public final class SimpleMethod { private final String access; private final String name; + private final String throwsString; SimpleMethod(ExecutableElement method) { this.access = access(method); this.name = method.getSimpleName().toString(); + this.throwsString = throwsString(method); } public String getAccess() { @@ -45,6 +49,10 @@ public final class SimpleMethod { return name; } + public String getThrows() { + return throwsString; + } + /** * Returns an appropriate string to be used in code for the access specification of the given * method. This will be {@code public} or {@code protected} followed by a space, or the empty @@ -60,4 +68,12 @@ public final class SimpleMethod { return ""; } } + + private static String throwsString(ExecutableElement method) { + if (method.getThrownTypes().isEmpty()) { + return ""; + } + return "throws " + + method.getThrownTypes().stream().map(TypeEncoder::encode).collect(joining(", ")); + } } diff --git a/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java b/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java index 55f9ae4e..23f6674c 100644 --- a/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java +++ b/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java @@ -15,20 +15,23 @@ */ package com.google.auto.value.processor; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.stream.Collectors.toList; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Streams; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; -import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.ServiceConfigurationError; +import java.util.regex.Pattern; /** * A replacement for {@link java.util.ServiceLoader} that avoids certain long-standing bugs. This @@ -43,6 +46,11 @@ public final class SimpleServiceLoader { private SimpleServiceLoader() {} public static <T> ImmutableList<T> load(Class<? extends T> service, ClassLoader loader) { + return load(service, loader, Optional.empty()); + } + + public static <T> ImmutableList<T> load( + Class<? extends T> service, ClassLoader loader, Optional<Pattern> allowedMissingClasses) { String resourceName = "META-INF/services/" + service.getName(); List<URL> resourceUrls; try { @@ -50,49 +58,64 @@ public final class SimpleServiceLoader { } catch (IOException e) { throw new ServiceConfigurationError("Could not look up " + resourceName, e); } - ImmutableList.Builder<T> providers = ImmutableList.builder(); + ImmutableSet.Builder<Class<? extends T>> providerClasses = ImmutableSet.builder(); for (URL resourceUrl : resourceUrls) { try { - providers.addAll(providersFromUrl(resourceUrl, service, loader)); + providerClasses.addAll( + providerClassesFromUrl(resourceUrl, service, loader, allowedMissingClasses)); } catch (IOException e) { throw new ServiceConfigurationError("Could not read " + resourceUrl, e); } } + ImmutableList.Builder<T> providers = ImmutableList.builder(); + for (Class<? extends T> providerClass : providerClasses.build()) { + try { + T provider = providerClass.getConstructor().newInstance(); + providers.add(provider); + } catch (ReflectiveOperationException e) { + throw new ServiceConfigurationError("Could not construct " + providerClass.getName(), e); + } + } return providers.build(); } - private static <T> ImmutableList<T> providersFromUrl( - URL resourceUrl, Class<T> service, ClassLoader loader) throws IOException { - ImmutableList.Builder<T> providers = ImmutableList.builder(); + private static <T> ImmutableSet<Class<? extends T>> providerClassesFromUrl( + URL resourceUrl, + Class<? extends T> service, + ClassLoader loader, + Optional<Pattern> allowedMissingClasses) + throws IOException { + ImmutableSet.Builder<Class<? extends T>> providerClasses = ImmutableSet.builder(); URLConnection urlConnection = resourceUrl.openConnection(); urlConnection.setUseCaches(false); + List<String> lines; try (InputStream in = urlConnection.getInputStream(); - BufferedReader reader = - new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { - for (String line : reader.lines().collect(toList())) { - Optional<String> maybeClassName = parseClassName(line); - if (maybeClassName.isPresent()) { - String className = maybeClassName.get(); - Class<?> c; - try { - c = Class.forName(className, false, loader); - } catch (ClassNotFoundException e) { - throw new ServiceConfigurationError("Could not load " + className, e); - } - if (!service.isAssignableFrom(c)) { - throw new ServiceConfigurationError( - "Class " + className + " is not assignable to " + service.getName()); - } - try { - Object provider = c.getConstructor().newInstance(); - providers.add(service.cast(provider)); - } catch (ReflectiveOperationException e) { - throw new ServiceConfigurationError("Could not construct " + className, e); - } + BufferedReader reader = new BufferedReader(new InputStreamReader(in, UTF_8))) { + lines = reader.lines().collect(toList()); + } + List<String> classNames = + lines.stream() + .map(SimpleServiceLoader::parseClassName) + .flatMap(Streams::stream) + .collect(toList()); + for (String className : classNames) { + Class<?> c; + try { + c = Class.forName(className, false, loader); + } catch (ClassNotFoundException e) { + if (allowedMissingClasses.isPresent() + && allowedMissingClasses.get().matcher(className).matches()) { + continue; } + throw new ServiceConfigurationError("Could not load " + className, e); + } + if (!service.isAssignableFrom(c)) { + throw new ServiceConfigurationError( + "Class " + className + " is not assignable to " + service.getName()); } - return providers.build(); + providerClasses.add(c.asSubclass(service)); } + return providerClasses.build(); } private static Optional<String> parseClassName(String line) { diff --git a/value/src/main/java/com/google/auto/value/processor/TemplateVars.java b/value/src/main/java/com/google/auto/value/processor/TemplateVars.java index 235f0ada..d9e3337b 100644 --- a/value/src/main/java/com/google/auto/value/processor/TemplateVars.java +++ b/value/src/main/java/com/google/auto/value/processor/TemplateVars.java @@ -99,7 +99,7 @@ abstract class TemplateVars { return parsedTemplate().evaluate(vars); } - private Map<String, Object> toVars() { + private ImmutableMap<String, Object> toVars() { Map<String, Object> vars = new TreeMap<>(); for (Field field : fields) { Object value = fieldValue(field, this); @@ -114,6 +114,11 @@ abstract class TemplateVars { return ImmutableMap.copyOf(vars); } + @Override + public String toString() { + return getClass().getSimpleName() + toVars(); + } + static Template parsedTemplateForResource(String resourceName) { try { return Template.parseFrom(resourceName, TemplateVars::readerFromResource); 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 d1bbbab5..81968a50 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 @@ -22,10 +22,12 @@ import static java.util.stream.Collectors.toList; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.auto.value.processor.MissingTypes.MissingTypeException; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.util.List; import java.util.OptionalInt; import java.util.Set; +import java.util.function.Function; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.TypeElement; @@ -97,20 +99,36 @@ final class TypeEncoder { * covers the details of annotation encoding. */ static String encodeWithAnnotations(TypeMirror type) { - return encodeWithAnnotations(type, ImmutableSet.of()); + return encodeWithAnnotations(type, ImmutableList.of(), 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. */ - static String encodeWithAnnotations(TypeMirror type, Set<TypeMirror> excludedAnnotationTypes) { + static String encodeWithAnnotations( + TypeMirror type, + ImmutableList<AnnotationMirror> extraAnnotations, + Set<TypeMirror> excludedAnnotationTypes) { StringBuilder sb = new StringBuilder(); - return new AnnotatedEncodingTypeVisitor(excludedAnnotationTypes).visit2(type, sb).toString(); + // A function that is equivalent to t.getAnnotationMirrors() except when the t in question is + // our starting type. In that case we also add extraAnnotations to the result. + Function<TypeMirror, List<? extends AnnotationMirror>> getTypeAnnotations = + t -> + (t == type) + ? ImmutableList.<AnnotationMirror>builder() + .addAll(t.getAnnotationMirrors()) + .addAll(extraAnnotations) + .build() + : t.getAnnotationMirrors(); + return new AnnotatedEncodingTypeVisitor(excludedAnnotationTypes, getTypeAnnotations) + .visit2(type, sb) + .toString(); } /** @@ -145,9 +163,11 @@ final class TypeEncoder { } /** - * Returns the formal type parameters of the given type. If we have {@code @AutoValue abstract - * class Foo<T extends SomeClass>} then this method will return an encoding of {@code <T extends - * SomeClass>} for {@code Foo}. Likewise it will return an encoding of the angle-bracket part of: + * Returns a string representing the given type parameters as they would appear in a class + * declaration. For example, if we have {@code @AutoValue abstract + * class Foo<T extends SomeClass>} then if we call {@link TypeElement#getTypeParameters()} on + * the representation of {@code Foo}, this method will return an encoding of {@code <T extends + * SomeClass>}. Likewise it will return an encoding of the angle-bracket part of: * <br> * {@code Foo<SomeClass>}<br> * {@code Foo<T extends Number>}<br> @@ -161,8 +181,7 @@ final class TypeEncoder { * {@code <E extends `java.lang.Enum`<E>>}<br> * {@code <K, V extends `java.lang.Comparable`<? extends K>>}. */ - static String formalTypeParametersString(TypeElement type) { - List<? extends TypeParameterElement> typeParameters = type.getTypeParameters(); + static String typeParametersString(List<? extends TypeParameterElement> typeParameters) { if (typeParameters.isEmpty()) { return ""; } else { @@ -308,9 +327,13 @@ final class TypeEncoder { */ private static class AnnotatedEncodingTypeVisitor extends EncodingTypeVisitor { private final Set<TypeMirror> excludedAnnotationTypes; + private final Function<TypeMirror, List<? extends AnnotationMirror>> getTypeAnnotations; - AnnotatedEncodingTypeVisitor(Set<TypeMirror> excludedAnnotationTypes) { + AnnotatedEncodingTypeVisitor( + Set<TypeMirror> excludedAnnotationTypes, + Function<TypeMirror, List<? extends AnnotationMirror>> getTypeAnnotations) { this.excludedAnnotationTypes = excludedAnnotationTypes; + this.getTypeAnnotations = getTypeAnnotations; } private void appendAnnotationsWithExclusions( @@ -330,7 +353,7 @@ final class TypeEncoder { @Override public StringBuilder visitPrimitive(PrimitiveType type, StringBuilder sb) { - appendAnnotationsWithExclusions(type.getAnnotationMirrors(), sb); + appendAnnotationsWithExclusions(getTypeAnnotations.apply(type), sb); // We can't just append type.toString(), because that will also have the annotation, but // without encoding. return sb.append(type.getKind().toString().toLowerCase()); @@ -338,7 +361,7 @@ final class TypeEncoder { @Override public StringBuilder visitTypeVariable(TypeVariable type, StringBuilder sb) { - appendAnnotationsWithExclusions(type.getAnnotationMirrors(), sb); + appendAnnotationsWithExclusions(getTypeAnnotations.apply(type), sb); return sb.append(type.asElement().getSimpleName()); } @@ -350,7 +373,7 @@ final class TypeEncoder { @Override public StringBuilder visitArray(ArrayType type, StringBuilder sb) { visit2(type.getComponentType(), sb); - List<? extends AnnotationMirror> annotationMirrors = type.getAnnotationMirrors(); + List<? extends AnnotationMirror> annotationMirrors = getTypeAnnotations.apply(type); if (!annotationMirrors.isEmpty()) { sb.append(" "); appendAnnotationsWithExclusions(annotationMirrors, sb); @@ -360,7 +383,7 @@ final class TypeEncoder { @Override public StringBuilder visitDeclared(DeclaredType type, StringBuilder sb) { - List<? extends AnnotationMirror> annotationMirrors = type.getAnnotationMirrors(); + List<? extends AnnotationMirror> annotationMirrors = getTypeAnnotations.apply(type); if (annotationMirrors.isEmpty()) { super.visitDeclared(type, sb); } else { diff --git a/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java b/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java index 290e59e0..e7bab774 100644 --- a/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java +++ b/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java @@ -140,8 +140,7 @@ final class TypeSimplifier { if (typeParameters.isEmpty()) { return ""; } else { - return typeParameters - .stream() + return typeParameters.stream() .map(e -> e.getSimpleName().toString()) .collect(joining(", ", "<", ">")); } @@ -264,8 +263,7 @@ final class TypeSimplifier { * {@code Map.Entry} everywhere rather than {@code Entry}. */ private static Set<TypeMirror> topLevelTypes(Types typeUtil, Set<TypeMirror> types) { - return types - .stream() + return types.stream() .map(typeMirror -> MoreElements.asType(typeUtil.asElement(typeMirror))) .map(typeElement -> topLevelType(typeElement).asType()) .collect(toCollection(TypeMirrorSet::new)); diff --git a/value/src/main/java/com/google/auto/value/processor/TypeVariables.java b/value/src/main/java/com/google/auto/value/processor/TypeVariables.java index d664ff46..ae27f91b 100644 --- a/value/src/main/java/com/google/auto/value/processor/TypeVariables.java +++ b/value/src/main/java/com/google/auto/value/processor/TypeVariables.java @@ -15,6 +15,8 @@ */ package com.google.auto.value.processor; +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; @@ -24,6 +26,7 @@ import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -38,9 +41,7 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleTypeVisitor8; import javax.lang.model.util.Types; -/** - * Methods for handling type variables. - */ +/** Methods for handling type variables. */ final class TypeVariables { private TypeVariables() {} @@ -60,13 +61,13 @@ final class TypeVariables { * } * </pre> * - * We want to be able to check that the parameter type of {@code setFoo} is the same as the - * return type of {@code getFoo}. But in fact it isn't, because the {@code T} of {@code Foo<T>} - * is not the same as the {@code T} of {@code Foo.Builder<T>}. So we create a parallel - * {@code Foo<T>} where the {@code T} <i>is</i> the one from {@code Foo.Builder<T>}. That way the - * types do correspond. This method then returns the return types of the given methods as they - * appear in that parallel class, meaning the type given for {@code getFoo()} is the {@code T} of - * {@code Foo.Builder<T>}. + * We want to be able to check that the parameter type of {@code setFoo} is the same as the return + * type of {@code getFoo}. But in fact it isn't, because the {@code T} of {@code Foo<T>} is not + * the same as the {@code T} of {@code Foo.Builder<T>}. So we create a parallel {@code Foo<T>} + * where the {@code T} <i>is</i> the one from {@code Foo.Builder<T>}. That way the types do + * correspond. This method then returns the return types of the given methods as they appear in + * that parallel class, meaning the type given for {@code getFoo()} is the {@code T} of {@code + * Foo.Builder<T>}. * * <p>We do the rewrite this way around (applying the type parameter from {@code Foo.Builder} to * {@code Foo}) because if we hit one of the historical Eclipse bugs with {@link Types#asMemberOf} @@ -101,14 +102,12 @@ final class TypeVariables { } DeclaredType parallelSource = typeUtils.getDeclaredType(sourceType, targetTypeParameterMirrors); return methods.stream() - .collect( - ImmutableMap.toImmutableMap( - m -> m, m -> eclipseHack.methodReturnType(m, parallelSource))); + .collect(toImmutableMap(m -> m, m -> eclipseHack.methodReturnType(m, parallelSource))); } /** - * Tests whether a given parameter can be given to a static method like - * {@code ImmutableMap.copyOf} to produce a value that can be assigned to the given target type. + * Tests whether a given parameter can be given to a static method like {@code + * ImmutableMap.copyOf} to produce a value that can be assigned to the given target type. * * <p>For example, suppose we have this method in {@code ImmutableMap}:<br> * {@code static <K, V> ImmutableMap<K, V> copyOf(Map<? extends K, ? extends V>)}<br> @@ -122,14 +121,14 @@ final class TypeVariables { * We will infer {@code K=String}, {@code V=Number} based on the target type, and then rewrite the * formal parameter type from<br> * {@code Map<? extends K, ? extends V>} to<br> - * {@code Map<? extends String, ? extends Number>}. Then we can check whether - * {@code actualParameter} is assignable to that. + * {@code Map<? extends String, ? extends Number>}. Then we can check whether {@code + * actualParameter} is assignable to that. * * <p>The logic makes some simplifying assumptions, which are met for the {@code copyOf} and * {@code of} methods that we use this for. The method must be static, it must have exactly one * parameter, and it must have type parameters without bounds that are the same as the type - * parameters of its return type. We can see that these assumptions are met for the - * {@code ImmutableMap.copyOf} example above. + * parameters of its return type. We can see that these assumptions are met for the {@code + * ImmutableMap.copyOf} example above. */ static boolean canAssignStaticMethodResult( ExecutableElement method, @@ -152,8 +151,10 @@ final class TypeVariables { TypeVariable v = MoreTypes.asTypeVariable(typeParameters.get(i).asType()); typeVariables.put(MoreTypes.equivalence().wrap(v), targetTypeArguments.get(i)); } + Function<TypeVariable, TypeMirror> substitute = + v -> typeVariables.get(MoreTypes.equivalence().wrap(v)); TypeMirror formalParameterType = method.getParameters().get(0).asType(); - SubstitutionVisitor substitutionVisitor = new SubstitutionVisitor(typeVariables, typeUtils); + SubstitutionVisitor substitutionVisitor = new SubstitutionVisitor(substitute, typeUtils); TypeMirror substitutedParameterType = substitutionVisitor.visit(formalParameterType, null); if (substitutedParameterType.getKind().equals(TypeKind.WILDCARD)) { // If the target type is Optional<? extends Foo> then <T> T Optional.of(T) will give us @@ -167,31 +168,38 @@ final class TypeVariables { return typeUtils.isAssignable(actualParameterType, substitutedParameterType); } + static TypeMirror substituteTypeVariables( + TypeMirror input, Function<TypeVariable, TypeMirror> substitute, Types typeUtils) { + SubstitutionVisitor substitutionVisitor = new SubstitutionVisitor(substitute, typeUtils); + return substitutionVisitor.visit(input, null); + } + /** * Rewrites types such that references to type variables in the given map are replaced by the * values of those variables. */ private static class SubstitutionVisitor extends SimpleTypeVisitor8<TypeMirror, Void> { - private final Map<Equivalence.Wrapper<TypeVariable>, TypeMirror> typeVariables; + private final Function<TypeVariable, TypeMirror> substitute; private final Types typeUtils; - SubstitutionVisitor( - Map<Equivalence.Wrapper<TypeVariable>, TypeMirror> typeVariables, - Types typeUtils) { - this.typeVariables = ImmutableMap.copyOf(typeVariables); + SubstitutionVisitor(Function<TypeVariable, TypeMirror> substitute, Types typeUtils) { + this.substitute = substitute; this.typeUtils = typeUtils; } - @Override protected TypeMirror defaultAction(TypeMirror t, Void p) { + @Override + protected TypeMirror defaultAction(TypeMirror t, Void p) { return t; } - @Override public TypeMirror visitTypeVariable(TypeVariable t, Void p) { - TypeMirror substituted = typeVariables.get(MoreTypes.equivalence().wrap(t)); + @Override + public TypeMirror visitTypeVariable(TypeVariable t, Void p) { + TypeMirror substituted = substitute.apply(t); return (substituted == null) ? t : substituted; } - @Override public TypeMirror visitDeclared(DeclaredType t, Void p) { + @Override + public TypeMirror visitDeclared(DeclaredType t, Void p) { List<? extends TypeMirror> typeArguments = t.getTypeArguments(); TypeMirror[] substitutedTypeArguments = new TypeMirror[typeArguments.size()]; for (int i = 0; i < typeArguments.size(); i++) { @@ -201,7 +209,8 @@ final class TypeVariables { MoreElements.asType(t.asElement()), substitutedTypeArguments); } - @Override public TypeMirror visitWildcard(WildcardType t, Void p) { + @Override + public TypeMirror visitWildcard(WildcardType t, Void p) { TypeMirror ext = visitOrNull(t.getExtendsBound()); if (ext != null && ext.getKind().equals(TypeKind.WILDCARD)) { // An example of where this happens is if we have this method in ImmutableSet: @@ -216,7 +225,8 @@ final class TypeVariables { return typeUtils.getWildcardType(ext, visitOrNull(t.getSuperBound())); } - @Override public TypeMirror visitArray(ArrayType t, Void p) { + @Override + public TypeMirror visitArray(ArrayType t, Void p) { TypeMirror comp = visit(t.getComponentType()); if (comp.getKind().equals(TypeKind.WILDCARD)) { // An example of where this happens is if we have this method in ImmutableSet: diff --git a/value/src/main/java/com/google/auto/value/processor/autoannotation.vm b/value/src/main/java/com/google/auto/value/processor/autoannotation.vm index a953edab..5cdad629 100644 --- a/value/src/main/java/com/google/auto/value/processor/autoannotation.vm +++ b/value/src/main/java/com/google/auto/value/processor/autoannotation.vm @@ -45,7 +45,8 @@ package $pkg; #else // Generated by com.google.auto.value.processor.AutoAnnotationProcessor #end -final class $className implements $annotationName { +final class $className implements $annotationName, `java.io.Serializable` { + private static final long serialVersionUID = ${serialVersionUID}L; ## Fields @@ -212,7 +213,7 @@ final class $className implements $annotationName { #end @`java.lang.Override` - public boolean equals(Object o) { + public boolean equals($equalsParameterType o) { if (o == this) { return true; } 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 new file mode 100644 index 00000000..01f61b9c --- /dev/null +++ b/value/src/main/java/com/google/auto/value/processor/autobuilder.vm @@ -0,0 +1,41 @@ +## Copyright 2014 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 AutoValue_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. +## +## 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 +#set($autoBuilder = true) +#parse("builder.vm") diff --git a/value/src/main/java/com/google/auto/value/processor/autooneof.vm b/value/src/main/java/com/google/auto/value/processor/autooneof.vm index 0249d9eb..328f6a59 100644 --- a/value/src/main/java/com/google/auto/value/processor/autooneof.vm +++ b/value/src/main/java/com/google/auto/value/processor/autooneof.vm @@ -94,19 +94,23 @@ final class $generatedClass { #end +#if (!$props.empty) // Parent class that each implementation will inherit from. private abstract static class Parent_$formalTypes extends $origClass$actualTypes { -#foreach ($p in $props) + $serialVersionUID + + #foreach ($p in $props) @`java.lang.Override` $p.access $p.type ${p.getter}() { throw new UnsupportedOperationException(${kindGetter}().toString()); } -#end + #end } +#end #foreach ($p in $props) @@ -120,6 +124,8 @@ final class $generatedClass { // Implementation when the contained property is "${p}". private static final class Impl_$p$formalTypes extends Parent_$actualTypes { + $serialVersionUID + #if ($p.type == "void") // There is only one instance of this class. diff --git a/value/src/main/java/com/google/auto/value/processor/autovalue.vm b/value/src/main/java/com/google/auto/value/processor/autovalue.vm index 6f50e934..86cfe493 100644 --- a/value/src/main/java/com/google/auto/value/processor/autovalue.vm +++ b/value/src/main/java/com/google/auto/value/processor/autovalue.vm @@ -177,9 +177,7 @@ ${modifiers}class $subclass$formalTypes extends $origClass$actualTypes { } #end -#if (!$serialVersionUID.empty) - private static final long serialVersionUID = $serialVersionUID; -#end + $serialVersionUID #if ($builderTypeName != "") @@ -194,241 +192,8 @@ ${modifiers}class $subclass$formalTypes extends $origClass$actualTypes { ## BUILDER CLASS - #foreach ($a in $builderAnnotations) - - $a## - #end - - static #if ($isFinal) final #end class Builder${builderFormalTypes} ## - #if ($builderIsInterface) implements #else extends #end - ${builderTypeName}${builderActualTypes} { - - #foreach ($p in $props) - - #if ($p.kind.primitive) - - private $types.boxedClass($p.typeMirror).simpleName $p; - - #else - - #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} ## - ${builderPropertyBuilders[$p.name].name}; - - #end - - private $p.type $p #if ($p.optional && !$p.nullable) = $p.optional.empty #end ; - - #end - #end - - Builder() { - } - - #if (!$toBuilderMethods.empty) - - private Builder(${origClass}${actualTypes} source) { - - #foreach ($p in $props) - - this.$p = source.${p.getter}(); - - #end - - } - - #end - - #foreach ($p in $props) - - ## The following is either null or an instance of PropertyBuilderClassifier.PropertyBuilder - #set ($propertyBuilder = $builderPropertyBuilders[$p.name]) - - ## Setter and/or property builder - - #foreach ($setter in $builderSetters[$p.name]) - - @`java.lang.Override` - ${setter.access}${builderTypeName}${builderActualTypes} ## - ${setter.name}(${setter.nullableAnnotation}$setter.parameterType $p) { - - ## Omit null check for primitive, or @Nullable, or if we are going to be calling a copy method - ## such as Optional.of, which will have its own null check if appropriate. - #if (!$setter.primitiveParameter && !$p.nullable && ${setter.copy($p)} == $p) - - #if ($identifiers) - - if ($p == null) { - throw new NullPointerException("Null $p.name"); - } - #else - ## Just throw NullPointerException with no message if it's null. - ## The Object cast has no effect on the code but silences an ErrorProne warning. - - ((`java.lang.Object`) ${p}).getClass(); - #end - - #end - - #if ($propertyBuilder) - - if (${propertyBuilder.name} != null) { - throw new IllegalStateException(#if ($identifiers)"Cannot set $p after calling ${p.name}Builder()"#end); - } - - #end - - this.$p = ${setter.copy($p)}; - return this; - } - - #end - - #if ($propertyBuilder) - - @`java.lang.Override` - ${propertyBuilder.access}$propertyBuilder.builderType ${p.name}Builder() { - 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. - ## 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) - - ${propertyBuilder.name} = ${propertyBuilder.initializer}; - - #else - - if ($p == null) { - ${propertyBuilder.name} = ${propertyBuilder.initializer}; - } else { - - #if (${propertyBuilder.builtToBuilder}) - - ${propertyBuilder.name} = ${p}.${propertyBuilder.builtToBuilder}(); - - #else - - ${propertyBuilder.name} = ${propertyBuilder.initializer}; - ${propertyBuilder.name}.${propertyBuilder.copyAll}($p); - - #end - - $p = null; - } - - #end - - } - return $propertyBuilder.name; - } - - #end - - ## Getter - - #if ($builderGetters[$p.name]) - - @`java.lang.Override` - ${p.nullableAnnotation}${builderGetters[$p.name].access}$builderGetters[$p.name].type ${p.getter}() { - #if ($builderGetters[$p.name].optional) - - if ($p == null) { - return $builderGetters[$p.name].optional.empty; - } else { - return ${builderGetters[$p.name].optional.rawType}.of($p); - } - - #else - #if ($builderRequiredProperties.contains($p)) - - if ($p == null) { - throw new IllegalStateException(#if ($identifiers)"Property \"$p.name\" has not been set"#end); - } - - #end - - #if ($propertyBuilder) - - if (${propertyBuilder.name} != null) { - return ${propertyBuilder.name}.build(); - } - if ($p == null) { - ${propertyBuilder.beforeInitDefault} - $p = ${propertyBuilder.initDefault}; - } - - #end - - return $p; - - #end - - } - - #end - #end - - @`java.lang.Override` - ${buildMethod.get().access}${origClass}${actualTypes} ${buildMethod.get().name}() { - - #foreach ($p in $props) - #set ($propertyBuilder = $builderPropertyBuilders[$p.name]) - #if ($propertyBuilder) - - if (${propertyBuilder.name} != null) { - this.$p = ${propertyBuilder.name}.build(); - } else if (this.$p == null) { - ${propertyBuilder.beforeInitDefault} - this.$p = ${propertyBuilder.initDefault}; - } - - #end - #end - - #if (!$builderRequiredProperties.empty) - #if ($identifiers) ## build a friendly message showing all missing properties - - `java.lang.String` missing = ""; - - #foreach ($p in $builderRequiredProperties) - - if (this.$p == null) { - missing += " $p.name"; - } - - #end - - if (!missing.isEmpty()) { - throw new IllegalStateException("Missing required properties:" + missing); - } - - #else ## just throw an exception if anything is missing - - if (#foreach ($p in $builderRequiredProperties)## - this.$p == null## - #if ($foreach.hasNext) || #end - #end) { - throw new IllegalStateException(); - } - #end - #end - - return new ${finalSubclass}${actualTypes}( - #foreach ($p in $props) - - this.$p #if ($foreach.hasNext) , #end - #end ); - } - } + #set($autoBuilder = false) + #parse("builder.vm") #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 new file mode 100644 index 00000000..630330ca --- /dev/null +++ b/value/src/main/java/com/google/auto/value/processor/builder.vm @@ -0,0 +1,285 @@ +## Copyright 2014 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 AutoValue and AutoBuilder builders. +## This template uses the Apache Velocity Template Language (VTL). +## The variables ($isFinal, $props, and so on) are defined by the fields of AutoValueOrBuilderTemplateVars. +## +## 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. +## +#foreach ($a in $builderAnnotations) +$a +#end +#if (!$autoBuilder) static #end## +#if ($isFinal) final #end## +class ${builderName}${builderFormalTypes} ## +#if ($builderIsInterface) implements #else extends #end + ${builderTypeName}${builderActualTypes} { + +#foreach ($p in $props) + + #if ($p.kind.primitive) + + private $types.boxedClass($p.typeMirror).simpleName $p; + + #else + + #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} ## + ${builderPropertyBuilders[$p.name].name}; + + #end + + private $p.type $p #if ($p.optional && !$p.nullable) = $p.optional.empty #end ; + + #end +#end + + ${builderName}() { + } + +#if ($toBuilderConstructor) + + private ${builderName}(${origClass}${actualTypes} source) { + + #foreach ($p in $props) + + this.$p = source.${p.getter}(); + + #end + + } + +#end + +#foreach ($p in $props) + + ## The following is either null or an instance of PropertyBuilderClassifier.PropertyBuilder + #set ($propertyBuilder = $builderPropertyBuilders[$p.name]) + + ## Setter and/or property builder + + #foreach ($setter in $builderSetters[$p.name]) + + @`java.lang.Override` + ${setter.access}${builderTypeName}${builderActualTypes} ## + ${setter.name}(${setter.nullableAnnotation}$setter.parameterType $p) { + + ## Omit null check for primitive, or @Nullable, or if we are going to be calling a copy method + ## such as Optional.of, which will have its own null check if appropriate. + #if (!$setter.primitiveParameter && !$p.nullable && ${setter.copy($p)} == $p) + + #if ($identifiers) + + if ($p == null) { + throw new NullPointerException("Null $p.name"); + } + #else + ## Just throw NullPointerException with no message if it's null. + ## The Object cast has no effect on the code but silences an ErrorProne warning. + + ((`java.lang.Object`) ${p}).getClass(); + #end + + #end + + #if ($propertyBuilder) + + if (${propertyBuilder.name} != null) { + throw new IllegalStateException(#if ($identifiers)"Cannot set $p after calling ${p.name}Builder()"#end); + } + + #end + + this.$p = ${setter.copy($p)}; + return this; + } + + #end + + #if ($propertyBuilder) + + @`java.lang.Override` + ${propertyBuilder.access}$propertyBuilder.builderType ${p.name}Builder($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. + ## 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) + + ${propertyBuilder.name} = ${propertyBuilder.initializer}; + + #else + + if ($p == null) { + ${propertyBuilder.name} = ${propertyBuilder.initializer}; + } else { + + #if (${propertyBuilder.builtToBuilder}) + + ${propertyBuilder.name} = ${p}.${propertyBuilder.builtToBuilder}(); + + #else + + ${propertyBuilder.name} = ${propertyBuilder.initializer}; + ${propertyBuilder.name}.${propertyBuilder.copyAll}($p); + + #end + + $p = null; + } + + #end + + } #if (!$propertyBuilder.propertyBuilderMethodParameters.empty) else { + ## This check only happens if the property-builder method has a parameter. + ## We don't know if the existing builder was created with the same parameter, + ## so we throw to avoid possibly giving you a builder that is different from + ## the one you asked for. + + throw new IllegalStateException("Property builder for $p.name is already defined"); + } + #end + + return $propertyBuilder.name; + } + + #end + + ## Getter + + #if ($builderGetters[$p.name]) + + @`java.lang.Override` + ${p.nullableAnnotation}${builderGetters[$p.name].access}$builderGetters[$p.name].type ${p.getter}() { + #if ($builderGetters[$p.name].optional) + + if ($p == null) { + return $builderGetters[$p.name].optional.empty; + } else { + return ${builderGetters[$p.name].optional.rawType}.of($p); + } + + #else + #if ($builderRequiredProperties.contains($p)) + + if ($p == null) { + throw new IllegalStateException(#if ($identifiers)"Property \"$p.name\" has not been set"#end); + } + + #end + + #if ($propertyBuilder) + + if (${propertyBuilder.name} != null) { + return ${propertyBuilder.name}.build(); + } + if ($p == null) { + ${propertyBuilder.beforeInitDefault} + $p = ${propertyBuilder.initDefault}; + } + + #end + + return $p; + + #end + + } + + #end +#end + +## build() method + + @`java.lang.Override` + ${buildMethod.get().access}${builtType} ${buildMethod.get().name}() ${buildMethod.get().throws} { + +#foreach ($p in $props) + #set ($propertyBuilder = $builderPropertyBuilders[$p.name]) + #if ($propertyBuilder) + + if (${propertyBuilder.name} != null) { + this.$p = ${propertyBuilder.name}.build(); + } else if (this.$p == null) { + ${propertyBuilder.beforeInitDefault} + this.$p = ${propertyBuilder.initDefault}; + } + + #end +#end + +#if (!$builderRequiredProperties.empty) + if (#foreach ($p in $builderRequiredProperties)## + this.$p == null## + #if ($foreach.hasNext) + + || #end + #end) { + + #if ($identifiers) ## build a friendly message showing all missing properties + #if ($builderRequiredProperties.size() == 1) + + `java.lang.String` missing = " $builderRequiredProperties.iterator().next()"; + + #else + + `java.lang.StringBuilder` missing = new `java.lang.StringBuilder`(); + + #foreach ($p in $builderRequiredProperties) + + if (this.$p == null) { + missing.append(" $p.name"); + } + + #end + #end + + throw new IllegalStateException("Missing required properties:" + missing); + + #else ## just throw an exception if anything is missing + + throw new IllegalStateException(); + + #end + + } + +#end + + #if ($builtType != "void") return #end ${build}( +#foreach ($p in $props) + + this.$p #if ($foreach.hasNext) , #end +#end ); + } +} 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 094f6c3c..03e25c2b 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 @@ -49,7 +49,7 @@ this.$p == that.${p.getter}() ## #elseif ($p.kind == "ARRAY") `java.util.Arrays`.equals(this.$p, ## - (that instanceof $subclass) ? (($subclass) that).$p : that.${p.getter}()) ## + (that instanceof $subclass) ? (($subclass$wildcardTypes) that).$p : that.${p.getter}()) ## #elseif ($p.nullable) (this.$p == null ? that.${p.getter}() == null : this.${p}.equals(that.${p.getter}())) ## #else diff --git a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubject.java b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubject.java index 18c77368..5d1462d0 100644 --- a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubject.java +++ b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubject.java @@ -51,9 +51,6 @@ final class MemoizedMethodSubject extends Subject { javac() .withProcessors(new AutoValueProcessor(ImmutableList.of(new MemoizeExtension()))) .compile(file); - assertThat(compilation) - .hadErrorContaining(error) - .inFile(file) - .onLineContaining(actual); + assertThat(compilation).hadErrorContaining(error).inFile(file).onLineContaining(actual); } } 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 7bd61ac7..5beb686b 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 @@ -22,6 +22,8 @@ import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue.CopyAnnotations; import com.google.auto.value.extension.memoized.MemoizedTest.HashCodeEqualsOptimization.EqualsCounter; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.ImmutableTypeParameter; import java.lang.reflect.AnnotatedType; import java.lang.reflect.Constructor; import java.lang.reflect.Method; @@ -123,7 +125,9 @@ public class MemoizedTest { @org.checkerframework.checker.nullness.qual.Nullable String nullableWithTypeAnnotation() { nullableWithTypeAnnotationCount++; - return "nullable derived " + stringWithTypeAnnotation() + " " + return "nullable derived " + + stringWithTypeAnnotation() + + " " + nullableWithTypeAnnotationCount; } @@ -234,8 +238,9 @@ public class MemoizedTest { @Before public void setUp() { - value = new AutoValue_MemoizedTest_Value( - "string", "stringWithTypeAnnotation", new HashCodeAndToStringCounter()); + value = + new AutoValue_MemoizedTest_Value( + "string", "stringWithTypeAnnotation", new HashCodeAndToStringCounter()); listValue = new AutoValue_MemoizedTest_ListValue<Integer, String>(0, "hello"); } @@ -375,8 +380,9 @@ public class MemoizedTest { Method nullable = AutoValue_MemoizedTest_Value.class.getDeclaredMethod("nullableWithTypeAnnotation"); AnnotatedType returnType = nullable.getAnnotatedReturnType(); - assertThat(returnType.isAnnotationPresent( - org.checkerframework.checker.nullness.qual.Nullable.class)) + assertThat( + returnType.isAnnotationPresent( + org.checkerframework.checker.nullness.qual.Nullable.class)) .isTrue(); } @@ -387,11 +393,13 @@ public class MemoizedTest { // [1] @org.checkerframework.checker.nullness.qual.Nullable String stringWithTypeAnnotation, // [2] HashCodeAndToStringCounter counter // We don't currently copy @javax.annotation.Nullable because it is not a TYPE_USE annotation. - Constructor<?> constructor = AutoValue_MemoizedTest_Value.class.getDeclaredConstructor( - String.class, String.class, HashCodeAndToStringCounter.class); + Constructor<?> constructor = + AutoValue_MemoizedTest_Value.class.getDeclaredConstructor( + String.class, String.class, HashCodeAndToStringCounter.class); AnnotatedType paramType = constructor.getAnnotatedParameterTypes()[1]; - assertThat(paramType.isAnnotationPresent( - org.checkerframework.checker.nullness.qual.Nullable.class)) + assertThat( + paramType.isAnnotationPresent( + org.checkerframework.checker.nullness.qual.Nullable.class)) .isTrue(); } @@ -448,8 +456,8 @@ public class MemoizedTest { assertThat(memoizedHashCodeAndFinalEqualsMethod.equals(second)).isTrue(); assertThat(memoizedHashCodeAndFinalEqualsMethod.hashCodeCount).isEqualTo(0); - memoizedHashCodeAndFinalEqualsMethod.hashCode(); - memoizedHashCodeAndFinalEqualsMethod.hashCode(); + int unused1 = memoizedHashCodeAndFinalEqualsMethod.hashCode(); + int unused2 = memoizedHashCodeAndFinalEqualsMethod.hashCode(); assertThat(memoizedHashCodeAndFinalEqualsMethod.hashCodeCount).isEqualTo(1); } @@ -465,8 +473,7 @@ public class MemoizedTest { @AutoValue abstract static class ResourceUriPath<InputT> extends AbstractTypePath<InputT, ResourceUri> { - static <InputT> ResourceUriPath<InputT> create( - TypeEdgeIterable<InputT, ResourceUri> edges) { + static <InputT> ResourceUriPath<InputT> create(TypeEdgeIterable<InputT, ResourceUri> edges) { return new AutoValue_MemoizedTest_ResourceUriPath<>(edges); } @@ -482,4 +489,27 @@ public class MemoizedTest { ResourceUriPath.create(new TypeEdgeIterable<String, ResourceUri>() {}); assertThat(path.edges()).isNotNull(); } + + @Immutable + @AutoValue + abstract static class Unchanging<@ImmutableTypeParameter T> { + abstract T value(); + + @Override + @Memoized + public abstract int hashCode(); + + static <@ImmutableTypeParameter T> Unchanging<T> of(T value) { + return new AutoValue_MemoizedTest_Unchanging<T>(value); + } + } + + @Test + public void copiedTypeAnnotations() { + for (Class<?> c = Unchanging.of("foo").getClass(); c != Object.class; c = c.getSuperclass()) { + assertThat(c.getTypeParameters()).hasLength(1); + assertThat(c.getTypeParameters()[0].isAnnotationPresent(ImmutableTypeParameter.class)) + .isTrue(); + } + } } 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 1879d1e5..064406da 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 @@ -415,4 +415,38 @@ public final class SerializableAutoValueExtensionTest { assertThat(actualAutoValue).isEqualTo(autoValue); } + + /** + * Type that may result in nested lambdas in the generated code. Including this allows us to + * verify that we handle those correctly, in particular not reusing a lambda parameter name in + * another lambda nested inside the first one. + */ + @SerializableAutoValue + @AutoValue + abstract static class ComplexType implements Serializable { + abstract ImmutableMap<String, ImmutableMap<String, Optional<String>>> a(); + + static ComplexType.Builder builder() { + return new AutoValue_SerializableAutoValueExtensionTest_ComplexType.Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + abstract ComplexType.Builder setA( + ImmutableMap<String, ImmutableMap<String, Optional<String>>> a); + + abstract ComplexType build(); + } + } + + @Test + public void complexType() { + ImmutableMap<String, ImmutableMap<String, Optional<String>>> map = + ImmutableMap.of("foo", ImmutableMap.of("bar", Optional.of("baz"))); + ComplexType complexType = ComplexType.builder().setA(map).build(); + + ComplexType reserialized = SerializableTester.reserialize(complexType); + + assertThat(reserialized).isEqualTo(complexType); + } } diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java index 388977fb..162d7cf6 100644 --- a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java +++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java @@ -42,6 +42,15 @@ public final class FakeSerializerFactory implements SerializerFactory { return new FakeIdentitySerializer(type, isIdentity); } + // This doesn't follow the contract, and always returns the same string for a given prefix. + // That means it will be wrong if two identifiers with the same prefix are in the same scope in + // the generated code, but for our purposes in this fake it is OK, and means we don't have to + // hardwire knowledge of the uniqueness algorithm into golden text in tests. + @Override + public CodeBlock newIdentifier(String prefix) { + return CodeBlock.of("$L$$", prefix); + } + private static class FakeIdentitySerializer implements Serializer { private final TypeMirror typeMirror; diff --git a/value/src/test/java/com/google/auto/value/extension/toprettystring/ToPrettyStringTest.java b/value/src/test/java/com/google/auto/value/extension/toprettystring/ToPrettyStringTest.java new file mode 100644 index 00000000..f5197472 --- /dev/null +++ b/value/src/test/java/com/google/auto/value/extension/toprettystring/ToPrettyStringTest.java @@ -0,0 +1,961 @@ +/* + * Copyright 2021 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.extension.toprettystring; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.toprettystring.ToPrettyStringTest.CollectionSubtypesWithFixedTypeParameters.StringList; +import com.google.auto.value.extension.toprettystring.ToPrettyStringTest.CollectionSubtypesWithFixedTypeParameters.StringMap; +import com.google.auto.value.extension.toprettystring.ToPrettyStringTest.PropertyHasToPrettyString.HasToPrettyString; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; +import com.google.common.primitives.ImmutableIntArray; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@SuppressWarnings("AutoValueImmutableFields") +@RunWith(JUnit4.class) +public class ToPrettyStringTest { + @AutoValue + abstract static class Primitives { + abstract int i(); + + abstract long l(); + + abstract byte b(); + + abstract short s(); + + abstract char c(); + + abstract float f(); + + abstract double d(); + + abstract boolean bool(); + + @ToPrettyString + abstract String toPrettyString(); + } + + @Test + public void primitives() { + Primitives valueType = + new AutoValue_ToPrettyStringTest_Primitives( + 1, 2L, (byte) 3, (short) 4, 'C', 6.6f, 7.7, false); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "Primitives {" + + "\n i = 1," + + "\n l = 2," + + "\n b = 3," + + "\n s = 4," + + "\n c = C," + + "\n f = 6.6," + + "\n d = 7.7," + + "\n bool = false," + + "\n}"); + } + + @AutoValue + abstract static class PrimitiveArray { + @Nullable + @SuppressWarnings("mutable") + abstract long[] longs(); + + @ToPrettyString + abstract String toPrettyString(); + } + + @Test + public void primitiveArray() { + PrimitiveArray valueType = + new AutoValue_ToPrettyStringTest_PrimitiveArray(new long[] {1L, 2L, 10L, 200L}); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "PrimitiveArray {" + + "\n longs = [" + + "\n 1," + + "\n 2," + + "\n 10," + + "\n 200," + + "\n ]," + + "\n}"); + } + + @Test + public void primitiveArray_empty() { + PrimitiveArray valueType = new AutoValue_ToPrettyStringTest_PrimitiveArray(new long[0]); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "PrimitiveArray {" // force newline + + "\n longs = []," + + "\n}"); + } + + @Test + public void primitiveArray_null() { + PrimitiveArray valueType = new AutoValue_ToPrettyStringTest_PrimitiveArray(null); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "PrimitiveArray {" // force newline + + "\n longs = null," + + "\n}"); + } + + @AutoValue + abstract static class PrettyCollection { + @Nullable + abstract Collection<Object> collection(); + + @ToPrettyString + abstract String toPrettyString(); + } + + @Test + public void prettyCollection() { + PrettyCollection valueType = + new AutoValue_ToPrettyStringTest_PrettyCollection(ImmutableList.of("hello", "world")); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "PrettyCollection {" + + "\n collection = [" + + "\n hello," + + "\n world," + + "\n ]," + + "\n}"); + } + + @Test + public void prettyCollection_elementsWithNewlines() { + PrettyCollection valueType = + new AutoValue_ToPrettyStringTest_PrettyCollection( + ImmutableList.of("hello\nworld\nnewline")); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "PrettyCollection {" + + "\n collection = [" + + "\n hello" + + "\n world" + + "\n newline," + + "\n ]," + + "\n}"); + } + + @Test + public void prettyCollection_empty() { + PrettyCollection valueType = + new AutoValue_ToPrettyStringTest_PrettyCollection(ImmutableList.of()); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "PrettyCollection {" // force newline + + "\n collection = []," + + "\n}"); + } + + @Test + public void prettyCollection_null() { + PrettyCollection valueType = new AutoValue_ToPrettyStringTest_PrettyCollection(null); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "PrettyCollection {" // force newline + + "\n collection = null," + + "\n}"); + } + + @AutoValue + abstract static class NestedCollection { + @Nullable + abstract Collection<Collection<Object>> nestedCollection(); + + @ToPrettyString + abstract String toPrettyString(); + } + + @Test + public void nestedCollection() { + NestedCollection valueType = + new AutoValue_ToPrettyStringTest_NestedCollection( + Arrays.asList( + ImmutableList.of("hello", "world"), + ImmutableList.of("hello2", "world2"), + null, + Arrays.asList("not null", null))); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "NestedCollection {" + + "\n nestedCollection = [" + + "\n [" + + "\n hello," + + "\n world," + + "\n ]," + + "\n [" + + "\n hello2," + + "\n world2," + + "\n ]," + + "\n null," + + "\n [" + + "\n not null," + + "\n null," + + "\n ]," + + "\n ]," + + "\n}"); + } + + @Test + public void nestedCollection_elementsWithNewlines() { + NestedCollection valueType = + new AutoValue_ToPrettyStringTest_NestedCollection( + ImmutableList.of( + ImmutableList.of((Object) "hello\nworld\nnewline", "hello2\nworld2\nnewline2"))); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "NestedCollection {" + + "\n nestedCollection = [" + + "\n [" + + "\n hello" + + "\n world" + + "\n newline," + + "\n hello2" + + "\n world2" + + "\n newline2," + + "\n ]," + + "\n ]," + + "\n}"); + } + + @Test + public void nestedCollection_empty() { + NestedCollection valueType = + new AutoValue_ToPrettyStringTest_NestedCollection(ImmutableList.of()); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "NestedCollection {" // force newline + + "\n nestedCollection = []," + + "\n}"); + } + + @Test + public void nestedCollection_nestedEmpty() { + NestedCollection valueType = + new AutoValue_ToPrettyStringTest_NestedCollection( + ImmutableList.of(ImmutableList.of(), ImmutableList.of())); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "NestedCollection {" + + "\n nestedCollection = [" + + "\n []," + + "\n []," + + "\n ]," + + "\n}"); + } + + @Test + public void nestedCollection_null() { + NestedCollection valueType = new AutoValue_ToPrettyStringTest_NestedCollection(null); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "NestedCollection {" // force newline + + "\n nestedCollection = null," + + "\n}"); + } + + @AutoValue + abstract static class ImmutablePrimitiveArray { + @Nullable + abstract ImmutableIntArray immutableIntArray(); + + @ToPrettyString + abstract String toPrettyString(); + } + + @Test + public void immutablePrimitiveArray() { + ImmutablePrimitiveArray valueType = + new AutoValue_ToPrettyStringTest_ImmutablePrimitiveArray(ImmutableIntArray.of(1, 2)); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "ImmutablePrimitiveArray {" + + "\n immutableIntArray = [" + + "\n 1," + + "\n 2," + + "\n ]," + + "\n}"); + } + + @Test + public void immutablePrimitiveArray_empty() { + ImmutablePrimitiveArray valueType = + new AutoValue_ToPrettyStringTest_ImmutablePrimitiveArray(ImmutableIntArray.of()); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "ImmutablePrimitiveArray {" // force newline + + "\n immutableIntArray = []," + + "\n}"); + } + + @Test + public void immutablePrimitiveArray_null() { + ImmutablePrimitiveArray valueType = + new AutoValue_ToPrettyStringTest_ImmutablePrimitiveArray(null); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "ImmutablePrimitiveArray {" // force newline + + "\n immutableIntArray = null," + + "\n}"); + } + + @AutoValue + abstract static class PrettyMap { + @Nullable + abstract Map<Object, Object> map(); + + @ToPrettyString + abstract String toPrettyString(); + } + + @Test + public void prettyMap() { + PrettyMap valueType = new AutoValue_ToPrettyStringTest_PrettyMap(ImmutableMap.of(1, 2)); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "PrettyMap {" // force newline + + "\n map = {" + + "\n 1: 2," + + "\n }," + + "\n}"); + } + + @Test + public void prettyMap_keysAndValuesWithNewlines() { + PrettyMap valueType = + new AutoValue_ToPrettyStringTest_PrettyMap( + ImmutableMap.of( + "key1\nnewline", "value1\nnewline", "key2\nnewline", "value2\nnewline")); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "PrettyMap {" + + "\n map = {" + + "\n key1" + + "\n newline: value1" + + "\n newline," + + "\n key2" + + "\n newline: value2" + + "\n newline," + + "\n }," + + "\n}"); + } + + @Test + public void prettyMap_empty() { + PrettyMap valueType = new AutoValue_ToPrettyStringTest_PrettyMap(ImmutableMap.of()); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "PrettyMap {" // force newline + + "\n map = {}," + + "\n}"); + } + + @Test + public void prettyMap_null() { + PrettyMap valueType = new AutoValue_ToPrettyStringTest_PrettyMap(null); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "PrettyMap {" // force newline + + "\n map = null," + + "\n}"); + } + + @AutoValue + abstract static class MapOfMaps { + @Nullable + abstract Map<Map<Object, Object>, Map<Object, Object>> mapOfMaps(); + + @ToPrettyString + abstract String toPrettyString(); + } + + private static <K, V> Map<K, V> mapWithNulls(K k, V v) { + Map<K, V> map = new LinkedHashMap<>(); + map.put(k, v); + return map; + } + + @Test + public void mapOfMaps() { + Map<Map<Object, Object>, Map<Object, Object>> mapOfMaps = new LinkedHashMap<>(); + mapOfMaps.put(ImmutableMap.of("k1_k", "k1_v"), ImmutableMap.of("v1_k", "v1_v")); + mapOfMaps.put(ImmutableMap.of("k2_k", "k2_v"), ImmutableMap.of("v2_k", "v2_v")); + mapOfMaps.put(mapWithNulls("keyForNullValue", null), mapWithNulls(null, "valueForNullKey")); + mapOfMaps.put(null, ImmutableMap.of("nullKeyKey", "nullKeyValue")); + mapOfMaps.put(ImmutableMap.of("nullValueKey", "nullValueValue"), null); + mapOfMaps.put( + ImmutableMap.of("keyForMapOfNullsKey", "keyForMapOfNullsValue"), mapWithNulls(null, null)); + MapOfMaps valueType = new AutoValue_ToPrettyStringTest_MapOfMaps(mapOfMaps); + assertThat(valueType.toPrettyString()) + .isEqualTo( + "MapOfMaps {" + + "\n mapOfMaps = {" + + "\n {" + + "\n k1_k: k1_v," + + "\n }: {" + + "\n v1_k: v1_v," + + "\n }," + + "\n {" + + "\n k2_k: k2_v," + + "\n }: {" + + "\n v2_k: v2_v," + + "\n }," + + "\n {" + + "\n keyForNullValue: null," + + "\n }: {" + + "\n null: valueForNullKey," + + "\n }," + + "\n null: {" + + "\n nullKeyKey: nullKeyValue," + + "\n }," + + "\n {" + + "\n nullValueKey: nullValueValue," + + "\n }: null," + + "\n {" + + "\n keyForMapOfNullsKey: keyForMapOfNullsValue," + + "\n }: {" + + "\n null: null," + + "\n }," + + "\n }," + + "\n}"); + } + + @Test + public void mapOfMaps_elementsWithNewlines() { + MapOfMaps valueType = + new AutoValue_ToPrettyStringTest_MapOfMaps( + ImmutableMap.of( + ImmutableMap.of((Object) "k_k\nnewline", (Object) "k_v\nnewline"), + ImmutableMap.of((Object) "v_k\nnewline", (Object) "v_v\nnewline"))); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "MapOfMaps {" + + "\n mapOfMaps = {" + + "\n {" + + "\n k_k" + + "\n newline: k_v" + + "\n newline," + + "\n }: {" + + "\n v_k" + + "\n newline: v_v" + + "\n newline," + + "\n }," + + "\n }," + + "\n}"); + } + + @Test + public void mapOfMaps_empty() { + MapOfMaps valueType = new AutoValue_ToPrettyStringTest_MapOfMaps(ImmutableMap.of()); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "MapOfMaps {" // force newline + + "\n mapOfMaps = {}," + + "\n}"); + } + + @Test + public void mapOfMaps_nestedEmpty() { + MapOfMaps valueType = + new AutoValue_ToPrettyStringTest_MapOfMaps( + ImmutableMap.of(ImmutableMap.of(), ImmutableMap.of())); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "MapOfMaps {" // force newline + + "\n mapOfMaps = {" + + "\n {}: {}," + + "\n }," + + "\n}"); + } + + @Test + public void mapOfMaps_null() { + MapOfMaps valueType = new AutoValue_ToPrettyStringTest_MapOfMaps(null); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "MapOfMaps {" // force newline + + "\n mapOfMaps = null," + + "\n}"); + } + + @AutoValue + abstract static class PrettyMultimap { + @Nullable + abstract Multimap<Object, Object> multimap(); + + @ToPrettyString + abstract String toPrettyString(); + } + + @Test + public void prettyMultimap() { + PrettyMultimap valueType = + new AutoValue_ToPrettyStringTest_PrettyMultimap( + ImmutableMultimap.builder().putAll("k", "v1", "v2").build()); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "PrettyMultimap {" // force newline + + "\n multimap = {" + + "\n k: [" + + "\n v1," + + "\n v2," + + "\n ]," + + "\n }," + + "\n}"); + } + + @Test + public void prettyMultimap_keysAndValuesWithNewlines() { + PrettyMultimap valueType = + new AutoValue_ToPrettyStringTest_PrettyMultimap( + ImmutableMultimap.builder() + .putAll("key\nnewline", "value1\nnewline", "value2\nnewline") + .build()); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "PrettyMultimap {" + + "\n multimap = {" + + "\n key" + + "\n newline: [" + + "\n value1" + + "\n newline," + + "\n value2" + + "\n newline," + + "\n ]," + + "\n }," + + "\n}"); + } + + @Test + public void prettyMultimap_empty() { + PrettyMultimap valueType = + new AutoValue_ToPrettyStringTest_PrettyMultimap(ImmutableMultimap.of()); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "PrettyMultimap {" // force newline + + "\n multimap = {}," + + "\n}"); + } + + @Test + public void prettyMultimap_null() { + PrettyMultimap valueType = new AutoValue_ToPrettyStringTest_PrettyMultimap(null); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "PrettyMultimap {" // force newline + + "\n multimap = null," + + "\n}"); + } + + @AutoValue + abstract static class JavaOptional { + @Nullable + abstract java.util.Optional<Object> optional(); + + @ToPrettyString + abstract String toPrettyString(); + } + + @Test + public void javaOptional_present() { + JavaOptional valueType = + new AutoValue_ToPrettyStringTest_JavaOptional(java.util.Optional.of("hello, world")); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "JavaOptional {" // force newline + + "\n optional = hello, world," + + "\n}"); + } + + @Test + public void javaOptional_empty() { + JavaOptional valueType = + new AutoValue_ToPrettyStringTest_JavaOptional(java.util.Optional.empty()); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "JavaOptional {" // force newline + + "\n optional = <empty>," + + "\n}"); + } + + @Test + public void javaOptional_valueWithNewlines() { + JavaOptional valueType = + new AutoValue_ToPrettyStringTest_JavaOptional( + java.util.Optional.of("optional\nwith\nnewline")); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "JavaOptional {" // force newline + + "\n optional = optional" + + "\n with" + + "\n newline," + + "\n}"); + } + + @Test + public void javaOptional_null() { + @SuppressWarnings("NullOptional") + JavaOptional valueType = new AutoValue_ToPrettyStringTest_JavaOptional(null); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "JavaOptional {" // force newline + + "\n optional = null," + + "\n}"); + } + + @AutoValue + abstract static class GuavaOptional { + @Nullable + abstract com.google.common.base.Optional<Object> optional(); + + @ToPrettyString + abstract String toPrettyString(); + } + + @Test + public void guavaOptional_present() { + GuavaOptional valueType = + new AutoValue_ToPrettyStringTest_GuavaOptional( + com.google.common.base.Optional.of("hello, world")); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "GuavaOptional {" // force newline + + "\n optional = hello, world," + + "\n}"); + } + + @Test + public void guavaOptional_absent() { + GuavaOptional valueType = + new AutoValue_ToPrettyStringTest_GuavaOptional(com.google.common.base.Optional.absent()); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "GuavaOptional {" // force newline + + "\n optional = <absent>," + + "\n}"); + } + + @Test + public void guavaOptional_valueWithNewlines() { + GuavaOptional valueType = + new AutoValue_ToPrettyStringTest_GuavaOptional( + com.google.common.base.Optional.of("optional\nwith\nnewline")); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "GuavaOptional {" // force newline + + "\n optional = optional" + + "\n with" + + "\n newline," + + "\n}"); + } + + @Test + public void guavaOptional_null() { + @SuppressWarnings("NullOptional") + GuavaOptional valueType = new AutoValue_ToPrettyStringTest_GuavaOptional(null); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "GuavaOptional {" // force newline + + "\n optional = null," + + "\n}"); + } + + @AutoValue + abstract static class NestAllTheThings { + @Nullable + abstract com.google.common.base.Optional< + java.util.Optional< + List< // open list + Map<ImmutableIntArray, Multimap<int[][], Object>> + // close list + >>> + value(); + + @ToPrettyString + abstract String toPrettyString(); + } + + @Test + public void nestAllTheThings() { + NestAllTheThings valueType = + new AutoValue_ToPrettyStringTest_NestAllTheThings( + com.google.common.base.Optional.of( + java.util.Optional.of( + ImmutableList.of( + ImmutableMap.of( + ImmutableIntArray.of(-1, -2, -3), + ImmutableMultimap.of( + new int[][] {{1, 2}, {3, 4, 5}, {}}, "value\nwith\nnewline")))))); + assertThat(valueType.toPrettyString()) + .isEqualTo( + "NestAllTheThings {" + + "\n value = [" + + "\n {" + + "\n [" + + "\n -1," + + "\n -2," + + "\n -3," + + "\n ]: {" + + "\n [" + + "\n [" + + "\n 1," + + "\n 2," + + "\n ]," + + "\n [" + + "\n 3," + + "\n 4," + + "\n 5," + + "\n ]," + + "\n []," + + "\n ]: [" + + "\n value" + + "\n with" + + "\n newline," + + "\n ]," + + "\n }," + + "\n }," + + "\n ]," + + "\n}"); + } + + @AutoValue + abstract static class WithCustomName { + abstract int i(); + + @ToPrettyString + abstract String customName(); + } + + @Test + public void withCustomName() { + WithCustomName valueType = new AutoValue_ToPrettyStringTest_WithCustomName(1); + + assertThat(valueType.customName()) + .isEqualTo( + "WithCustomName {" // force newline + + "\n i = 1," + + "\n}"); + } + + @AutoValue + abstract static class OverridesToString { + abstract int i(); + + @ToPrettyString + @Override + public abstract String toString(); + } + + @Test + public void overridesToString() { + OverridesToString valueType = new AutoValue_ToPrettyStringTest_OverridesToString(1); + + assertThat(valueType.toString()) + .isEqualTo( + "OverridesToString {" // force newline + + "\n i = 1," + + "\n}"); + } + + @AutoValue + abstract static class PropertyHasToPrettyString { + static class HasToPrettyString<A> { + @Override + public String toString() { + throw new AssertionError(); + } + + @ToPrettyString + String toPrettyString() { + return "custom\n@ToPrettyString\nmethod"; + } + } + + static class HasInheritedToPrettyString extends HasToPrettyString<String> {} + + interface HasToPrettyStringInInterface { + @ToPrettyString + default String toPrettyString() { + return "custom\n@ToPrettyString\nmethod\ninterface"; + } + } + + static class HasToPrettyStringFromSuperInterface implements HasToPrettyStringInInterface {} + + abstract HasToPrettyString<String> parameterizedWithString(); + + abstract HasToPrettyString<Void> parameterizedWithVoid(); + + abstract HasInheritedToPrettyString superclass(); + + abstract HasToPrettyStringFromSuperInterface superinterface(); + + @ToPrettyString + abstract String toPrettyString(); + } + + @Test + public void propertyHasToPrettyString() { + PropertyHasToPrettyString valueType = + new AutoValue_ToPrettyStringTest_PropertyHasToPrettyString( + new PropertyHasToPrettyString.HasToPrettyString<>(), + new PropertyHasToPrettyString.HasToPrettyString<>(), + new PropertyHasToPrettyString.HasInheritedToPrettyString(), + new PropertyHasToPrettyString.HasToPrettyStringFromSuperInterface()); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "PropertyHasToPrettyString {" + + "\n parameterizedWithString = custom" + + "\n @ToPrettyString" + + "\n method," + + "\n parameterizedWithVoid = custom" + + "\n @ToPrettyString" + + "\n method," + + "\n superclass = custom" + + "\n @ToPrettyString" + + "\n method," + + "\n superinterface = custom" + + "\n @ToPrettyString" + + "\n method" + + "\n interface," + + "\n}"); + } + + @AutoValue + abstract static class CollectionSubtypesWithFixedTypeParameters { + static class StringList extends ArrayList<String> {} + + static class StringMap extends LinkedHashMap<String, String> {} + + abstract StringList list(); + + abstract StringMap map(); + + @ToPrettyString + abstract String toPrettyString(); + } + + @Test + public void fixedTypeParameters() { + StringList stringList = new StringList(); + stringList.addAll(ImmutableList.of("a", "b", "c")); + StringMap stringMap = new StringMap(); + stringMap.putAll(ImmutableMap.of("A", "a", "B", "b")); + CollectionSubtypesWithFixedTypeParameters valueType = + new AutoValue_ToPrettyStringTest_CollectionSubtypesWithFixedTypeParameters( + stringList, stringMap); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "CollectionSubtypesWithFixedTypeParameters {" + + "\n list = [" + + "\n a," + + "\n b," + + "\n c," + + "\n ]," + + "\n map = {" + + "\n A: a," + + "\n B: b," + + "\n }," + + "\n}"); + } + + @AutoValue + abstract static class JavaBeans { + abstract int getInt(); + + abstract boolean isBoolean(); + + abstract String getNotAJavaIdentifier(); + + @ToPrettyString + abstract String toPrettyString(); + } + + @Test + public void javaBeans() { + JavaBeans valueType = new AutoValue_ToPrettyStringTest_JavaBeans(4, false, "not"); + + assertThat(valueType.toPrettyString()) + .isEqualTo( + "JavaBeans {" + + "\n int = 4," + + "\n boolean = false," + + "\n notAJavaIdentifier = not," + + "\n}"); + + // Check to make sure that we use the same property names that AutoValue does. This is mostly + // defensive, since in some scenarios AutoValue considers the property names of a java bean as + // having the prefix removed. + assertThat(valueType.toString()) + .isEqualTo("JavaBeans{int=4, boolean=false, notAJavaIdentifier=not}"); + } +} diff --git a/value/src/test/java/com/google/auto/value/extension/toprettystring/ToPrettyStringValidatorTest.java b/value/src/test/java/com/google/auto/value/extension/toprettystring/ToPrettyStringValidatorTest.java new file mode 100644 index 00000000..6c51be1d --- /dev/null +++ b/value/src/test/java/com/google/auto/value/extension/toprettystring/ToPrettyStringValidatorTest.java @@ -0,0 +1,240 @@ +/* + * Copyright 2021 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.extension.toprettystring; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static com.google.testing.compile.Compiler.javac; + +import com.google.auto.value.extension.toprettystring.processor.ToPrettyStringValidator; +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ToPrettyStringValidatorTest { + @Test + public void cannotBeStatic() { + JavaFileObject file = + JavaFileObjects.forSourceLines( + "test.Test", + "package test;", + "", + "import com.google.auto.value.extension.toprettystring.ToPrettyString;", + "", + "class Test {", + " @ToPrettyString", + " static String toPretty() {", + " return new String();", + " }", + "}", + ""); + Compilation compilation = compile(file); + + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining("must be instance methods") + .inFile(file) + .onLineContaining("static String toPretty()"); + } + + @Test + public void mustReturnString() { + JavaFileObject file = + JavaFileObjects.forSourceLines( + "test.Test", + "package test;", + "", + "import com.google.auto.value.extension.toprettystring.ToPrettyString;", + "", + "class Test {", + " @ToPrettyString", + " CharSequence toPretty() {", + " return new String();", + " }", + "}", + ""); + Compilation compilation = compile(file); + + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining("must return String") + .inFile(file) + .onLineContaining("CharSequence toPretty()"); + } + + @Test + public void noParameters() { + JavaFileObject file = + JavaFileObjects.forSourceLines( + "test.Test", + "package test;", + "", + "import com.google.auto.value.extension.toprettystring.ToPrettyString;", + "", + "class Test {", + " @ToPrettyString", + " String toPretty(String value) {", + " return value;", + " }", + "}", + ""); + Compilation compilation = compile(file); + + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining("cannot have parameters") + .inFile(file) + .onLineContaining("String toPretty(String value)"); + } + + @Test + public void onlyOneToPrettyStringMethod_sameClass() { + JavaFileObject file = + JavaFileObjects.forSourceLines( + "test.Test", + "package test;", + "", + "import com.google.auto.value.extension.toprettystring.ToPrettyString;", + "", + "class Test {", + " @ToPrettyString", + " String toPretty1() {", + " return new String();", + " }", + "", + " @ToPrettyString", + " String toPretty2() {", + " return new String();", + " }", + "}", + ""); + Compilation compilation = compile(file); + + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining( + error( + "test.Test has multiple @ToPrettyString methods:", + " - test.Test.toPretty1()", + " - test.Test.toPretty2()")) + .inFile(file) + .onLineContaining("class Test"); + } + + @Test + public void onlyOneToPrettyStringMethod_superclass() { + JavaFileObject superclass = + JavaFileObjects.forSourceLines( + "test.Superclass", + "package test;", + "", + "import com.google.auto.value.extension.toprettystring.ToPrettyString;", + "", + "class Superclass {", + " @ToPrettyString", + " String toPretty1() {", + " return new String();", + " }", + "}", + ""); + JavaFileObject subclass = + JavaFileObjects.forSourceLines( + "test.Subclass", + "package test;", + "", + "import com.google.auto.value.extension.toprettystring.ToPrettyString;", + "", + "class Subclass extends Superclass {", + " @ToPrettyString", + " String toPretty2() {", + " return new String();", + " }", + "}", + ""); + Compilation compilation = compile(superclass, subclass); + + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining( + error( + "test.Subclass has multiple @ToPrettyString methods:", + " - test.Superclass.toPretty1()", + " - test.Subclass.toPretty2()")) + .inFile(subclass) + .onLineContaining("class Subclass"); + } + + @Test + public void onlyOneToPrettyStringMethod_superinterface() { + JavaFileObject superinterface = + JavaFileObjects.forSourceLines( + "test.Superinterface", + "package test;", + "", + "import com.google.auto.value.extension.toprettystring.ToPrettyString;", + "", + "interface Superinterface {", + " @ToPrettyString", + " default String toPretty1() {", + " return new String();", + " }", + "}", + ""); + JavaFileObject subclass = + JavaFileObjects.forSourceLines( + "test.Subclass", + "package test;", + "", + "import com.google.auto.value.extension.toprettystring.ToPrettyString;", + "", + "class Subclass implements Superinterface {", + " @ToPrettyString", + " String toPretty2() {", + " return new String();", + " }", + "}", + ""); + Compilation compilation = compile(superinterface, subclass); + + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining( + error( + "test.Subclass has multiple @ToPrettyString methods:", + " - test.Superinterface.toPretty1()", + " - test.Subclass.toPretty2()")) + .inFile(subclass) + .onLineContaining("class Subclass"); + } + + private static Compilation compile(JavaFileObject... javaFileObjects) { + return javac().withProcessors(new ToPrettyStringValidator()).compile(javaFileObjects); + } + + private static String error(String... lines) { + return String.join("\n ", lines); + } +} diff --git a/value/src/test/java/com/google/auto/value/processor/AutoAnnotationCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationCompilationTest.java index b8a36eac..1f79a074 100644 --- a/value/src/test/java/com/google/auto/value/processor/AutoAnnotationCompilationTest.java +++ b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationCompilationTest.java @@ -74,11 +74,13 @@ public class AutoAnnotationCompilationTest { "", "import com.example.annotations.MyAnnotation;", "import com.example.enums.MyEnum;", + "import java.io.Serializable;", GeneratedImport.importGeneratedAnnotationType(), "", "@Generated(\"" + AutoAnnotationProcessor.class.getName() + "\")", "final class AutoAnnotation_AnnotationFactory_newMyAnnotation", - " implements MyAnnotation {", + " implements MyAnnotation, Serializable {", + " private static final long serialVersionUID = -7473814294717163169L;", " private final MyEnum value;", " private static final int defaultedValue = 23;", "", @@ -129,6 +131,7 @@ public class AutoAnnotationCompilationTest { Compilation compilation = javac() .withProcessors(new AutoAnnotationProcessor()) + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") .compile(annotationFactoryJavaFile, myAnnotationJavaFile, myEnumJavaFile); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -157,11 +160,13 @@ public class AutoAnnotationCompilationTest { JavaFileObject expectedOutput = JavaFileObjects.forSourceLines( "AutoAnnotation_AnnotationFactory_newMyAnnotation", + "import java.io.Serializable;", GeneratedImport.importGeneratedAnnotationType(), "", "@Generated(\"" + AutoAnnotationProcessor.class.getName() + "\")", "final class AutoAnnotation_AnnotationFactory_newMyAnnotation", - " implements MyAnnotation {", + " implements MyAnnotation, Serializable {", + " private static final long serialVersionUID = 0L;", " AutoAnnotation_AnnotationFactory_newMyAnnotation() {", " }", "", @@ -191,6 +196,7 @@ public class AutoAnnotationCompilationTest { Compilation compilation = javac() .withProcessors(new AutoAnnotationProcessor()) + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") .compile(annotationFactoryJavaFile, myAnnotationJavaFile); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -237,12 +243,14 @@ public class AutoAnnotationCompilationTest { "package com.example.factories;", "", "import com.example.annotations.MyAnnotation;", + "import java.io.Serializable", "import java.util.Arrays;", GeneratedImport.importGeneratedAnnotationType(), "", "@Generated(\"" + AutoAnnotationProcessor.class.getName() + "\")", - "final class AutoAnnotation_AnnotationFactory_newMyAnnotation implements MyAnnotation" - + " {", + "final class AutoAnnotation_AnnotationFactory_newMyAnnotation implements MyAnnotation," + + " Serializable {", + " private static final long serialVersionUID = -8116050813861599066L;", " private final int[] value;", "", " AutoAnnotation_AnnotationFactory_newMyAnnotation(int[] value) {", @@ -288,6 +296,7 @@ public class AutoAnnotationCompilationTest { Compilation compilation = javac() .withProcessors(new AutoAnnotationProcessor()) + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") .compile(annotationFactoryJavaFile, myAnnotationJavaFile, gwtCompatibleJavaFile); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -343,6 +352,7 @@ public class AutoAnnotationCompilationTest { "", "import com.example.annotations.MyAnnotation;", "import com.example.enums.MyEnum;", + "import java.io.Serializable;", "import java.util.Arrays;", "import java.util.Collection;", "import java.util.List;", @@ -350,8 +360,9 @@ public class AutoAnnotationCompilationTest { GeneratedImport.importGeneratedAnnotationType(), "", "@Generated(\"" + AutoAnnotationProcessor.class.getName() + "\")", - "final class AutoAnnotation_AnnotationFactory_newMyAnnotation implements MyAnnotation" - + " {", + "final class AutoAnnotation_AnnotationFactory_newMyAnnotation implements MyAnnotation," + + " Serializable {", + " private static final long serialVersionUID = -2102364343628921304L;", " private final int[] value;", " private final MyEnum[] enums;", "", @@ -426,6 +437,7 @@ public class AutoAnnotationCompilationTest { Compilation compilation = javac() .withProcessors(new AutoAnnotationProcessor()) + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") .compile(annotationFactoryJavaFile, myEnumJavaFile, myAnnotationJavaFile); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -457,9 +469,7 @@ public class AutoAnnotationCompilationTest { " @NotAutoAnnotation Empty notNewEmpty() {}", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(erroneousJavaFileObject); + javac().withProcessors(new AutoAnnotationProcessor()).compile(erroneousJavaFileObject); assertThat(compilation) .hadErrorContaining("NotAutoAnnotation") .inFile(erroneousJavaFileObject) 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 55a4c5db..094b570d 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 @@ -57,9 +57,7 @@ public class AutoAnnotationErrorsTest { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(TEST_ANNOTATION, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource); assertThat(compilation).succeededWithoutWarnings(); } @@ -79,9 +77,7 @@ public class AutoAnnotationErrorsTest { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(TEST_ANNOTATION, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource); assertThat(compilation) .hadErrorContaining("must be static") .inFile(testSource) @@ -103,9 +99,7 @@ public class AutoAnnotationErrorsTest { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(TEST_ANNOTATION, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource); assertThat(compilation) .hadErrorContaining("must be an annotation type, not java.lang.String") .inFile(testSource) @@ -132,9 +126,7 @@ public class AutoAnnotationErrorsTest { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(TEST_ANNOTATION, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource); assertThat(compilation) .hadErrorContaining("@AutoAnnotation methods cannot be overloaded") .inFile(testSource) @@ -196,9 +188,7 @@ public class AutoAnnotationErrorsTest { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(TEST_ANNOTATION, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource); assertThat(compilation) .hadErrorContaining("method parameter 'fred' must have the same name") .inFile(testSource) @@ -221,9 +211,7 @@ public class AutoAnnotationErrorsTest { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(TEST_ANNOTATION, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource); assertThat(compilation) .hadErrorContaining( "method parameter 'value' has type java.lang.String " @@ -267,9 +255,7 @@ public class AutoAnnotationErrorsTest { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(testSource, testAnnotation); + javac().withProcessors(new AutoAnnotationProcessor()).compile(testSource, testAnnotation); assertThat(compilation) .hadErrorContaining( "method parameter 'value' has type " @@ -296,9 +282,7 @@ public class AutoAnnotationErrorsTest { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(TEST_ANNOTATION, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource); assertThat(compilation) .hadErrorContaining( "method parameter 'other' must have the same name as a member of " @@ -323,9 +307,7 @@ public class AutoAnnotationErrorsTest { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(TEST_ANNOTATION, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource); assertThat(compilation) .hadErrorContaining("method needs a parameter with name 'value' and type int") .inFile(testSource) @@ -357,9 +339,7 @@ public class AutoAnnotationErrorsTest { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(annotationSource, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(annotationSource, testSource); assertThat(compilation) .hadErrorContaining( "@AutoAnnotation cannot yet supply a default value for annotation-valued member " @@ -399,10 +379,7 @@ public class AutoAnnotationErrorsTest { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(annotationSource, testSource); - assertThat(compilation) - .hadErrorContaining("variable value$ is already defined in constructor"); + javac().withProcessors(new AutoAnnotationProcessor()).compile(annotationSource, testSource); + assertThat(compilation).hadErrorContaining("variable value$ is already defined in constructor"); } } 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 new file mode 100644 index 00000000..50b6b271 --- /dev/null +++ b/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java @@ -0,0 +1,875 @@ +/* + * Copyright 2021 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.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; +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 com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class AutoBuilderCompilationTest { + private static final JavaFileObject EXPECTED_SIMPLE_OUTPUT = + JavaFileObjects.forSourceLines( + "foo.bar.AutoBuilder_Baz_Builder", + "package foo.bar;", + "", + GeneratedImport.importGeneratedAnnotationType(), + "", + "@Generated(\"" + AutoBuilderProcessor.class.getName() + "\")", + "class AutoBuilder_Baz_Builder implements Baz.Builder {", + " private Integer anInt;", + " private String aString;", + "", + " AutoBuilder_Baz_Builder() {}", + "", + " @Override public Baz.Builder setAnInt(int anInt) {", + " this.anInt = anInt;", + " return this;", + " }", + "", + " @Override public Baz.Builder setAString(String aString) {", + " if (aString == null) {", + " throw new NullPointerException(\"Null aString\");", + " }", + " this.aString = aString;", + " return this;", + " }", + "", + " @Override", + " public Baz build() {", + " if (this.anInt == null", + " || this.aString == null) {", + " StringBuilder missing = new StringBuilder();", + " if (this.anInt == null) {", + " missing.append(\" anInt\");", + " }", + " if (this.aString == null) {", + " missing.append(\" aString\");", + " }", + " throw new IllegalStateException(\"Missing required properties:\" + missing);", + " }", + " return new Baz(", + " this.anInt,", + " this.aString);", + " }", + "}"); + + @Test + public void simpleSuccess() { + 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);", + " Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation) + .generatedSourceFile("foo.bar.AutoBuilder_Baz_Builder") + .hasSourceEquivalentTo(EXPECTED_SIMPLE_OUTPUT); + } + + @Test + public void simpleRecord() { + double version = Double.parseDouble(JAVA_SPECIFICATION_VERSION.value()); + assume().that(version).isAtLeast(16.0); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "public record Baz(int anInt, String aString) {", + " public static Builder builder() {", + " return new AutoBuilder_Baz_Builder();", + " }", + "", + " @AutoBuilder", + " public interface Builder {", + " Builder setAnInt(int x);", + " Builder setAString(String x);", + " Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation) + .generatedSourceFile("foo.bar.AutoBuilder_Baz_Builder") + .hasSourceEquivalentTo(EXPECTED_SIMPLE_OUTPUT); + } + + @Test + public void buildOtherPackage() { + JavaFileObject built = + JavaFileObjects.forSourceLines( + "com.example.Built", + "package com.example;", + "", + "public class Built {", + " private final int anInt;", + " private final String aString;", + "", + " public Built(int anInt, String aString) {", + " this.anInt = anInt;", + " this.aString = aString;", + " }", + "}"); + JavaFileObject builder = + JavaFileObjects.forSourceLines( + "foo.bar.Builder", + "package foo.bar;", + "", + "import com.example.Built;", + "import com.google.auto.value.AutoBuilder;", + "", + "@AutoBuilder(ofClass = Built.class)", + "public interface Builder {", + " public static Builder builder() {", + " return new AutoBuilder_Builder();", + " }", + "", + " Builder setAnInt(int x);", + " Builder setAString(String x);", + " Built build();", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(built, builder); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(compilation).generatedSourceFile("foo.bar.AutoBuilder_Builder"); + } + + @Test + public void autoBuilderOnEnum() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "@AutoBuilder", + "public enum Baz {", + " ZIG, ZAG, DUSTIN,", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderWrongType] @AutoBuilder only applies to classes and interfaces") + .inFile(javaFileObject) + .onLineContaining("enum Baz"); + } + + @Test + public void autoBuilderPrivate() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "public class Baz {", + " @AutoBuilder", + " private interface Builder {", + " Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("[AutoBuilderPrivate] @AutoBuilder class must not be private") + .inFile(javaFileObject) + .onLineContaining("interface Builder"); + } + + @Test + public void autoBuilderNestedInPrivate() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "public class Baz {", + " private static class Private {", + " @AutoBuilder", + " public interface Builder {", + " Baz build();", + " }", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderInPrivate] @AutoBuilder class must not be nested in a private class") + .inFile(javaFileObject) + .onLineContaining("interface Builder"); + } + + @Test + public void autoBuilderInner() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "public class Baz {", + " @AutoBuilder", + " abstract class Builder {", + " abstract Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("[AutoBuilderInner] Nested @AutoBuilder class must be static") + .inFile(javaFileObject) + .onLineContaining("class Builder"); + } + + @Test + public void innerConstructor() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "public class Baz {", + " class Inner {}", + "", + " @AutoBuilder(ofClass = Inner.class)", + " interface Builder {", + " abstract Inner build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("[AutoBuilderInner] Nested @AutoBuilder ofClass class must be static") + .inFile(javaFileObject) + .onLineContaining("interface Builder"); + } + + @Test + public void noVisibleConstructor() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "public class Baz {", + " @AutoBuilder(ofClass = System.class)", + " interface Builder {", + " abstract System build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("[AutoBuilderNoVisible] No visible constructor for java.lang.System") + .inFile(javaFileObject) + .onLineContaining("interface Builder"); + } + + @Test + public void noVisibleMethod() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "public class Baz {", + " private static Baz of() {", + " return new Baz();", + " }", + "", + " @AutoBuilder(callMethod = \"of\")", + " interface Builder {", + " abstract Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderNoVisible] No visible static method named \"of\" for foo.bar.Baz") + .inFile(javaFileObject) + .onLineContaining("interface Builder"); + } + + @Test + public void methodNotStatic() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "public class Baz {", + " Baz of() {", + " return this;", + " }", + "", + " @AutoBuilder(callMethod = \"of\")", + " interface Builder {", + " abstract Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderNoVisible] No visible static method named \"of\" for foo.bar.Baz") + .inFile(javaFileObject) + .onLineContaining("interface Builder"); + } + + @Test + public void noMatchingConstructor() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "public class Baz {", + " public Baz(int notMe) {}", + "", + " public Baz(String notMeEither) {}", + "", + " @AutoBuilder", + " interface Builder {", + " Builder setBuh(String x);", + " abstract Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderNoMatch] Property names do not correspond to the parameter names of any" + + " constructor:\n" + + " Baz(int notMe)\n" + + " Baz(java.lang.String notMeEither)") + .inFile(javaFileObject) + .onLineContaining("interface Builder"); + } + + @Test + public void twoMatchingConstructors() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "public class Baz {", + " public Baz() {}", + "", + " public Baz(int buh) {}", + "", + " public Baz(String buh) {}", + "", + " @AutoBuilder", + " interface Builder {", + " Builder setBuh(String x);", + " abstract Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderAmbiguous] Property names correspond to more than one constructor:\n" + + " Baz(int buh)\n" + + " Baz(java.lang.String buh)") + .inFile(javaFileObject) + .onLineContaining("interface Builder"); + } + + @Test + public void constructInterface() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "public interface Baz {", + " @AutoBuilder", + " interface Builder {", + " abstract Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderEnclosing] @AutoBuilder must specify ofClass=Something.class or it must" + + " be nested inside the class to be built; actually nested inside interface" + + " foo.bar.Baz") + .inFile(javaFileObject) + .onLineContaining("interface Builder"); + } + + @Test + public void inconsistentSetPrefix() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "class Baz {", + " Baz(int one, int two) {}", + "", + " @AutoBuilder", + " interface Builder {", + " abstract Builder one(int x);", + " abstract Builder setTwo(int x);", + " abstract Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderSetNotSet] If any setter methods use the setFoo convention then all must") + .inFile(javaFileObject) + .onLineContaining("Builder one(int x)"); + } + + @Test + public void missingSetter() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "class Baz {", + " Baz(int one, int two) {}", + "", + " @AutoBuilder", + " interface Builder {", + " abstract Builder one(int x);", + " abstract Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderBuilderMissingMethod] Expected a method with this signature:" + + " foo.bar.Baz.Builder two(int), or a twoBuilder() method") + .inFile(javaFileObject) + .onLineContaining("interface Builder"); + } + + @Test + public void tooManyArgs() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "class Baz {", + " Baz(int one, int two) {}", + "", + " @AutoBuilder", + " interface Builder {", + " abstract Builder one(int x);", + " abstract Builder two(int x);", + " abstract Builder many(int x, int y);", + " abstract Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("[AutoBuilderBuilderArgs] Builder methods must have 0 or 1 parameters") + .inFile(javaFileObject) + .onLineContaining("many(int x, int y)"); + } + + @Test + public void alienNoArgMethod() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "class Baz {", + " Baz(int one, int two) {}", + "", + " @AutoBuilder", + " interface Builder {", + " abstract Builder one(int x);", + " abstract Builder two(int x);", + " abstract String alien();", + " abstract Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderBuilderNoArg] Method without arguments should be a build method returning" + + " foo.bar.Baz, or a getter method with the same name and type as a parameter of" + + " Baz(int one, int two), or fooBuilder() where foo is a parameter of Baz(int" + + " one, int two)") + .inFile(javaFileObject) + .onLineContaining("String alien()"); + } + + @Test + public void alienOneArgMethod() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "class Baz {", + " Baz(int one, int two) {}", + "", + " @AutoBuilder", + " interface Builder {", + " abstract Builder one(int x);", + " abstract Builder two(int x);", + " abstract Builder three(int x);", + " abstract Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderBuilderWhatProp] Method three does not correspond to " + + "a parameter of Baz(int one, int two)") + .inFile(javaFileObject) + .onLineContaining("three(int x)"); + } + + @Test + public void setterReturnType() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "class Baz {", + " Baz(int one, int two) {}", + "", + " @AutoBuilder", + " interface Builder {", + " abstract Builder one(int x);", + " abstract void two(int x);", + " abstract Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderBuilderRet] Setter methods must return foo.bar.Baz.Builder") + .inFile(javaFileObject) + .onLineContaining("two(int x)"); + } + + @Test + public void nullableSetterForNonNullableParameter() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "import org.checkerframework.checker.nullness.qual.Nullable;", + "", + "class Baz {", + " Baz(String thing) {}", + "", + " @AutoBuilder", + " interface Builder {", + " abstract Builder thing(@Nullable String x);", + " abstract Baz build();", + " }", + "}"); + JavaFileObject nullableFileObject = + JavaFileObjects.forSourceLines( + "org.checkerframework.checker.nullness.qual.Nullable", + "package org.jspecify.nullness;", + "", + "import java.lang.annotation.ElementType;", + "import java.lang.annotation.Target;", + "", + "@Target(ElementType.TYPE_USE)", + "public @interface Nullable {}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject, nullableFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderNullNotNull] Parameter of setter method is @Nullable but parameter" + + " \"thing\" of Baz(java.lang.String thing) is not") + .inFile(javaFileObject) + .onLineContaining("thing(@Nullable String x)"); + } + + @Test + public void setterWrongType() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "class Baz {", + " Baz(int up, int down) {}", + "", + " @AutoBuilder", + " interface Builder {", + " abstract Builder up(int x);", + " abstract Builder down(String x);", + " abstract Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderGetVsSet] Parameter type java.lang.String of setter method should be int" + + " to match parameter \"down\" of Baz(int up, int down)") + .inFile(javaFileObject) + .onLineContaining("down(String x)"); + } + + @Test + public void setterWrongTypeEvenWithConversion() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "import java.util.Optional;", + "", + "class Baz {", + " Baz(Optional<String> maybe) {}", + "", + " @AutoBuilder", + " interface Builder {", + " abstract Builder maybe(int x);", + " abstract Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderGetVsSetOrConvert] Parameter type int of setter method should be" + + " java.util.Optional<java.lang.String> to match parameter \"maybe\" of" + + " Baz(java.util.Optional<java.lang.String> maybe), or it should be a type that" + + " can be passed to Optional.of to produce java.util.Optional<java.lang.String>") + .inFile(javaFileObject) + .onLineContaining("maybe(int x)"); + } + + @Test + public void typeParamMismatch() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "import java.util.Optional;", + "", + "class Baz<T> {", + " Baz(T param) {}", + "", + " @AutoBuilder", + " interface Builder<E> {", + " abstract Builder<E> param(E param);", + " abstract Baz<E> build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderTypeParams] Builder type parameters <E> must match type parameters <T> of" + + " Baz(T param)") + .inFile(javaFileObject) + .onLineContaining("interface Builder<E>"); + } +} diff --git a/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java index 63e84199..788b543a 100644 --- a/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java +++ b/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java @@ -43,7 +43,9 @@ public class AutoOneOfCompilationTest { "import java.io.Serializable;", "", "@AutoOneOf(TaskResult.Kind.class)", - "public abstract class TaskResult<V, T extends Throwable> {", + "public abstract class TaskResult<V, T extends Throwable> implements Serializable {", + " private static final long serialVersionUID = 1234L;", + "", " public enum Kind {VALUE, EXCEPTION, EMPTY}", " public abstract Kind getKind();", "", @@ -92,6 +94,8 @@ public class AutoOneOfCompilationTest { " // Parent class that each implementation will inherit from.", " private abstract static class Parent_<V, T extends Throwable> " + "extends TaskResult<V, T> {", + " private static final long serialVersionUID = 1234L;", + "", " @Override", " public V value() {", " throw new UnsupportedOperationException(getKind().toString());", @@ -111,6 +115,8 @@ public class AutoOneOfCompilationTest { " // Implementation when the contained property is \"value\".", " private static final class Impl_value<V, T extends Throwable> " + "extends Parent_<V, T> {", + " private static final long serialVersionUID = 1234L;", + "", " private final V value;", "", " Impl_value(V value) {", @@ -152,6 +158,8 @@ public class AutoOneOfCompilationTest { " // Implementation when the contained property is \"exception\".", " private static final class Impl_exception<V, T extends Throwable> " + "extends Parent_<V, T> {", + " private static final long serialVersionUID = 1234L;", + "", " private final Throwable exception;", "", " Impl_exception(Throwable exception) {", @@ -193,6 +201,8 @@ public class AutoOneOfCompilationTest { " // Implementation when the contained property is \"empty\".", " private static final class Impl_empty<V, T extends Throwable> " + "extends Parent_<V, T> {", + " private static final long serialVersionUID = 1234L;", + "", " static final Impl_empty<?, ?> INSTANCE = new Impl_empty<>();", "", " private Impl_empty() {}", @@ -200,6 +210,10 @@ public class AutoOneOfCompilationTest { " @Override", " public void empty() {}", "", + " private Object readResolve() {", + " return INSTANCE;", + " }", + "", " @Override", " public String toString() {", " return \"TaskResult{empty}\";", @@ -223,7 +237,8 @@ public class AutoOneOfCompilationTest { Compilation compilation = javac() .withProcessors(new AutoOneOfProcessor()) - .withOptions("-Xlint:-processing", "-implicit:none") + .withOptions( + "-Xlint:-processing", "-implicit:none", "-A" + Nullables.NULLABLE_OPTION + "=") .compile(javaFileObject); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -307,7 +322,8 @@ public class AutoOneOfCompilationTest { Compilation compilation = javac() .withProcessors(new AutoOneOfProcessor()) - .withOptions("-Xlint:-processing", "-implicit:none") + .withOptions( + "-Xlint:-processing", "-implicit:none", "-A" + Nullables.NULLABLE_OPTION + "=") .compile(javaFileObject); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) 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 e46c9f51..ab6690fd 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 @@ -57,8 +57,8 @@ public class AutoValueCompilationTest { public void simpleSuccess() { // Positive test case that ensures we generate the expected code for at least one case. // Most AutoValue code-generation tests are functional, meaning that we check that the generated - // code does the right thing rather than checking what it looks like, but this test is a sanity - // check that we are not generating correct but weird code. + // code does the right thing rather than checking what it looks like, but this test checks that + // we are not generating correct but weird code. JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( "foo.bar.Baz", @@ -118,7 +118,10 @@ public class AutoValueCompilationTest { " }", "}"); Compilation compilation = - javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject); + javac() + .withProcessors(new AutoValueProcessor()) + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") + .compile(javaFileObject); assertThat(compilation) .generatedSourceFile("foo.bar.AutoValue_Baz") .hasSourceEquivalentTo(expectedOutput); @@ -216,7 +219,10 @@ public class AutoValueCompilationTest { " }", "}"); Compilation compilation = - javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject); + javac() + .withProcessors(new AutoValueProcessor()) + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") + .compile(javaFileObject); assertThat(compilation) .generatedSourceFile("foo.bar.AutoValue_Baz") .hasSourceEquivalentTo(expectedOutput); @@ -335,7 +341,6 @@ public class AutoValueCompilationTest { " return false;", " }", "", - " @Override", " public int hashCode() {", " int h$ = 1;", @@ -348,7 +353,8 @@ public class AutoValueCompilationTest { Compilation compilation = javac() .withProcessors(new AutoValueProcessor()) - .withOptions("-Xlint:-processing", "-implicit:none") + .withOptions( + "-Xlint:-processing", "-implicit:none", "-A" + Nullables.NULLABLE_OPTION + "=") .compile(annotFileObject, outerFileObject, nestyFileObject); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -1129,10 +1135,10 @@ public class AutoValueCompilationTest { " return this.anInt == that.anInt()", " && Arrays.equals(this.aByteArray, " + "(that instanceof AutoValue_Baz) " - + "? ((AutoValue_Baz) that).aByteArray : that.aByteArray())", + + "? ((AutoValue_Baz<?>) that).aByteArray : that.aByteArray())", " && Arrays.equals(this.aNullableIntArray, " + "(that instanceof AutoValue_Baz) " - + "? ((AutoValue_Baz) that).aNullableIntArray : that.aNullableIntArray())", + + "? ((AutoValue_Baz<?>) that).aNullableIntArray : that.aNullableIntArray())", " && this.aList.equals(that.aList())", " && this.anImmutableList.equals(that.anImmutableList())", " && this.anOptionalString.equals(that.anOptionalString())", @@ -1312,17 +1318,19 @@ public class AutoValueCompilationTest { + "NestedAutoValue.builder();", " this.aNestedAutoValue = aNestedAutoValue$builder.build();", " }", - " String missing = \"\";", - " if (this.anInt == null) {", - " missing += \" anInt\";", - " }", - " if (this.aByteArray == null) {", - " missing += \" aByteArray\";", - " }", - " if (this.aList == null) {", - " missing += \" aList\";", - " }", - " if (!missing.isEmpty()) {", + " if (this.anInt == null", + " || this.aByteArray == null", + " || this.aList == null) {", + " StringBuilder missing = new StringBuilder();", + " if (this.anInt == null) {", + " 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>(", @@ -1339,7 +1347,8 @@ public class AutoValueCompilationTest { Compilation compilation = javac() .withProcessors(new AutoValueProcessor()) - .withOptions("-Xlint:-processing", "-implicit:none") + .withOptions( + "-Xlint:-processing", "-implicit:none", "-A" + Nullables.NULLABLE_OPTION + "=") .compile(javaFileObject, nestedJavaFileObject); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -1587,7 +1596,7 @@ public class AutoValueCompilationTest { assertThat(compilation) .hadErrorContaining( "Parameter type java.lang.String of setter method should be int " - + "to match getter foo.bar.Baz.blim") + + "to match property method foo.bar.Baz.blim()") .inFile(javaFileObject) .onLineContaining("Builder blim(String x)"); } @@ -1620,10 +1629,10 @@ public class AutoValueCompilationTest { .compile(javaFileObject); assertThat(compilation) .hadErrorContaining( - "Parameter type java.lang.String of setter method should be " - + "com.google.common.collect.ImmutableList<java.lang.String> to match getter " - + "foo.bar.Baz.blam, or it should be a type that can be passed to " - + "ImmutableList.copyOf") + "Parameter type java.lang.String of setter method should be" + + " com.google.common.collect.ImmutableList<java.lang.String> to match property" + + " method foo.bar.Baz.blam(), or it should be a type that can be passed to" + + " ImmutableList.copyOf") .inFile(javaFileObject) .onLineContaining("Builder blam(String x)"); } @@ -1657,11 +1666,11 @@ public class AutoValueCompilationTest { .compile(javaFileObject); assertThat(compilation) .hadErrorContaining( - "Parameter type java.util.Collection<java.lang.Integer> of setter method should be " - + "com.google.common.collect.ImmutableList<java.lang.String> to match getter " - + "foo.bar.Baz.blam, or it should be a type that can be passed to " - + "ImmutableList.copyOf to produce " - + "com.google.common.collect.ImmutableList<java.lang.String>") + "Parameter type java.util.Collection<java.lang.Integer> of setter method should be" + + " com.google.common.collect.ImmutableList<java.lang.String> to match property" + + " method foo.bar.Baz.blam(), or it should be a type that can be passed to" + + " ImmutableList.copyOf to produce" + + " com.google.common.collect.ImmutableList<java.lang.String>") .inFile(javaFileObject) .onLineContaining("Builder blam(Collection<Integer> x)"); } @@ -1694,7 +1703,7 @@ public class AutoValueCompilationTest { assertThat(compilation) .hadErrorContaining( "Parameter type java.lang.String of setter method should be int " - + "to match getter foo.bar.Baz.getBlim") + + "to match property method foo.bar.Baz.getBlim()") .inFile(javaFileObject) .onLineContaining("Builder blim(String x)"); } @@ -1768,7 +1777,8 @@ public class AutoValueCompilationTest { .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor()) .compile(javaFileObject); assertThat(compilation) - .hadErrorContaining("Method does not correspond to a property of foo.bar.Item") + .hadErrorContaining( + "Method setTitle does not correspond to a property method of foo.bar.Item") .inFile(javaFileObject) .onLineContaining("Builder setTitle(String title)"); assertThat(compilation) @@ -1802,7 +1812,7 @@ public class AutoValueCompilationTest { .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor()) .compile(javaFileObject); assertThat(compilation) - .hadErrorContaining("Method does not correspond to a property of foo.bar.Baz") + .hadErrorContaining("Method blim does not correspond to a property method of foo.bar.Baz") .inFile(javaFileObject) .onLineContaining("Builder blim(int x)"); } @@ -1839,6 +1849,35 @@ public class AutoValueCompilationTest { } @Test + public void autoValueBuilderSetterReturnType() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "", + "@AutoValue", + "public abstract class Baz {", + " abstract int blim();", + "", + " @AutoValue.Builder", + " public interface Builder {", + " void blim(int x);", + " Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor()) + .compile(javaFileObject); + assertThat(compilation) + .hadErrorContaining("Setter methods must return foo.bar.Baz.Builder") + .inFile(javaFileObject) + .onLineContaining("void blim(int x)"); + } + + @Test public void autoValueBuilderWrongTypeGetter() { JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( @@ -1866,10 +1905,15 @@ public class AutoValueCompilationTest { .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor()) .compile(javaFileObject); assertThat(compilation) - .hadErrorContaining( - "Method matches a property of foo.bar.Baz but has return type T instead of U") + .hadErrorContainingMatch( + "Method matches a property of foo\\.bar\\.Baz<T, ?U> but has return type T instead of" + + " U") .inFile(javaFileObject) .onLineContaining("T blam()"); + // The <T, ?U> is because we're depending on TypeMirror.toString(), and the JDK actually spells + // this as <T,U> with no space. While it's not completely sound to expect a given string from + // TypeMirror.toString(), in practice it's hard to imagine that it would be anything other + // than "foo.bar.Baz<T,U>" or "foo.bar.Baz<T, U>" given the specification. } @Test @@ -1929,9 +1973,9 @@ public class AutoValueCompilationTest { .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor()) .compile(javaFileObject); assertThat(compilation) - .hadErrorContaining("Property strings has a property builder so it cannot be @Nullable") + .hadErrorContaining("Property strings is @Nullable so it cannot have a property builder") .inFile(javaFileObject) - .onLineContaining("@Nullable ImmutableList<String> strings()"); + .onLineContaining("stringsBuilder()"); } @Test @@ -1963,9 +2007,9 @@ public class AutoValueCompilationTest { .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor()) .compile(javaFileObject); assertThat(compilation) - .hadErrorContaining("Property strings has a property builder so it cannot be @Nullable") + .hadErrorContaining("Property strings is @Nullable so it cannot have a property builder") .inFile(javaFileObject) - .onLineContaining("@Nullable ImmutableList<String> strings()"); + .onLineContaining("stringsBuilder()"); } @Test @@ -2451,8 +2495,8 @@ public class AutoValueCompilationTest { assertThat(compilation) .hadErrorContaining( "Method without arguments should be a build method returning foo.bar.Baz, or a getter" - + " method with the same name and type as a getter method of foo.bar.Baz, or" - + " fooBuilder() where foo() or getFoo() is a getter method of foo.bar.Baz") + + " method with the same name and type as a property method of foo.bar.Baz, or" + + " fooBuilder() where foo() or getFoo() is a property method of foo.bar.Baz") .inFile(javaFileObject) .onLineContaining("Builder whut()"); } @@ -2481,7 +2525,7 @@ public class AutoValueCompilationTest { .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor()) .compile(javaFileObject); assertThat(compilation) - .hadErrorContaining("Method does not correspond to a property of foo.bar.Baz") + .hadErrorContaining("Method whut does not correspond to a property method of foo.bar.Baz") .inFile(javaFileObject) .onLineContaining("void whut(String x)"); } @@ -2539,7 +2583,8 @@ public class AutoValueCompilationTest { .compile(javaFileObject); assertThat(compilation) .hadErrorContaining( - "Builder must have a single no-argument method returning foo.bar.Baz<T>") + "Builder must have a single no-argument method, typically called build(), that returns" + + " foo.bar.Baz<T>") .inFile(javaFileObject) .onLineContaining("public interface Builder<T>"); } @@ -2569,11 +2614,15 @@ public class AutoValueCompilationTest { .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor()) .compile(javaFileObject); assertThat(compilation) - .hadErrorContaining("Builder must have a single no-argument method returning foo.bar.Baz") + .hadErrorContaining( + "Builder must have a single no-argument method, typically called build(), that returns" + + " foo.bar.Baz") .inFile(javaFileObject) .onLineContaining("Baz build()"); assertThat(compilation) - .hadErrorContaining("Builder must have a single no-argument method returning foo.bar.Baz") + .hadErrorContaining( + "Builder must have a single no-argument method, typically called build(), that returns" + + " foo.bar.Baz") .inFile(javaFileObject) .onLineContaining("Baz create()"); } @@ -3286,7 +3335,7 @@ public class AutoValueCompilationTest { "}"); private static final String GENERATED_PROPERTY_TYPE = String.join( - "\n", + "\n", // "package foo.baz;", "", "public class GeneratedPropertyType {}"); @@ -3315,18 +3364,14 @@ public class AutoValueCompilationTest { GENERATED_TYPES.forEach( (typeName, source) -> { try { - JavaFileObject generated = - processingEnv - .getFiler() - .createSourceFile(typeName); + JavaFileObject generated = processingEnv.getFiler().createSourceFile(typeName); try (Writer writer = generated.openWriter()) { writer.write(source); } } catch (IOException e) { throw new UncheckedIOException(e); } - } - ); + }); } return false; } @@ -3337,7 +3382,41 @@ public class AutoValueCompilationTest { } } + // This is a regression test for the problem described in + // https://github.com/google/auto/issues/1087. + @Test + public void kotlinMetadataAnnotationsAreImplicitlyExcludedFromCopying() { + JavaFileObject metadata = + JavaFileObjects.forSourceLines( + "kotlin.Metadata", "package kotlin;", "", "public @interface Metadata {", "}"); + JavaFileObject test = + JavaFileObjects.forSourceLines( + "foo.bar.Test", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "import kotlin.Metadata;", + "", + "@AutoValue.CopyAnnotations", + "@Metadata", + "@AutoValue", + "public abstract class Test {", + " public abstract String string();", + "}"); + AutoValueProcessor autoValueProcessor = new AutoValueProcessor(); + Compilation compilation = + javac() + .withProcessors(autoValueProcessor) + .withOptions("-Xlint:-processing", "-implicit:none") + .compile(test, metadata); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(compilation) + .generatedSourceFile("foo.bar.AutoValue_Test") + .contentsAsUtf8String() + .doesNotContain("kotlin.Metadata"); + } + private String sorted(String... imports) { - return Arrays.stream(imports).sorted().collect(joining("\n")); - } + return Arrays.stream(imports).sorted().collect(joining("\n")); + } } diff --git a/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java b/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java index ce9eeed0..56eaad25 100644 --- a/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java +++ b/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java @@ -172,6 +172,7 @@ public class ExtensionTest { Compilation compilation = javac() .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension()))) + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") .compile(javaFileObject); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -250,9 +251,7 @@ public class ExtensionTest { " abstract String dizzle();", "}"); Compilation compilation = - javac() - .withProcessors(new AutoValueProcessor(ImmutableList.of(ext1, ext2))) - .compile(impl); + javac().withProcessors(new AutoValueProcessor(ImmutableList.of(ext1, ext2))).compile(impl); assertThat(compilation) .hadErrorContaining("wants to consume a method that was already consumed") .inFile(impl) @@ -596,10 +595,9 @@ public class ExtensionTest { "}"); Compilation compilation = javac() - .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension()))) - .compile(javaFileObject); - assertThat(compilation) - .hadErrorContaining("writeToParcel"); + .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension()))) + .compile(javaFileObject); + assertThat(compilation).hadErrorContaining("writeToParcel"); assertThat(compilation) .hadWarningContaining( "Abstract method is neither a property getter nor a Builder converter, " @@ -647,13 +645,12 @@ public class ExtensionTest { "public abstract class Baz {", "}"); Compilation compilation = - javac() - .withProcessors(new AutoValueProcessor(badJarLoader)) - .compile(javaFileObject); + javac().withProcessors(new AutoValueProcessor(badJarLoader)).compile(javaFileObject); assertThat(compilation).succeeded(); - assertThat(compilation).hadWarningContaining( - "This may be due to a corrupt jar file in the compiler's classpath.\n " - + ServiceConfigurationError.class.getName()); + assertThat(compilation) + .hadWarningContaining( + "This may be due to a corrupt jar file in the compiler's classpath.\n " + + ServiceConfigurationError.class.getName()); assertThat(compilation).generatedSourceFile("foo.bar.AutoValue_Baz"); } @@ -857,8 +854,12 @@ public class ExtensionTest { String sideClassName = "Side_" + context.autoValueClass().getSimpleName(); String sideClass = "" // - + "package " + context.packageName() + ";\n" - + "class " + sideClassName + " {}\n"; + + "package " + + context.packageName() + + ";\n" + + "class " + + sideClassName + + " {}\n"; Filer filer = context.processingEnvironment().getFiler(); try { String sideClassFqName = context.packageName() + "." + sideClassName; @@ -912,25 +913,27 @@ public class ExtensionTest { @Test public void propertyTypes() { - JavaFileObject parent = JavaFileObjects.forSourceLines( - "foo.bar.Parent", - "package foo.bar;", - "", - "import java.util.List;", - "", - "interface Parent<T> {", - " T thing();", - " List<T> list();", - "}"); - JavaFileObject autoValueClass = JavaFileObjects.forSourceLines( - "foo.bar.Baz", - "package foo.bar;", - "", - "import com.google.auto.value.AutoValue;", - "", - "@AutoValue", - "abstract class Baz implements Parent<String> {", - "}"); + JavaFileObject parent = + JavaFileObjects.forSourceLines( + "foo.bar.Parent", + "package foo.bar;", + "", + "import java.util.List;", + "", + "interface Parent<T> {", + " T thing();", + " List<T> list();", + "}"); + JavaFileObject autoValueClass = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "", + "@AutoValue", + "abstract class Baz implements Parent<String> {", + "}"); ContextChecker checker = context -> { assertThat(context.builder()).isEmpty(); @@ -955,15 +958,16 @@ public class ExtensionTest { @Test public void finalAutoValueClassName() { - JavaFileObject autoValueClass = JavaFileObjects.forSourceLines( - "foo.bar.Baz", - "package foo.bar;", - "", - "import com.google.auto.value.AutoValue;", - "", - "@AutoValue", - "abstract class Baz {", - "}"); + JavaFileObject autoValueClass = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "", + "@AutoValue", + "abstract class Baz {", + "}"); ContextChecker checker = context -> { assertThat(context.finalAutoValueClassName()).isEqualTo("foo.bar.AutoValue_Baz"); @@ -982,43 +986,45 @@ public class ExtensionTest { @Test public void builderContext() { - JavaFileObject parent = JavaFileObjects.forSourceLines( - "foo.bar.Parent", - "package foo.bar;", - "", - "import com.google.common.collect.ImmutableList;", - "", - "interface Parent<T> {", - " T thing();", - " ImmutableList<T> list();", - "}"); - JavaFileObject autoValueClass = JavaFileObjects.forSourceLines( - "foo.bar.Baz", - "package foo.bar;", - "", - "import com.google.auto.value.AutoValue;", - "import com.google.common.collect.ImmutableList;", - "", - "@AutoValue", - "abstract class Baz implements Parent<String> {", - " static Builder builder() {", - " return new AutoValue_Baz.Builder();", - " }", - "", - " abstract Builder toBuilder();", - "", - " @AutoValue.Builder", - " abstract static class Builder {", - " abstract Builder setThing(String x);", - " abstract Builder setList(Iterable<String> x);", - " abstract Builder setList(ImmutableList<String> x);", - " abstract ImmutableList.Builder<String> listBuilder();", - " abstract Baz autoBuild();", - " Baz build() {", - " return autoBuild();", - " }", - " }", - "}"); + JavaFileObject parent = + JavaFileObjects.forSourceLines( + "foo.bar.Parent", + "package foo.bar;", + "", + "import com.google.common.collect.ImmutableList;", + "", + "interface Parent<T> {", + " T thing();", + " ImmutableList<T> list();", + "}"); + JavaFileObject autoValueClass = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "import com.google.common.collect.ImmutableList;", + "", + "@AutoValue", + "abstract class Baz implements Parent<String> {", + " static Builder builder() {", + " return new AutoValue_Baz.Builder();", + " }", + "", + " abstract Builder toBuilder();", + "", + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setThing(String x);", + " abstract Builder setList(Iterable<String> x);", + " abstract Builder setList(ImmutableList<String> x);", + " abstract ImmutableList.Builder<String> listBuilder();", + " abstract Baz autoBuild();", + " Baz build() {", + " return autoBuild();", + " }", + " }", + "}"); ContextChecker checker = context -> { assertThat(context.builder()).isPresent(); @@ -1075,27 +1081,102 @@ public class ExtensionTest { } @Test + public void builderContextWithInheritance() { + JavaFileObject parent = + JavaFileObjects.forSourceLines( + "foo.bar.Parent", + "package foo.bar;", + "", + "interface Parent<BuilderT> {", + " BuilderT toBuilder();", + " interface Builder<T, BuilderT, BuiltT> {", + " BuilderT setThing(T x);", + " BuiltT build();", + " }", + "}"); + JavaFileObject autoValueClass = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "", + "@AutoValue", + "abstract class Baz<T> implements Parent<Baz.Builder<T>> {", + " abstract T thing();", + " static <T> Builder<T> builder() {", + " return new AutoValue_Baz.Builder<>();", + " }", + "", + " @AutoValue.Builder", + " abstract static class Builder<T> implements Parent.Builder<T, Builder<T>, Baz<T>> {", + " }", + "}"); + ContextChecker checker = + context -> { + assertThat(context.builder()).isPresent(); + BuilderContext builderContext = context.builder().get(); + + assertThat(builderContext.builderType().getQualifiedName().toString()) + .isEqualTo("foo.bar.Baz.Builder"); + + Set<ExecutableElement> builderMethods = builderContext.builderMethods(); + assertThat(builderMethods).hasSize(1); + ExecutableElement builderMethod = Iterables.getOnlyElement(builderMethods); + assertThat(builderMethod.getSimpleName().toString()).isEqualTo("builder"); + + Set<ExecutableElement> toBuilderMethods = builderContext.toBuilderMethods(); + assertThat(toBuilderMethods).hasSize(1); + ExecutableElement toBuilderMethod = Iterables.getOnlyElement(toBuilderMethods); + assertThat(toBuilderMethod.getSimpleName().toString()).isEqualTo("toBuilder"); + + Optional<ExecutableElement> buildMethod = builderContext.buildMethod(); + assertThat(buildMethod).isPresent(); + assertThat(buildMethod.get().getSimpleName().toString()).isEqualTo("build"); + assertThat(buildMethod.get().getParameters()).isEmpty(); + assertThat(buildMethod.get().getReturnType().toString()).isEqualTo("BuiltT"); + + ExecutableElement autoBuildMethod = builderContext.autoBuildMethod(); + assertThat(autoBuildMethod).isEqualTo(buildMethod.get()); + + Map<String, Set<ExecutableElement>> setters = builderContext.setters(); + assertThat(setters.keySet()).containsExactly("thing"); + Set<ExecutableElement> thingSetters = setters.get("thing"); + assertThat(thingSetters).hasSize(1); + ExecutableElement thingSetter = Iterables.getOnlyElement(thingSetters); + assertThat(thingSetter.getSimpleName().toString()).isEqualTo("setThing"); + }; + ContextCheckingExtension extension = new ContextCheckingExtension(checker); + Compilation compilation = + javac() + .withProcessors(new AutoValueProcessor(ImmutableList.of(extension))) + .compile(autoValueClass, parent); + assertThat(compilation).succeededWithoutWarnings(); + } + + @Test public void oddBuilderContext() { - JavaFileObject autoValueClass = JavaFileObjects.forSourceLines( - "foo.bar.Baz", - "package foo.bar;", - "", - "import com.google.auto.value.AutoValue;", - "import com.google.common.collect.ImmutableList;", - "", - "@AutoValue", - "abstract class Baz {", - " abstract String string();", - "", - " @AutoValue.Builder", - " abstract static class Builder {", - " abstract Builder setString(String x);", - " abstract Baz oddBuild();", - " Baz build(int butNotReallyBecauseOfThisParameter) {", - " return null;", - " }", - " }", - "}"); + JavaFileObject autoValueClass = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "import com.google.common.collect.ImmutableList;", + "", + "@AutoValue", + "abstract class Baz {", + " abstract String string();", + "", + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setString(String x);", + " abstract Baz oddBuild();", + " Baz build(int butNotReallyBecauseOfThisParameter) {", + " return null;", + " }", + " }", + "}"); ContextChecker checker = context -> { assertThat(context.builder()).isPresent(); @@ -1126,25 +1207,26 @@ public class ExtensionTest { // https://github.com/google/auto/issues/809 @Test public void propertyErrorShouldNotCrash() { - JavaFileObject autoValueClass = JavaFileObjects.forSourceLines( - "test.Test", - "package test;", - "import com.google.auto.value.AutoValue;", - "import java.util.List;", - "", - "@AutoValue", - "public abstract class Test {", - " abstract Integer property();", - " abstract List<String> listProperty();", - "", - " @AutoValue.Builder", - " public interface Builder {", - " Builder property(Integer property);", - " Builder listProperty(List<String> listProperty);", - " Builder listProperty(Integer listPropertyValues);", - " Test build();", - " }", - "}"); + JavaFileObject autoValueClass = + JavaFileObjects.forSourceLines( + "test.Test", + "package test;", + "import com.google.auto.value.AutoValue;", + "import java.util.List;", + "", + "@AutoValue", + "public abstract class Test {", + " abstract Integer property();", + " abstract List<String> listProperty();", + "", + " @AutoValue.Builder", + " public interface Builder {", + " Builder property(Integer property);", + " Builder listProperty(List<String> listProperty);", + " Builder listProperty(Integer listPropertyValues);", + " Test build();", + " }", + "}"); // We don't actually expect the extension to be invoked. Previously it was, and that led to a // NullPointerException when calling .setters() in the checker. ContextChecker checker = 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 f3d3d611..18cca5e4 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 @@ -48,6 +48,8 @@ import org.junit.runners.Parameterized.Parameters; */ @RunWith(Parameterized.class) public class GeneratedDoesNotExistTest { + private static final ImmutableList<String> STANDARD_OPTIONS = + ImmutableList.of("-A" + Nullables.NULLABLE_OPTION + "="); @Parameters(name = "{0}") public static Collection<Object[]> data() { @@ -57,12 +59,16 @@ public class GeneratedDoesNotExistTest { // TODO(b/72513371): use --release 8 once compile-testing supports that params.add( new Object[] { - ImmutableList.of(), "javax.annotation.processing.Generated", + STANDARD_OPTIONS, "javax.annotation.processing.Generated", }); } params.add( new Object[] { - ImmutableList.of("-source", "8", "-target", "8"), "javax.annotation.Generated", + ImmutableList.<String>builder() + .addAll(STANDARD_OPTIONS) + .add("-source", "8", "-target", "8") + .build(), + "javax.annotation.Generated", }); return params.build(); } @@ -223,9 +229,9 @@ public class GeneratedDoesNotExistTest { Processor noGeneratedProcessor = partialProxy(Processor.class, handler); Compilation compilation = javac() - .withOptions(javacOptions) - .withProcessors(noGeneratedProcessor) - .compile(javaFileObject); + .withOptions(javacOptions) + .withProcessors(noGeneratedProcessor) + .compile(javaFileObject); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) .generatedSourceFile("foo.bar.AutoValue_Baz") diff --git a/value/src/test/java/com/google/auto/value/processor/IncrementalExtensionTest.java b/value/src/test/java/com/google/auto/value/processor/IncrementalExtensionTest.java index 27cb0936..472f62db 100644 --- a/value/src/test/java/com/google/auto/value/processor/IncrementalExtensionTest.java +++ b/value/src/test/java/com/google/auto/value/processor/IncrementalExtensionTest.java @@ -22,6 +22,7 @@ import com.google.auto.value.extension.AutoValueExtension; import com.google.auto.value.extension.AutoValueExtension.IncrementalExtensionType; import com.google.auto.value.extension.memoized.processor.MemoizeExtension; import com.google.auto.value.extension.serializable.processor.SerializableAutoValueExtension; +import com.google.auto.value.extension.toprettystring.processor.ToPrettyStringExtension; import com.google.common.collect.ImmutableList; import javax.annotation.processing.ProcessingEnvironment; import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; @@ -46,7 +47,10 @@ public class IncrementalExtensionTest { // different <?>. assertThat(builtInExtensions) .comparingElementsUsing(transforming(e -> (Object) e.getClass(), "is class")) - .containsExactly(MemoizeExtension.class, SerializableAutoValueExtension.class); + .containsExactly( + MemoizeExtension.class, + SerializableAutoValueExtension.class, + ToPrettyStringExtension.class); AutoValueProcessor processor = new AutoValueProcessor(builtInExtensions); assertThat(processor.getSupportedOptions()) @@ -58,10 +62,12 @@ public class IncrementalExtensionTest { AutoValueExtension nonIsolatingExtension = new NonIsolatingExtension(); assertThat(nonIsolatingExtension.incrementalType((ProcessingEnvironment) null)) .isEqualTo(IncrementalExtensionType.UNKNOWN); - ImmutableList<AutoValueExtension> extensions = ImmutableList.<AutoValueExtension>builder() - .addAll(AutoValueProcessor.extensionsFromLoader(AutoValueProcessor.class.getClassLoader())) - .add(nonIsolatingExtension) - .build(); + ImmutableList<AutoValueExtension> extensions = + ImmutableList.<AutoValueExtension>builder() + .addAll( + AutoValueProcessor.extensionsFromLoader(AutoValueProcessor.class.getClassLoader())) + .add(nonIsolatingExtension) + .build(); AutoValueProcessor processor = new AutoValueProcessor(extensions); assertThat(processor.getSupportedOptions()) @@ -73,10 +79,12 @@ public class IncrementalExtensionTest { AutoValueExtension isolatingExtension = new IsolatingExtension(); assertThat(isolatingExtension.incrementalType((ProcessingEnvironment) null)) .isEqualTo(IncrementalExtensionType.ISOLATING); - ImmutableList<AutoValueExtension> extensions = ImmutableList.<AutoValueExtension>builder() - .addAll(AutoValueProcessor.extensionsFromLoader(AutoValueProcessor.class.getClassLoader())) - .add(isolatingExtension) - .build(); + ImmutableList<AutoValueExtension> extensions = + ImmutableList.<AutoValueExtension>builder() + .addAll( + AutoValueProcessor.extensionsFromLoader(AutoValueProcessor.class.getClassLoader())) + .add(isolatingExtension) + .build(); AutoValueProcessor processor = new AutoValueProcessor(extensions); assertThat(processor.getSupportedOptions()) 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 new file mode 100644 index 00000000..9e345f53 --- /dev/null +++ b/value/src/test/java/com/google/auto/value/processor/NullablesTest.java @@ -0,0 +1,179 @@ +/* + * Copyright 2021 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.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; +import static com.google.common.truth.OptionalSubject.optionals; +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.util.stream.Collectors.partitioningBy; + +import com.google.auto.common.MoreTypes; +import com.google.common.collect.ImmutableList; +import com.google.common.truth.Expect; +import com.google.testing.compile.Compilation; +import com.google.testing.compile.Compiler; +import com.google.testing.compile.JavaFileObjects; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.util.ElementFilter; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class NullablesTest { + @Rule public Expect expect = Expect.create(); + + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + public @interface Nullable {} + + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + public @interface Irrelevant {} + + // The class here has various methods that we will examine with + // Nullables.nullableMentionedInMethods to ensure that we do indeed detect @Nullable annotations + // in various contexts. + // This test is a lot more complicated than it should be. Ideally we would just have this Methods + // class be an actual class nested inside this test, and we would use CompilationRule to get + // the corresponding TypeElement so we could check the various methods. Unfortunately, if we + // do that then we get a TypeElement where all type annotations have disappeared because of + // https://bugs.openjdk.java.net/browse/JDK-8225377. So instead we have to use Compiler to compile + // the code here with a special annotation processor that will check the annotations of the + // just-compiled class. Since the processor is running as part of the same compilation, we don't + // lose the type annotations. + + private static final ImmutableList<String> METHOD_LINES = + ImmutableList.of( + // Methods in this class whose names begin with "no" do not mention @Nullable anywhere.", + // All other methods do.", + "package foo.bar;", + "", + "import " + Irrelevant.class.getCanonicalName() + ";", + "import " + Nullable.class.getCanonicalName() + ";", + "import java.util.List;", + "", + "abstract class Methods {", + " void noAnnotations() {}", + " abstract int noAnnotationsEither(int x);", + " abstract @Irrelevant String noRelevantAnnotations(@Irrelevant int x);", + " abstract @Nullable String nullableString();", + " abstract String @Nullable [] nullableArrayOfString();", + " abstract @Nullable String[] arrayOfNullableString();", + " abstract @Nullable String @Nullable [] nullableArrayOfNullableString();", + " abstract List<@Nullable String> listOfNullableString();", + " abstract List<? extends @Nullable Object> listOfExtendsNullable();", + " abstract List<? super @Nullable Number> listOfSuperNullable();", + " abstract <T extends @Nullable Object> T nullableTypeParamBound();", + " abstract <T> @Nullable T nullableTypeParamRef();", + " void nullableParam(@Nullable String x) {}", + " void nullableParamBound(List<? extends @Nullable String> x) {}", + "}"); + + @Test + public void nullableMentionedInMethods() { + // 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. + double javaVersion = Double.parseDouble(JAVA_SPECIFICATION_VERSION.value()); + assume().that(javaVersion).isAtLeast(9.0); + NullableProcessor processor = new NullableProcessor(expect); + Compilation compilation = + Compiler.javac() + .withProcessors(processor) + .compile(JavaFileObjects.forSourceLines("foo.bar.Methods", METHOD_LINES)); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(processor.ran).isTrue(); + // If any `expect` calls failed then the test will fail now because of the Expect rule. + } + + @SupportedAnnotationTypes("*") + private static class NullableProcessor extends AbstractProcessor { + + private final Expect expect; + boolean ran; + + NullableProcessor(Expect expect) { + this.expect = expect; + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + if (roundEnv.processingOver()) { + TypeElement methodsElement = + processingEnv.getElementUtils().getTypeElement("foo.bar.Methods"); + expect.that(methodsElement).isNotNull(); + + List<ExecutableElement> methods = + ElementFilter.methodsIn(methodsElement.getEnclosedElements()); + Map<Boolean, List<ExecutableElement>> partitionedMethods = + methods.stream() + .collect(partitioningBy(p -> !p.getSimpleName().toString().startsWith("no"))); + List<ExecutableElement> nullableMethods = partitionedMethods.get(true); + List<ExecutableElement> notNullableMethods = partitionedMethods.get(false); + + expect + .about(optionals()) + .that(Nullables.nullableMentionedInMethods(notNullableMethods)) + .isEmpty(); + + TypeElement nullableElement = + processingEnv.getElementUtils().getTypeElement(Nullable.class.getCanonicalName()); + expect.that(nullableElement).isNotNull(); + DeclaredType nullableType = MoreTypes.asDeclared(nullableElement.asType()); + + for (ExecutableElement nullableMethod : nullableMethods) { + // Make a list with all the methods that don't have @Nullable plus one method that does. + ImmutableList<ExecutableElement> notNullablePlusNullable = + ImmutableList.<ExecutableElement>builder() + .addAll(notNullableMethods) + .add(nullableMethod) + .build(); + expect + .withMessage("method %s should have @Nullable", nullableMethod) + .about(optionals()) + .that( + Nullables.nullableMentionedInMethods(notNullablePlusNullable) + .map(AnnotationMirror::getAnnotationType)) + .hasValue(nullableType); + } + ran = true; + } + return false; + } + } +} diff --git a/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java b/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java index 2c3bea0d..48d8cd6e 100644 --- a/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java +++ b/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java @@ -15,12 +15,13 @@ */ package com.google.auto.value.processor; -import static com.google.common.truth.Truth.assertAbout; -import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; +import static com.google.testing.compile.CompilationSubject.assertThat; +import static com.google.testing.compile.Compiler.javac; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; +import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; @@ -40,8 +41,7 @@ import org.junit.runners.JUnit4; */ @RunWith(JUnit4.class) public class PropertyAnnotationsTest { - private static final String TEST_ANNOTATION = - "@PropertyAnnotationsTest.TestAnnotation"; + private static final String TEST_ANNOTATION = "@PropertyAnnotationsTest.TestAnnotation"; private static final String TEST_ARRAY_ANNOTATION = "@PropertyAnnotationsTest.TestArrayAnnotation"; @@ -272,12 +272,15 @@ public class PropertyAnnotationsTest { .addMethodAnnotations(expectedMethodAnnotations) .build(); - assertAbout(javaSource()) - .that(javaFileObject) - .processedWith(new AutoValueProcessor()) - .compilesWithoutError() - .and() - .generatesSources(expectedOutput); + Compilation compilation = + javac() + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") + .withProcessors(new AutoValueProcessor()) + .compile(javaFileObject); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("foo.bar.AutoValue_Baz") + .hasSourceEquivalentTo(expectedOutput); } @Test @@ -445,10 +448,12 @@ public class PropertyAnnotationsTest { assertGeneratedMatches( getImports(PropertyAnnotationsTest.class), ImmutableList.of( - TEST_ARRAY_ANNOTATION + "(testEnums = {PropertyAnnotationsTest.TestEnum.A," + TEST_ARRAY_ANNOTATION + + "(testEnums = {PropertyAnnotationsTest.TestEnum.A," + " PropertyAnnotationsTest.TestEnum.B})"), ImmutableList.of( - TEST_ARRAY_ANNOTATION + "(testEnums = {PropertyAnnotationsTest.TestEnum.A," + TEST_ARRAY_ANNOTATION + + "(testEnums = {PropertyAnnotationsTest.TestEnum.A," + " PropertyAnnotationsTest.TestEnum.B})")); } @@ -512,12 +517,15 @@ public class PropertyAnnotationsTest { .addFieldAnnotations("@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation") .build(); - assertAbout(javaSource()) - .that(inputFile) - .processedWith(new AutoValueProcessor()) - .compilesWithoutError() - .and() - .generatesSources(outputFile); + Compilation compilation = + javac() + .withOptions("-A" + Nullables.NULLABLE_OPTION) + .withProcessors(new AutoValueProcessor()) + .compile(inputFile); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("foo.bar.AutoValue_Baz") + .hasSourceEquivalentTo(outputFile); } /** @@ -545,16 +553,17 @@ public class PropertyAnnotationsTest { .setImports(getImports(PropertyAnnotationsTest.class)) .addFieldAnnotations("@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation") .addMethodAnnotations( - "@Deprecated", - "@PropertyAnnotationsTest.InheritedAnnotation", - "@Baz.MethodsOnly") + "@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation", "@Baz.MethodsOnly") .build(); - assertAbout(javaSource()) - .that(inputFile) - .processedWith(new AutoValueProcessor()) - .compilesWithoutError() - .and() - .generatesSources(outputFile); + Compilation compilation = + javac() + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") + .withProcessors(new AutoValueProcessor()) + .compile(inputFile); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("foo.bar.AutoValue_Baz") + .hasSourceEquivalentTo(outputFile); } } diff --git a/value/src/test/java/com/google/auto/value/processor/PropertyNamesTest.java b/value/src/test/java/com/google/auto/value/processor/PropertyNamesTest.java index 7af240c6..d488e599 100644 --- a/value/src/test/java/com/google/auto/value/processor/PropertyNamesTest.java +++ b/value/src/test/java/com/google/auto/value/processor/PropertyNamesTest.java @@ -34,14 +34,12 @@ public class PropertyNamesTest { .put("x", "x") .put("", "") .build(); - + @Test public void decapitalizeLikeJavaBeans() { - NORMAL_CASES - .forEach( - (input, output) -> { - expect.that(PropertyNames.decapitalizeLikeJavaBeans(input)).isEqualTo(output); - }); + NORMAL_CASES.forEach( + (input, output) -> + expect.that(PropertyNames.decapitalizeLikeJavaBeans(input)).isEqualTo(output)); expect.that(PropertyNames.decapitalizeLikeJavaBeans(null)).isNull(); expect.that(PropertyNames.decapitalizeLikeJavaBeans("HTMLPage")).isEqualTo("HTMLPage"); expect.that(PropertyNames.decapitalizeLikeJavaBeans("OAuth")).isEqualTo("OAuth"); @@ -49,11 +47,9 @@ public class PropertyNamesTest { @Test public void decapitalizeNormally() { - NORMAL_CASES - .forEach( - (input, output) -> { - expect.that(PropertyNames.decapitalizeNormally(input)).isEqualTo(output); - }); + NORMAL_CASES.forEach( + (input, output) -> + expect.that(PropertyNames.decapitalizeNormally(input)).isEqualTo(output)); expect.that(PropertyNames.decapitalizeNormally(null)).isNull(); expect.that(PropertyNames.decapitalizeNormally("HTMLPage")).isEqualTo("hTMLPage"); expect.that(PropertyNames.decapitalizeNormally("OAuth")).isEqualTo("oAuth"); diff --git a/value/src/test/java/com/google/auto/value/processor/ReformatterTest.java b/value/src/test/java/com/google/auto/value/processor/ReformatterTest.java index 98d31b48..48ecf5be 100644 --- a/value/src/test/java/com/google/auto/value/processor/ReformatterTest.java +++ b/value/src/test/java/com/google/auto/value/processor/ReformatterTest.java @@ -15,7 +15,7 @@ */ package com.google.auto.value.processor; -import static org.junit.Assert.assertEquals; +import static com.google.common.truth.Truth.assertThat; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,7 +29,7 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class ReformatterTest { @Test - public void testSimple() { + public void simple() { String input = "\n" + "package com.latin.declension; \n" @@ -50,7 +50,7 @@ public class ReformatterTest { + "\n" + " Eodem ( Eadem eodem ) { }\n"; String output = - "package com.latin.declension;\n" + "package com.latin.declension;\n" + "\n" + "public class Idem {\n" + "\n" @@ -62,11 +62,11 @@ public class ReformatterTest { + " }\n" + "\n" + " Eodem (Eadem eodem) { }\n"; - assertEquals(output, Reformatter.fixup(input)); + assertThat(Reformatter.fixup(input)).isEqualTo(output); } @Test - public void testSpecialSpaces() { + public void specialSpaces() { String input = "\n" + "package com.example.whatever;\n" @@ -79,7 +79,7 @@ public class ReformatterTest { + " static final char QUOTE2 = '\\\"' ;\n" + "}\n"; String output = - "package com.example.whatever;\n" + "package com.example.whatever;\n" + "\n" + "public class SomeClass {\n" + " static final String STRING = \" hello world \\n\";\n" @@ -88,13 +88,66 @@ public class ReformatterTest { + " static final char QUOTE = '\"';\n" + " static final char QUOTE2 = '\\\"';\n" + "}\n"; - assertEquals(output, Reformatter.fixup(input)); + assertThat(Reformatter.fixup(input)).isEqualTo(output); } @Test public void noTrailingNewline() { String input = "package com.example.whatever;\n\npublic class SomeClass {}"; String output = input + "\n"; - assertEquals(output, Reformatter.fixup(input)); + assertThat(Reformatter.fixup(input)).isEqualTo(output); + } + + @Test + public void indent() { + String input = + " class Test {\n" + + "private final int field;\n" + + "\n" + + "Test(\n" + + "@Interesting Integer field,\n" + + "boolean ignored) {\n" + + "this.field = field;\n" + + "}\n" + + "\n" + + "@Override\n" + + "public boolean equals(Object x) {\n" + + "return x instanceof Test\n" + + "&& ((Test) x).field == field;\n" + + "// interesting\n" + + "}\n" + + "\n" + + "@Override\n" + + "public String toString() {\n" + + "return \"Test{\"\n" + + "+ \"field=\" + field\n" + + "+ \"}\";\n" + + "}\n" + + "}\n"; + String output = + "class Test {\n" + + " private final int field;\n" + + "\n" + + " Test(\n" + + " @Interesting Integer field,\n" + + " boolean ignored) {\n" + + " this.field = field;\n" + + " }\n" + + "\n" + + " @Override\n" + + " public boolean equals(Object x) {\n" + + " return x instanceof Test\n" + + " && ((Test) x).field == field;\n" + + " // interesting\n" + + " }\n" + + "\n" + + " @Override\n" + + " public String toString() {\n" + + " return \"Test{\"\n" + + " + \"field=\" + field\n" + + " + \"}\";\n" + + " }\n" + + "}\n"; + assertThat(Reformatter.fixup(input)).isEqualTo(output); } } diff --git a/value/src/test/java/com/google/auto/value/processor/SimpleServiceLoaderTest.java b/value/src/test/java/com/google/auto/value/processor/SimpleServiceLoaderTest.java index fab18056..5e2d230d 100644 --- a/value/src/test/java/com/google/auto/value/processor/SimpleServiceLoaderTest.java +++ b/value/src/test/java/com/google/auto/value/processor/SimpleServiceLoaderTest.java @@ -28,6 +28,8 @@ import java.io.PrintWriter; import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.ServiceConfigurationError; @@ -54,6 +56,35 @@ public final class SimpleServiceLoaderTest { assertThat(classes).containsExactly(String.class, StringBuilder.class).inOrder(); } + // Sometimes you can have the same jar appear more than once in the classpath, perhaps in + // different versions. In that case we don't want to instantiate the same class more than once. + // This test checks that we don't. + @Test + public void loadWithDuplicates() throws Exception { + ClassLoader loader1 = + loaderForJarWithEntries( + CharSequence.class.getName(), String.class.getName(), StringBuilder.class.getName()); + ClassLoader loader2 = + loaderForJarWithEntries( + CharSequence.class.getName(), String.class.getName(), StringBuilder.class.getName()); + ClassLoader combinedLoader = + new ClassLoader() { + @Override + public Enumeration<URL> getResources(String name) throws IOException { + List<URL> urls = new ArrayList<>(Collections.list(loader1.getResources(name))); + urls.addAll(Collections.list(loader2.getResources(name))); + return Collections.enumeration(urls); + } + }; + + ImmutableList<CharSequence> providers = + SimpleServiceLoader.load(CharSequence.class, combinedLoader); + + assertThat(providers).contains(""); + List<Class<?>> classes = providers.stream().map(Object::getClass).collect(toList()); + assertThat(classes).containsExactly(String.class, StringBuilder.class).inOrder(); + } + @Test public void blankLinesAndComments() throws Exception { ClassLoader loader = diff --git a/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java b/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java index 39e2dc0e..7bc67790 100644 --- a/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java +++ b/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java @@ -157,8 +157,7 @@ public class SimplifyWithAnnotationsTest { void testTypeSpellings(TypeElement testClass) { ExecutableElement witness = - ElementFilter.methodsIn(testClass.getEnclosedElements()) - .stream() + ElementFilter.methodsIn(testClass.getEnclosedElements()).stream() .filter(m -> m.getSimpleName().contentEquals("witness")) .collect(onlyElement()); if (witness.getReturnType().getAnnotationMirrors().isEmpty()) { diff --git a/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java b/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java index c31f711e..83951e0a 100644 --- a/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java +++ b/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java @@ -292,7 +292,7 @@ public class TypeEncoderTest { TypeMirror multipleBoundsMirror = multipleBoundsElement.asType(); String text = "`import`\n"; text += "{" + TypeEncoder.encode(multipleBoundsMirror) + "}"; - text += "{" + TypeEncoder.formalTypeParametersString(multipleBoundsElement) + "}"; + text += "{" + TypeEncoder.typeParametersString(multipleBoundsElement.getTypeParameters()) + "}"; String myPackage = getClass().getPackage().getName(); String decoded = TypeEncoder.decode(text, elementUtils, typeUtils, myPackage, baseWithoutContainedTypes()); @@ -306,6 +306,7 @@ public class TypeEncoderTest { @SuppressWarnings("ClassCanBeStatic") static class Outer<T extends Number> { class InnerWithoutTypeParam {} + class Middle<U> { class InnerWithTypeParam<V> {} } diff --git a/value/src/test/java/com/google/auto/value/processor/TypeVariablesTest.java b/value/src/test/java/com/google/auto/value/processor/TypeVariablesTest.java index 078ef513..895bed25 100644 --- a/value/src/test/java/com/google/auto/value/processor/TypeVariablesTest.java +++ b/value/src/test/java/com/google/auto/value/processor/TypeVariablesTest.java @@ -115,10 +115,12 @@ public class TypeVariablesTest { abstract static class Outer<T, U extends T> { abstract Map<T, U> getFoo(); + abstract List<? extends T> getBar(); abstract static class Inner<T, U extends T> { abstract void setFoo(Map<T, U> foo); + abstract void setBar(List<? extends T> bar); } } @@ -165,13 +167,15 @@ public class TypeVariablesTest { List<ExecutableElement> immutableMapMethods = ElementFilter.methodsIn(immutableMap.getEnclosedElements()); ExecutableElement copyOf = methodNamed(immutableMapMethods, "copyOf", erasedMap); - expect.that( - TypeVariables.canAssignStaticMethodResult( - copyOf, immutableMapStringInteger, immutableMapStringNumber, typeUtils)) + expect + .that( + TypeVariables.canAssignStaticMethodResult( + copyOf, immutableMapStringInteger, immutableMapStringNumber, typeUtils)) .isTrue(); - expect.that( - TypeVariables.canAssignStaticMethodResult( - copyOf, immutableMapStringNumber, immutableMapStringInteger, typeUtils)) + expect + .that( + TypeVariables.canAssignStaticMethodResult( + copyOf, immutableMapStringNumber, immutableMapStringInteger, typeUtils)) .isFalse(); } @@ -184,7 +188,9 @@ public class TypeVariablesTest { return methods.stream() .filter(m -> m.getSimpleName().contentEquals(name)) .filter(m -> m.getParameters().size() == 1) - .filter(m -> typeUtils.isSameType( + .filter( + m -> + typeUtils.isSameType( erasedParameterType, typeUtils.erasure(m.getParameters().get(0).asType()))) .findFirst() .get(); diff --git a/value/userguide/autobuilder.md b/value/userguide/autobuilder.md new file mode 100644 index 00000000..ccd191ca --- /dev/null +++ b/value/userguide/autobuilder.md @@ -0,0 +1,491 @@ +# AutoBuilder + + +AutoBuilder makes it easy to create a generalized builder, with setter methods +that accumulate values, and a build method that calls a constructor or static +method with those values as parameters. Callers don't need to know the order of +those parameters. Parameters can also have default values. There can be +validation before the constructor or method call. + +If you are familiar with [AutoValue builders](builders.md) then AutoBuilder +should also be familiar. Where an `@AutoValue.Builder` has setter methods +corresponding to the getter methods in the `@AutoValue` class, an `@AutoBuilder` +has setter methods corresponding to the parameters of a constructor or static +method. Apart from that, the two are very similar. + +AutoBuilder is **unstable** and it is possible that its API +may change. We do not recommend depending on it for production code yet. + +## Example: calling a constructor + +Here is a simple example: + +``` +@AutoBuilder(ofClass = Person.class) +abstract class PersonBuilder { + static PersonBuilder personBuilder() { + return new AutoBuilder_PersonBuilder(); + } + + abstract PersonBuilder setName(String name); + abstract PersonBuilder setId(int id); + abstract Person build(); +} +``` + +It might be used like this: + +``` +Person p = PersonBuilder.personBuilder().setName("Priz").setId(6).build(); +``` + +That would have the same effect as this: + +``` +Person p = new Person("Priz", 6); +``` + +But it doesn't require you to know what order the constructor parameters are in. + +Here, `setName` and `setId` are _setter methods_. Calling +`builder.setName("Priz")` records the value `"Priz"` for the parameter `name`, +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 + +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 +you are constructing an instance of a Kotlin data class from Java code, +AutoBuilder can help. + +Given this trivial Kotlin data class: + +``` +class KotlinData(val int: Int, val string: String?) +``` + +You might make a builder for it like this: + +``` +@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 KotlinData build(); +} +``` + +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 generated subclass + +Like `@AutoValue.Builder`, compiling an `@AutoBuilder` class will generate a +concrete subclass. In the example above, this will be `class +AutoBuilder_PersonBuilder extends PersonBuilder`. It is common to have a static +`builder()` method, as in the example, which calls `new AutoBuilder_...()`. That +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: + +``` +class Outer { + static class Inner { + @AutoBuilder + abstract static class Builder {...} + } + static Inner.Builder builder() { + return new AutoBuilder_Outer_Inner_Builder(); + } +} +``` + +## `@AutoBuilder` annotation parameters + +`@AutoBuilder` has two annotation parameters, `ofClass` and `callMethod`. + +If `ofClass` is specified, then `build()` will call a constructor or static +method of that class. Otherwise it will call a constructor or static method of +the class _containing_ the `@AutoBuilder` class. + +If `callMethod` is specified, then `build()` will call a static method with that +name. Otherwise `build()` will call a constructor. + +The following examples illustrate the various possibilities. These examples use +an interface for the `@AutoBuilder` type. You can also use an abstract class; if +it is nested then it must be static. + +### Both `callMethod` and `ofClass` + +``` +@AutoBuilder(callMethod = "of", ofClass = LocalTime.class) +interface LocalTimeBuilder { + ... + LocalTime build(); // calls: LocalTime.of(...) +} +``` + +### Only `ofClass` + +``` +@AutoBuilder(ofClass = Thread.class) +interface ThreadBuilder { + ... + Thread build(); // calls: new Thread(...) +} +``` + +### Only `callMethod` + +``` +class Foo { + static String concat(String first, String middle, String last) {...} + + @AutoBuilder(callMethod = "concat") + interface ConcatBuilder { + ... + String build(); // calls: Foo.concat(first, middle, last) + } +} +``` + +Notice in this example that the static method returns `String`. The implicit +`ofClass` is `Foo`, but the static method can return any type. + +### Neither `callMethod` nor `ofClass` + +``` +class Person { + Person(String name, int id) {...} + + @AutoBuilder + interface Builder { + ... + Person build(); // calls: new Person(name, id) + } +} +``` + +## The build method + +The build method must have a certain return type. If it calls a constructor then +its return type must be the type of the constructed class. If it calls a static +method then its return type must be the return type of the static method. + +The build method is often called `build()` but it does not have to be. The only +requirement is that there must be exactly one no-arg abstract method that has +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: + +``` +public class LogUtil { + public static void log(Level severity, String message, Object... params) {...} + + @AutoBuilder(callMethod = "log") + public interface Caller { + Caller setSeverity(Level level); + Caller setMessage(String message); + Caller setParams(Object... params); + void call(); // calls: LogUtil.log(severity, message, params) + } +``` + +## Overloaded constructors or methods + +There might be more than one constructor or static method that matches the +`callMethod` and `ofClass`. AutoBuilder will ignore any that are not visible to +the generated class, meaning private, or package-private and in a different +package. Of the others, it will pick the one whose parameter names match the +`@AutoBuilder` setter methods. It is a compilation error if there is not exactly +one such method or constructor. + +## Generics + +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: + +``` +class NumberPair<T extends Number> { + NumberPair(T first, T second) {...} + + @AutoBuilder + interface Builder<T extends Number> { + Builder<T> setFirst(T x); + Builder<T> setSecond(T x); + NumberPair<T> build(); + } +} +``` + +If the builder calls a static method with type parameters, then it must have the +same type parameters, as in this example: + +``` +class Utils { + static <K extends Number, V> Map<K, V> singletonNumberMap(K key, V value) {...} + + @AutoBuilder(callMethod = "singletonNumberMap") + interface Builder<K extends Number, V> { + Builder<K, V> setKey(K x); + Builder<K, V> setValue(V x); + Map<K, V> build(); + } +} +``` + +Although it's unusual, a Java constructor can have its own type parameters, +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: + +``` +class CheckedSet<E> implements Set<E> { + <T extends E> CheckedSet(Class<T> type) {...} + + @AutoBuilder + interface Builder<E, T extends E> { + Builder<E, T> setType(Class<T> type); + CheckedSet<E> build(); + } +} +``` + +## Required, optional, and nullable parameters + +Parameters that are annotated `@Nullable` are null by default. Parameters of +type `Optional`, `OptionalInt`, `OptionalLong`, and `OptionalDouble` are empty +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. + +``` +class Foo { + Foo(String bar, @Nullable String baz, String buh) {...} + + static Builder builder() { + return new AutoBuilder_Foo_Builder() + .setBar(DEFAULT_BAR); + } + + @AutoBuilder + interface Builder { + Builder setBar(String x); + Builder setBaz(String x); + Builder setBuh(String x); + Foo build(); + } + + { + builder().build(); // IllegalStateException, buh is not set + builder().setBuh("buh").build(); // OK, bar=DEFAULT_BAR and baz=null + builder().setBaz(null).setBuh("buh").build(); // OK + builder().setBar(null); // NullPointerException, bar is not @Nullable + } +} +``` + +Trying to set a parameter that is _not_ annotated `@Nullable` to `null` will +produce a `NullPointerException`. + +`@Nullable` here is any annotation with that name, such as +`javax.annotation.Nullable` or +`org.checkerframework.checker.nullness.qual.Nullable`. + +## Getters + +The `@AutoBuilder` class or interface can also have _getter_ methods. A getter +method returns the value that has been set for a certain parameter. Its return +type can be either the same as the parameter type, or an `Optional` wrapping +that type. Calling the getter before any value has been set will throw an +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: + +``` +public class Named { + Named(String name, String nickname) {...} + + @AutoBuilder + public abstract static class Builder { + public abstract Builder setName(String x); + public abstract Builder setNickname(String x); + abstract String getName(); + abstract Optional<String> getNickname(); + abstract Named autoBuild(); + + public Named build() { + if (!getNickname().isPresent()) { + setNickname(getName()); + } + return autoBuild(); + } + } +} +``` + +The example illustrates having a package-private `autoBuild()` method that +AutoBuilder implements. The public `build()` method calls it after adjusting the +nickname if necessary. + +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. + +## Naming conventions + +A setter method for the parameter `foo` can be called either `setFoo` or `foo`. +A getter method can be called either `getFoo` or `foo`, and for a `boolean` +parameter it can also be called `isFoo`. The choice for getters and setters is +independent. For example your getter might be `foo()` while your setter is +`setFoo(T)`. + +By convention, the parameter name of a setter method either echoes the parameter +being set:<br> +`Builder setName(String name);`<br> +or it is just `x`:<br> +`Builder setName(String x);`<br> + +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: + +``` +Foo foo1 = Foo.builder().setBar(bar).setBaz(baz).build(); +Foo.Builder fooBuilder = Foo.builder(); +``` + +If an `@AutoBuilder` for `Foo` is its own top-level class then that class will +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. + +``` +@AutoBuilder(ofClass = Foo.class) +public abstract class FooBuilder { + public static FooBuilder fooBuilder() { + return new AutoBuilder_FooBuilder(); + } + ... + public abstract Foo build(); +} +``` + +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()`). + +``` +@AutoBuilder(callMethod = "log", ofClass = MyLogger.class) +public abstract class LogCaller { + public static LogCaller logCaller() { + return new AutoBuilder_LogCaller(); + } + ... + public abstract void call(); +} + +// used as: +logCaller().setLevel(Level.INFO).setMessage("oops").call(); +``` + +## Other builder features + +There are a number of other builder features that have not been detailed here +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 +not always available in Java. They _are_ available in these cases, at least: + +* In code that is being compiled at the same time as the `@AutoBuilder` class + or interface. +* In _records_ (from Java 16 onwards). +* In the constructors of Kotlin data classes. +* In code that was compiled with the [`-parameters`] option. + +A Java compiler bug means that parameter names are not available to AutoBuilder +when compiling with JDK versions before 11, in any of these cases except the +first. We recommend building with a recent JDK, using the `--release` option if +necessary to produce code that can run on earlier versions. + +If parameter names are unavailable, you always have the option of introducing a +static method in the same class as the `@AutoBuilder` type, and having it call +the method you want. Since it is compiled at the same time, its parameter names +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: + +``` +import java.time.LocalTime; + +public class TimeUtils { + // Does not work, since parameter names from LocalTime.of are unavailable. + @AutoBuilder(callMethod = "of", ofClass = LocalTime.class) + public interface TimeBuilder { + TimeBuilder setHour(int x); + TimeBuilder setMinute(int x); + TimeBuilder setSecond(int x); + LocalTime build(); + } +} +``` + +It will produce an error message like this: + +``` +error: [AutoBuilderNoMatch] Property names do not correspond to the parameter names of any static method named "of": + public interface TimeBuilder { + ^ + of(int arg0, int arg1) + of(int arg0, int arg1, int arg2) + of(int arg0, int arg1, int arg2, int arg3) +``` + +The names `arg0`, `arg1`, etc are concocted by the compiler because it doesn't +have the real names. + +Introducing a static method fixes the problem: + +``` +import java.time.LocalTime; + +public class TimeUtils { + static LocalTime localTimeOf(int hour, int second, int second) { + return LocalTime.of(hour, minute, second); + } + + @AutoBuilder(callMethod = "localTimeOf") + public interface TimeBuilder { + TimeBuilder setHour(int x); + TimeBuilder setMinute(int x); + TimeBuilder setSecond(int x); + LocalTime build(); + } +} +``` + +[`-parameters`]: https://docs.oracle.com/en/java/javase/16/docs/specs/man/javac.html#option-parameters diff --git a/value/userguide/builders-howto.md b/value/userguide/builders-howto.md index 00038e70..e7cf5373 100644 --- a/value/userguide/builders-howto.md +++ b/value/userguide/builders-howto.md @@ -32,6 +32,8 @@ How do I... * ... [offer **both** accumulation and set-at-once methods for the same collection-valued property?](#collection_both) * ... [access nested builders while building?](#nested_builders) +* ... [create a "step builder"?](#step) +* ... [create a builder for something other than an `@AutoValue`?](#autobuilder) ## <a name="beans"></a>... use (or not use) `set` prefixes? @@ -79,10 +81,13 @@ it will simply default to `null` as you would expect. And if it is [Optional](#optional) it will default to an empty `Optional` as you might also expect. But if it isn't either of those things (including if it is a primitive-valued property, which *can't* be null), then `build()` will throw an -unchecked exception. +unchecked exception. This includes collection properties, which must be given a +value. They don't default to empty unless there is a +[collection builder](#accumulate). -But this presents a problem, since one of the main *advantages* of a builder in -the first place is that callers can specify only the properties they care about! +But this requirement to supply a value presents a problem, since one of the main +*advantages* of a builder in the first place is that callers can specify only +the properties they care about! The solution is to provide a default value for such properties. Fortunately this is easy: just set it on the newly-constructed builder instance before returning @@ -306,7 +311,8 @@ property of type `Optional<String>`, say, then it will default to an empty `Optional` without needing to [specify](#default) a default explicitly. And, instead of or as well as the normal `setFoo(Optional<String>)` method, you can have `setFoo(String)`. Then `setFoo(s)` is equivalent to -`setFoo(Optional.of(s))`. +`setFoo(Optional.of(s))`. (If it is `setFoo(@Nullable String)`, then `setFoo(s)` +is equivalent to `setFoo(Optional.ofNullable(s))`.) Here, `Optional` means either [`java.util.Optional`] from Java (Java 8 or later), or [`com.google.common.base.Optional`] from Guava. Java 8 also @@ -419,7 +425,22 @@ followed by the string `Builder`. Even if the properties follow the `getCountries()` convention, the builder method must be `countriesBuilder()` and not `getCountriesBuilder()`. -You may notice a small problem with this example: the caller can no longer +It's also possible to have a method like `countriesBuilder` with a single +argument, provided that the `Builder` class has a public constructor or a +static `builder` method, with one parameter that the argument can be assigned +to. For example, if `countries()` were an `ImmutableSortedSet<String>` and you +wanted to supply a `Comparator` to `ImmutableSortedSet.Builder`, you could +write: + +```java + public abstract ImmutableSortedSet.Builder<String> + countriesBuilder(Comparator<String> comparator); +``` + +That works because `ImmutableSortedSet.Builder` has a constructor that +accepts a `Comparator` parameter. + +You may notice a small problem with these examples: the caller can no longer create their instance in a single chained statement: ```java @@ -598,5 +619,21 @@ If `speciesBuilder()` is never called then the final `species()` property will be set as if by `speciesBuilder().build()`. In the example, that would result in an exception because the required properties of `Species` have not been set. +## <a name="step"></a>... create a "step builder"? + +A [_step builder_](http://rdafbn.blogspot.com/2012/07/step-builder-pattern_28.html) +is a collection of builder interfaces that take you step by step through the +setting of each of a list of required properties. We think that these are a nice +idea in principle but not necessarily in practice. Regardless, if you want to +use AutoValue to implement a step builder, +[this example](https://github.com/google/auto/issues/1000#issuecomment-792875738) +shows you how. + +## <a name="autobuilder"></a> ... create a builder for something other than an `@AutoValue`? + +Sometimes you want to make a builder like the kind described here, but have it +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). [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 11c68c68..1de6bfa4 100644 --- a/value/userguide/builders.md +++ b/value/userguide/builders.md @@ -10,15 +10,14 @@ Fortunately, AutoValue can generate builder classes too! This page explains how. Note that we recommend reading and understanding the basic usage shown in the [introduction](index.md) first. - -## How to use AutoValue with Builders<a name="howto"></a> +## How to use AutoValue with Builders <a name="howto"></a> As explained in the introduction, the AutoValue concept is that **you write an abstract value class, and AutoValue implements it**. Builder generation works in the exact same way: you also create an abstract builder class, nesting it inside your abstract value class, and AutoValue generates implementations for both. -### In `Animal.java`<a name="example_java"></a> +### In `Animal.java` <a name="example_java"></a> ```java import com.google.auto.value.AutoValue; @@ -45,7 +44,7 @@ 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 clean and short. -### Usage<a name="usage"></a> +### Usage <a name="usage"></a> ```java public void testAnimal() { @@ -65,12 +64,12 @@ public void testAnimal() { } ``` -### What does AutoValue generate?<a name="generated"></a> +### What does AutoValue generate? <a name="generated"></a> For the `Animal` example shown above, here is [typical code AutoValue might generate](generated-builder-example.md). -## Warnings<a name="warnings"></a> +## Warnings <a name="warnings"></a> Be sure to put the static `builder()` method directly in your value class (e.g., `Animal`) and not the nested abstract `Builder` class. That ensures that the @@ -92,10 +91,16 @@ exposing yourself to initialization-order problems. time?](builders-howto.md#normalize) * ... [expose **both** a builder and a factory method?](builders-howto.md#both) +* ... [handle `Optional` properties?](builders-howto.md#optional) * ... [use a **collection**-valued property?](builders-howto.md#collection) * ... [let my builder **accumulate** values for a collection-valued property (not require them all at once)?](builders-howto.md#accumulate) * ... [accumulate values for a collection-valued property, without - **breaking the chain**?](builders-howto.md#add) + **"breaking the chain"**?](builders-howto.md#add) * ... [offer **both** accumulation and set-at-once methods for the same collection-valued property?](builders-howto.md#collection_both) +* ... [access nested builders while + building?](builders-howto.md#nested_builders) +* ... [create a "step builder"?](builders-howto.md#step) +* ... [create a builder for something other than an + `@AutoValue`?](builders-howto.md#autobuilder) diff --git a/value/userguide/extensions.md b/value/userguide/extensions.md index ec6c4675..6acb3ca9 100644 --- a/value/userguide/extensions.md +++ b/value/userguide/extensions.md @@ -9,7 +9,6 @@ AutoValue can be extended to implement new features for classes annotated with Each extension is a class. If that class is on the `processorpath` when you compile your `@AutoValue` class, the extension can run. - Some extensions are triggered by their own annotations, which you add to your class; others may be triggered in other ways. Consult the extension's documentation for usage instructions. @@ -40,4 +39,3 @@ behavior by overriding or implementing new methods. [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 [`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 6f9142c1..c4511854 100644 --- a/value/userguide/howto.md +++ b/value/userguide/howto.md @@ -42,6 +42,7 @@ How do I... * ... [make a class where only one of its properties is ever set?](#oneof) * ... [copy annotations from a class/method to the implemented class/method/field?](#copy_annotations) +* ... [create a **pretty string** representation?](#toprettystring) ## <a name="builder"></a>... also generate a builder for my value class? @@ -239,16 +240,16 @@ If you're sure, here is how to do it: abstract class IgnoreExample { static IgnoreExample create(String normalProperty, String ignoredProperty) { IgnoreExample ie = new AutoValue_IgnoreExample(normalProperty); - ie.ignoredProperty = ignoredProperty; + ie.ignoredProperty.set(ignoredProperty); return ie; } abstract String normalProperty(); - private String ignoredProperty; // sadly, it can't be `final` + private final AtomicReference<String> ignoredProperty = new AtomicReference<>(); final String ignoredProperty() { - return ignoredProperty; + return ignoredProperty.get(); } } ``` @@ -256,6 +257,11 @@ abstract class IgnoreExample { Note that this means the field is also ignored by `toString`; to AutoValue it 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 +`volatile`, or use `synchronized` (`synchronized (ie)` around the assignment and +`synchronized` on the `ignoredProperty()` method). + ## <a name="supertypes"></a>... have AutoValue also implement abstract methods from my supertypes? AutoValue will recognize every abstract accessor method whether it is defined @@ -347,7 +353,6 @@ This is not allowed. Object arrays are very badly-behaved and unlike primitive arrays, they can be replaced with a proper `List` implementation for very little added cost. - If it's important to accept an object array at construction time, refer to the *first* example shown [here](#mutable_property). @@ -676,3 +681,44 @@ final class AutoValue_Example extends Example { [`@AutoValue.CopyAnnotations`]: http://static.javadoc.io/com.google.auto.value/auto-value/1.6/com/google/auto/value/AutoValue.CopyAnnotations.html +## <a name="toprettystring"></a>... create a pretty string representation? + +If you have a value class with a long `toString()` representation, annotate a +method with [`@ToPrettyString`] and AutoValue will generate an implementation that +returns a pretty String rendering of the instance. For example: + +```java +@AutoValue +abstract class Song { + abstract String lyrics(); + abstract List<Artist> artists(); + + @ToPrettyString + abstract String toPrettyString(); +} +``` + +Below is a sample rendering of the result of calling `toPrettyString()`. + +``` +Song { + lyrics = I'm off the deep end, watch as I dive in + I'll never meet the ground + Crash through the surface, where they can't hurt us + We're far from the shallow now., + artists = [ + Artist { + name = Lady Gaga, + }, + Artist { + name = Bradley Cooper, + } + ], +} +``` + +`@ToPrettyString` can be used on the default `toString()` to override the +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 diff --git a/value/userguide/index.md b/value/userguide/index.md index 2e05d54b..d6ef95c7 100644 --- a/value/userguide/index.md +++ b/value/userguide/index.md @@ -13,7 +13,6 @@ > > -- *Joshua Bloch, author, Effective Java* - ## <a name="background"></a>Background **Value classes** are extremely common in Java projects. These are classes for @@ -150,18 +149,18 @@ Gradle users can declare the dependencies in their `build.gradle` script: ```groovy dependencies { - // Use 'api' rather than 'compile' for Android or java-library projects. - compile "com.google.auto.value:auto-value-annotations:${autoValueVersion}" + compileOnlyApi "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 4.6, you must apply an -annotation processing plugin [as described in these instructions][tbroyer-apt]. +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 +[as described in these instructions][tbroyer-apt]. [tbroyer-apt]: https://plugins.gradle.org/plugin/net.ltgt.apt - ### <a name="usage"></a>Usage Your choice to use AutoValue is essentially *API-invisible*. This means that, to @@ -276,13 +275,7 @@ How do I... set?](howto.md#oneof) * ... [copy annotations from a class/method to the implemented 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? --> - -## <a name="more"></a>More information - -See the links in the sidebar at the top left. - -<!-- TODO(kevinb): there are some tidbits of information that don't seem to - belong anywhere yet; such as how it implements floating-point equality --> +<!-- TODO(kevinb): should the above be only a selected subset? --> diff --git a/value/userguide/practices.md b/value/userguide/practices.md index 0121bd83..e78c6e5a 100644 --- a/value/userguide/practices.md +++ b/value/userguide/practices.md @@ -1,7 +1,6 @@ # Best practices - ## <a name="interchangeable"></a>"Equals means interchangeable" Don't use AutoValue to implement value semantics unless you really want value diff --git a/value/userguide/why.md b/value/userguide/why.md index 603abb83..40c994f6 100644 --- a/value/userguide/why.md +++ b/value/userguide/why.md @@ -15,5 +15,4 @@ This [slide presentation] compares AutoValue to numerous alternatives and explains why we think it is better. - [slide presentation]: https://docs.google.com/presentation/d/14u_h-lMn7f1rXE1nDiLX0azS3IkgjGl5uxp5jGJ75RE/edit |