diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 00:58:09 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 00:58:09 +0000 |
commit | 0260f896969dae34df7290fd75132e5d2bc4b2a5 (patch) | |
tree | ccd72f5c53dbf7261407709e8cb7b6778d33ea17 | |
parent | 558f459f324ee0cc24b2786ddc169b9d087acceb (diff) | |
parent | 941c7a94ba9ccee7b4f2b2bfe49d2c87ec7d2b3e (diff) | |
download | auto-android14-mainline-wifi-release.tar.gz |
Snap for 10447354 from 941c7a94ba9ccee7b4f2b2bfe49d2c87ec7d2b3e to mainline-wifi-releaseaml_wif_341610000aml_wif_341510000aml_wif_341410080aml_wif_341310010aml_wif_341110010aml_wif_341011010aml_wif_340913010android14-mainline-wifi-release
Change-Id: I0a05330dd86d65f183c1dc9012138248f92ec820
55 files changed, 1753 insertions, 301 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec922435..44718421 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,13 +18,13 @@ jobs: 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 + uses: styfle/cancel-workflow-action@0.9.1 with: access_token: ${{ github.token }} - name: 'Check out repository' - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v2.4.0 - name: 'Cache local Maven repository' - uses: actions/cache@v2.1.6 + uses: actions/cache@v2.1.7 with: path: ~/.m2/repository key: maven-${{ hashFiles('**/pom.xml') }} @@ -49,9 +49,9 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Check out repository' - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v2.4.0 - name: 'Cache local Maven repository' - uses: actions/cache@v2.1.6 + uses: actions/cache@v2.1.7 with: path: ~/.m2/repository key: maven-${{ hashFiles('**/pom.xml') }} @@ -78,9 +78,9 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Check out repository' - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v2.4.0 - name: 'Cache local Maven repository' - uses: actions/cache@v2.1.6 + uses: actions/cache@v2.1.7 with: path: ~/.m2/repository key: maven-${{ hashFiles('**/pom.xml') }} @@ -11,7 +11,7 @@ third_party { type: GIT value: "https://github.com/google/auto" } - version: "auto-value-1.7.4" - last_upgrade_date { year: 2020 month: 11 day: 18 } + version: "auto-value-1.9" + last_upgrade_date { year: 2022 month: 04 day: 11 } license_type: NOTICE } diff --git a/common/pom.xml b/common/pom.xml index 2754b81d..a9c1e3f1 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -26,7 +26,7 @@ <groupId>com.google.auto</groupId> <artifactId>auto-common</artifactId> - <version>0.11</version> + <version>HEAD-SNAPSHOT</version> <name>Auto Common Libraries</name> <description> Common utilities for creating annotation processors. @@ -36,7 +36,7 @@ <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> - <guava.version>30.1.1-jre</guava.version> + <guava.version>31.0.1-jre</guava.version> <truth.version>1.1.3</truth.version> </properties> @@ -128,7 +128,7 @@ <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>1.0.7</version> + <version>1.1.0</version> </dependency> </dependencies> </plugin> 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 9ce5cd9b..2f59dd39 100644 --- a/common/src/main/java/com/google/auto/common/AnnotationMirrors.java +++ b/common/src/main/java/com/google/auto/common/AnnotationMirrors.java @@ -191,5 +191,16 @@ public final class AnnotationMirrors { .collect(toImmutableSet()); } + /** + * Returns a string representation of the given annotation mirror, suitable for inclusion in a + * Java source file to reproduce the annotation in source form. + * + * <p>Fully qualified names are used for types in annotations, class literals, and enum constants, + * ensuring that the source form will compile without requiring additional imports. + */ + public static String toString(AnnotationMirror annotationMirror) { + return AnnotationOutput.toString(annotationMirror); + } + private AnnotationMirrors() {} } diff --git a/common/src/main/java/com/google/auto/common/AnnotationOutput.java b/common/src/main/java/com/google/auto/common/AnnotationOutput.java new file mode 100644 index 00000000..e52bde66 --- /dev/null +++ b/common/src/main/java/com/google/auto/common/AnnotationOutput.java @@ -0,0 +1,266 @@ +/* + * 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. + */ +package com.google.auto.common; + +import static com.google.auto.common.MoreTypes.asTypeElement; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.AnnotationValueVisitor; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleAnnotationValueVisitor8; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Handling of default values for annotation members. + * + * @author emcmanus@google.com (Éamonn McManus) + */ +final class AnnotationOutput { + private AnnotationOutput() {} // There are no instances of this class. + + /** + * Visitor that produces a string representation of an annotation value, suitable for inclusion in + * a Java source file as an annotation member or as the initializer of a variable of the + * appropriate type. The syntax for the two is the same except for annotation members that are + * themselves annotations. Within an annotation, an annotation member can be written as + * {@code @NestedAnnotation(...)}, while in an initializer it must be written as an object, for + * example the construction of an {@code @AutoAnnotation} class. That's why we have this abstract + * class and two concrete subclasses. + */ + private static class SourceFormVisitor + extends SimpleAnnotationValueVisitor8<@Nullable Void, StringBuilder> { + + private String formatType(TypeMirror typeMirror) { + return asTypeElement(typeMirror).getQualifiedName().toString(); + } + + @Override + protected @Nullable Void defaultAction(Object value, StringBuilder sb) { + sb.append(value); + return null; + } + + @Override + public @Nullable Void visitArray(List<? extends AnnotationValue> values, StringBuilder sb) { + sb.append('{'); + String sep = ""; + for (AnnotationValue value : values) { + sb.append(sep); + visit(value, sb); + sep = ", "; + } + sb.append('}'); + return null; + } + + @Override + public @Nullable Void visitByte(byte b, StringBuilder sb) { + sb.append("(byte) ").append(b); + return null; + } + + @Override + public @Nullable Void visitShort(short s, StringBuilder sb) { + sb.append("(short) ").append(s); + return null; + } + + @Override + public @Nullable Void visitChar(char c, StringBuilder sb) { + appendQuoted(sb, c); + return null; + } + + @Override + public @Nullable Void visitLong(long i, StringBuilder sb) { + sb.append(i).append('L'); + return null; + } + + @Override + public @Nullable Void visitDouble(double d, StringBuilder sb) { + if (Double.isNaN(d)) { + sb.append("Double.NaN"); + } else if (d == Double.POSITIVE_INFINITY) { + sb.append("Double.POSITIVE_INFINITY"); + } else if (d == Double.NEGATIVE_INFINITY) { + sb.append("Double.NEGATIVE_INFINITY"); + } else { + sb.append(d); + } + return null; + } + + @Override + public @Nullable Void visitFloat(float f, StringBuilder sb) { + if (Float.isNaN(f)) { + sb.append("Float.NaN"); + } else if (f == Float.POSITIVE_INFINITY) { + sb.append("Float.POSITIVE_INFINITY"); + } else if (f == Float.NEGATIVE_INFINITY) { + sb.append("Float.NEGATIVE_INFINITY"); + } else { + sb.append(f).append('F'); + } + return null; + } + + @Override + public @Nullable Void visitEnumConstant(VariableElement c, StringBuilder sb) { + sb.append(formatType(c.asType())).append('.').append(c.getSimpleName()); + return null; + } + + @Override + public @Nullable Void visitString(String s, StringBuilder sb) { + appendQuoted(sb, s); + return null; + } + + @Override + public @Nullable Void visitType(TypeMirror classConstant, StringBuilder sb) { + sb.append(formatType(classConstant)).append(".class"); + return null; + } + + @Override + public @Nullable Void visitAnnotation(AnnotationMirror a, StringBuilder sb) { + sb.append('@').append(formatType(a.getAnnotationType())); + ImmutableMap<ExecutableElement, AnnotationValue> map = + ImmutableMap.copyOf(a.getElementValues()); + if (!map.isEmpty()) { + sb.append('('); + Optional<AnnotationValue> shortForm = shortForm(map); + if (shortForm.isPresent()) { + this.visit(maybeShorten(shortForm.get()), sb); + } else { + String sep = ""; + for (Map.Entry<ExecutableElement, AnnotationValue> entry : map.entrySet()) { + sb.append(sep).append(entry.getKey().getSimpleName()).append(" = "); + sep = ", "; + this.visit(maybeShorten(entry.getValue()), sb); + } + } + sb.append(')'); + } + return null; + } + } + + private static AnnotationValue maybeShorten(AnnotationValue value) { + return ARRAY_VISITOR.visit(value, value); + } + + private static final AnnotationValueVisitor<AnnotationValue, AnnotationValue> ARRAY_VISITOR = + new SimpleAnnotationValueVisitor8<AnnotationValue, AnnotationValue>() { + @Override + public AnnotationValue visitArray( + List<? extends AnnotationValue> values, AnnotationValue input) { + if (values.size() == 1) { + // We can shorten @Foo(a = {23}) to @Foo(a = 23). For the specific case where `a` is + // actually `value`, we'll already have shortened that in visitAnnotation, so + // effectively we go from @Foo(value = {23}) to @Foo({23}) to @Foo(23). + return Iterables.getOnlyElement(values); + } + return input; + } + + @Override + protected AnnotationValue defaultAction(Object o, AnnotationValue input) { + return input; + } + }; + + // We can shorten @Annot(value = 23) to @Annot(23). + private static Optional<AnnotationValue> shortForm( + Map<ExecutableElement, AnnotationValue> values) { + if (values.size() == 1 + && Iterables.getOnlyElement(values.keySet()).getSimpleName().contentEquals("value")) { + return Optional.of(Iterables.getOnlyElement(values.values())); + } + return Optional.empty(); + } + + /** + * Returns a string representation of the given annotation value, suitable for inclusion in a Java + * source file as the initializer of a variable of the appropriate type. + */ + static String toString(AnnotationValue annotationValue) { + StringBuilder sb = new StringBuilder(); + new SourceFormVisitor().visit(annotationValue, sb); + return sb.toString(); + } + + /** + * Returns a string representation of the given annotation mirror, suitable for inclusion in a + * Java source file to reproduce the annotation in source form. + */ + static String toString(AnnotationMirror annotationMirror) { + StringBuilder sb = new StringBuilder(); + new SourceFormVisitor().visitAnnotation(annotationMirror, sb); + return sb.toString(); + } + + private static StringBuilder appendQuoted(StringBuilder sb, String s) { + sb.append('"'); + for (int i = 0; i < s.length(); i++) { + appendEscaped(sb, s.charAt(i)); + } + return sb.append('"'); + } + + private static StringBuilder appendQuoted(StringBuilder sb, char c) { + sb.append('\''); + appendEscaped(sb, c); + return sb.append('\''); + } + + private static void appendEscaped(StringBuilder sb, char c) { + switch (c) { + case '\\': + case '"': + case '\'': + sb.append('\\').append(c); + break; + case '\n': + sb.append("\\n"); + break; + case '\r': + sb.append("\\r"); + break; + case '\t': + sb.append("\\t"); + break; + default: + if (c < 0x20) { + sb.append(String.format("\\%03o", (int) c)); + } else if (c < 0x7f || Character.isLetter(c)) { + sb.append(c); + } else { + sb.append(String.format("\\u%04x", (int) c)); + } + break; + } + } +} 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 0712e56a..a3cc9495 100644 --- a/common/src/main/java/com/google/auto/common/AnnotationValues.java +++ b/common/src/main/java/com/google/auto/common/AnnotationValues.java @@ -512,5 +512,22 @@ public final class AnnotationValues { return ANNOTATION_VALUES_VISITOR.visit(value); } + /** + * Returns a string representation of the given annotation value, suitable for inclusion in a Java + * source file as part of an annotation. For example, if {@code annotationValue} represents the + * string {@code unchecked} in the annotation {@code @SuppressWarnings("unchecked")}, this method + * will return the string {@code "unchecked"}, which you can then use as part of an annotation + * being generated. + * + * <p>For all annotation values other than nested annotations, the returned string can also be + * used to initialize a variable of the appropriate type. + * + * <p>Fully qualified names are used for types in annotations, class literals, and enum constants, + * ensuring that the source form will compile without requiring additional imports. + */ + public static String toString(AnnotationValue annotationValue) { + return AnnotationOutput.toString(annotationValue); + } + private AnnotationValues() {} } 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 775c304a..cdcd741d 100644 --- a/common/src/main/java/com/google/auto/common/Overrides.java +++ b/common/src/main/java/com/google/auto/common/Overrides.java @@ -117,7 +117,7 @@ abstract class Overrides { // can't be overridden. return false; } - if (!(overridden.getEnclosingElement() instanceof TypeElement)) { + if (!MoreElements.isType(overridden.getEnclosingElement())) { return false; // We don't know how this could happen but we avoid blowing up if it does. } @@ -170,9 +170,14 @@ abstract class Overrides { return false; } } else { - return in.getKind().isInterface(); - // Method mI in or inherited by interface I (JLS 9.4.1.1). We've already checked everything. + // Method mI in or inherited by interface I (JLS 9.4.1.1). We've already checked everything, + // except that `overrider` must also be in a subinterface of `overridden`. // If this is not an interface then we don't know what it is so we say no. + TypeElement overriderType = MoreElements.asType(overrider.getEnclosingElement()); + return in.getKind().isInterface() + && typeUtils.isSubtype( + typeUtils.erasure(overriderType.asType()), + typeUtils.erasure(overriddenType.asType())); } } 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 36f4ad6d..db15f8bd 100644 --- a/common/src/main/java/com/google/auto/common/Visibility.java +++ b/common/src/main/java/com/google/auto/common/Visibility.java @@ -16,10 +16,10 @@ 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; @@ -77,7 +77,9 @@ public enum Visibility { Visibility effectiveVisibility = PUBLIC; Element currentElement = element; while (currentElement != null) { - effectiveVisibility = min(effectiveVisibility, ofElement(currentElement)); + // NOTE: We don't use Guava's Comparators.min() because that requires Guava 30, which would + // make this library unusable in annotation processors using Bazel < 5.0. + effectiveVisibility = Ordering.natural().min(effectiveVisibility, ofElement(currentElement)); currentElement = currentElement.getEnclosingElement(); } return effectiveVisibility; 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 b1dfe3b3..83494a84 100644 --- a/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java +++ b/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java @@ -296,6 +296,73 @@ public class AnnotationMirrorsTest { AnnotationMirrors.getAnnotatedAnnotations(element, annotatingAnnotationElement)); } + @Test + public void toSourceString() { + assertThat(AnnotationMirrors.toString(annotationOn(AlsoSimplyAnnotated.class))) + .isEqualTo("@com.google.auto.common.AnnotationMirrorsTest.SimpleAnnotation"); + assertThat(AnnotationMirrors.toString(annotationOn(SimplyAnnotated.class))) + .isEqualTo("@com.google.auto.common.AnnotationMirrorsTest.SimpleAnnotation"); + assertThat(AnnotationMirrors.toString(annotationOn(StringySet.class))) + .isEqualTo("@com.google.auto.common.AnnotationMirrorsTest.Stringy(\"foo\")"); + assertThat(AnnotationMirrors.toString(annotationOn(StringyUnset.class))) + .isEqualTo("@com.google.auto.common.AnnotationMirrorsTest.Stringy"); + assertThat(AnnotationMirrors.toString(annotationOn(TestBlahNestedAnnotated.class))) + .isEqualTo( + "@com.google.auto.common.AnnotationMirrorsTest.AnnotatedOuter(@com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter(com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.BLAH))"); + assertThat(AnnotationMirrors.toString(annotationOn(TestClassBlah2.class))) + .isEqualTo( + "@com.google.auto.common.AnnotationMirrorsTest.Outer(com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.BLAH)"); + assertThat(AnnotationMirrors.toString(annotationOn(TestClassBlah.class))) + .isEqualTo( + "@com.google.auto.common.AnnotationMirrorsTest.Outer(com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.BLAH)"); + assertThat(AnnotationMirrors.toString(annotationOn(TestClassFoo.class))) + .isEqualTo( + "@com.google.auto.common.AnnotationMirrorsTest.Outer(com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.FOO)"); + assertThat(AnnotationMirrors.toString(annotationOn(TestDefaultNestedAnnotated.class))) + .isEqualTo( + "@com.google.auto.common.AnnotationMirrorsTest.AnnotatedOuter(@com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter)"); + assertThat(AnnotationMirrors.toString(annotationOn(TestFooNestedAnnotated.class))) + .isEqualTo( + "@com.google.auto.common.AnnotationMirrorsTest.AnnotatedOuter(@com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter(com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.FOO))"); + assertThat(AnnotationMirrors.toString(annotationOn(TestValueArrayWithBlahFoo.class))) + .isEqualTo( + "@com.google.auto.common.AnnotationMirrorsTest.OuterWithValueArray({@com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter(com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.BLAH)," + + " @com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter(com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.FOO)})"); + assertThat(AnnotationMirrors.toString(annotationOn(TestValueArrayWithDefault.class))) + .isEqualTo("@com.google.auto.common.AnnotationMirrorsTest.OuterWithValueArray"); + assertThat(AnnotationMirrors.toString(annotationOn(TestValueArrayWithEmpty.class))) + .isEqualTo("@com.google.auto.common.AnnotationMirrorsTest.OuterWithValueArray({})"); + assertThat(AnnotationMirrors.toString(annotationOn(TestValueArrayWithFooAndDefaultBlah.class))) + .isEqualTo( + "@com.google.auto.common.AnnotationMirrorsTest.OuterWithValueArray({@com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter(com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.FOO)," + + " @com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter})"); + assertThat(AnnotationMirrors.toString(annotationOn(TestValueArrayWithFooBlah2.class))) + .isEqualTo( + "@com.google.auto.common.AnnotationMirrorsTest.OuterWithValueArray({@com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter(com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.FOO)," + + " @com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter(com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.BLAH)})"); + assertThat(AnnotationMirrors.toString(annotationOn(TestValueArrayWithFooBlah.class))) + .isEqualTo( + "@com.google.auto.common.AnnotationMirrorsTest.OuterWithValueArray({@com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter(com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.FOO)," + + " @com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter(com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.BLAH)})"); + assertThat(AnnotationMirrors.toString(annotationOn(TestValueArrayWithOneBlah.class))) + .isEqualTo( + "@com.google.auto.common.AnnotationMirrorsTest.OuterWithValueArray(@com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter(com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.BLAH))"); + assertThat(AnnotationMirrors.toString(annotationOn(TestValueArrayWithOneDefault.class))) + .isEqualTo( + "@com.google.auto.common.AnnotationMirrorsTest.OuterWithValueArray(@com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter)"); + assertThat(AnnotationMirrors.toString(annotationOn(TestValueArrayWithOneFoo.class))) + .isEqualTo( + "@com.google.auto.common.AnnotationMirrorsTest.OuterWithValueArray(@com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter(com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.FOO))"); + assertThat(AnnotationMirrors.toString(annotationOn(TestWithDefaultingOuterBlah.class))) + .isEqualTo( + "@com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter(com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.BLAH)"); + assertThat(AnnotationMirrors.toString(annotationOn(TestWithDefaultingOuterDefault.class))) + .isEqualTo("@com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter"); + assertThat(AnnotationMirrors.toString(annotationOn(TestWithDefaultingOuterFoo.class))) + .isEqualTo( + "@com.google.auto.common.AnnotationMirrorsTest.DefaultingOuter(com.google.auto.common.AnnotationMirrorsTest.SimpleEnum.FOO)"); + } + private void getAnnotatedAnnotationsAsserts( ImmutableSet<? extends AnnotationMirror> annotatedAnnotations) { assertThat(annotatedAnnotations) diff --git a/common/src/test/java/com/google/auto/common/AnnotationValuesTest.java b/common/src/test/java/com/google/auto/common/AnnotationValuesTest.java index 825c85af..c6997b2a 100644 --- a/common/src/test/java/com/google/auto/common/AnnotationValuesTest.java +++ b/common/src/test/java/com/google/auto/common/AnnotationValuesTest.java @@ -17,8 +17,10 @@ package com.google.auto.common; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; +import static java.util.stream.Collectors.joining; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.truth.Correspondence; import com.google.testing.compile.CompilationRule; import javax.lang.model.element.AnnotationMirror; @@ -344,6 +346,66 @@ public final class AnnotationValuesTest { assertThat(AnnotationValues.getChars(value)).containsExactly('b', 'c').inOrder(); } + @Test + public void toSourceString() { + ImmutableMap<String, String> inputs = + ImmutableMap.<String, String>builder() + .put("classValue", "com.google.auto.common.AnnotationValuesTest.InsideClassA.class") + .put( + "classValues", + "{com.google.auto.common.AnnotationValuesTest.InsideClassA.class," + + " com.google.auto.common.AnnotationValuesTest.InsideClassB.class}") + .put( + "genericClassValue", + "com.google.auto.common.AnnotationValuesTest.GenericClass.class") + .put( + "insideAnnotationValue", + "@com.google.auto.common.AnnotationValuesTest.InsideAnnotation(19)") + .put( + "insideAnnotationValues", + "{@com.google.auto.common.AnnotationValuesTest.InsideAnnotation(20)," + + " @com.google.auto.common.AnnotationValuesTest.InsideAnnotation(21)}") + .put("stringValue", "\"hello\"") + .put("stringValues", "{\"it\\'s\", \"me\"}") + .put("enumValue", "com.google.auto.common.AnnotationValuesTest.Foo.BAR") + .put( + "enumValues", + "{com.google.auto.common.AnnotationValuesTest.Foo.BAZ," + + " com.google.auto.common.AnnotationValuesTest.Foo.BAH}") + .put("intValue", "5") + .put("intValues", "{1, 2}") + .put("longValue", "6L") + .put("longValues", "{3L, 4L}") + .put("byteValue", "(byte) 7") + .put("byteValues", "{(byte) 8, (byte) 9}") + .put("shortValue", "(short) 10") + .put("shortValues", "{(short) 11, (short) 12}") + .put("floatValue", "13.0F") + .put("floatValues", "{14.0F, 15.0F}") + .put("doubleValue", "16.0") + .put("doubleValues", "{17.0, 18.0}") + .put("booleanValue", "true") + .put("booleanValues", "{true, false}") + .put("charValue", "'a'") + .put("charValues", "{'b', 'c'}") + .build(); + inputs.forEach( + (name, expected) -> + assertThat( + AnnotationValues.toString( + AnnotationMirrors.getAnnotationValue(annotationMirror, name))) + .isEqualTo(expected)); + assertThat(AnnotationMirrors.toString(annotationMirror)) + .isEqualTo( + inputs.entrySet().stream() + .map(e -> e.getKey() + " = " + e.getValue()) + .collect( + joining( + ", ", + "@com.google.auto.common.AnnotationValuesTest.MultiValueAnnotation(", + ")"))); + } + private TypeElement getTypeElement(Class<?> clazz) { return elements.getTypeElement(clazz.getCanonicalName()); } 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 b98b79b9..eaa504a1 100644 --- a/common/src/test/java/com/google/auto/common/MoreElementsTest.java +++ b/common/src/test/java/com/google/auto/common/MoreElementsTest.java @@ -388,40 +388,6 @@ public class MoreElementsTest { .inOrder(); } - static class Injectable {} - - public static class MenuManager { - public interface ParentComponent extends MenuItemA.ParentComponent, MenuItemB.ParentComponent {} - } - - public static class MenuItemA { - public interface ParentComponent { - Injectable injectable(); - } - } - - public static class MenuItemB { - public interface ParentComponent { - Injectable injectable(); - } - } - - public static class Main { - public interface ParentComponent extends MenuManager.ParentComponent {} - } - - // Example from https://github.com/williamlian/daggerbug - @Test - public void getLocalAndInheritedMethods_DaggerBug() { - 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"); - assertThat(method.getParameters()).isEmpty(); - } - private Set<ExecutableElement> visibleMethodsFromObject() { Types types = compilation.getTypes(); TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT); 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 c5ccc5f6..8d77fc76 100644 --- a/common/src/test/java/com/google/auto/common/OverridesTest.java +++ b/common/src/test/java/com/google/auto/common/OverridesTest.java @@ -79,8 +79,8 @@ import org.junit.runners.model.Statement; @RunWith(Parameterized.class) public class OverridesTest { @Parameterized.Parameters(name = "{0}") - public static ImmutableList<CompilerType> data() { - return ImmutableList.of(CompilerType.JAVAC, CompilerType.ECJ); + public static CompilerType[] data() { + return CompilerType.values(); } @Rule public CompilationRule compilation = new CompilationRule(); @@ -133,12 +133,16 @@ public class OverridesTest { void m(String x); void n(); + + Number number(); } interface Two { void m(); void m(int x); + + Integer number(); } static class Parent { @@ -156,6 +160,11 @@ public class OverridesTest { @Override public void n() {} + + @Override + public Number number() { + return 0; + } } static class ChildOfOneAndTwo implements One, Two { @@ -170,6 +179,11 @@ public class OverridesTest { @Override public void n() {} + + @Override + public Integer number() { + return 0; + } } static class ChildOfParentAndOne extends Parent implements One { @@ -181,6 +195,11 @@ public class OverridesTest { @Override public void n() {} + + @Override + public Number number() { + return 0; + } } static class ChildOfParentAndOneAndTwo extends Parent implements One, Two { @@ -192,6 +211,11 @@ public class OverridesTest { @Override public void n() {} + + @Override + public Integer number() { + return 0; + } } abstract static class AbstractChildOfOne implements One {} @@ -199,6 +223,8 @@ public class OverridesTest { abstract static class AbstractChildOfOneAndTwo implements One, Two {} abstract static class AbstractChildOfParentAndOneAndTwo extends Parent implements One, Two {} + + interface ExtendingOneAndTwo extends One, Two {} } static class MoreTypesForInheritance { diff --git a/factory/pom.xml b/factory/pom.xml index 629223a9..922cdbac 100644 --- a/factory/pom.xml +++ b/factory/pom.xml @@ -19,12 +19,6 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> - <parent> - <groupId>org.sonatype.oss</groupId> - <artifactId>oss-parent</artifactId> - <version>7</version> - </parent> - <groupId>com.google.auto.factory</groupId> <artifactId>auto-factory</artifactId> <version>HEAD-SNAPSHOT</version> @@ -36,10 +30,10 @@ <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> + <auto-service.version>1.0.1</auto-service.version> + <auto-value.version>1.8.2</auto-value.version> <java.version>1.8</java.version> - <guava.version>30.1.1-jre</guava.version> + <guava.version>31.0.1-jre</guava.version> <truth.version>1.1.3</truth.version> </properties> @@ -67,11 +61,24 @@ <url>http://www.google.com</url> </organization> + <distributionManagement> + <snapshotRepository> + <id>sonatype-nexus-snapshots</id> + <name>Sonatype Nexus Snapshots</name> + <url>https://oss.sonatype.org/content/repositories/snapshots/</url> + </snapshotRepository> + <repository> + <id>sonatype-nexus-staging</id> + <name>Nexus Release Repository</name> + <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> + </repository> + </distributionManagement> + <dependencies> <dependency> <groupId>com.google.auto</groupId> <artifactId>auto-common</artifactId> - <version>1.1</version> + <version>1.2.1</version> </dependency> <dependency> <groupId>com.google.auto.value</groupId> @@ -170,7 +177,7 @@ <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>1.0.7</version> + <version>1.1.0</version> </dependency> </dependencies> </plugin> 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 b7f9c3e4..8d6027dd 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 @@ -29,6 +29,7 @@ import static javax.lang.model.element.Modifier.STATIC; import com.google.auto.common.AnnotationMirrors; import com.google.auto.common.AnnotationValues; +import com.google.auto.common.MoreTypes; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; @@ -59,6 +60,7 @@ import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; @@ -338,9 +340,28 @@ final class FactoryWriter { for (ProviderField provider : descriptor.providers().values()) { typeVariables.addAll(getReferencedTypeParameterNames(provider.key().type().get())); } + // If a parent type has a type parameter, like FooFactory<T>, then the generated factory needs + // to have the same parameter, like FooImplFactory<T> extends FooFactory<T>. This is a little + // approximate, at least in the case where there is more than one parent type that has a type + // parameter. But that should be pretty rare, so let's keep it simple for now. + typeVariables.addAll(typeVariablesFrom(descriptor.extendingType())); + for (TypeMirror implementing : descriptor.implementingTypes()) { + typeVariables.addAll(typeVariablesFrom(implementing)); + } return typeVariables.build(); } + private static List<TypeVariableName> typeVariablesFrom(TypeMirror type) { + if (type.getKind().equals(TypeKind.DECLARED)) { + DeclaredType declaredType = MoreTypes.asDeclared(type); + return declaredType.getTypeArguments().stream() + .filter(t -> t.getKind().equals(TypeKind.TYPEVAR)) + .map(t -> TypeVariableName.get(MoreTypes.asTypeVariable(t))) + .collect(toList()); + } + return ImmutableList.of(); + } + private static ImmutableSet<TypeVariableName> getMethodTypeVariables( FactoryMethodDescriptor methodDescriptor, ImmutableSet<TypeVariableName> factoryTypeVariables) { 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 728149eb..6dc76445 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 @@ -97,7 +97,7 @@ abstract class Key { public final String toString() { String typeQualifiedName = MoreTypes.asTypeElement(type().get()).toString(); return qualifier().isPresent() - ? qualifier().get() + "/" + typeQualifiedName + ? AnnotationMirrors.toString(qualifier().get()) + "/" + typeQualifiedName : typeQualifiedName; } } 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 0df4c9ca..2ab0fe95 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 @@ -508,6 +508,22 @@ public class AutoFactoryProcessorTest { .hasSourceEquivalentTo(loadExpectedFile("expected/DefaultPackageFactory.java")); } + @Test + public void generics() { + JavaFileObject file = JavaFileObjects.forResource("good/Generics.java"); + Compilation compilation = javac.compile(file); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(compilation) + .generatedSourceFile("tests.Generics_FooImplFactory") + .hasSourceEquivalentTo(loadExpectedFile("expected/Generics_FooImplFactory.java")); + assertThat(compilation) + .generatedSourceFile("tests.Generics_ExplicitFooImplFactory") + .hasSourceEquivalentTo(loadExpectedFile("expected/Generics_ExplicitFooImplFactory.java")); + assertThat(compilation) + .generatedSourceFile("tests.Generics_FooImplWithClassFactory") + .hasSourceEquivalentTo(loadExpectedFile("expected/Generics_FooImplWithClassFactory.java")); + } + private JavaFileObject loadExpectedFile(String resourceName) { if (isJavaxAnnotationProcessingGeneratedAvailable()) { return JavaFileObjects.forResource(resourceName); diff --git a/factory/src/test/resources/expected/Generics_ExplicitFooImplFactory.java b/factory/src/test/resources/expected/Generics_ExplicitFooImplFactory.java new file mode 100644 index 00000000..00c6d92c --- /dev/null +++ b/factory/src/test/resources/expected/Generics_ExplicitFooImplFactory.java @@ -0,0 +1,48 @@ +/* + * 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 tests; + +import javax.annotation.processing.Generated; +import javax.inject.Inject; +import javax.inject.Provider; + +@Generated( + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) +final class Generics_ExplicitFooImplFactory<M extends Generics.Bar> + implements Generics.FooFactory<M> { + private final Provider<M> unusedProvider; + + @Inject + Generics_ExplicitFooImplFactory(Provider<M> unusedProvider) { + this.unusedProvider = checkNotNull(unusedProvider, 1); + } + + @Override + public Generics.ExplicitFooImpl<M> create() { + return new Generics.ExplicitFooImpl<M>(checkNotNull(unusedProvider.get(), 1)); + } + + 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/Generics_FooImplFactory.java b/factory/src/test/resources/expected/Generics_FooImplFactory.java new file mode 100644 index 00000000..2fb560a7 --- /dev/null +++ b/factory/src/test/resources/expected/Generics_FooImplFactory.java @@ -0,0 +1,34 @@ +/* + * 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 tests; + +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 Generics_FooImplFactory<M extends Generics.Bar> implements Generics.FooFactory<M> { + @Inject + Generics_FooImplFactory() { + } + + @Override + public Generics.FooImpl<M> create() { + return new Generics.FooImpl<M>(); + } +} diff --git a/factory/src/test/resources/expected/Generics_FooImplWithClassFactory.java b/factory/src/test/resources/expected/Generics_FooImplWithClassFactory.java new file mode 100644 index 00000000..b338454f --- /dev/null +++ b/factory/src/test/resources/expected/Generics_FooImplWithClassFactory.java @@ -0,0 +1,34 @@ +/* + * 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 tests; + +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 Generics_FooImplWithClassFactory<M extends Generics.Bar> extends Generics.FooFactoryClass<M> { + @Inject + Generics_FooImplWithClassFactory() { + } + + @Override + public Generics.FooImplWithClass<M> create() { + return new Generics.FooImplWithClass<M>(); + } +} diff --git a/factory/src/test/resources/good/Generics.java b/factory/src/test/resources/good/Generics.java new file mode 100644 index 00000000..638302fe --- /dev/null +++ b/factory/src/test/resources/good/Generics.java @@ -0,0 +1,50 @@ +/* + * 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 tests; + +import com.google.auto.factory.AutoFactory; +import com.google.auto.factory.Provided; + +class Generics { + interface Bar {} + + interface Foo<M extends Bar> {} + + interface FooFactory<M extends Bar> { + Foo<M> create(); + } + + // The generated FooImplFactory should also have an <M extends Bar> type parameter, so we can + // have FooImplFactory<M extends Bar> implements FooFactory<M>. + @AutoFactory(implementing = FooFactory.class) + static final class FooImpl<M extends Bar> implements Foo<M> { + FooImpl() {} + } + + // The generated ExplicitFooImplFactory should have an <M extends Bar> type parameter, which + // serves both for FooFactory<M> and for Provider<M> in the constructor. + @AutoFactory(implementing = FooFactory.class) + static final class ExplicitFooImpl<M extends Bar> implements Foo<M> { + ExplicitFooImpl(@Provided M unused) {} + } + + abstract static class FooFactoryClass<M extends Bar> { + abstract Foo<M> create(); + } + + @AutoFactory(extending = FooFactoryClass.class) + static final class FooImplWithClass<M extends Bar> implements Foo<M> {} +} diff --git a/service/pom.xml b/service/pom.xml index e29bdc37..f3068854 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -37,7 +37,7 @@ <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> - <guava.version>30.1.1-jre</guava.version> + <guava.version>31.0.1-jre</guava.version> <truth.version>1.1.3</truth.version> </properties> @@ -123,7 +123,7 @@ <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>1.0.7</version> + <version>1.1.0</version> </dependency> </dependencies> </plugin> diff --git a/service/processor/pom.xml b/service/processor/pom.xml index 262493a9..cef19ade 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>1.1</version> + <version>1.2.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 f12299a5..85a24cb4 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 @@ -25,12 +25,15 @@ import com.google.auto.common.MoreTypes; import com.google.auto.service.AutoService; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -68,6 +71,8 @@ public class AutoServiceProcessor extends AbstractProcessor { @VisibleForTesting static final String MISSING_SERVICES_ERROR = "No service interfaces provided for element!"; + private final List<String> exceptionStacks = Collections.synchronizedList(new ArrayList<>()); + /** * Maps the class names of service provider interfaces to the * class names of the concrete classes which implement them. @@ -109,11 +114,17 @@ public class AutoServiceProcessor extends AbstractProcessor { processImpl(annotations, roundEnv); } catch (RuntimeException e) { // We don't allow exceptions of any kind to propagate to the compiler - fatalError(getStackTraceAsString(e)); + String trace = getStackTraceAsString(e); + exceptionStacks.add(trace); + fatalError(trace); } return false; } + ImmutableList<String> exceptionStacks() { + return ImmutableList.copyOf(exceptionStacks); + } + private void processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver()) { generateConfigFiles(); @@ -291,7 +302,7 @@ public class AutoServiceProcessor extends AbstractProcessor { private ImmutableSet<DeclaredType> getValueFieldOfClasses(AnnotationMirror annotationMirror) { return getAnnotationValue(annotationMirror, "value") .accept( - new SimpleAnnotationValueVisitor8<ImmutableSet<DeclaredType>, Void>() { + new SimpleAnnotationValueVisitor8<ImmutableSet<DeclaredType>, Void>(ImmutableSet.of()) { @Override public ImmutableSet<DeclaredType> visitType(TypeMirror typeMirror, Void v) { // TODO(ronshapiro): class literals may not always be declared types, i.e. 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 35615689..7a176dd9 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 @@ -17,6 +17,7 @@ package com.google.auto.service.processor; import static com.google.auto.service.processor.AutoServiceProcessor.MISSING_SERVICES_ERROR; import static com.google.testing.compile.CompilationSubject.assertThat; +import static com.google.common.truth.Truth.assertThat; import com.google.common.io.Resources; import com.google.testing.compile.Compilation; @@ -144,4 +145,18 @@ public class AutoServiceProcessorTest { .contentsAsUtf8String() .isEqualTo("test.EnclosingGeneric$GenericServiceProvider\n"); } + + @Test + public void missing() { + AutoServiceProcessor processor = new AutoServiceProcessor(); + Compilation compilation = + Compiler.javac() + .withProcessors(processor) + .withOptions("-Averify=true") + .compile( + JavaFileObjects.forResource( + "test/GenericServiceProviderWithMissingServiceClass.java")); + assertThat(compilation).failed(); + assertThat(processor.exceptionStacks()).isEmpty(); + } } diff --git a/service/processor/src/test/resources/test/GenericServiceProviderWithMissingServiceClass.java b/service/processor/src/test/resources/test/GenericServiceProviderWithMissingServiceClass.java new file mode 100644 index 00000000..3ca34454 --- /dev/null +++ b/service/processor/src/test/resources/test/GenericServiceProviderWithMissingServiceClass.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 test; + +import com.google.auto.service.AutoService; + +/** + * A service that references a missing class. This is useful for testing that the processor behaves + * correctly. + */ +@AutoService(MissingServiceClass.class) +public class GenericServiceProviderWithMissingServiceClass<T> implements MissingServiceClass<T> {} diff --git a/value/Android.bp b/value/Android.bp index f22a6fd4..a723474d 100644 --- a/value/Android.bp +++ b/value/Android.bp @@ -50,6 +50,8 @@ java_library { // a dependency from an apex it is required to have a min_sdk_version min_sdk_version: "19", visibility: ["//visibility:public"], + // b/267831518: Pin tradefed and dependencies to Java 11. + java_version: "11", apex_available: [ "//apex_available:platform", "com.android.extservices", diff --git a/value/annotations/pom.xml b/value/annotations/pom.xml index 8bc63ac9..4fc183b6 100644 --- a/value/annotations/pom.xml +++ b/value/annotations/pom.xml @@ -21,15 +21,14 @@ <parent> <groupId>com.google.auto.value</groupId> <artifactId>auto-value-parent</artifactId> - <version>1.7.4</version> + <version>HEAD-SNAPSHOT</version> </parent> - <groupId>com.google.auto.value</groupId> <artifactId>auto-value-annotations</artifactId> - <version>1.7.4</version> + <version>HEAD-SNAPSHOT</version> <name>AutoValue Annotations</name> <description> - Immutable value-type code generation for Java 1.6+. + Immutable value-type code generation for Java 1.7+. </description> <url>https://github.com/google/auto/tree/master/value</url> diff --git a/value/pom.xml b/value/pom.xml index 5405c7cd..8b1e238d 100644 --- a/value/pom.xml +++ b/value/pom.xml @@ -18,15 +18,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> - <parent> - <groupId>org.sonatype.oss</groupId> - <artifactId>oss-parent</artifactId> - <version>7</version> - </parent> - <groupId>com.google.auto.value</groupId> <artifactId>auto-value-parent</artifactId> - <version>1.7.4</version> + <version>HEAD-SNAPSHOT</version> <name>AutoValue Parent</name> <description> Immutable value-type code generation for Java 7+. @@ -37,7 +31,7 @@ <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> - <guava.version>30.1.1-jre</guava.version> + <guava.version>31.0.1-jre</guava.version> <truth.version>1.1.3</truth.version> </properties> @@ -65,6 +59,21 @@ <url>http://www.google.com</url> </organization> + <developers> + <developer> + <id>eamonnmcmanus</id> + <name>Éamonn McManus</name> + <email>emcmanus@google.com</email> + <organization>Google</organization> + <organizationUrl>http://www.google.com</organizationUrl> + <roles> + <role>owner</role> + <role>developer</role> + </roles> + <timezone>-8</timezone> + </developer> + </developers> + <modules> <module>annotations</module> <module>processor</module> @@ -72,6 +81,19 @@ <module>src/it/gwtserializer</module> </modules> + <distributionManagement> + <snapshotRepository> + <id>sonatype-nexus-snapshots</id> + <name>Sonatype Nexus Snapshots</name> + <url>https://oss.sonatype.org/content/repositories/snapshots/</url> + </snapshotRepository> + <repository> + <id>sonatype-nexus-staging</id> + <name>Nexus Release Repository</name> + <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> + </repository> + </distributionManagement> + <dependencyManagement> <dependencies> <!-- main dependencies --> @@ -150,7 +172,7 @@ <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>1.0.7</version> + <version>1.1.0</version> </dependency> </dependencies> </plugin> @@ -189,4 +211,56 @@ </plugins> </pluginManagement> </build> + <profiles> + <profile> + <id>sonatype-oss-release</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <version>3.2.1</version> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar-no-fork</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>3.3.1</version> + <configuration> + <failOnError>false</failOnError> + </configuration> + <executions> + <execution> + <id>attach-javadocs</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-gpg-plugin</artifactId> + <version>3.0.1</version> + <executions> + <execution> + <id>sign-artifacts</id> + <phase>verify</phase> + <goals> + <goal>sign</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> </project> diff --git a/value/processor/pom.xml b/value/processor/pom.xml index 8892c4f6..b00457d5 100644 --- a/value/processor/pom.xml +++ b/value/processor/pom.xml @@ -21,15 +21,14 @@ <parent> <groupId>com.google.auto.value</groupId> <artifactId>auto-value-parent</artifactId> - <version>1.7.4</version> + <version>HEAD-SNAPSHOT</version> </parent> - <groupId>com.google.auto.value</groupId> <artifactId>auto-value</artifactId> - <version>1.7.4</version> + <version>HEAD-SNAPSHOT</version> <name>AutoValue Processor</name> <description> - Immutable value-type code generation for Java 1.6+. + Immutable value-type code generation for Java 1.7+. </description> <url>https://github.com/google/auto/tree/master/value</url> @@ -41,15 +40,15 @@ </scm> <properties> - <auto-service.version>1.0</auto-service.version> - <errorprone.version>2.7.1</errorprone.version> + <auto-service.version>1.0.1</auto-service.version> + <errorprone.version>2.10.0</errorprone.version> </properties> <dependencies> <dependency> <groupId>com.google.auto</groupId> <artifactId>auto-common</artifactId> - <version>1.1</version> + <version>1.2.1</version> </dependency> <dependency> <groupId>com.google.auto.service</groupId> @@ -125,7 +124,7 @@ <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> - <version>3.11.2</version> + <version>4.1.0</version> <scope>test</scope> </dependency> </dependencies> diff --git a/value/src/it/functional/pom.xml b/value/src/it/functional/pom.xml index d4ae1386..18f3718b 100644 --- a/value/src/it/functional/pom.xml +++ b/value/src/it/functional/pom.xml @@ -22,17 +22,17 @@ <parent> <groupId>com.google.auto.value</groupId> <artifactId>auto-value-parent</artifactId> - <version>1.7.4</version> + <version>HEAD-SNAPSHOT</version> <relativePath>../../../pom.xml</relativePath> </parent> <url>https://github.com/google/auto/tree/master/value</url> <groupId>com.google.auto.value.it.functional</groupId> <artifactId>functional</artifactId> - <version>1.7.4</version> + <version>HEAD-SNAPSHOT</version> <name>Auto-Value Functional Integration Test</name> <properties> - <kotlin.version>1.5.21</kotlin.version> + <kotlin.version>1.6.0</kotlin.version> <exclude.tests>this-matches-nothing</exclude.tests> </properties> <dependencies> @@ -49,7 +49,7 @@ <dependency> <groupId>com.google.auto.service</groupId> <artifactId>auto-service</artifactId> - <version>1.0</version> + <version>1.0.1</version> </dependency> <dependency> <groupId>com.google.guava</groupId> @@ -93,13 +93,13 @@ <dependency> <groupId>dev.gradleplugins</groupId> <artifactId>gradle-test-kit</artifactId> - <version>6.8.3</version> + <version>7.3</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> - <version>3.11.2</version> + <version>4.1.0</version> <scope>test</scope> </dependency> <dependency> @@ -166,7 +166,7 @@ <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>1.0.7</version> + <version>1.1.0</version> </dependency> </dependencies> <configuration> @@ -176,7 +176,6 @@ <arg>-Xlint:all</arg> <arg>-encoding</arg> <arg>utf8</arg> - <arg>-Acom.google.auto.value.AutoBuilderIsUnstable</arg> </compilerArgs> <showWarnings>true</showWarnings> <showDeprecation>true</showDeprecation> @@ -221,7 +220,7 @@ <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>1.0.7</version> + <version>1.1.0</version> </dependency> </dependencies> <configuration> diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java index 952edaac..dba81992 100644 --- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java +++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java @@ -149,6 +149,12 @@ public final class AutoBuilderTest { return new AutoAnnotation_AutoBuilderTest_myAnnotation(value, truthiness); } + // This method has parameters for all the annotation elements. + @AutoAnnotation + static MyAnnotation myAnnotationAll(String value, int id, Truthiness truthiness) { + return new AutoAnnotation_AutoBuilderTest_myAnnotationAll(value, id, truthiness); + } + @AutoBuilder(callMethod = "myAnnotation") interface MyAnnotationBuilder { MyAnnotationBuilder value(String x); @@ -159,12 +165,28 @@ public final class AutoBuilderTest { } static MyAnnotationBuilder myAnnotationBuilder() { - return new AutoBuilder_AutoBuilderTest_MyAnnotationBuilder() - .truthiness(MyAnnotation.DEFAULT_TRUTHINESS); + return new AutoBuilder_AutoBuilderTest_MyAnnotationBuilder(); + } + + @AutoBuilder(callMethod = "myAnnotationAll") + interface MyAnnotationAllBuilder { + MyAnnotationAllBuilder value(String x); + + MyAnnotationAllBuilder id(int x); + + MyAnnotationAllBuilder truthiness(Truthiness x); + + MyAnnotation build(); + } + + static MyAnnotationAllBuilder myAnnotationAllBuilder() { + return new AutoBuilder_AutoBuilderTest_MyAnnotationAllBuilder(); } @Test public void simpleAutoAnnotation() { + // We haven't supplied a value for `truthiness`, so AutoBuilder should use the default one in + // the annotation. MyAnnotation annotation1 = myAnnotationBuilder().value("foo").build(); assertThat(annotation1.value()).isEqualTo("foo"); assertThat(annotation1.id()).isEqualTo(MyAnnotation.DEFAULT_ID); @@ -174,6 +196,15 @@ public final class AutoBuilderTest { assertThat(annotation2.value()).isEqualTo("bar"); assertThat(annotation2.id()).isEqualTo(MyAnnotation.DEFAULT_ID); assertThat(annotation2.truthiness()).isEqualTo(Truthiness.TRUTHY); + + MyAnnotation annotation3 = myAnnotationAllBuilder().value("foo").build(); + MyAnnotation annotation4 = + myAnnotationAllBuilder() + .value("foo") + .id(MyAnnotation.DEFAULT_ID) + .truthiness(MyAnnotation.DEFAULT_TRUTHINESS) + .build(); + assertThat(annotation3).isEqualTo(annotation4); } static class Overload { diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java index 3a7e7bc4..fd87b3e5 100644 --- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java +++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java @@ -3623,4 +3623,48 @@ public class AutoValueTest { } catch (IllegalStateException expected) { } } + + @AutoValue + public abstract static class Stepped { + public abstract String one(); + + public abstract int two(); + + public abstract double three(); + + public interface StepOne<T> { + StepTwo setOne(T x); + } + + public interface StepTwo { + StepThree setTwo(int x); + } + + public interface StepThree { + Stepped setThreeAndBuild(double x); + } + + public static StepOne<String> builder() { + return new AutoValue_AutoValueTest_Stepped.Builder(); + } + + @AutoValue.Builder + abstract static class Builder implements StepOne<String>, StepTwo, StepThree { + abstract Builder setThree(double x); + abstract Stepped build(); + + @Override + public Stepped setThreeAndBuild(double x) { + return setThree(x).build(); + } + } + } + + @Test + public void stepBuilder() { + Stepped stepped = Stepped.builder().setOne("one").setTwo(2).setThreeAndBuild(3.0); + assertThat(stepped.one()).isEqualTo("one"); + assertThat(stepped.two()).isEqualTo(2); + assertThat(stepped.three()).isEqualTo(3.0); + } } diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java index ca10fb45..5f08a725 100644 --- a/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java +++ b/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java @@ -127,8 +127,7 @@ public class CompileWithEclipseTest { version, "-target", version, - "-warn:-warningToken,-intfAnnotation", - "-Acom.google.auto.value.AutoBuilderIsUnstable"); + "-warn:-warningToken,-intfAnnotation"); JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, options, null, sourceFileObjects); // Explicitly supply an empty list of extensions for AutoValueProcessor, because otherwise this diff --git a/value/src/it/gwtserializer/pom.xml b/value/src/it/gwtserializer/pom.xml index 42cc2fe2..88bf677a 100644 --- a/value/src/it/gwtserializer/pom.xml +++ b/value/src/it/gwtserializer/pom.xml @@ -22,14 +22,14 @@ <parent> <groupId>com.google.auto.value</groupId> <artifactId>auto-value-parent</artifactId> - <version>1.7.4</version> + <version>HEAD-SNAPSHOT</version> <relativePath>../../../pom.xml</relativePath> </parent> <url>https://github.com/google/auto/tree/master/value</url> <groupId>com.google.auto.value.it.gwtserializer</groupId> <artifactId>gwtserializer</artifactId> - <version>1.7.4</version> + <version>HEAD-SNAPSHOT</version> <name>Auto-Value GWT-RPC Serialization Integration Test</name> <dependencyManagement> <dependencies> @@ -99,7 +99,7 @@ <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>1.0.7</version> + <version>1.1.0</version> </dependency> </dependencies> <configuration> diff --git a/value/src/main/java/com/google/auto/value/AutoAnnotation.java b/value/src/main/java/com/google/auto/value/AutoAnnotation.java index d36d8e28..c6fab240 100644 --- a/value/src/main/java/com/google/auto/value/AutoAnnotation.java +++ b/value/src/main/java/com/google/auto/value/AutoAnnotation.java @@ -71,6 +71,39 @@ import java.lang.reflect.AnnotatedElement; * parameter corresponding to an array-valued annotation member, and the implementation of each such * member will also return a clone of the array. * + * <p>If your annotation has many elements, you may consider using {@code @AutoBuilder} to make it + * easier to construct instances. In that case, {@code default} values from the annotation will + * become default values for the parameters of the {@code @AutoAnnotation} method. For example: + * + * <pre> + * class Example { + * {@code @interface} MyAnnotation { + * String name() default "foo"; + * int number() default 23; + * } + * + * {@code @AutoAnnotation} + * static MyAnnotation myAnnotation(String value) { + * return new AutoAnnotation_Example_myAnnotation(value); + * } + * + * {@code @AutoBuilder(callMethod = "myAnnotation")} + * interface MyAnnotationBuilder { + * MyAnnotationBuilder name(String name); + * MyAnnotationBuilder number(int number); + * MyAnnotation build(); + * } + * + * static MyAnnotationBuilder myAnnotationBuilder() { + * return new AutoBuilder_Example_MyAnnotationBuilder(); + * } + * } + * </pre> + * + * Here, {@code myAnnotationBuilder().build()} is the same as {@code + * myAnnotationBuilder().name("foo").number(23).build()} because those are the defaults in the + * annotation definition. + * * @author emcmanus@google.com (Éamonn McManus) */ @Target(ElementType.METHOD) diff --git a/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java b/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java index ed986ab7..ed6abaa6 100644 --- a/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java +++ b/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java @@ -15,6 +15,8 @@ */ package com.google.auto.value.processor; +import com.google.auto.common.MoreTypes; +import com.google.auto.value.processor.MissingTypes.MissingTypeException; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import java.util.List; @@ -24,8 +26,10 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleAnnotationValueVisitor8; import javax.tools.Diagnostic; @@ -130,13 +134,13 @@ final class AnnotationOutput { private static class InitializerSourceFormVisitor extends SourceFormVisitor { private final ProcessingEnvironment processingEnv; private final String memberName; - private final Element context; + private final Element errorContext; InitializerSourceFormVisitor( - ProcessingEnvironment processingEnv, String memberName, Element context) { + ProcessingEnvironment processingEnv, String memberName, Element errorContext) { this.processingEnv = processingEnv; this.memberName = memberName; - this.context = context; + this.errorContext = errorContext; } @Override @@ -148,7 +152,7 @@ final class AnnotationOutput { "@AutoAnnotation cannot yet supply a default value for annotation-valued member '" + memberName + "'", - context); + errorContext); sb.append("null"); return null; } @@ -209,9 +213,9 @@ final class AnnotationOutput { AnnotationValue annotationValue, ProcessingEnvironment processingEnv, String memberName, - Element context) { + Element errorContext) { SourceFormVisitor visitor = - new InitializerSourceFormVisitor(processingEnv, memberName, context); + new InitializerSourceFormVisitor(processingEnv, memberName, errorContext); StringBuilder sb = new StringBuilder(); visitor.visit(annotationValue, sb); return sb.toString(); @@ -222,11 +226,59 @@ final class AnnotationOutput { * Java source file to reproduce the annotation in source form. */ static String sourceFormForAnnotation(AnnotationMirror annotationMirror) { + // If a value in the annotation is a reference to a class constant and that class constant is + // undefined, javac unhelpfully converts it into a string "<error>" and visits that instead. We + // want to catch this case and defer processing to allow the class to be defined by another + // annotation processor. So we look for annotation elements whose type is Class but whose + // reported value is a string. Unfortunately we can't extract the ErrorType corresponding to the + // missing class portably. With javac, the AttributeValue is a + // com.sun.tools.javac.code.Attribute.UnresolvedClass, which has a public field classType that + // is the ErrorType we need, but obviously that's nonportable and fragile. + validateClassValues(annotationMirror); StringBuilder sb = new StringBuilder(); new AnnotationSourceFormVisitor().visitAnnotation(annotationMirror, sb); return sb.toString(); } + /** + * Throws an exception if this annotation contains a value for a Class element that is not + * actually a type. The assumption is that the value is the string {@code "<error>"} which javac + * presents when a Class value is an undefined type. + */ + private static void validateClassValues(AnnotationMirror annotationMirror) { + // A class literal can appear in three places: + // * for an element of type Class, for example @SomeAnnotation(Foo.class); + // * for an element of type Class[], for example @SomeAnnotation({Foo.class, Bar.class}); + // * inside a nested annotation, for example @SomeAnnotation(@Nested(Foo.class)). + // These three possibilities are the three branches of the if/else chain below. + annotationMirror + .getElementValues() + .forEach( + (method, value) -> { + TypeMirror type = method.getReturnType(); + if (isJavaLangClass(type) && !(value.getValue() instanceof TypeMirror)) { + throw new MissingTypeException(null); + } else if (type.getKind().equals(TypeKind.ARRAY) + && isJavaLangClass(MoreTypes.asArray(type).getComponentType()) + && value.getValue() instanceof List<?>) { + @SuppressWarnings("unchecked") // a List can only be a List<AnnotationValue> here + List<AnnotationValue> values = (List<AnnotationValue>) value.getValue(); + if (values.stream().anyMatch(av -> !(av.getValue() instanceof TypeMirror))) { + throw new MissingTypeException(null); + } + } else if (type.getKind().equals(TypeKind.DECLARED) + && MoreTypes.asElement(type).getKind().equals(ElementKind.ANNOTATION_TYPE) + && value.getValue() instanceof AnnotationMirror) { + validateClassValues((AnnotationMirror) value.getValue()); + } + }); + } + + private static boolean isJavaLangClass(TypeMirror type) { + return type.getKind().equals(TypeKind.DECLARED) + && MoreTypes.asTypeElement(type).getQualifiedName().contentEquals("java.lang.Class"); + } + private static StringBuilder appendQuoted(StringBuilder sb, String s) { sb.append('"'); for (int i = 0; i < s.length(); i++) { diff --git a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java index 3acf9332..cc0e62ec 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java @@ -287,7 +287,7 @@ public class AutoAnnotationProcessor extends AbstractProcessor { private String generatedClassName(ExecutableElement method) { TypeElement type = MoreElements.asType(method.getEnclosingElement()); String name = type.getSimpleName().toString(); - while (type.getEnclosingElement() instanceof TypeElement) { + while (MoreElements.isType(type.getEnclosingElement())) { type = MoreElements.asType(type.getEnclosingElement()); name = type.getSimpleName() + "_" + name; } diff --git a/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java index b6a578fc..fc0d8b3e 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java @@ -20,6 +20,7 @@ import static com.google.auto.common.MoreElements.getPackage; import static com.google.auto.common.MoreStreams.toImmutableList; import static com.google.auto.common.MoreStreams.toImmutableSet; import static com.google.auto.value.processor.AutoValueProcessor.OMIT_IDENTIFIERS_OPTION; +import static com.google.auto.value.processor.ClassNames.AUTO_ANNOTATION_NAME; import static com.google.auto.value.processor.ClassNames.AUTO_BUILDER_NAME; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toCollection; @@ -38,6 +39,7 @@ import com.google.auto.value.processor.MissingTypes.MissingTypeException; import com.google.common.base.Ascii; import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import java.lang.reflect.Field; @@ -60,6 +62,7 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; @@ -77,7 +80,7 @@ public class AutoBuilderProcessor extends AutoValueishProcessor { private static final String ALLOW_OPTION = "com.google.auto.value.AutoBuilderIsUnstable"; public AutoBuilderProcessor() { - super(AUTO_BUILDER_NAME); + super(AUTO_BUILDER_NAME, /* appliesToInterfaces= */ true); } @Override @@ -95,21 +98,9 @@ public class AutoBuilderProcessor extends AutoValueishProcessor { @Override void processType(TypeElement autoBuilderType) { - if (!processingEnv.getOptions().containsKey(ALLOW_OPTION)) { - errorReporter() - .abortWithError( - autoBuilderType, - "Compile with -A%s to enable this UNSUPPORTED AND UNSTABLE prototype", - ALLOW_OPTION); - } - if (autoBuilderType.getKind() != ElementKind.CLASS - && autoBuilderType.getKind() != ElementKind.INTERFACE) { - errorReporter() - .abortWithError( - autoBuilderType, - "[AutoBuilderWrongType] @AutoBuilder only applies to classes and interfaces"); + if (processingEnv.getOptions().containsKey(ALLOW_OPTION)) { + errorReporter().reportWarning(autoBuilderType, "The -A%s option is obsolete", ALLOW_OPTION); } - checkModifiersIfNested(autoBuilderType); // The annotation is guaranteed to be present by the contract of Processor#process AnnotationMirror autoBuilderAnnotation = getAnnotationMirror(autoBuilderType, AUTO_BUILDER_NAME).get(); @@ -126,7 +117,7 @@ public class AutoBuilderProcessor extends AutoValueishProcessor { Optional<BuilderMethodClassifier<VariableElement>> maybeClassifier = BuilderMethodClassifierForAutoBuilder.classify( methods, errorReporter(), processingEnv, executable, builtType, autoBuilderType); - if (!maybeClassifier.isPresent()) { + if (!maybeClassifier.isPresent() || errorReporter().errorCount() > 0) { // We've already output one or more error messages. return; } @@ -134,7 +125,7 @@ public class AutoBuilderProcessor extends AutoValueishProcessor { Map<String, String> propertyToGetterName = Maps.transformValues(classifier.builderGetters(), PropertyGetter::getName); AutoBuilderTemplateVars vars = new AutoBuilderTemplateVars(); - vars.props = propertySet(executable, propertyToGetterName); + vars.props = propertySet(autoBuilderType, executable, propertyToGetterName); builder.defineVars(vars, classifier); vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION); String generatedClassName = generatedClassName(autoBuilderType, "AutoBuilder_"); @@ -152,7 +143,15 @@ public class AutoBuilderProcessor extends AutoValueishProcessor { } private ImmutableSet<Property> propertySet( - ExecutableElement executable, Map<String, String> propertyToGetterName) { + TypeElement autoBuilderType, + ExecutableElement executable, + Map<String, String> propertyToGetterName) { + boolean autoAnnotation = + MoreElements.getAnnotationMirror(executable, AUTO_ANNOTATION_NAME).isPresent(); + ImmutableMap<String, String> builderInitializers = + autoAnnotation + ? autoAnnotationInitializers(autoBuilderType, executable) + : ImmutableMap.of(); // Fix any parameter names that are reserved words in Java. Java source code can't have // such parameter names, but Kotlin code might, for example. Map<VariableElement, String> identifiers = @@ -161,18 +160,58 @@ public class AutoBuilderProcessor extends AutoValueishProcessor { fixReservedIdentifiers(identifiers); return executable.getParameters().stream() .map( - v -> - newProperty( - v, identifiers.get(v), propertyToGetterName.get(v.getSimpleName().toString()))) + v -> { + String name = v.getSimpleName().toString(); + return newProperty( + v, + identifiers.get(v), + propertyToGetterName.get(name), + Optional.ofNullable(builderInitializers.get(name))); + }) .collect(toImmutableSet()); } - private Property newProperty(VariableElement var, String identifier, String getterName) { + private Property newProperty( + VariableElement var, + String identifier, + String getterName, + Optional<String> builderInitializer) { String name = var.getSimpleName().toString(); TypeMirror type = var.asType(); Optional<String> nullableAnnotation = nullableAnnotationFor(var, var.asType()); return new Property( - name, identifier, TypeEncoder.encode(type), type, nullableAnnotation, getterName); + name, + identifier, + TypeEncoder.encode(type), + type, + nullableAnnotation, + getterName, + builderInitializer); + } + + private ImmutableMap<String, String> autoAnnotationInitializers( + TypeElement autoBuilderType, ExecutableElement autoAnnotationMethod) { + // We expect the return type of an @AutoAnnotation method to be an annotation type. If it isn't, + // AutoAnnotation will presumably complain, so we don't need to complain further. + TypeMirror returnType = autoAnnotationMethod.getReturnType(); + if (!returnType.getKind().equals(TypeKind.DECLARED)) { + return ImmutableMap.of(); + } + // This might not actually be an annotation (if the code is wrong), but if that's the case we + // just won't see any contained ExecutableElement where getDefaultValue() returns something. + TypeElement annotation = MoreTypes.asTypeElement(returnType); + ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); + for (ExecutableElement method : methodsIn(annotation.getEnclosedElements())) { + AnnotationValue defaultValue = method.getDefaultValue(); + if (defaultValue != null) { + String memberName = method.getSimpleName().toString(); + builder.put( + memberName, + AnnotationOutput.sourceFormForInitializer( + defaultValue, processingEnv, memberName, autoBuilderType)); + } + } + return builder.build(); } private ExecutableElement findExecutable( diff --git a/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java index 711b138c..4d19d216 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java @@ -60,7 +60,7 @@ import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING) public class AutoOneOfProcessor extends AutoValueishProcessor { public AutoOneOfProcessor() { - super(AUTO_ONE_OF_NAME); + super(AUTO_ONE_OF_NAME, /* appliesToInterfaces= */ false); } @Override @@ -75,13 +75,6 @@ public class AutoOneOfProcessor extends AutoValueishProcessor { @Override void processType(TypeElement autoOneOfType) { - if (autoOneOfType.getKind() != ElementKind.CLASS) { - errorReporter() - .abortWithError( - autoOneOfType, - "[AutoOneOfNotClass] @" + AUTO_ONE_OF_NAME + " only applies to classes"); - } - checkModifiersIfNested(autoOneOfType); DeclaredType kindMirror = mirrorForKindType(autoOneOfType); // We are going to classify the methods of the @AutoOneOf class into several categories. diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java index 86cf4974..9fbc1652 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java @@ -109,7 +109,8 @@ abstract class AutoValueOrBuilderTemplateVars extends AutoValueishTemplateVars { * * <ul> * <li>it is {@code @Nullable} (in which case it defaults to null); - * <li>it is {@code Optional} (in which case it defaults to empty); + * <li>it has a builder initializer (for example it is {@code Optional}, which will have an + * initializer of {@code Optional.empty()}); * <li>it has a property-builder method (in which case it defaults to empty). * </ul> */ diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java index ab7da924..4479a056 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java @@ -18,6 +18,7 @@ package com.google.auto.value.processor; import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods; import static com.google.auto.common.MoreStreams.toImmutableList; import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_NAME; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Sets.difference; import static com.google.common.collect.Sets.intersection; import static java.util.Comparator.naturalOrder; @@ -45,7 +46,6 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; @@ -79,21 +79,24 @@ public class AutoValueProcessor extends AutoValueishProcessor { @VisibleForTesting AutoValueProcessor(ClassLoader loaderForExtensions) { - super(AUTO_VALUE_NAME); - this.extensions = null; - this.loaderForExtensions = loaderForExtensions; + this(ImmutableList.of(), loaderForExtensions); } @VisibleForTesting - public AutoValueProcessor(Iterable<? extends AutoValueExtension> extensions) { - super(AUTO_VALUE_NAME); - this.extensions = ImmutableList.copyOf(extensions); - this.loaderForExtensions = null; + public AutoValueProcessor(Iterable<? extends AutoValueExtension> testExtensions) { + this(testExtensions, null); + } + + private AutoValueProcessor( + Iterable<? extends AutoValueExtension> testExtensions, ClassLoader loaderForExtensions) { + super(AUTO_VALUE_NAME, /* appliesToInterfaces= */ false); + this.extensions = ImmutableList.copyOf(testExtensions); + this.loaderForExtensions = loaderForExtensions; } // Depending on how this AutoValueProcessor was constructed, we might already have a list of - // extensions when init() is run, or, if `extensions` is null, we have a ClassLoader that will be - // used to get the list using the ServiceLoader API. + // extensions when init() is run, or, if `loaderForExtensions` is not null, it is a ClassLoader + // that will be used to get the list using the ServiceLoader API. private ImmutableList<AutoValueExtension> extensions; private final ClassLoader loaderForExtensions; @@ -108,7 +111,8 @@ public class AutoValueProcessor extends AutoValueishProcessor { public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); - if (extensions == null) { + if (loaderForExtensions != null) { + checkState(extensions.isEmpty()); try { extensions = extensionsFromLoader(loaderForExtensions); } catch (RuntimeException | Error e) { @@ -165,10 +169,6 @@ public class AutoValueProcessor extends AutoValueishProcessor { @Override void processType(TypeElement type) { - if (type.getKind() != ElementKind.CLASS) { - errorReporter() - .abortWithError(type, "[AutoValueNotClass] @AutoValue only applies to classes"); - } if (ancestorIsAutoValue(type)) { errorReporter() .abortWithError(type, "[AutoValueExtend] One @AutoValue class may not extend another"); @@ -180,7 +180,6 @@ public class AutoValueProcessor extends AutoValueishProcessor { "[AutoValueImplAnnotation] @AutoValue may not be used to implement an annotation" + " interface; try using @AutoAnnotation instead"); } - checkModifiersIfNested(type); // We are going to classify the methods of the @AutoValue class into several categories. // This covers the methods in the class itself and the ones it inherits from supertypes. diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java index 93f2f79e..31f1ec1c 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java @@ -29,6 +29,7 @@ import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; +import static javax.lang.model.util.ElementFilter.constructorsIn; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; @@ -92,19 +93,22 @@ import javax.tools.JavaFileObject; */ abstract class AutoValueishProcessor extends AbstractProcessor { private final String annotationClassName; + private final boolean appliesToInterfaces; /** - * Qualified names of {@code @AutoValue} or {@code AutoOneOf} classes that we attempted to process - * but had to abandon because we needed other types that they referenced and those other types - * were missing. + * Qualified names of {@code @AutoValue} (etc) classes that we attempted to process but had to + * abandon because we needed other types that they referenced and those other types were missing. */ private final List<String> deferredTypeNames = new ArrayList<>(); - AutoValueishProcessor(String annotationClassName) { + AutoValueishProcessor(String annotationClassName, boolean appliesToInterfaces) { this.annotationClassName = annotationClassName; + this.appliesToInterfaces = appliesToInterfaces; } - /** The annotation we are processing, {@code AutoValue} or {@code AutoOneOf}. */ + /** + * The annotation we are processing, for example {@code AutoValue} or {@code AutoBuilder}. + */ private TypeElement annotationType; /** The simple name of {@link #annotationType}. */ private String simpleAnnotationName; @@ -117,6 +121,10 @@ abstract class AutoValueishProcessor extends AbstractProcessor { super.init(processingEnv); errorReporter = new ErrorReporter(processingEnv); nullables = new Nullables(processingEnv); + annotationType = elementUtils().getTypeElement(annotationClassName); + if (annotationType != null) { + simpleAnnotationName = annotationType.getSimpleName().toString(); + } } final ErrorReporter errorReporter() { @@ -132,9 +140,9 @@ abstract class AutoValueishProcessor extends AbstractProcessor { } /** - * Qualified names of {@code @AutoValue} or {@code AutoOneOf} classes that we attempted to process - * but had to abandon because we needed other types that they referenced and those other types - * were missing. This is used by tests. + * Qualified names of {@code @AutoValue} (etc) classes that we attempted to process but had to + * abandon because we needed other types that they referenced and those other types were missing. + * This is used by tests. */ final ImmutableList<String> deferredTypeNames() { return ImmutableList.copyOf(deferredTypeNames); @@ -160,6 +168,7 @@ abstract class AutoValueishProcessor extends AbstractProcessor { private final Optional<String> nullableAnnotation; private final Optionalish optional; private final String getter; + private final String builderInitializer; // empty, or with initial ` = `. Property( String name, @@ -167,17 +176,41 @@ abstract class AutoValueishProcessor extends AbstractProcessor { String type, TypeMirror typeMirror, Optional<String> nullableAnnotation, - String getter) { + String getter, + Optional<String> maybeBuilderInitializer) { this.name = name; this.identifier = identifier; this.type = type; this.typeMirror = typeMirror; this.nullableAnnotation = nullableAnnotation; this.optional = Optionalish.createIfOptional(typeMirror); + this.builderInitializer = + maybeBuilderInitializer.isPresent() + ? " = " + maybeBuilderInitializer.get() + : builderInitializer(); this.getter = getter; } /** + * Returns the appropriate initializer for a builder property. Builder properties are never + * primitive; if the built property is an {@code int} the builder property will be an {@code + * Integer}. So the default value for a builder property will be null unless there is an + * initializer. The caller of the constructor may have supplied an initializer, but otherwise we + * supply one only if this property is an {@code Optional} and is not {@code @Nullable}. In that + * case the initializer sets it to {@code Optional.empty()}. + */ + private String builderInitializer() { + if (nullableAnnotation.isPresent()) { + return ""; + } + Optionalish optional = Optionalish.createIfOptional(typeMirror); + if (optional == null) { + return ""; + } + return " = " + optional.getEmpty(); + } + + /** * Returns the name of the property as it should be used when declaring identifiers (fields and * parameters). If the original getter method was {@code foo()} then this will be {@code foo}. * If it was {@code getFoo()} then it will be {@code foo}. If it was {@code getPackage()} then @@ -219,6 +252,14 @@ abstract class AutoValueishProcessor extends AbstractProcessor { } /** + * Returns a string to be used as an initializer for a builder field for this property, + * including the leading {@code =}, or an empty string if there is no explicit initializer. + */ + public String getBuilderInitializer() { + return builderInitializer; + } + + /** * Returns the string to use as a method annotation to indicate the nullability of this * property. It is either the empty string, if the property is not nullable, or an annotation * string with a trailing space, such as {@code "@`javax.annotation.Nullable` "}, where the @@ -266,7 +307,8 @@ abstract class AutoValueishProcessor extends AbstractProcessor { type, method.getReturnType(), nullableAnnotation, - method.getSimpleName().toString()); + method.getSimpleName().toString(), + Optional.empty()); this.method = method; this.fieldAnnotations = fieldAnnotations; this.methodAnnotations = methodAnnotations; @@ -305,7 +347,6 @@ abstract class AutoValueishProcessor extends AbstractProcessor { @Override public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { - annotationType = elementUtils().getTypeElement(annotationClassName); if (annotationType == null) { // This should not happen. If the annotation type is not found, how did the processor get // triggered? @@ -318,7 +359,6 @@ abstract class AutoValueishProcessor extends AbstractProcessor { + " because the annotation class was not found"); return false; } - simpleAnnotationName = annotationType.getSimpleName().toString(); List<TypeElement> deferredTypes = deferredTypeNames.stream() .map(name -> elementUtils().getTypeElement(name)) @@ -330,9 +370,10 @@ abstract class AutoValueishProcessor extends AbstractProcessor { for (TypeElement type : deferredTypes) { errorReporter.reportError( type, - "[AutoValueUndefined] Did not generate @%s class for %s because it references" + "[%sUndefined] Did not generate @%s class for %s because it references" + " undefined types", simpleAnnotationName, + simpleAnnotationName, type.getQualifiedName()); } return false; @@ -347,6 +388,7 @@ abstract class AutoValueishProcessor extends AbstractProcessor { deferredTypeNames.clear(); for (TypeElement type : types) { try { + validateType(type); processType(type); } catch (AbortProcessingException e) { // We abandoned this type; continue with the next. @@ -362,7 +404,8 @@ abstract class AutoValueishProcessor extends AbstractProcessor { String trace = Throwables.getStackTraceAsString(e); errorReporter.reportError( type, - "[AutoValueException] @%s processor threw an exception: %s", + "[%sException] @%s processor threw an exception: %s", + simpleAnnotationName, simpleAnnotationName, trace); throw e; @@ -372,8 +415,44 @@ abstract class AutoValueishProcessor extends AbstractProcessor { } /** - * Analyzes a single {@code @AutoValue} or {@code @AutoOneOf} class, and outputs the corresponding - * implementation class or classes. + * Validations common to all the subclasses. An {@code @AutoFoo} type must be a class, or possibly + * an interface for {@code @AutoBuilder}. If it is a class then it must have a non-private no-arg + * constructor. And, since we'll be generating a subclass, it can't be final. + */ + private void validateType(TypeElement type) { + ElementKind kind = type.getKind(); + boolean kindOk = + kind.equals(ElementKind.CLASS) + || (appliesToInterfaces && kind.equals(ElementKind.INTERFACE)); + if (!kindOk) { + String appliesTo = appliesToInterfaces ? "classes and interfaces" : "classes"; + errorReporter.abortWithError( + type, + "[%sWrongType] @%s only applies to %s", + simpleAnnotationName, + simpleAnnotationName, + appliesTo); + } + checkModifiersIfNested(type); + if (!hasVisibleNoArgConstructor(type)) { + errorReporter.reportError( + type, + "[%sConstructor] @%s class must have a non-private no-arg constructor", + simpleAnnotationName, + simpleAnnotationName); + } + if (type.getModifiers().contains(Modifier.FINAL)) { + errorReporter.abortWithError( + type, + "[%sFinal] @%s class must not be final", + simpleAnnotationName, + simpleAnnotationName); + } + } + + /** + * Analyzes a single {@code @AutoValue} (etc) class, and outputs the corresponding implementation + * class or classes. * * @param type the class with the {@code @AutoValue} or {@code @AutoOneOf} annotation. */ @@ -435,7 +514,9 @@ abstract class AutoValueishProcessor extends AbstractProcessor { if (p.isNullable() && returnType.getKind().isPrimitive()) { errorReporter() .reportError( - propertyMethod, "[AutoValueNullPrimitive] Primitive types cannot be @Nullable"); + propertyMethod, + "[%sNullPrimitive] Primitive types cannot be @Nullable", + simpleAnnotationName); } }); return props.build(); @@ -467,24 +548,23 @@ abstract class AutoValueishProcessor extends AbstractProcessor { /** Returns the spelling to be used in the generated code for the given list of annotations. */ static ImmutableList<String> annotationStrings(List<? extends AnnotationMirror> annotations) { - // TODO(b/68008628): use ImmutableList.toImmutableList() when that works. return annotations.stream() .map(AnnotationOutput::sourceFormForAnnotation) + .sorted() // ensures deterministic order .collect(toImmutableList()); } /** - * Returns the name of the generated {@code @AutoValue} or {@code @AutoOneOf} class, for example - * {@code AutoOneOf_TaskResult} or {@code $$AutoValue_SimpleMethod}. + * Returns the name of the generated {@code @AutoValue} (etc) class, for example {@code + * AutoOneOf_TaskResult} or {@code $$AutoValue_SimpleMethod}. * - * @param type the name of the type bearing the {@code @AutoValue} or {@code @AutoOneOf} - * annotation. + * @param type the name of the type bearing the {@code @AutoValue} (etc) annotation. * @param prefix the prefix to use in the generated class. This may start with one or more dollar * signs, for an {@code @AutoValue} implementation where there are AutoValue extensions. */ static String generatedClassName(TypeElement type, String prefix) { String name = type.getSimpleName().toString(); - while (type.getEnclosingElement() instanceof TypeElement) { + while (MoreElements.isType(type.getEnclosingElement())) { type = MoreElements.asType(type.getEnclosingElement()); name = type.getSimpleName() + "_" + name; } @@ -555,7 +635,8 @@ abstract class AutoValueishProcessor extends AbstractProcessor { for (ExecutableElement context : contexts) { errorReporter.reportError( context, - "[AutoValueDupProperty] More than one @%s property called %s", + "[%sDupProperty] More than one @%s property called %s", + simpleAnnotationName, simpleAnnotationName, name); } @@ -589,8 +670,9 @@ abstract class AutoValueishProcessor extends AbstractProcessor { List<? extends AnnotationMirror> elementAnnotations = element.getAnnotationMirrors(); OptionalInt nullableAnnotationIndex = nullableAnnotationIndex(elementAnnotations); if (nullableAnnotationIndex.isPresent()) { - ImmutableList<String> annotations = annotationStrings(elementAnnotations); - return Optional.of(annotations.get(nullableAnnotationIndex.getAsInt()) + " "); + AnnotationMirror annotation = elementAnnotations.get(nullableAnnotationIndex.getAsInt()); + String annotationString = AnnotationOutput.sourceFormForAnnotation(annotation); + return Optional.of(annotationString + " "); } else { return Optional.empty(); } @@ -1152,6 +1234,14 @@ abstract class AutoValueishProcessor extends AbstractProcessor { return getAnnotationMirror(element, annotationName).isPresent(); } + /** True if the type is a class with a non-private no-arg constructor, or is an interface. */ + static boolean hasVisibleNoArgConstructor(TypeElement type) { + return type.getKind().isInterface() + || constructorsIn(type.getEnclosedElements()).stream() + .anyMatch( + c -> c.getParameters().isEmpty() && !c.getModifiers().contains(Modifier.PRIVATE)); + } + final void writeSourceFile(String className, String text, TypeElement originatingType) { try { JavaFileObject sourceFile = diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java index 51773e6f..a4336f5e 100644 --- a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java +++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java @@ -386,14 +386,17 @@ abstract class BuilderMethodClassifier<E extends Element> { DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType()); ExecutableType methodMirror = MoreTypes.asExecutable(typeUtils.asMemberOf(builderTypeMirror, method)); - if (TYPE_EQUIVALENCE.equivalent(methodMirror.getReturnType(), builderType.asType())) { + TypeMirror returnType = methodMirror.getReturnType(); + if (typeUtils.isSubtype(builderType.asType(), returnType) + && !MoreTypes.isTypeOf(Object.class, returnType)) { + // We allow the return type to be a supertype (other than Object), to support step builders. TypeMirror parameterType = Iterables.getOnlyElement(methodMirror.getParameterTypes()); propertyNameToSetters.put( propertyName, new PropertySetter(method, parameterType, function.get())); } else { errorReporter.reportError( method, - "[%sBuilderRet] Setter methods must return %s", + "[%sBuilderRet] Setter methods must return %s or a supertype", autoWhat(), builderType.asType()); } diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java index 9f45d172..b612c104 100644 --- a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java +++ b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java @@ -18,6 +18,7 @@ package com.google.auto.value.processor; import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods; import static com.google.auto.common.MoreStreams.toImmutableSet; import static com.google.auto.value.processor.AutoValueishProcessor.hasAnnotationMirror; +import static com.google.auto.value.processor.AutoValueishProcessor.hasVisibleNoArgConstructor; import static com.google.auto.value.processor.AutoValueishProcessor.nullableAnnotationFor; import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_BUILDER_NAME; import static com.google.common.collect.Sets.immutableEnumSet; @@ -86,16 +87,9 @@ class BuilderSpec { Optional<TypeElement> builderTypeElement = Optional.empty(); for (TypeElement containedClass : typesIn(autoValueClass.getEnclosedElements())) { if (hasAnnotationMirror(containedClass, AUTO_VALUE_BUILDER_NAME)) { - if (!CLASS_OR_INTERFACE.contains(containedClass.getKind())) { - errorReporter.reportError( - containedClass, - "[AutoValueBuilderClass] @AutoValue.Builder can only apply to a class or an" - + " interface"); - } else if (!containedClass.getModifiers().contains(Modifier.STATIC)) { - errorReporter.reportError( - containedClass, - "[AutoValueInnerBuilder] @AutoValue.Builder cannot be applied to a non-static class"); - } else if (builderTypeElement.isPresent()) { + findBuilderError(containedClass) + .ifPresent(error -> errorReporter.reportError(containedClass, "%s", error)); + if (builderTypeElement.isPresent()) { errorReporter.reportError( containedClass, "[AutoValueTwoBuilders] %s already has a Builder: %s", @@ -114,6 +108,24 @@ class BuilderSpec { } } + /** Finds why this {@code @AutoValue.Builder} class is bad, if it is bad. */ + private Optional<String> findBuilderError(TypeElement builderTypeElement) { + if (!CLASS_OR_INTERFACE.contains(builderTypeElement.getKind())) { + return Optional.of( + "[AutoValueBuilderClass] @AutoValue.Builder can only apply to a class or an" + + " interface"); + } else if (!builderTypeElement.getModifiers().contains(Modifier.STATIC)) { + return Optional.of( + "[AutoValueInnerBuilder] @AutoValue.Builder cannot be applied to a non-static class"); + } else if (builderTypeElement.getKind().equals(ElementKind.CLASS) + && !hasVisibleNoArgConstructor(builderTypeElement)) { + return Optional.of( + "[AutoValueBuilderConstructor] @AutoValue.Builder class must have a non-private no-arg" + + " constructor"); + } + return Optional.empty(); + } + /** Representation of an {@code AutoValue.Builder} class or interface. */ class Builder implements AutoValueExtension.BuilderContext { private final TypeElement builderTypeElement; @@ -333,7 +345,7 @@ class BuilderSpec { vars.builderRequiredProperties = vars.props.stream() .filter(p -> !p.isNullable()) - .filter(p -> p.getOptional() == null) + .filter(p -> p.getBuilderInitializer().isEmpty()) .filter(p -> !vars.builderPropertyBuilders.containsKey(p.getName())) .collect(toImmutableSet()); } diff --git a/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java b/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java index fae4e092..35fcbbf0 100644 --- a/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java +++ b/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java @@ -15,15 +15,9 @@ */ package com.google.auto.value.processor; -import static java.util.stream.Collectors.joining; - -import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Optional; import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; @@ -46,29 +40,7 @@ class GwtCompatibility { return gwtCompatibleAnnotation; } - // Get rid of the misconceived <? extends ExecutableElement, ? extends AnnotationValue> - // in the return type of getElementValues(). - static Map<ExecutableElement, AnnotationValue> getElementValues(AnnotationMirror annotation) { - return Collections.<ExecutableElement, AnnotationValue>unmodifiableMap( - annotation.getElementValues()); - } - String gwtCompatibleAnnotationString() { - if (gwtCompatibleAnnotation.isPresent()) { - AnnotationMirror annotation = gwtCompatibleAnnotation.get(); - TypeElement annotationElement = (TypeElement) annotation.getAnnotationType().asElement(); - String annotationArguments; - if (annotation.getElementValues().isEmpty()) { - annotationArguments = ""; - } else { - annotationArguments = - getElementValues(annotation).entrySet().stream() - .map(e -> e.getKey().getSimpleName() + " = " + e.getValue()) - .collect(joining(", ", "(", ")")); - } - return "@" + annotationElement.getQualifiedName() + annotationArguments; - } else { - return ""; - } + return gwtCompatibleAnnotation.map(AnnotationOutput::sourceFormForAnnotation).orElse(""); } } diff --git a/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java b/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java index 30ad0926..8673d3db 100644 --- a/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java +++ b/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java @@ -18,6 +18,7 @@ package com.google.auto.value.processor; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.stream.Collectors.toList; +import com.google.auto.common.AnnotationMirrors; import com.google.auto.value.processor.AutoValueishProcessor.GetterProperty; import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder; import com.google.common.collect.ImmutableMap; @@ -26,13 +27,10 @@ import com.google.escapevelocity.Template; import java.io.IOException; import java.io.Writer; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.zip.CRC32; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; @@ -60,13 +58,11 @@ class GwtSerialization { Optional<AnnotationMirror> optionalGwtCompatible = gwtCompatibility.gwtCompatibleAnnotation(); if (optionalGwtCompatible.isPresent()) { AnnotationMirror gwtCompatible = optionalGwtCompatible.get(); - for (Map.Entry<ExecutableElement, AnnotationValue> entry : - GwtCompatibility.getElementValues(gwtCompatible).entrySet()) { - if (entry.getKey().getSimpleName().contentEquals("serializable") - && entry.getValue().getValue().equals(true)) { - return true; - } - } + return AnnotationMirrors.getAnnotationValuesWithDefaults(gwtCompatible).entrySet().stream() + .anyMatch( + e -> + e.getKey().getSimpleName().contentEquals("serializable") + && e.getValue().getValue().equals(true)); } return false; } diff --git a/value/src/main/java/com/google/auto/value/processor/autovalue.vm b/value/src/main/java/com/google/auto/value/processor/autovalue.vm index 86cfe493..18ca827a 100644 --- a/value/src/main/java/com/google/auto/value/processor/autovalue.vm +++ b/value/src/main/java/com/google/auto/value/processor/autovalue.vm @@ -75,15 +75,11 @@ ${modifiers}class $subclass$formalTypes extends $origClass$actualTypes { ## the constructor is called from the extension code. #if ($identifiers) - if ($p == null) { throw new NullPointerException("Null $p.name"); } #else - ## Just throw NullPointerException with no message if it's null. - ## The Object cast has no effect on the code but silences an ErrorProne warning. - - ((`java.lang.Object`) ${p}).getClass(); + `java.util.Objects`.requireNonNull($p); #end #end diff --git a/value/src/main/java/com/google/auto/value/processor/builder.vm b/value/src/main/java/com/google/auto/value/processor/builder.vm index 630330ca..b1787f25 100644 --- a/value/src/main/java/com/google/auto/value/processor/builder.vm +++ b/value/src/main/java/com/google/auto/value/processor/builder.vm @@ -40,7 +40,7 @@ class ${builderName}${builderFormalTypes} ## #if ($p.kind.primitive) - private $types.boxedClass($p.typeMirror).simpleName $p; + private $types.boxedClass($p.typeMirror).simpleName $p $p.builderInitializer; #else @@ -54,7 +54,7 @@ class ${builderName}${builderFormalTypes} ## #end - private $p.type $p #if ($p.optional && !$p.nullable) = $p.optional.empty #end ; + private $p.type $p $p.builderInitializer; #end #end @@ -94,15 +94,11 @@ class ${builderName}${builderFormalTypes} ## #if (!$setter.primitiveParameter && !$p.nullable && ${setter.copy($p)} == $p) #if ($identifiers) - if ($p == null) { throw new NullPointerException("Null $p.name"); } #else - ## Just throw NullPointerException with no message if it's null. - ## The Object cast has no effect on the code but silences an ErrorProne warning. - - ((`java.lang.Object`) ${p}).getClass(); + `java.util.Objects`.requireNonNull($p); #end #end diff --git a/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java index 50b6b271..96649bd1 100644 --- a/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java +++ b/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java @@ -115,7 +115,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation) .generatedSourceFile("foo.bar.AutoBuilder_Baz_Builder") @@ -148,7 +147,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation) .generatedSourceFile("foo.bar.AutoBuilder_Baz_Builder") @@ -192,7 +190,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(built, builder); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation).generatedSourceFile("foo.bar.AutoBuilder_Builder"); @@ -214,7 +211,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -242,7 +238,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -252,6 +247,66 @@ public final class AutoBuilderCompilationTest { } @Test + public void autoBuilderClassMustHaveNoArgConstructor() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "public class Baz {", + " @AutoBuilder", + " abstract static class Builder {", + " Builder(int bogus) {}", + " Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderConstructor] @AutoBuilder class must have a non-private no-arg" + + " constructor") + .inFile(javaFileObject) + .onLineContaining("class Builder"); + } + + @Test + public void autoBuilderClassMustHaveVisibleNoArgConstructor() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "public class Baz {", + " @AutoBuilder", + " abstract static class Builder {", + " private Builder() {}", + " Baz build();", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderConstructor] @AutoBuilder class must have a non-private no-arg" + + " constructor") + .inFile(javaFileObject) + .onLineContaining("class Builder"); + } + + @Test public void autoBuilderNestedInPrivate() { JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( @@ -271,7 +326,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -299,7 +353,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -328,7 +381,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -355,7 +407,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -386,7 +437,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -418,7 +468,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -451,7 +500,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -489,7 +537,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -519,7 +566,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -553,7 +599,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -584,7 +629,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -618,7 +662,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -650,7 +693,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -686,7 +728,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -719,12 +760,11 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( - "[AutoBuilderBuilderRet] Setter methods must return foo.bar.Baz.Builder") + "[AutoBuilderBuilderRet] Setter methods must return foo.bar.Baz.Builder or a supertype") .inFile(javaFileObject) .onLineContaining("two(int x)"); } @@ -761,7 +801,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject, nullableFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -794,7 +833,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -827,7 +865,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -862,7 +899,6 @@ public final class AutoBuilderCompilationTest { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) diff --git a/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java index 788b543a..a55b74d0 100644 --- a/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java +++ b/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java @@ -463,6 +463,33 @@ public class AutoOneOfCompilationTest { } @Test + public void mustBeClass() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Pet", + "package foo.bar;", + "", + "import com.google.auto.value.AutoOneOf;", + "", + "@AutoOneOf(Pet.Kind.class)", + "public interface Pet {", + " public enum Kind {", + " DOG,", + " CAT,", + " }", + " Kind getKind();", + " String dog();", + " String cat();", + "}"); + Compilation compilation = + javac().withProcessors(new AutoOneOfProcessor()).compile(javaFileObject); + assertThat(compilation) + .hadErrorContaining("@AutoOneOf only applies to classes") + .inFile(javaFileObject) + .onLineContaining("interface Pet"); + } + + @Test public void cantBeNullable() { JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( @@ -490,4 +517,62 @@ public class AutoOneOfCompilationTest { .inFile(javaFileObject) .onLineContaining("@Nullable String dog()"); } + + @Test + public void mustHaveNoArgConstructor() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Pet", + "package foo.bar;", + "", + "import com.google.auto.value.AutoOneOf;", + "", + "@AutoOneOf(Pet.Kind.class)", + "public abstract class Pet {", + " Pet(boolean cuddly) {}", + "", + " public enum Kind {", + " DOG,", + " CAT,", + " }", + " public abstract Kind getKind();", + " public abstract String dog();", + " public abstract String cat();", + "}"); + Compilation compilation = + javac().withProcessors(new AutoOneOfProcessor()).compile(javaFileObject); + assertThat(compilation) + .hadErrorContaining("@AutoOneOf class must have a non-private no-arg constructor") + .inFile(javaFileObject) + .onLineContaining("class Pet"); + } + + @Test + public void mustHaveVisibleNoArgConstructor() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Pet", + "package foo.bar;", + "", + "import com.google.auto.value.AutoOneOf;", + "", + "@AutoOneOf(Pet.Kind.class)", + "public abstract class Pet {", + " private Pet() {}", + "", + " public enum Kind {", + " DOG,", + " CAT,", + " }", + " public abstract Kind getKind();", + " public abstract String dog();", + " public abstract String cat();", + "}"); + Compilation compilation = + javac().withProcessors(new AutoOneOfProcessor()).compile(javaFileObject); + assertThat(compilation) + .hadErrorContaining("@AutoOneOf class must have a non-private no-arg constructor") + .inFile(javaFileObject) + .onLineContaining("class Pet"); + } } diff --git a/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java index ab6690fd..09d4faf9 100644 --- a/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java +++ b/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java @@ -21,6 +21,7 @@ import static com.google.testing.compile.CompilationSubject.compilations; import static com.google.testing.compile.Compiler.javac; import static java.util.stream.Collectors.joining; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.truth.Expect; @@ -555,6 +556,50 @@ public class AutoValueCompilationTest { } @Test + public void autoValueMustBeClass() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "", + "@AutoValue", + "public interface Baz {", + " String buh();", + "}"); + Compilation compilation = + javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject); + assertThat(compilation) + .hadErrorContaining("@AutoValue only applies to classes") + .inFile(javaFileObject) + .onLineContaining("interface Baz"); + } + + @Test + public void autoValueMustNotBeFinal() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "", + "@AutoValue", + "public final class Baz {", + " public Baz create() {", + " return new AutoValue_Baz();", + " }", + "}"); + Compilation compilation = + javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject); + assertThat(compilation) + .hadErrorContaining("@AutoValue class must not be final") + .inFile(javaFileObject) + .onLineContaining("class Baz"); + } + + @Test public void autoValueMustBeStatic() { JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( @@ -581,7 +626,7 @@ public class AutoValueCompilationTest { } @Test - public void autoValueMustBeNotBePrivate() { + public void autoValueMustNotBePrivate() { JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( "foo.bar.Baz", @@ -635,6 +680,52 @@ public class AutoValueCompilationTest { } @Test + public void autoValueMustHaveNoArgConstructor() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "", + "@AutoValue", + "public abstract class Baz {", + " Baz(int buh) {}", + "", + " public abstract int buh();", + "}"); + Compilation compilation = + javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject); + assertThat(compilation) + .hadErrorContaining("@AutoValue class must have a non-private no-arg constructor") + .inFile(javaFileObject) + .onLineContaining("class Baz"); + } + + @Test + public void autoValueMustHaveVisibleNoArgConstructor() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "", + "@AutoValue", + "public abstract class Baz {", + " private Baz() {}", + "", + " public abstract int buh();", + "}"); + Compilation compilation = + javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject); + assertThat(compilation) + .hadErrorContaining("@AutoValue class must have a non-private no-arg constructor") + .inFile(javaFileObject) + .onLineContaining("class Baz"); + } + + @Test public void noMultidimensionalPrimitiveArrays() { JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( @@ -1448,6 +1539,42 @@ public class AutoValueCompilationTest { } @Test + public void autoValueBuilderMustHaveNoArgConstructor() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Example", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "", + "class Example {", + " @AutoValue", + " abstract static class Baz {", + " abstract int foo();", + "", + " static Builder builder() {", + " return new AutoValue_Example_Baz.Builder();", + " }", + "", + " @AutoValue.Builder", + " abstract static class Builder {", + " Builder(int defaultFoo) {}", + " abstract Builder foo(int x);", + " abstract Baz build();", + " }", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor()) + .compile(javaFileObject); + assertThat(compilation) + .hadErrorContaining("@AutoValue.Builder class must have a non-private no-arg constructor") + .inFile(javaFileObject) + .onLineContaining("class Builder"); + } + + @Test public void autoValueBuilderOnEnum() { JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( @@ -1850,6 +1977,8 @@ public class AutoValueCompilationTest { @Test public void autoValueBuilderSetterReturnType() { + // We do allow the return type of a setter to be a supertype of the builder type, to support + // step builders. But we don't allow it to be Object. JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( "foo.bar.Baz", @@ -1863,7 +1992,7 @@ public class AutoValueCompilationTest { "", " @AutoValue.Builder", " public interface Builder {", - " void blim(int x);", + " Object blim(int x);", " Baz build();", " }", "}"); @@ -1874,7 +2003,7 @@ public class AutoValueCompilationTest { assertThat(compilation) .hadErrorContaining("Setter methods must return foo.bar.Baz.Builder") .inFile(javaFileObject) - .onLineContaining("void blim(int x)"); + .onLineContaining("Object blim(int x)"); } @Test @@ -2913,8 +3042,6 @@ public class AutoValueCompilationTest { "foo.bar.Bar", "package foo.bar;", "", - "import com.google.auto.value.AutoValue;", - "", "@" + Foo.class.getCanonicalName(), "public abstract class Bar {", " public abstract BarFoo barFoo();", @@ -2928,6 +3055,73 @@ public class AutoValueCompilationTest { } @Test + public void referencingGeneratedClassInAnnotation() { + // Test that ensures that a type that does not exist can be referenced by a copied annotation + // as long as it later does come into existence. The BarFoo type referenced here does not exist + // when the AutoValueProcessor runs on the first round, but the FooProcessor then generates it. + // That generation provokes a further round of annotation processing and AutoValueProcessor + // should succeed then. + // We test the three places that a class reference could appear: as the value of a Class + // element, as the value of a Class[] element, in a nested annotation. + JavaFileObject barFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Bar", + "package foo.bar;", + "", + "@" + Foo.class.getCanonicalName(), + "public abstract class Bar {", + "}"); + JavaFileObject referenceClassFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.ReferenceClass", + "package foo.bar;", + "", + "@interface ReferenceClass {", + " Class<?> value() default Void.class;", + " Class<?>[] values() default {};", + " Nested nested() default @Nested;", + " @interface Nested {", + " Class<?>[] values() default {};", + " }", + "}"); + ImmutableList<String> annotations = ImmutableList.of( + "@ReferenceClass(BarFoo.class)", + "@ReferenceClass(values = {Void.class, BarFoo.class})", + "@ReferenceClass(nested = @ReferenceClass.Nested(values = {Void.class, BarFoo.class}))"); + for (String annotation : annotations) { + JavaFileObject bazFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "", + "@AutoValue", + "@AutoValue.CopyAnnotations", + annotation, + "public abstract class Baz {", + " public abstract int foo();", + "", + " public static Baz create(int foo) {", + " return new AutoValue_Baz(foo);", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoValueProcessor(), new FooProcessor()) + .withOptions("-Xlint:-processing", "-implicit:none") + .compile(bazFileObject, barFileObject, referenceClassFileObject); + expect.about(compilations()).that(compilation).succeededWithoutWarnings(); + if (compilation.status().equals(Compilation.Status.SUCCESS)) { + expect.about(compilations()).that(compilation) + .generatedSourceFile("foo.bar.AutoValue_Baz") + .contentsAsUtf8String() + .contains(annotation); + } + } + } + + @Test public void annotationReferencesUndefined() { // Test that we don't throw an exception if asked to compile @SuppressWarnings(UNDEFINED) // where UNDEFINED is an undefined symbol. @@ -3052,6 +3246,63 @@ public class AutoValueCompilationTest { } @Test + public void methodAnnotationsCopiedInLexicographicalOrder() { + JavaFileObject bazFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "import com.package1.Annotation1;", + "import com.package2.Annotation0;", + "", + "@AutoValue", + "public abstract class Baz extends Parent {", + " @Annotation0", + " @Annotation1", + " @Override", + " public abstract String foo();", + "}"); + JavaFileObject parentFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Parent", + "package foo.bar;", + "", + "public abstract class Parent {", + " public abstract String foo();", + "}"); + JavaFileObject annotation1FileObject = + JavaFileObjects.forSourceLines( + "com.package1.Annotation1", + "package com.package1;", + "", + "import java.lang.annotation.ElementType;", + "import java.lang.annotation.Target;", + "", + "@Target({ElementType.FIELD, ElementType.METHOD})", + "public @interface Annotation1 {}"); + JavaFileObject annotation0FileObject = + JavaFileObjects.forSourceLines( + "com.package2.Annotation0", + "package com.package2;", + "", + "public @interface Annotation0 {}"); + Compilation compilation = + javac() + .withProcessors(new AutoValueProcessor()) + .withOptions("-Xlint:-processing", "-implicit:none") + .compile(bazFileObject, parentFileObject, annotation1FileObject, annotation0FileObject); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(compilation) + .generatedSourceFile("foo.bar.AutoValue_Baz") + .contentsAsUtf8String() + .containsMatch( + "(?s:@Annotation1\\s+@Annotation0\\s+@Override\\s+public String foo\\(\\))"); + // @Annotation1 precedes @Annotation 0 because + // @com.package2.Annotation1 precedes @com.package1.Annotation0 + } + + @Test public void nonVisibleProtectedAnnotationFromOtherPackage() { JavaFileObject bazFileObject = JavaFileObjects.forSourceLines( diff --git a/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java b/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java index 48d8cd6e..1d7e89f5 100644 --- a/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java +++ b/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java @@ -510,11 +510,14 @@ public class PropertyAnnotationsTest { "@PropertyAnnotationsTest.InheritedAnnotation") .build(); + // Annotations are in lexicographical order of FQN: + // @com.google.auto.value.processor.PropertyAnnotationsTest.InheritedAnnotation precedes + // @java.lang.Deprecated JavaFileObject outputFile = new OutputFileBuilder() .setImports(imports) - .addMethodAnnotations("@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation") - .addFieldAnnotations("@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation") + .addMethodAnnotations("@PropertyAnnotationsTest.InheritedAnnotation", "@Deprecated") + .addFieldAnnotations("@PropertyAnnotationsTest.InheritedAnnotation", "@Deprecated") .build(); Compilation compilation = @@ -548,12 +551,16 @@ public class PropertyAnnotationsTest { .addInnerTypes("@Target(ElementType.METHOD) @interface MethodsOnly {}") .build(); + // Annotations are in lexicographical order of FQN: + // @com.google.auto.value.processor.PropertyAnnotationsTest.InheritedAnnotation precedes + // @foo.bar.Baz.MethodsOnly precedes + // @java.lang.Deprecated JavaFileObject outputFile = new OutputFileBuilder() .setImports(getImports(PropertyAnnotationsTest.class)) - .addFieldAnnotations("@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation") + .addFieldAnnotations("@PropertyAnnotationsTest.InheritedAnnotation", "@Deprecated") .addMethodAnnotations( - "@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation", "@Baz.MethodsOnly") + "@PropertyAnnotationsTest.InheritedAnnotation", "@Baz.MethodsOnly", "@Deprecated") .build(); Compilation compilation = diff --git a/value/userguide/autobuilder.md b/value/userguide/autobuilder.md index ccd191ca..af9058bd 100644 --- a/value/userguide/autobuilder.md +++ b/value/userguide/autobuilder.md @@ -13,9 +13,6 @@ corresponding to the getter methods in the `@AutoValue` class, an `@AutoBuilder` has setter methods corresponding to the parameters of a constructor or static method. Apart from that, the two are very similar. -AutoBuilder is **unstable** and it is possible that its API -may change. We do not recommend depending on it for production code yet. - ## Example: calling a constructor Here is a simple example: diff --git a/value/userguide/builders-howto.md b/value/userguide/builders-howto.md index e7cf5373..3ff89468 100644 --- a/value/userguide/builders-howto.md +++ b/value/userguide/builders-howto.md @@ -154,7 +154,7 @@ public abstract class Animal { abstract Builder toBuilder(); - public Animal withName(String name) { + public final Animal withName(String name) { return toBuilder().setName(name).build(); } @@ -201,7 +201,7 @@ public abstract class Animal { abstract Animal autoBuild(); // not public - public Animal build() { + public final Animal build() { Animal animal = autoBuild(); Preconditions.checkState(animal.numberOfLegs() >= 0, "Negative legs"); return animal; @@ -235,7 +235,7 @@ public abstract class Animal { abstract Animal autoBuild(); // not public - public Animal build() { + public final Animal build() { setName(name().toLowerCase()); return autoBuild(); } @@ -279,8 +279,8 @@ public abstract class Animal { abstract Animal autoBuild(); // not public - public Animal build() { - if (!name().isPresent()) { + public final Animal build() { + if (name().isEmpty()) { setName(numberOfLegs() + "-legged creature"); } return autoBuild(); @@ -491,7 +491,7 @@ public abstract class Animal { public abstract Builder setNumberOfLegs(int value); abstract ImmutableSet.Builder<String> countriesBuilder(); - public Builder addCountry(String value) { + public final Builder addCountry(String value) { countriesBuilder().add(value); return this; } @@ -623,11 +623,75 @@ in an exception because the required properties of `Species` have not been set. A [_step builder_](http://rdafbn.blogspot.com/2012/07/step-builder-pattern_28.html) is a collection of builder interfaces that take you step by step through the -setting of each of a list of required properties. We think that these are a nice -idea in principle but not necessarily in practice. Regardless, if you want to -use AutoValue to implement a step builder, -[this example](https://github.com/google/auto/issues/1000#issuecomment-792875738) -shows you how. +setting of each of a list of required properties. This means you can be sure at +compile time that all the properties are set before you build, at the expense of +some extra code and a bit less flexibility. + +Here is an example: + +```java +@AutoValue +public abstract class Stepped { + public abstract String foo(); + public abstract String bar(); + public abstract int baz(); + + public static FooStep builder() { + return new AutoValue_Stepped.Builder(); + } + + public interface FooStep { + BarStep setFoo(String foo); + } + + public interface BarStep { + BazStep setBar(String bar); + } + + public interface BazStep { + Build setBaz(int baz); + } + + public interface Build { + Stepped build(); + } + + @AutoValue.Builder + abstract static class Builder implements FooStep, BarStep, BazStep, Build {} +} +``` + +It might be used like this: + +```java +Stepped stepped = Stepped.builder().setFoo("foo").setBar("bar").setBaz(3).build(); +``` + +The idea is that the only way to build an instance of `Stepped` +is to go through the steps imposed by the `FooStep`, `BarStep`, and +`BazStep` interfaces to set the properties in order, with a final build step. + +Once you have set the `baz` property there is nothing else to do except build, +so you could also combine the `setBaz` and `build` methods like this: + +```java + ... + + public interface BazStep { + Stepped setBazAndBuild(int baz); + } + + @AutoValue.Builder + abstract static class Builder implements FooStep, BarStep, BazStep { + abstract Builder setBaz(int baz); + abstract Stepped build(); + + @Override + public Stepped setBazAndBuild(int baz) { + return setBaz(baz).build(); + } + } +``` ## <a name="autobuilder"></a> ... create a builder for something other than an `@AutoValue`? diff --git a/value/userguide/howto.md b/value/userguide/howto.md index c4511854..0a7607b5 100644 --- a/value/userguide/howto.md +++ b/value/userguide/howto.md @@ -608,7 +608,7 @@ variant as just described. ### Copying to the generated class If you want to copy annotations from your `@AutoValue`-annotated class to the -generated `AutoValue_...` implemention, annotate your class with +generated `AutoValue_...` implementation, annotate your class with [`@AutoValue.CopyAnnotations`]. For example, if `Example.java` is: |