diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 136ca38806..e366cf4cc0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,20 +18,20 @@ 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.11.0 with: access_token: ${{ github.token }} - name: 'Check out repository' - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: 'Cache local Maven repository' - uses: actions/cache@v2.1.5 + uses: actions/cache@v3 with: path: ~/.m2/repository key: maven-${{ hashFiles('**/pom.xml') }} restore-keys: | maven- - name: 'Set up JDK ${{ matrix.java }}' - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: java-version: ${{ matrix.java }} distribution: 'zulu' @@ -49,16 +49,16 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Check out repository' - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: 'Cache local Maven repository' - uses: actions/cache@v2.1.5 + uses: actions/cache@v3 with: path: ~/.m2/repository key: maven-${{ hashFiles('**/pom.xml') }} restore-keys: | maven- - name: 'Set up JDK 11' - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: java-version: 11 distribution: 'zulu' @@ -78,16 +78,16 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Check out repository' - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: 'Cache local Maven repository' - uses: actions/cache@v2.1.5 + uses: actions/cache@v3 with: path: ~/.m2/repository key: maven-${{ hashFiles('**/pom.xml') }} restore-keys: | maven- - name: 'Set up JDK 11' - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: java-version: 11 distribution: 'zulu' diff --git a/README.md b/README.md index bbefd2751e..ca304e0cad 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A collection of source code generators for [Java][java]. -## Auto‽ +## Overview [Java][java] is full of code that is mechanical, repetitive, typically untested and sometimes the source of subtle bugs. _Sounds like a job for robots!_ diff --git a/common/README.md b/common/README.md index 990aa31b8b..f85bca043a 100644 --- a/common/README.md +++ b/common/README.md @@ -1,28 +1,38 @@ -Auto Common Utilities -======== +# Auto Common Utilities ## Overview -The Auto project has a set of common utilities to help ease use of the annotation processing -environment. +The Auto project has a set of common utilities to help ease use of the +annotation processing environment. ## Utility classes of note - * MoreTypes - utilities and Equivalence wrappers for TypeMirror and related subtypes - * MoreElements - utilities for Element and related subtypes - * SuperficialValidation - very simple scanner to ensure an Element is valid and free from - distortion from upstream compilation errors - * Visibility - utilities for working with Elements' visibility levels (public, protected, etc.) - * BasicAnnotationProcessor/ProcessingStep - simple types that - - implement a validating annotation processor - - defer invalid elements until later - - break processor actions into multiple steps (which may each handle different annotations) +`MoreTypes` +: Utilities and `Equivalence` wrappers for `TypeMirror` and related subtypes + +`MoreElements` +: Utilities for `Element` and related subtypes + +`SuperficialValidation` +: Very simple scanner to ensure an `Element` is valid and free from distortion + from upstream compilation errors + +`Visibility` +: Utilities for working with `Element`s' visibility levels (public, protected, + etc.) + +`BasicAnnotationProcessor`/`Step` +: Simple types that + - implement a validating annotation processor + - defer invalid elements until later + - break processor actions into multiple steps (which may each handle + different annotations) ## Usage/Setup -Auto common utilities have a standard [Maven](http://maven.apache.org) setup which can also be -used from Gradle, Ivy, Ant, or other systems which consume binary artifacts from the central Maven -binary artifact repositories. +Auto common utilities have a standard [Maven](http://maven.apache.org) setup +which can also be used from Gradle, Ivy, Ant, or other systems which consume +binary artifacts from the central Maven binary artifact repositories. ```xml @@ -31,49 +41,3 @@ binary artifact repositories. 1.0-SNAPSHOT ``` - -## Processor Resilience - -Auto Common Utilities is used by a variety of annotation processors in Google and new versions -may have breaking changes. Users of auto-common are urged to use -[shade](https://maven.apache.org/plugins/maven-shade-plugin/) or -[jarjar](https://code.google.com/p/jarjar/) (or something similar) in packaging their processors -so that conflicting versions of this library do not adversely interact with each other. - -For example, in a Maven build you can repackage `com.google.auto.common` into -`your.processor.shaded.auto.common` like this: - -```xml - - - - - - maven-shade-plugin - - - package - - shade - - - - - - - - - - com.google.auto.common - your.processor.shaded.auto.common - - - - - - - - - -``` - diff --git a/common/pom.xml b/common/pom.xml index f83d73fb69..42646ebbda 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -36,8 +36,8 @@ UTF-8 1.8 - 30.1.1-jre - 1.1.2 + 31.1-jre + 1.1.3 @@ -104,39 +104,71 @@ ${truth.version} test - - org.eclipse.jdt - ecj - 3.25.0 - test - maven-compiler-plugin - 3.8.1 + 3.10.1 ${java.version} ${java.version} -Xlint:all true true + org.codehaus.plexus plexus-java - 1.0.7 + 1.1.1 org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.3.0 + + + + test-with-ecj + + [11,) + + + + + org.eclipse.jdt + ecj + 3.31.0 + test + + + + + + test-without-ecj + + (,11) + + + + + maven-compiler-plugin + + + **/OverridesTest.java + + + + + + + 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 737857b0f7..2f59dd39c0 100644 --- a/common/src/main/java/com/google/auto/common/AnnotationMirrors.java +++ b/common/src/main/java/com/google/auto/common/AnnotationMirrors.java @@ -16,21 +16,21 @@ package com.google.auto.common; import static com.google.auto.common.MoreElements.isAnnotationPresent; +import static com.google.auto.common.MoreStreams.toImmutableSet; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Collections.unmodifiableMap; import com.google.common.base.Equivalence; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.lang.annotation.Annotation; import java.util.Arrays; -import java.util.List; import java.util.Map; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; @@ -45,25 +45,32 @@ public final class AnnotationMirrors { new Equivalence() { @Override protected boolean doEquivalent(AnnotationMirror left, AnnotationMirror right) { - return MoreTypes.equivalence().equivalent(left.getAnnotationType(), - right.getAnnotationType()) && AnnotationValues.equivalence().pairwise().equivalent( - getAnnotationValuesWithDefaults(left).values(), - getAnnotationValuesWithDefaults(right).values()); + return MoreTypes.equivalence() + .equivalent(left.getAnnotationType(), right.getAnnotationType()) + && AnnotationValues.equivalence() + .pairwise() + .equivalent( + getAnnotationValuesWithDefaults(left).values(), + getAnnotationValuesWithDefaults(right).values()); } + @Override protected int doHash(AnnotationMirror annotation) { DeclaredType type = annotation.getAnnotationType(); Iterable annotationValues = getAnnotationValuesWithDefaults(annotation).values(); - return Arrays.hashCode(new int[] {MoreTypes.equivalence().hash(type), - AnnotationValues.equivalence().pairwise().hash(annotationValues)}); + return Arrays.hashCode( + new int[] { + MoreTypes.equivalence().hash(type), + AnnotationValues.equivalence().pairwise().hash(annotationValues) + }); } @Override public String toString() { - return "AnnotationMirrors.equivalence()"; + return "AnnotationMirrors.equivalence()"; } - }; + }; /** * Returns an {@link Equivalence} for {@link AnnotationMirror} as some implementations @@ -88,8 +95,10 @@ public static Equivalence equivalence() { public static ImmutableMap getAnnotationValuesWithDefaults( AnnotationMirror annotation) { ImmutableMap.Builder values = ImmutableMap.builder(); - Map declaredValues = - annotation.getElementValues(); + // Use unmodifiableMap to eliminate wildcards, which cause issues for our nullness checker. + @SuppressWarnings("GetElementValues") + Map declaredValues = + unmodifiableMap(annotation.getElementValues()); for (ExecutableElement method : ElementFilter.methodsIn(annotation.getAnnotationType().asElement().getEnclosedElements())) { // Must iterate and put in this order, to ensure consistency in generated code. @@ -100,8 +109,10 @@ public static ImmutableMap getAnnotationValu } else { throw new IllegalStateException( "Unset annotation value without default should never happen: " - + MoreElements.asType(method.getEnclosingElement()).getQualifiedName() - + '.' + method.getSimpleName() + "()"); + + MoreElements.asType(method.getEnclosingElement()).getQualifiedName() + + '.' + + method.getSimpleName() + + "()"); } } return values.build(); @@ -136,25 +147,59 @@ public static Map.Entry getAnnotationElement return entry; } } - throw new IllegalArgumentException(String.format("@%s does not define an element %s()", - MoreElements.asType(annotationMirror.getAnnotationType().asElement()).getQualifiedName(), - elementName)); + throw new IllegalArgumentException( + String.format( + "@%s does not define an element %s()", + MoreElements.asType(annotationMirror.getAnnotationType().asElement()) + .getQualifiedName(), + elementName)); + } + + /** + * Returns all {@linkplain AnnotationMirror annotations} that are present on the given {@link + * Element} which are themselves annotated with {@code annotationClass}. + */ + public static ImmutableSet getAnnotatedAnnotations( + Element element, Class annotationClass) { + String name = annotationClass.getCanonicalName(); + if (name == null) { + return ImmutableSet.of(); + } + return getAnnotatedAnnotations(element, name); } /** - * Returns all {@linkplain AnnotationMirror annotations} that are present on the given - * {@link Element} which are themselves annotated with {@code annotationType}. + * Returns all {@linkplain AnnotationMirror annotations} that are present on the given {@link + * Element} which are themselves annotated with {@code annotation}. + */ + public static ImmutableSet getAnnotatedAnnotations( + Element element, TypeElement annotation) { + return element.getAnnotationMirrors().stream() + .filter(input -> isAnnotationPresent(input.getAnnotationType().asElement(), annotation)) + .collect(toImmutableSet()); + } + + /** + * Returns all {@linkplain AnnotationMirror annotations} that are present on the given {@link + * Element} which are themselves annotated with an annotation whose type's canonical name is + * {@code annotationName}. + */ + public static ImmutableSet getAnnotatedAnnotations( + Element element, String annotationName) { + return element.getAnnotationMirrors().stream() + .filter(input -> isAnnotationPresent(input.getAnnotationType().asElement(), annotationName)) + .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. + * + *

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 ImmutableSet getAnnotatedAnnotations(Element element, - final Class annotationType) { - List annotations = element.getAnnotationMirrors(); - return FluentIterable.from(annotations) - .filter(new Predicate() { - @Override public boolean apply(AnnotationMirror input) { - return isAnnotationPresent(input.getAnnotationType().asElement(), annotationType); - } - }) - .toSet(); + 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 0000000000..e52bde66c7 --- /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 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 map = + ImmutableMap.copyOf(a.getElementValues()); + if (!map.isEmpty()) { + sb.append('('); + Optional shortForm = shortForm(map); + if (shortForm.isPresent()) { + this.visit(maybeShorten(shortForm.get()), sb); + } else { + String sep = ""; + for (Map.Entry 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 ARRAY_VISITOR = + new SimpleAnnotationValueVisitor8() { + @Override + public AnnotationValue visitArray( + List 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 shortForm( + Map 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 a5db2cca0a..f301238271 100644 --- a/common/src/main/java/com/google/auto/common/AnnotationValues.java +++ b/common/src/main/java/com/google/auto/common/AnnotationValues.java @@ -15,8 +15,8 @@ */ package com.google.auto.common; +import static com.google.auto.common.MoreStreams.toImmutableList; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableList.toImmutableList; import com.google.common.base.Equivalence; import com.google.common.collect.ImmutableList; @@ -37,91 +37,125 @@ public final class AnnotationValues { private static final Equivalence ANNOTATION_VALUE_EQUIVALENCE = new Equivalence() { - @Override protected boolean doEquivalent(AnnotationValue left, AnnotationValue right) { - return left.accept(new SimpleAnnotationValueVisitor8() { - // LHS is not an annotation or array of annotation values, so just test equality. - @Override protected Boolean defaultAction(Object left, AnnotationValue right) { - return left.equals(right.accept( - new SimpleAnnotationValueVisitor8() { - @Override protected Object defaultAction(Object object, Void unused) { - return object; - } - }, null)); - } - - // LHS is an annotation mirror so test equivalence for RHS annotation mirrors - // and false for other types. - @Override public Boolean visitAnnotation(AnnotationMirror left, AnnotationValue right) { - return right.accept( - new SimpleAnnotationValueVisitor8() { - @Override protected Boolean defaultAction(Object right, AnnotationMirror left) { - return false; // Not an annotation mirror, so can't be equal to such. - } - @Override - public Boolean visitAnnotation(AnnotationMirror right, AnnotationMirror left) { - return AnnotationMirrors.equivalence().equivalent(left, right); - } - }, left); - } - - // LHS is a list of annotation values have to collect-test equivalences, or false - // for any other types. - @Override - public Boolean visitArray(List left, AnnotationValue right) { - return right.accept( - new SimpleAnnotationValueVisitor8>() { - @Override protected Boolean defaultAction( - Object ignored, List alsoIgnored) { - return false; // Not an array, so can't be equal to such. - } - - @SuppressWarnings("unchecked") // safe covariant cast - @Override public Boolean visitArray( - List right , - List left) { - return AnnotationValues.equivalence().pairwise().equivalent( - (List) left, (List) right); - } - }, left); - } - - @Override - public Boolean visitType(TypeMirror left, AnnotationValue right) { - return right.accept( - new SimpleAnnotationValueVisitor8() { - @Override protected Boolean defaultAction( - Object ignored, TypeMirror alsoIgnored) { - return false; // Not an annotation mirror, so can't be equal to such. - } - - @Override public Boolean visitType(TypeMirror right, TypeMirror left) { - return MoreTypes.equivalence().equivalent(left, right); - } - }, left); - } - }, right); + @Override + protected boolean doEquivalent(AnnotationValue left, AnnotationValue right) { + return left.accept( + new SimpleAnnotationValueVisitor8() { + // LHS is not an annotation or array of annotation values, so just test equality. + @Override + protected Boolean defaultAction(Object left, AnnotationValue right) { + return left.equals( + right.accept( + new SimpleAnnotationValueVisitor8() { + @Override + protected Object defaultAction(Object object, Void unused) { + return object; + } + }, + null)); + } + + // LHS is an annotation mirror so test equivalence for RHS annotation mirrors + // and false for other types. + @Override + public Boolean visitAnnotation(AnnotationMirror left, AnnotationValue right) { + return right.accept( + new SimpleAnnotationValueVisitor8() { + @Override + protected Boolean defaultAction(Object right, AnnotationMirror left) { + return false; // Not an annotation mirror, so can't be equal to such. + } + + @Override + public Boolean visitAnnotation( + AnnotationMirror right, AnnotationMirror left) { + return AnnotationMirrors.equivalence().equivalent(left, right); + } + }, + left); + } + + // LHS is a list of annotation values have to collect-test equivalences, or false + // for any other types. + @Override + public Boolean visitArray( + List left, AnnotationValue right) { + return right.accept( + new SimpleAnnotationValueVisitor8< + Boolean, List>() { + @Override + protected Boolean defaultAction( + Object ignored, List alsoIgnored) { + return false; // Not an array, so can't be equal to such. + } + + @SuppressWarnings("unchecked") // safe covariant cast + @Override + public Boolean visitArray( + List right, + List left) { + return AnnotationValues.equivalence() + .pairwise() + .equivalent( + (List) left, (List) right); + } + }, + left); + } + + @Override + public Boolean visitType(TypeMirror left, AnnotationValue right) { + return right.accept( + new SimpleAnnotationValueVisitor8() { + @Override + protected Boolean defaultAction(Object ignored, TypeMirror alsoIgnored) { + return false; // Not an annotation mirror, so can't be equal to such. + } + + @Override + public Boolean visitType(TypeMirror right, TypeMirror left) { + return MoreTypes.equivalence().equivalent(left, right); + } + }, + left); + } + }, + right); } - @Override protected int doHash(AnnotationValue value) { - return value.accept(new SimpleAnnotationValueVisitor8() { - @Override public Integer visitAnnotation(AnnotationMirror value, Void ignore) { - return AnnotationMirrors.equivalence().hash(value); - } - - @SuppressWarnings("unchecked") // safe covariant cast - @Override public Integer visitArray( - List values, Void ignore) { - return AnnotationValues.equivalence().pairwise().hash((List) values); - } - - @Override public Integer visitType(TypeMirror value, Void ignore) { - return MoreTypes.equivalence().hash(value); - } - - @Override protected Integer defaultAction(Object value, Void ignored) { - return value.hashCode(); - } - }, null); + @Override + protected int doHash(AnnotationValue value) { + return value.accept( + new SimpleAnnotationValueVisitor8() { + @Override + public Integer visitAnnotation(AnnotationMirror value, Void ignore) { + return AnnotationMirrors.equivalence().hash(value); + } + + @SuppressWarnings("unchecked") // safe covariant cast + @Override + public Integer visitArray(List values, Void ignore) { + return AnnotationValues.equivalence() + .pairwise() + .hash((List) values); + } + + @Override + public Integer visitType(TypeMirror value, Void ignore) { + return MoreTypes.equivalence().hash(value); + } + + @Override + protected Integer defaultAction(Object value, Void ignored) { + return value.hashCode(); + } + }, + null); + } + + @Override + public String toString() { + return "AnnotationValues.equivalence()"; } }; @@ -319,7 +353,7 @@ private static final class ArrayVisitor @Override public ImmutableList defaultAction(Object o, Void unused) { - throw new IllegalStateException("Expected an array, got instead: " + o); + throw new IllegalArgumentException("Expected an array, got instead: " + o); } @Override @@ -483,6 +517,22 @@ public static ImmutableList getAnnotationValues(AnnotationValue 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. + * + *

For all annotation values other than nested annotations, the returned string can also be + * used to initialize a variable of the appropriate type. + * + *

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/BasicAnnotationProcessor.java b/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java index 31336cc35c..7b69d33271 100644 --- a/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java +++ b/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java @@ -17,19 +17,19 @@ import static com.google.auto.common.MoreElements.asExecutable; import static com.google.auto.common.MoreElements.asPackage; +import static com.google.auto.common.MoreElements.isAnnotationPresent; +import static com.google.auto.common.MoreStreams.toImmutableMap; +import static com.google.auto.common.MoreStreams.toImmutableSet; import static com.google.auto.common.SuperficialValidation.validateElement; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Multimaps.filterKeys; -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toMap; +import static java.util.Objects.requireNonNull; import static javax.lang.model.element.ElementKind.PACKAGE; import static javax.tools.Diagnostic.Kind.ERROR; import com.google.common.base.Ascii; -import com.google.common.base.Optional; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -42,6 +42,7 @@ import java.lang.annotation.Annotation; import java.util.LinkedHashSet; import java.util.Objects; +import java.util.Optional; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Messager; @@ -49,13 +50,13 @@ import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.ErrorType; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleElementVisitor8; +import org.checkerframework.checker.nullness.qual.Nullable; /** * An abstract {@link Processor} implementation that defers processing of {@link Element}s to later @@ -162,14 +163,14 @@ private ImmutableSet getSupportedAnnotationTypeElements() { checkState(steps != null); return steps.stream() .flatMap(step -> getSupportedAnnotationTypeElements(step).stream()) - .collect(collectingAndThen(toList(), ImmutableSet::copyOf)); + .collect(toImmutableSet()); } private ImmutableSet getSupportedAnnotationTypeElements(Step step) { return step.annotations().stream() .map(elements::getTypeElement) .filter(Objects::nonNull) - .collect(collectingAndThen(toList(), ImmutableSet::copyOf)); + .collect(toImmutableSet()); } /** @@ -179,9 +180,7 @@ private ImmutableSet getSupportedAnnotationTypeElements(Step step) @Override public final ImmutableSet getSupportedAnnotationTypes() { checkState(steps != null); - return steps.stream() - .flatMap(step -> step.annotations().stream()) - .collect(collectingAndThen(toList(), ImmutableSet::copyOf)); + return steps.stream().flatMap(step -> step.annotations().stream()).collect(toImmutableSet()); } @Override @@ -287,10 +286,7 @@ private ImmutableSetMultimap validElements(RoundEnvironmen // Look at the elements we've found and the new elements from this round and validate them. for (TypeElement annotationType : getSupportedAnnotationTypeElements()) { - Set roundElements = - (annotationType == null) - ? ImmutableSet.of() - : roundEnv.getElementsAnnotatedWith(annotationType); + Set roundElements = roundEnv.getElementsAnnotatedWith(annotationType); ImmutableSet prevRoundElements = deferredElementsByAnnotation.get(annotationType); for (Element element : Sets.union(roundElements, prevRoundElements)) { ElementName elementName = ElementName.forAnnotatedElement(element); @@ -353,10 +349,14 @@ private static void findAnnotatedElements( } // element.getEnclosedElements() does NOT return parameter elements - if (element instanceof ExecutableElement) { - for (Element parameterElement : asExecutable(element).getParameters()) { - findAnnotatedElements(parameterElement, annotationTypes, annotatedElements); - } + switch (element.getKind()) { + case METHOD: + case CONSTRUCTOR: + for (Element parameterElement : asExecutable(element).getParameters()) { + findAnnotatedElements(parameterElement, annotationTypes, annotatedElements); + } + break; + default: // do nothing } for (TypeElement annotationType : annotationTypes) { if (isAnnotationPresent(element, annotationType)) { @@ -365,12 +365,6 @@ private static void findAnnotatedElements( } } - private static boolean isAnnotationPresent(Element element, TypeElement annotationType) { - return element.getAnnotationMirrors().stream() - .anyMatch( - mirror -> MoreTypes.asTypeElement(mirror.getAnnotationType()).equals(annotationType)); - } - /** * Returns the nearest enclosing {@link TypeElement} to the current element, throwing an {@link * IllegalArgumentException} if the provided {@link Element} is a {@link PackageElement} or is @@ -478,10 +472,9 @@ private static class ProcessingStepAsStep implements Step { this.annotationsByName = processingStep.annotations().stream() .collect( - collectingAndThen( - toMap( - Class::getCanonicalName, (Class aClass) -> aClass), - ImmutableMap::copyOf)); + toImmutableMap( + c -> requireNonNull(c.getCanonicalName()), + (Class aClass) -> aClass)); } @Override @@ -502,8 +495,12 @@ private ImmutableSetMultimap, Element> toClassKeyedM elements .asMap() .forEach( - (annotation, annotatedElements) -> - builder.putAll(annotationsByName.get(annotation), annotatedElements)); + (annotationName, annotatedElements) -> { + Class annotation = annotationsByName.get(annotationName); + if (annotation != null) { // should not be null + builder.putAll(annotation, annotatedElements); + } + }); return builder.build(); } } @@ -537,6 +534,9 @@ private ElementName(Kind kind, Name name) { * An {@link ElementName} for an annotated element. If {@code element} is a package, uses the * fully qualified name of the package. If it's a type, uses its fully qualified name. * Otherwise, uses the fully-qualified name of the nearest enclosing type. + * + *

A package can be annotated if it has a {@code package-info.java} with annotations on the + * package declaration. */ static ElementName forAnnotatedElement(Element element) { return element.getKind() == PACKAGE @@ -550,18 +550,18 @@ String name() { } /** - * The {@link Element} whose fully-qualified name is {@link #name()}. Absent if the relevant + * The {@link Element} whose fully-qualified name is {@link #name()}. Empty if the relevant * method on {@link Elements} returns {@code null}. */ Optional getElement(Elements elements) { - return Optional.fromNullable( + return Optional.ofNullable( kind == Kind.PACKAGE_NAME ? elements.getPackageElement(name) : elements.getTypeElement(name)); } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { if (!(object instanceof ElementName)) { return false; } diff --git a/common/src/main/java/com/google/auto/common/MoreElements.java b/common/src/main/java/com/google/auto/common/MoreElements.java index 5e8e3541f0..dfbbaeef14 100644 --- a/common/src/main/java/com/google/auto/common/MoreElements.java +++ b/common/src/main/java/com/google/auto/common/MoreElements.java @@ -16,6 +16,7 @@ */ package com.google.auto.common; +import static com.google.auto.common.MoreStreams.toImmutableSet; import static javax.lang.model.element.ElementKind.PACKAGE; import static javax.lang.model.element.Modifier.STATIC; @@ -212,29 +213,80 @@ public static ExecutableElement asExecutable(Element element) { } /** - * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose - * {@linkplain AnnotationMirror#getAnnotationType() annotation type} has the same canonical name - * as that of {@code annotationClass}. This method is a safer alternative to calling - * {@link Element#getAnnotation} and checking for {@code null} as it avoids any interaction with + * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@linkplain + * AnnotationMirror#getAnnotationType() annotation type} has the same canonical name as that of + * {@code annotationClass}. This method is a safer alternative to calling {@link + * Element#getAnnotation} and checking for {@code null} as it avoids any interaction with * annotation proxies. */ - public static boolean isAnnotationPresent(Element element, - Class annotationClass) { + public static boolean isAnnotationPresent( + Element element, Class annotationClass) { return getAnnotationMirror(element, annotationClass).isPresent(); } + /** + * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@linkplain + * AnnotationMirror#getAnnotationType() annotation type} has the same fully qualified name as that + * of {@code annotation}. This method is a safer alternative to calling {@link + * Element#getAnnotation} and checking for {@code null} as it avoids any interaction with + * annotation proxies. + */ + public static boolean isAnnotationPresent(Element element, TypeElement annotation) { + return getAnnotationMirror(element, annotation).isPresent(); + } + + /** + * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@linkplain + * AnnotationMirror#getAnnotationType() annotation type} has {@code annotationName} as its + * canonical name. This method is a safer alternative to calling {@link Element#getAnnotation} and + * checking for {@code null} as it avoids any interaction with annotation proxies. + */ + public static boolean isAnnotationPresent(Element element, String annotationName) { + return getAnnotationMirror(element, annotationName).isPresent(); + } + /** * Returns an {@link AnnotationMirror} for the annotation of type {@code annotationClass} on * {@code element}, or {@link Optional#absent()} if no such annotation exists. This method is a * safer alternative to calling {@link Element#getAnnotation} as it avoids any interaction with * annotation proxies. */ - public static Optional getAnnotationMirror(Element element, - Class annotationClass) { - String annotationClassName = annotationClass.getCanonicalName(); + public static Optional getAnnotationMirror( + Element element, Class annotationClass) { + String name = annotationClass.getCanonicalName(); + if (name == null) { + return Optional.absent(); + } + return getAnnotationMirror(element, name); + } + + /** + * Returns an {@link AnnotationMirror} for the annotation of type {@code annotation} on {@code + * element}, or {@link Optional#absent()} if no such annotation exists. This method is a safer + * alternative to calling {@link Element#getAnnotation} as it avoids any interaction with + * annotation proxies. + */ + public static Optional getAnnotationMirror( + Element element, TypeElement annotation) { + for (AnnotationMirror elementAnnotation : element.getAnnotationMirrors()) { + if (elementAnnotation.getAnnotationType().asElement().equals(annotation)) { + return Optional.of(elementAnnotation); + } + } + return Optional.absent(); + } + + /** + * Returns an {@link AnnotationMirror} for the annotation whose type's canonical name is on {@code + * element}, or {@link Optional#absent()} if no such annotation exists. This method is a safer + * alternative to calling {@link Element#getAnnotation} as it avoids any interaction with + * annotation proxies. + */ + public static Optional getAnnotationMirror( + Element element, String annotationName) { for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { TypeElement annotationTypeElement = asType(annotationMirror.getAnnotationType().asElement()); - if (annotationTypeElement.getQualifiedName().contentEquals(annotationClassName)) { + if (annotationTypeElement.getQualifiedName().contentEquals(annotationName)) { return Optional.of(annotationMirror); } } @@ -435,9 +487,9 @@ private static ImmutableSet getAllMethods( } } } - Set methods = new LinkedHashSet(methodMap.values()); - methods.removeAll(overridden); - return ImmutableSet.copyOf(methods); + return methodMap.values().stream() + .filter(m -> !overridden.contains(m)) + .collect(toImmutableSet()); } // Add to `methods` the static and instance methods from `type`. This means all methods from diff --git a/common/src/main/java/com/google/auto/common/MoreStreams.java b/common/src/main/java/com/google/auto/common/MoreStreams.java new file mode 100644 index 0000000000..934514ab47 --- /dev/null +++ b/common/src/main/java/com/google/auto/common/MoreStreams.java @@ -0,0 +1,75 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.auto.common; + +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toList; + +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +/** + * A utility class that provides Android compatible alternatives to Guava's streaming APIs. + * + *

This is useful when the Android flavor of Guava somehow finds its way onto the processor + * classpath. + */ +public final class MoreStreams { + + /** Returns a collector for an {@link ImmutableList}. */ + public static Collector> toImmutableList() { + return collectingAndThen(toList(), ImmutableList::copyOf); + } + + /** Returns a collector for an {@link ImmutableSet}. */ + public static Collector> toImmutableSet() { + return collectingAndThen(toList(), ImmutableSet::copyOf); + } + + /** Returns a collector for an {@link ImmutableMap}. */ + public static Collector> toImmutableMap( + Function keyMapper, Function valueMapper) { + return Collectors.mapping( + value -> Maps.immutableEntry(keyMapper.apply(value), valueMapper.apply(value)), + Collector.of( + ImmutableMap::builder, + (ImmutableMap.Builder builder, Map.Entry entry) -> builder.put(entry), + (left, right) -> left.putAll(right.build()), + ImmutableMap.Builder::build)); + } + + /** Returns a collector for an {@link ImmutableBiMap}. */ + public static Collector> toImmutableBiMap( + Function keyMapper, Function valueMapper) { + return Collectors.mapping( + value -> Maps.immutableEntry(keyMapper.apply(value), valueMapper.apply(value)), + Collector.of( + ImmutableBiMap::builder, + (ImmutableBiMap.Builder builder, Map.Entry entry) -> builder.put(entry), + (left, right) -> left.putAll(right.build()), + ImmutableBiMap.Builder::build)); + } + + private MoreStreams() {} +} diff --git a/common/src/main/java/com/google/auto/common/MoreTypes.java b/common/src/main/java/com/google/auto/common/MoreTypes.java index af433aa06a..c8be62556b 100644 --- a/common/src/main/java/com/google/auto/common/MoreTypes.java +++ b/common/src/main/java/com/google/auto/common/MoreTypes.java @@ -25,7 +25,6 @@ import static javax.lang.model.type.TypeKind.WILDCARD; import com.google.common.base.Equivalence; -import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -55,6 +54,7 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleTypeVisitor8; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Utilities related to {@link TypeMirror} instances. @@ -140,25 +140,45 @@ private static class ComparedElements { } @Override - public boolean equals(Object o) { - if (o instanceof ComparedElements) { - ComparedElements that = (ComparedElements) o; - int nArguments = aArguments.size(); - if (!this.a.equals(that.a) - || !this.b.equals(that.b) - || nArguments != bArguments.size()) { - // The arguments must be the same size, but we check anyway. + public boolean equals(@Nullable Object o) { + if (!(o instanceof ComparedElements)) { + return false; + } + ComparedElements that = (ComparedElements) o; + + int nArguments = this.aArguments.size(); + if (nArguments != that.aArguments.size()) { + return false; + } + // The arguments must be the same size, but we check anyway. + if (nArguments != this.bArguments.size() || nArguments != that.bArguments.size()) { + return false; + } + + if (!this.a.equals(that.a) || !this.b.equals(that.b)) { + return false; + } + + /* + * The purpose here is just to avoid the infinite recursion that we would otherwise have + * if Enum> is compared against itself, for example. If we are able to + * see that the inner Enum is the same object as the outer one then we don't need a + * recursive call to compare the "a" Enum against the "b" Enum. The same-object check + * may not be completely justified, but it relies on the practical assumption that the + * compiler is not going to conjure up an infinite regress of objects to represent this + * recursive type. Other comparison methods like comparing their toString() are expensive + * and not warranted. + */ + for (int i = 0; i < nArguments; i++) { + if (this.aArguments.get(i) != that.aArguments.get(i)) { return false; } - for (int i = 0; i < nArguments; i++) { - if (aArguments.get(i) != bArguments.get(i)) { - return false; - } + if (this.bArguments.get(i) != that.bArguments.get(i)) { + return false; } - return true; - } else { - return false; } + + return true; } @Override @@ -299,7 +319,14 @@ private Set visitingSetPlus( } @SuppressWarnings("TypeEquals") - private static boolean equal(TypeMirror a, TypeMirror b, Set visiting) { + private static boolean equal( + @Nullable TypeMirror a, @Nullable TypeMirror b, Set visiting) { + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } // TypeMirror.equals is not guaranteed to return true for types that are equal, but we can // assume that if it does return true then the types are equal. This check also avoids getting // stuck in infinite recursion when Eclipse decrees that the upper bound of the second K in @@ -307,13 +334,15 @@ private static boolean equal(TypeMirror a, TypeMirror b, Set v // The javac implementation of ExecutableType, at least in some versions, does not take thrown // exceptions into account in its equals implementation, so avoid this optimization for // ExecutableType. - if (Objects.equal(a, b) && !(a instanceof ExecutableType)) { + @SuppressWarnings("TypesEquals") + boolean equal = a.equals(b); + if (equal && a.getKind() != TypeKind.EXECUTABLE) { return true; } EqualVisitorParam p = new EqualVisitorParam(); p.type = b; p.visiting = visiting; - return (a == b) || (a != null && b != null && a.accept(EqualVisitor.INSTANCE, p)); + return a.accept(EqualVisitor.INSTANCE, p); } /** @@ -323,7 +352,7 @@ private static boolean equal(TypeMirror a, TypeMirror b, Set v * this bug whereby * the Eclipse compiler returns a value for static classes that is not NoType. */ - private static TypeMirror enclosingType(DeclaredType t) { + private static @Nullable TypeMirror enclosingType(DeclaredType t) { TypeMirror enclosing = t.getEnclosingType(); if (enclosing.getKind().equals(TypeKind.NONE) || t.asElement().getModifiers().contains(Modifier.STATIC)) { @@ -436,7 +465,7 @@ public Integer visitWildcard(WildcardType t, Set visiting) { public Integer visitUnknown(TypeMirror t, Set visiting) { throw new UnsupportedOperationException(); } - }; + } private static int hashList(List mirrors, Set visiting) { int result = HASH_SEED; @@ -463,17 +492,17 @@ public static ImmutableSet referencedTypes(TypeMirror type) { } private static final class ReferencedTypes - extends SimpleTypeVisitor8> { + extends SimpleTypeVisitor8<@Nullable Void, ImmutableSet.Builder> { private static final ReferencedTypes INSTANCE = new ReferencedTypes(); @Override - public Void visitArray(ArrayType t, ImmutableSet.Builder p) { + public @Nullable Void visitArray(ArrayType t, ImmutableSet.Builder p) { t.getComponentType().accept(this, p); return null; } @Override - public Void visitDeclared(DeclaredType t, ImmutableSet.Builder p) { + public @Nullable Void visitDeclared(DeclaredType t, ImmutableSet.Builder p) { p.add(MoreElements.asType(t.asElement())); for (TypeMirror typeArgument : t.getTypeArguments()) { typeArgument.accept(this, p); @@ -482,14 +511,14 @@ public Void visitDeclared(DeclaredType t, ImmutableSet.Builder p) { } @Override - public Void visitTypeVariable(TypeVariable t, ImmutableSet.Builder p) { + public @Nullable Void visitTypeVariable(TypeVariable t, ImmutableSet.Builder p) { t.getLowerBound().accept(this, p); t.getUpperBound().accept(this, p); return null; } @Override - public Void visitWildcard(WildcardType t, ImmutableSet.Builder p) { + public @Nullable Void visitWildcard(WildcardType t, ImmutableSet.Builder p) { TypeMirror extendsBound = t.getExtendsBound(); if (extendsBound != null) { extendsBound.accept(this, p); @@ -537,7 +566,8 @@ public Element visitError(ErrorType t, Void p) { public Element visitTypeVariable(TypeVariable t, Void p) { return t.asElement(); } - }; + } + ; // TODO(gak): consider removing these two methods as they're pretty trivial now public static TypeElement asTypeElement(TypeMirror mirror) { @@ -882,10 +912,10 @@ public Boolean visitDeclared(DeclaredType type, Void ignored) { * is {@link Object}. */ // TODO(bcorso): Remove unused parameter Elements? - public static Optional nonObjectSuperclass(Types types, Elements elements, - DeclaredType type) { + public static Optional nonObjectSuperclass( + Types types, Elements elements, DeclaredType type) { checkNotNull(types); - checkNotNull(elements); // This is no longer used, but here to avoid changing the API. + checkNotNull(elements); // This is no longer used, but here to avoid changing the API. checkNotNull(type); TypeMirror superclassType = asTypeElement(type).getSuperclass(); @@ -893,7 +923,7 @@ public static Optional nonObjectSuperclass(Types types, Elements e return Optional.absent(); } - DeclaredType superclass = asDeclared(superclassType); + DeclaredType superclass = asDeclared(superclassType); if (isObjectType(superclass)) { return Optional.absent(); } @@ -920,8 +950,8 @@ private static boolean isObjectType(DeclaredType type) { * {@code container} of type {@code Set}, and a variable corresponding to the {@code E e} * parameter in the {@code Set.add(E e)} method, this will return a TypeMirror for {@code String}. */ - public static TypeMirror asMemberOf(Types types, DeclaredType container, - VariableElement variable) { + public static TypeMirror asMemberOf( + Types types, DeclaredType container, VariableElement variable) { if (variable.getKind().equals(ElementKind.PARAMETER)) { ExecutableElement methodOrConstructor = MoreElements.asExecutable(variable.getEnclosingElement()); @@ -1013,7 +1043,7 @@ private static boolean uncheckedTypeArgument(TypeMirror arg) { } return true; } - + private static boolean isJavaLangObject(TypeMirror type) { if (type.getKind() != TypeKind.DECLARED) { return false; 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 19a4586223..46d765bcdd 100644 --- a/common/src/main/java/com/google/auto/common/Overrides.java +++ b/common/src/main/java/com/google/auto/common/Overrides.java @@ -15,6 +15,8 @@ */ package com.google.auto.common; +import static java.util.stream.Collectors.toList; + import com.google.common.base.Preconditions; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; @@ -23,6 +25,7 @@ import java.util.List; import java.util.Map; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -38,6 +41,7 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleTypeVisitor8; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Determines if one method overrides another. This class defines two ways of doing that: @@ -114,12 +118,11 @@ public boolean overrides( // can't be overridden. return false; } - TypeElement overriddenType; - 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. } - overriddenType = MoreElements.asType(overridden.getEnclosingElement()); + TypeElement overriddenType = MoreElements.asType(overridden.getEnclosingElement()); // We erase the types before checking subtypes, because the TypeMirror we get for List is // not a subtype of the one we get for Collection since the two E instances are not the // same. For the purposes of overriding, type parameters in the containing type should not @@ -141,7 +144,8 @@ public boolean overrides( // the enclosing elements rather than the methods themselves for the reason described // at the start of the method. ExecutableElement inherited = methodFromSuperclasses(in, overridden); - return !overridden.getEnclosingElement().equals(inherited.getEnclosingElement()); + return inherited != null + && !overridden.getEnclosingElement().equals(inherited.getEnclosingElement()); } else if (overriddenType.getKind().isInterface()) { // ...overrides from C another method mI declared in interface I. We've already checked // the conditions (assuming that the only alternative to mI being abstract or default is @@ -157,7 +161,8 @@ public boolean overrides( // to methodFromSuperclasses above. if (overrider.getModifiers().contains(Modifier.ABSTRACT)) { ExecutableElement inherited = methodFromSuperinterfaces(in, overridden); - return !overridden.getEnclosingElement().equals(inherited.getEnclosingElement()); + return inherited != null + && !overridden.getEnclosingElement().equals(inherited.getEnclosingElement()); } else { return true; } @@ -166,9 +171,14 @@ public boolean 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())); } } @@ -215,6 +225,7 @@ private boolean isSubsignature( * implements List}. The parameter types are erased since the purpose of this method is to * determine whether two methods are candidates for one to override the other. */ + @Nullable ImmutableList erasedParameterTypes(ExecutableElement method, TypeElement in) { if (method.getParameters().isEmpty()) { return ImmutableList.of(); @@ -241,6 +252,7 @@ private class TypeSubstVisitor extends SimpleTypeVisitor8 { */ private final Map typeBindings = Maps.newLinkedHashMap(); + @Nullable ImmutableList erasedParameterTypes(ExecutableElement method, TypeElement in) { if (method.getEnclosingElement().equals(in)) { ImmutableList.Builder params = ImmutableList.builder(); @@ -261,6 +273,10 @@ ImmutableList erasedParameterTypes(ExecutableElement method, TypeEle TypeElement element = MoreElements.asType(declared.asElement()); List actuals = declared.getTypeArguments(); List formals = element.getTypeParameters(); + if (actuals.isEmpty()) { + // Either the formal type arguments are also empty or `declared` is raw. + actuals = formals.stream().map(t -> t.getBounds().get(0)).collect(toList()); + } Verify.verify(actuals.size() == formals.size()); for (int i = 0; i < actuals.size(); i++) { typeBindings.put(formals.get(i), actuals.get(i)); @@ -280,8 +296,8 @@ protected TypeMirror defaultAction(TypeMirror e, Void p) { @Override public TypeMirror visitTypeVariable(TypeVariable t, Void p) { - Element element = typeUtils.asElement(t); - if (element instanceof TypeParameterElement) { + Element element = t.asElement(); + if (element.getKind() == ElementKind.TYPE_PARAMETER) { TypeParameterElement e = (TypeParameterElement) element; if (typeBindings.containsKey(e)) { return visit(typeBindings.get(e)); @@ -315,7 +331,7 @@ public TypeMirror visitArray(ArrayType t, Void p) { * or the nearest override in a superclass of the given type, or null if the method is not * found in the given type or any of its superclasses. */ - ExecutableElement methodFromSuperclasses(TypeElement in, ExecutableElement method) { + @Nullable ExecutableElement methodFromSuperclasses(TypeElement in, ExecutableElement method) { for (TypeElement t = in; t != null; t = superclass(t)) { ExecutableElement tMethod = methodInType(t, method); if (tMethod != null) { @@ -330,6 +346,7 @@ ExecutableElement methodFromSuperclasses(TypeElement in, ExecutableElement metho * itself, or the nearest override in a superinterface of the given type, or null if the method * is not found in the given type or any of its transitive superinterfaces. */ + @Nullable ExecutableElement methodFromSuperinterfaces(TypeElement in, ExecutableElement method) { TypeElement methodContainer = MoreElements.asType(method.getEnclosingElement()); Preconditions.checkArgument(methodContainer.getKind().isInterface()); @@ -366,7 +383,7 @@ ExecutableElement methodFromSuperinterfaces(TypeElement in, ExecutableElement me * Returns the method from within the given type that has the same erased signature as the given * method, or null if there is no such method. */ - private ExecutableElement methodInType(TypeElement type, ExecutableElement method) { + private @Nullable ExecutableElement methodInType(TypeElement type, ExecutableElement method) { int nParams = method.getParameters().size(); List params = erasedParameterTypes(method, type); if (params == null) { @@ -388,7 +405,7 @@ private ExecutableElement methodInType(TypeElement type, ExecutableElement metho return null; } - private TypeElement superclass(TypeElement type) { + private @Nullable TypeElement superclass(TypeElement type) { TypeMirror sup = type.getSuperclass(); if (sup.getKind() == TypeKind.DECLARED) { return MoreElements.asType(typeUtils.asElement(sup)); diff --git a/common/src/main/java/com/google/auto/common/SimpleAnnotationMirror.java b/common/src/main/java/com/google/auto/common/SimpleAnnotationMirror.java index 7d508e32ae..7952eb3748 100644 --- a/common/src/main/java/com/google/auto/common/SimpleAnnotationMirror.java +++ b/common/src/main/java/com/google/auto/common/SimpleAnnotationMirror.java @@ -16,8 +16,8 @@ package com.google.auto.common; +import static com.google.auto.common.MoreStreams.toImmutableMap; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.ImmutableMap.toImmutableMap; import static javax.lang.model.util.ElementFilter.methodsIn; import com.google.common.base.Joiner; @@ -32,6 +32,7 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A simple implementation of the {@link AnnotationMirror} interface. @@ -65,7 +66,7 @@ private SimpleAnnotationMirror( missingMembers.add(memberName); } } - + checkArgument( unusedValues.isEmpty(), "namedValues has entries for members that are not in %s: %s", @@ -77,8 +78,7 @@ private SimpleAnnotationMirror( this.annotationType = annotationType; this.namedValues = ImmutableMap.copyOf(namedValues); this.elementValues = - methodsIn(annotationType.getEnclosedElements()) - .stream() + methodsIn(annotationType.getEnclosedElements()).stream() .collect(toImmutableMap(e -> e, e -> values.get(e.getSimpleName().toString()))); } @@ -123,7 +123,7 @@ public String toString() { } @Override - public boolean equals(Object other) { + public boolean equals(@Nullable Object other) { return other instanceof AnnotationMirror && AnnotationMirrors.equivalence().equivalent(this, (AnnotationMirror) other); } diff --git a/common/src/main/java/com/google/auto/common/SuperficialValidation.java b/common/src/main/java/com/google/auto/common/SuperficialValidation.java index 5ef4dbf2b3..614e26265f 100644 --- a/common/src/main/java/com/google/auto/common/SuperficialValidation.java +++ b/common/src/main/java/com/google/auto/common/SuperficialValidation.java @@ -57,23 +57,27 @@ public static boolean validateElements(Iterable elements) { private static final ElementVisitor ELEMENT_VALIDATING_VISITOR = new AbstractElementVisitor8() { - @Override public Boolean visitPackage(PackageElement e, Void p) { + @Override + public Boolean visitPackage(PackageElement e, Void p) { // don't validate enclosed elements because it will return types in the package return validateAnnotations(e.getAnnotationMirrors()); } - @Override public Boolean visitType(TypeElement e, Void p) { + @Override + public Boolean visitType(TypeElement e, Void p) { return isValidBaseElement(e) && validateElements(e.getTypeParameters()) && validateTypes(e.getInterfaces()) && validateType(e.getSuperclass()); } - @Override public Boolean visitVariable(VariableElement e, Void p) { + @Override + public Boolean visitVariable(VariableElement e, Void p) { return isValidBaseElement(e); } - @Override public Boolean visitExecutable(ExecutableElement e, Void p) { + @Override + public Boolean visitExecutable(ExecutableElement e, Void p) { AnnotationValue defaultValue = e.getDefaultValue(); return isValidBaseElement(e) && (defaultValue == null || validateAnnotationValue(defaultValue, e.getReturnType())) @@ -83,12 +87,13 @@ && validateElements(e.getTypeParameters()) && validateElements(e.getParameters()); } - @Override public Boolean visitTypeParameter(TypeParameterElement e, Void p) { - return isValidBaseElement(e) - && validateTypes(e.getBounds()); + @Override + public Boolean visitTypeParameter(TypeParameterElement e, Void p) { + return isValidBaseElement(e) && validateTypes(e.getBounds()); } - @Override public Boolean visitUnknown(Element e, Void p) { + @Override + public Boolean visitUnknown(Element e, Void p) { // just assume that unknown elements are OK return true; } @@ -206,16 +211,19 @@ private static boolean validateAnnotationValues( private static final AnnotationValueVisitor VALUE_VALIDATING_VISITOR = new SimpleAnnotationValueVisitor8() { - @Override protected Boolean defaultAction(Object o, TypeMirror expectedType) { + @Override + protected Boolean defaultAction(Object o, TypeMirror expectedType) { return MoreTypes.isTypeOf(o.getClass(), expectedType); } - @Override public Boolean visitUnknown(AnnotationValue av, TypeMirror expectedType) { + @Override + public Boolean visitUnknown(AnnotationValue av, TypeMirror expectedType) { // just take the default action for the unknown return defaultAction(av, expectedType); } - @Override public Boolean visitAnnotation(AnnotationMirror a, TypeMirror expectedType) { + @Override + public Boolean visitAnnotation(AnnotationMirror a, TypeMirror expectedType) { return MoreTypes.equivalence().equivalent(a.getAnnotationType(), expectedType) && validateAnnotation(a); } @@ -235,7 +243,8 @@ public Boolean visitEnumConstant(VariableElement enumConstant, TypeMirror expect && validateElement(enumConstant); } - @Override public Boolean visitType(TypeMirror type, TypeMirror ignored) { + @Override + public Boolean visitType(TypeMirror type, TypeMirror ignored) { // We could check assignability here, but would require a Types instance. Since this // isn't really the sort of thing that shows up in a bad AST from upstream compilation // we ignore the expected type and just validate the type. It might be wrong, but @@ -243,35 +252,43 @@ public Boolean visitEnumConstant(VariableElement enumConstant, TypeMirror expect return validateType(type); } - @Override public Boolean visitBoolean(boolean b, TypeMirror expectedType) { + @Override + public Boolean visitBoolean(boolean b, TypeMirror expectedType) { return MoreTypes.isTypeOf(Boolean.TYPE, expectedType); } - @Override public Boolean visitByte(byte b, TypeMirror expectedType) { + @Override + public Boolean visitByte(byte b, TypeMirror expectedType) { return MoreTypes.isTypeOf(Byte.TYPE, expectedType); } - @Override public Boolean visitChar(char c, TypeMirror expectedType) { + @Override + public Boolean visitChar(char c, TypeMirror expectedType) { return MoreTypes.isTypeOf(Character.TYPE, expectedType); } - @Override public Boolean visitDouble(double d, TypeMirror expectedType) { + @Override + public Boolean visitDouble(double d, TypeMirror expectedType) { return MoreTypes.isTypeOf(Double.TYPE, expectedType); } - @Override public Boolean visitFloat(float f, TypeMirror expectedType) { + @Override + public Boolean visitFloat(float f, TypeMirror expectedType) { return MoreTypes.isTypeOf(Float.TYPE, expectedType); } - @Override public Boolean visitInt(int i, TypeMirror expectedType) { + @Override + public Boolean visitInt(int i, TypeMirror expectedType) { return MoreTypes.isTypeOf(Integer.TYPE, expectedType); } - @Override public Boolean visitLong(long l, TypeMirror expectedType) { + @Override + public Boolean visitLong(long l, TypeMirror expectedType) { return MoreTypes.isTypeOf(Long.TYPE, expectedType); } - @Override public Boolean visitShort(short s, TypeMirror expectedType) { + @Override + public Boolean visitShort(short s, TypeMirror expectedType) { return MoreTypes.isTypeOf(Short.TYPE, expectedType); } }; diff --git a/common/src/main/java/com/google/auto/common/Visibility.java b/common/src/main/java/com/google/auto/common/Visibility.java index f82fdd5933..db15f8bd43 100644 --- a/common/src/main/java/com/google/auto/common/Visibility.java +++ b/common/src/main/java/com/google/auto/common/Visibility.java @@ -24,6 +24,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Represents the visibility of a given {@link Element}: {@code public}, {@code protected}, @@ -41,7 +42,7 @@ public enum Visibility { // TODO(ronshapiro): remove this and reference ElementKind.MODULE directly once we start building // with -source 9 - private static final ElementKind MODULE = + private static final @Nullable ElementKind MODULE = Enums.getIfPresent(ElementKind.class, "MODULE").orNull(); /** @@ -76,8 +77,9 @@ public static Visibility effectiveVisibilityOfElement(Element element) { Visibility effectiveVisibility = PUBLIC; Element currentElement = element; while (currentElement != null) { - effectiveVisibility = - Ordering.natural().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/main/java/com/google/auto/common/package-info.java b/common/src/main/java/com/google/auto/common/package-info.java new file mode 100644 index 0000000000..22b0c45ac8 --- /dev/null +++ b/common/src/main/java/com/google/auto/common/package-info.java @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.auto.common; + diff --git a/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java b/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java index f63ee031d2..83494a8467 100644 --- a/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java +++ b/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java @@ -22,12 +22,17 @@ import static com.google.testing.compile.CompilationSubject.assertThat; import static org.junit.Assert.fail; +import com.google.common.collect.ImmutableSet; import com.google.common.testing.EquivalenceTester; +import com.google.common.truth.Correspondence; import com.google.testing.compile.CompilationRule; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Map; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleAnnotationValueVisitor6; @@ -46,110 +51,134 @@ public class AnnotationMirrorsTest { private Elements elements; - @Before public void setUp() { + @Before + public void setUp() { this.elements = compilationRule.getElements(); } @interface SimpleAnnotation {} - @SimpleAnnotation class SimplyAnnotated {} - @SimpleAnnotation class AlsoSimplyAnnotated {} + @SimpleAnnotation + static class SimplyAnnotated {} + + @SimpleAnnotation + static class AlsoSimplyAnnotated {} enum SimpleEnum { - BLAH, FOO + BLAH, + FOO } @interface Outer { SimpleEnum value(); } - @Outer(BLAH) static class TestClassBlah {} - @Outer(BLAH) static class TestClassBlah2 {} - @Outer(FOO) static class TestClassFoo {} + @Outer(BLAH) + static class TestClassBlah {} + + @Outer(BLAH) + static class TestClassBlah2 {} + + @Outer(FOO) + static class TestClassFoo {} @interface DefaultingOuter { SimpleEnum value() default SimpleEnum.BLAH; } - @DefaultingOuter class TestWithDefaultingOuterDefault {} - @DefaultingOuter(BLAH) class TestWithDefaultingOuterBlah {} - @DefaultingOuter(FOO) class TestWithDefaultingOuterFoo {} + @DefaultingOuter + static class TestWithDefaultingOuterDefault {} + + @DefaultingOuter(BLAH) + static class TestWithDefaultingOuterBlah {} + + @DefaultingOuter(FOO) + static class TestWithDefaultingOuterFoo {} @interface AnnotatedOuter { DefaultingOuter value(); } - @AnnotatedOuter(@DefaultingOuter) class TestDefaultNestedAnnotated {} - @AnnotatedOuter(@DefaultingOuter(BLAH)) class TestBlahNestedAnnotated {} - @AnnotatedOuter(@DefaultingOuter(FOO)) class TestFooNestedAnnotated {} + @AnnotatedOuter(@DefaultingOuter) + static class TestDefaultNestedAnnotated {} + + @AnnotatedOuter(@DefaultingOuter(BLAH)) + static class TestBlahNestedAnnotated {} + + @AnnotatedOuter(@DefaultingOuter(FOO)) + static class TestFooNestedAnnotated {} @interface OuterWithValueArray { DefaultingOuter[] value() default {}; } - @OuterWithValueArray class TestValueArrayWithDefault {} - @OuterWithValueArray({}) class TestValueArrayWithEmpty {} + @OuterWithValueArray + static class TestValueArrayWithDefault {} + + @OuterWithValueArray({}) + static class TestValueArrayWithEmpty {} + + @OuterWithValueArray({@DefaultingOuter}) + static class TestValueArrayWithOneDefault {} - @OuterWithValueArray({@DefaultingOuter}) class TestValueArrayWithOneDefault {} - @OuterWithValueArray(@DefaultingOuter(BLAH)) class TestValueArrayWithOneBlah {} - @OuterWithValueArray(@DefaultingOuter(FOO)) class TestValueArrayWithOneFoo {} + @OuterWithValueArray(@DefaultingOuter(BLAH)) + static class TestValueArrayWithOneBlah {} + + @OuterWithValueArray(@DefaultingOuter(FOO)) + static class TestValueArrayWithOneFoo {} @OuterWithValueArray({@DefaultingOuter(FOO), @DefaultingOuter}) class TestValueArrayWithFooAndDefaultBlah {} + @OuterWithValueArray({@DefaultingOuter(FOO), @DefaultingOuter(BLAH)}) class TestValueArrayWithFooBlah {} + @OuterWithValueArray({@DefaultingOuter(FOO), @DefaultingOuter(BLAH)}) class TestValueArrayWithFooBlah2 {} // Different instances than on TestValueArrayWithFooBlah. + @OuterWithValueArray({@DefaultingOuter(BLAH), @DefaultingOuter(FOO)}) class TestValueArrayWithBlahFoo {} - @Test public void testEquivalences() { + @Test + public void testEquivalences() { EquivalenceTester tester = EquivalenceTester.of(AnnotationMirrors.equivalence()); tester.addEquivalenceGroup( - annotationOn(SimplyAnnotated.class), - annotationOn(AlsoSimplyAnnotated.class)); + annotationOn(SimplyAnnotated.class), annotationOn(AlsoSimplyAnnotated.class)); tester.addEquivalenceGroup( - annotationOn(TestClassBlah.class), - annotationOn(TestClassBlah2.class)); + annotationOn(TestClassBlah.class), annotationOn(TestClassBlah2.class)); - tester.addEquivalenceGroup( - annotationOn(TestClassFoo.class)); + tester.addEquivalenceGroup(annotationOn(TestClassFoo.class)); tester.addEquivalenceGroup( annotationOn(TestWithDefaultingOuterDefault.class), annotationOn(TestWithDefaultingOuterBlah.class)); - tester.addEquivalenceGroup( - annotationOn(TestWithDefaultingOuterFoo.class)); + tester.addEquivalenceGroup(annotationOn(TestWithDefaultingOuterFoo.class)); tester.addEquivalenceGroup( annotationOn(TestDefaultNestedAnnotated.class), annotationOn(TestBlahNestedAnnotated.class)); - tester.addEquivalenceGroup( - annotationOn(TestFooNestedAnnotated.class)); + tester.addEquivalenceGroup(annotationOn(TestFooNestedAnnotated.class)); tester.addEquivalenceGroup( - annotationOn(TestValueArrayWithDefault.class), - annotationOn(TestValueArrayWithEmpty.class)); + annotationOn(TestValueArrayWithDefault.class), annotationOn(TestValueArrayWithEmpty.class)); tester.addEquivalenceGroup( annotationOn(TestValueArrayWithOneDefault.class), annotationOn(TestValueArrayWithOneBlah.class)); - tester.addEquivalenceGroup( - annotationOn(TestValueArrayWithOneFoo.class)); + tester.addEquivalenceGroup(annotationOn(TestValueArrayWithOneFoo.class)); tester.addEquivalenceGroup( annotationOn(TestValueArrayWithFooAndDefaultBlah.class), annotationOn(TestValueArrayWithFooBlah.class), annotationOn(TestValueArrayWithFooBlah2.class)); - tester.addEquivalenceGroup( - annotationOn(TestValueArrayWithBlahFoo.class)); + tester.addEquivalenceGroup(annotationOn(TestValueArrayWithBlahFoo.class)); tester.test(); } @@ -158,44 +187,61 @@ class TestValueArrayWithBlahFoo {} String value() default "default"; } - @Stringy class StringyUnset {} - @Stringy("foo") class StringySet {} + @Stringy + static class StringyUnset {} + + @Stringy("foo") + static class StringySet {} - @Test public void testGetDefaultValuesUnset() { + @Test + public void testGetDefaultValuesUnset() { assertThat(annotationOn(StringyUnset.class).getElementValues()).isEmpty(); - Iterable values = AnnotationMirrors.getAnnotationValuesWithDefaults( - annotationOn(StringyUnset.class)).values(); - String value = getOnlyElement(values).accept(new SimpleAnnotationValueVisitor6() { - @Override public String visitString(String value, Void ignored) { - return value; - } - }, null); + Iterable values = + AnnotationMirrors.getAnnotationValuesWithDefaults(annotationOn(StringyUnset.class)) + .values(); + String value = + getOnlyElement(values) + .accept( + new SimpleAnnotationValueVisitor6() { + @Override + public String visitString(String value, Void ignored) { + return value; + } + }, + null); assertThat(value).isEqualTo("default"); } - @Test public void testGetDefaultValuesSet() { - Iterable values = AnnotationMirrors.getAnnotationValuesWithDefaults( - annotationOn(StringySet.class)).values(); - String value = getOnlyElement(values).accept(new SimpleAnnotationValueVisitor6() { - @Override public String visitString(String value, Void ignored) { - return value; - } - }, null); + @Test + public void testGetDefaultValuesSet() { + Iterable values = + AnnotationMirrors.getAnnotationValuesWithDefaults(annotationOn(StringySet.class)).values(); + String value = + getOnlyElement(values) + .accept( + new SimpleAnnotationValueVisitor6() { + @Override + public String visitString(String value, Void ignored) { + return value; + } + }, + null); assertThat(value).isEqualTo("foo"); } - @Test public void testGetValueEntry() { + @Test + public void testGetValueEntry() { Map.Entry elementValue = - AnnotationMirrors.getAnnotationElementAndValue( - annotationOn(TestClassBlah.class), "value"); + AnnotationMirrors.getAnnotationElementAndValue(annotationOn(TestClassBlah.class), "value"); assertThat(elementValue.getKey().getSimpleName().toString()).isEqualTo("value"); assertThat(elementValue.getValue().getValue()).isInstanceOf(VariableElement.class); - AnnotationValue value = AnnotationMirrors.getAnnotationValue( - annotationOn(TestClassBlah.class), "value"); + AnnotationValue value = + AnnotationMirrors.getAnnotationValue(annotationOn(TestClassBlah.class), "value"); assertThat(value.getValue()).isInstanceOf(VariableElement.class); } - @Test public void testGetValueEntryFailure() { + @Test + public void testGetValueEntryFailure() { try { AnnotationMirrors.getAnnotationValue(annotationOn(TestClassBlah.class), "a"); } catch (IllegalArgumentException e) { @@ -211,4 +257,120 @@ class TestValueArrayWithBlahFoo {} private AnnotationMirror annotationOn(Class clazz) { return getOnlyElement(elements.getTypeElement(clazz.getCanonicalName()).getAnnotationMirrors()); } + + @Retention(RetentionPolicy.RUNTIME) + private @interface AnnotatingAnnotation {} + + @AnnotatingAnnotation + @Retention(RetentionPolicy.RUNTIME) + private @interface AnnotatedAnnotation1 {} + + @AnnotatingAnnotation + @Retention(RetentionPolicy.RUNTIME) + private @interface AnnotatedAnnotation2 {} + + @Retention(RetentionPolicy.RUNTIME) + private @interface NotAnnotatedAnnotation {} + + @AnnotatedAnnotation1 + @NotAnnotatedAnnotation + @AnnotatedAnnotation2 + private static final class AnnotatedClass {} + + @Test + public void getAnnotatedAnnotations() { + TypeElement element = elements.getTypeElement(AnnotatedClass.class.getCanonicalName()); + + // Test Class API + getAnnotatedAnnotationsAsserts( + AnnotationMirrors.getAnnotatedAnnotations(element, AnnotatingAnnotation.class)); + + // Test String API + String annotatingAnnotationName = AnnotatingAnnotation.class.getCanonicalName(); + getAnnotatedAnnotationsAsserts( + AnnotationMirrors.getAnnotatedAnnotations(element, annotatingAnnotationName)); + + // Test TypeElement API + TypeElement annotatingAnnotationElement = elements.getTypeElement(annotatingAnnotationName); + getAnnotatedAnnotationsAsserts( + AnnotationMirrors.getAnnotatedAnnotations(element, annotatingAnnotationElement)); + } + + @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 annotatedAnnotations) { + assertThat(annotatedAnnotations) + .comparingElementsUsing( + Correspondence.transforming( + (AnnotationMirror a) -> MoreTypes.asTypeElement(a.getAnnotationType()), "has type")) + .containsExactly( + elements.getTypeElement(AnnotatedAnnotation1.class.getCanonicalName()), + elements.getTypeElement(AnnotatedAnnotation2.class.getCanonicalName())); + } } diff --git a/common/src/test/java/com/google/auto/common/AnnotationValuesTest.java b/common/src/test/java/com/google/auto/common/AnnotationValuesTest.java index 825c85afb1..c6997b2aca 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 @@ 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 void getChars() { assertThat(AnnotationValues.getChars(value)).containsExactly('b', 'c').inOrder(); } + @Test + public void toSourceString() { + ImmutableMap inputs = + ImmutableMap.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/BasicAnnotationProcessorTest.java b/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java index f75345032d..03944e5df9 100644 --- a/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java +++ b/common/src/test/java/com/google/auto/common/BasicAnnotationProcessorTest.java @@ -79,7 +79,7 @@ private static class RequiresGeneratedCodeProcessor extends BaseAnnotationProces @Override protected Iterable steps() { - return ImmutableSet.of( + return ImmutableList.of( new Step() { @Override public ImmutableSet process( @@ -126,7 +126,7 @@ ImmutableList> processArguments() { public static class GeneratesCodeProcessor extends BaseAnnotationProcessor { @Override protected Iterable steps() { - return ImmutableSet.of( + return ImmutableList.of( new Step() { @Override public ImmutableSet process( @@ -150,7 +150,7 @@ public static class AnAnnotationProcessor extends BaseAnnotationProcessor { @Override protected Iterable steps() { - return ImmutableSet.of( + return ImmutableList.of( new Step() { @Override public ImmutableSet process( @@ -177,7 +177,7 @@ public static class CauseErrorProcessor extends BaseAnnotationProcessor { @Override protected Iterable steps() { - return ImmutableSet.of( + return ImmutableList.of( new Step() { @Override public ImmutableSet process( @@ -202,7 +202,7 @@ public static class MissingAnnotationProcessor extends BaseAnnotationProcessor { @Override protected Iterable steps() { - return ImmutableSet.of( + return ImmutableList.of( new Step() { @Override public ImmutableSet process( diff --git a/common/src/test/java/com/google/auto/common/GeneratedAnnotationsTest.java b/common/src/test/java/com/google/auto/common/GeneratedAnnotationsTest.java index 1c816c1043..f9426527ba 100644 --- a/common/src/test/java/com/google/auto/common/GeneratedAnnotationsTest.java +++ b/common/src/test/java/com/google/auto/common/GeneratedAnnotationsTest.java @@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; import static org.junit.Assume.assumeTrue; import com.google.common.collect.ImmutableList; @@ -31,6 +32,7 @@ import java.lang.reflect.Method; import java.net.URI; import java.nio.file.Files; +import java.util.Objects; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; @@ -44,6 +46,7 @@ import javax.tools.StandardJavaFileManager; import javax.tools.StandardLocation; import javax.tools.ToolProvider; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -99,12 +102,12 @@ public boolean process(Set annotations, RoundEnvironment * Run {@link TestProcessor} in a compilation with the given {@code options}, and prevent the * compilation from accessing classes with the qualified names in {@code maskFromClasspath}. */ - private String runProcessor(ImmutableList options, String packageToMask) + private String runProcessor(ImmutableList options, @Nullable String packageToMask) throws IOException { File tempDir = temporaryFolder.newFolder(); JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager standardFileManager = - compiler.getStandardFileManager(/* diagnostics= */ null, /* locale= */ null, UTF_8); + compiler.getStandardFileManager(/* diagnosticListener= */ null, /* locale= */ null, UTF_8); standardFileManager.setLocation(StandardLocation.CLASS_OUTPUT, ImmutableList.of(tempDir)); StandardJavaFileManager proxyFileManager = Reflection.newProxy( @@ -142,18 +145,20 @@ public CharSequence getCharContent(boolean ignoreEncodingErrors) */ private static class FileManagerInvocationHandler implements InvocationHandler { private final StandardJavaFileManager fileManager; - private final String packageToMask; + private final @Nullable String packageToMask; - FileManagerInvocationHandler(StandardJavaFileManager fileManager, String packageToMask) { + FileManagerInvocationHandler( + StandardJavaFileManager fileManager, @Nullable String packageToMask) { this.fileManager = fileManager; this.packageToMask = packageToMask; } @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + public Object invoke(Object proxy, Method method, @Nullable Object @Nullable [] args) + throws Throwable { if (method.getName().equals("list")) { - String packageName = (String) args[1]; - if (packageName.equals(packageToMask)) { + String packageName = (String) requireNonNull(args)[1]; + if (Objects.equals(packageName, packageToMask)) { return ImmutableList.of(); } } @@ -187,8 +192,7 @@ public void source8_masked() throws Exception { // An alternative would be to delete this test method. JDK8 always has // javax.annotation.Generated so it isn't really meaningful to test it without. ImmutableList options = ImmutableList.of("-source", "8", "-target", "8"); - String generated = - runProcessor(options, "javax.annotation"); + String generated = runProcessor(options, "javax.annotation"); assertThat(generated).doesNotContain(JAVAX_ANNOTATION_GENERATED); assertThat(generated).doesNotContain(JAVAX_ANNOTATION_PROCESSING_GENERATED); } diff --git a/common/src/test/java/com/google/auto/common/MoreElementsTest.java b/common/src/test/java/com/google/auto/common/MoreElementsTest.java index 95043cf376..eaa504a10a 100644 --- a/common/src/test/java/com/google/auto/common/MoreElementsTest.java +++ b/common/src/test/java/com/google/auto/common/MoreElementsTest.java @@ -18,6 +18,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static java.util.Objects.requireNonNull; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -58,13 +59,14 @@ public class MoreElementsTest { @Rule public CompilationRule compilation = new CompilationRule(); @Rule public Expect expect = Expect.create(); + private Elements elements; private PackageElement javaLangPackageElement; private TypeElement objectElement; private TypeElement stringElement; @Before public void initializeTestElements() { - Elements elements = compilation.getElements(); + this.elements = compilation.getElements(); this.javaLangPackageElement = elements.getPackageElement("java.lang"); this.objectElement = elements.getTypeElement(Object.class.getCanonicalName()); this.stringElement = elements.getTypeElement(String.class.getCanonicalName()); @@ -80,8 +82,7 @@ public void getPackage() { @Test public void asPackage() { - assertThat(MoreElements.asPackage(javaLangPackageElement)) - .isEqualTo(javaLangPackageElement); + assertThat(MoreElements.asPackage(javaLangPackageElement)).isEqualTo(javaLangPackageElement); } @Test @@ -89,19 +90,20 @@ public void asPackage_illegalArgument() { try { MoreElements.asPackage(stringElement); fail(); - } catch (IllegalArgumentException expected) {} + } catch (IllegalArgumentException expected) { + } } - @Test public void asTypeElement() { - Element typeElement = - compilation.getElements().getTypeElement(String.class.getCanonicalName()); + @Test + public void asTypeElement() { + Element typeElement = elements.getTypeElement(String.class.getCanonicalName()); assertTrue(MoreElements.isType(typeElement)); assertThat(MoreElements.asType(typeElement)).isEqualTo(typeElement); } - @Test public void asTypeElement_notATypeElement() { - TypeElement typeElement = - compilation.getElements().getTypeElement(String.class.getCanonicalName()); + @Test + public void asTypeElement_notATypeElement() { + TypeElement typeElement = elements.getTypeElement(String.class.getCanonicalName()); for (ExecutableElement e : ElementFilter.methodsIn(typeElement.getEnclosedElements())) { assertFalse(MoreElements.isType(e)); try { @@ -143,7 +145,8 @@ public void asType_illegalArgument() { try { MoreElements.asType(javaLangPackageElement); fail(); - } catch (IllegalArgumentException expected) {} + } catch (IllegalArgumentException expected) { + } } @Test @@ -158,7 +161,8 @@ public void asVariable_illegalArgument() { try { MoreElements.asVariable(javaLangPackageElement); fail(); - } catch (IllegalArgumentException expected) {} + } catch (IllegalArgumentException expected) { + } } @Test @@ -166,8 +170,8 @@ public void asExecutable() { for (Element methodElement : ElementFilter.methodsIn(stringElement.getEnclosedElements())) { assertThat(MoreElements.asExecutable(methodElement)).isEqualTo(methodElement); } - for (Element methodElement - : ElementFilter.constructorsIn(stringElement.getEnclosedElements())) { + for (Element methodElement : + ElementFilter.constructorsIn(stringElement.getEnclosedElements())) { assertThat(MoreElements.asExecutable(methodElement)).isEqualTo(methodElement); } } @@ -177,7 +181,8 @@ public void asExecutable_illegalArgument() { try { MoreElements.asExecutable(javaLangPackageElement); fail(); - } catch (IllegalArgumentException expected) {} + } catch (IllegalArgumentException expected) { + } } @Retention(RetentionPolicy.RUNTIME) @@ -190,39 +195,90 @@ public void asExecutable_illegalArgument() { @Test public void isAnnotationPresent() { TypeElement annotatedAnnotationElement = - compilation.getElements().getTypeElement(AnnotatedAnnotation.class.getCanonicalName()); - assertThat(MoreElements.isAnnotationPresent(annotatedAnnotationElement, Documented.class)) - .isTrue(); - assertThat(MoreElements.isAnnotationPresent(annotatedAnnotationElement, InnerAnnotation.class)) - .isTrue(); - assertThat(MoreElements.isAnnotationPresent(annotatedAnnotationElement, SuppressWarnings.class)) - .isFalse(); + elements.getTypeElement(AnnotatedAnnotation.class.getCanonicalName()); + + // Test Class API + isAnnotationPresentAsserts( + MoreElements.isAnnotationPresent(annotatedAnnotationElement, Documented.class), + MoreElements.isAnnotationPresent(annotatedAnnotationElement, InnerAnnotation.class), + MoreElements.isAnnotationPresent(annotatedAnnotationElement, SuppressWarnings.class)); + + // Test String API + String documentedName = Documented.class.getCanonicalName(); + String innerAnnotationName = InnerAnnotation.class.getCanonicalName(); + String suppressWarningsName = SuppressWarnings.class.getCanonicalName(); + isAnnotationPresentAsserts( + MoreElements.isAnnotationPresent(annotatedAnnotationElement, documentedName), + MoreElements.isAnnotationPresent(annotatedAnnotationElement, innerAnnotationName), + MoreElements.isAnnotationPresent(annotatedAnnotationElement, suppressWarningsName)); + + // Test TypeElement API + TypeElement documentedElement = elements.getTypeElement(documentedName); + TypeElement innerAnnotationElement = elements.getTypeElement(innerAnnotationName); + TypeElement suppressWarningsElement = elements.getTypeElement(suppressWarningsName); + isAnnotationPresentAsserts( + MoreElements.isAnnotationPresent(annotatedAnnotationElement, documentedElement), + MoreElements.isAnnotationPresent(annotatedAnnotationElement, innerAnnotationElement), + MoreElements.isAnnotationPresent(annotatedAnnotationElement, suppressWarningsElement)); + } + + private void isAnnotationPresentAsserts( + boolean isDocumentedPresent, + boolean isInnerAnnotationPresent, + boolean isSuppressWarningsPresent) { + assertThat(isDocumentedPresent).isTrue(); + assertThat(isInnerAnnotationPresent).isTrue(); + assertThat(isSuppressWarningsPresent).isFalse(); } @Test public void getAnnotationMirror() { TypeElement element = - compilation.getElements().getTypeElement(AnnotatedAnnotation.class.getCanonicalName()); - - Optional documented = - MoreElements.getAnnotationMirror(element, Documented.class); - Optional innerAnnotation = - MoreElements.getAnnotationMirror(element, InnerAnnotation.class); - Optional suppressWarnings = - MoreElements.getAnnotationMirror(element, SuppressWarnings.class); - + elements.getTypeElement(AnnotatedAnnotation.class.getCanonicalName()); + + // Test Class API + getAnnotationMirrorAsserts( + MoreElements.getAnnotationMirror(element, Documented.class), + MoreElements.getAnnotationMirror(element, InnerAnnotation.class), + MoreElements.getAnnotationMirror(element, SuppressWarnings.class)); + + // Test String API + String documentedName = Documented.class.getCanonicalName(); + String innerAnnotationName = InnerAnnotation.class.getCanonicalName(); + String suppressWarningsName = SuppressWarnings.class.getCanonicalName(); + getAnnotationMirrorAsserts( + MoreElements.getAnnotationMirror(element, documentedName), + MoreElements.getAnnotationMirror(element, innerAnnotationName), + MoreElements.getAnnotationMirror(element, suppressWarningsName)); + + // Test TypeElement API + TypeElement documentedElement = elements.getTypeElement(documentedName); + TypeElement innerAnnotationElement = elements.getTypeElement(innerAnnotationName); + TypeElement suppressWarningsElement = elements.getTypeElement(suppressWarningsName); + getAnnotationMirrorAsserts( + MoreElements.getAnnotationMirror(element, documentedElement), + MoreElements.getAnnotationMirror(element, innerAnnotationElement), + MoreElements.getAnnotationMirror(element, suppressWarningsElement)); + } + + private void getAnnotationMirrorAsserts( + Optional documented, + Optional innerAnnotation, + Optional suppressWarnings) { expect.that(documented).isPresent(); expect.that(innerAnnotation).isPresent(); expect.that(suppressWarnings).isAbsent(); Element annotationElement = documented.get().getAnnotationType().asElement(); expect.that(MoreElements.isType(annotationElement)).isTrue(); - expect.that(MoreElements.asType(annotationElement).getQualifiedName().toString()) + expect + .that(MoreElements.asType(annotationElement).getQualifiedName().toString()) .isEqualTo(Documented.class.getCanonicalName()); annotationElement = innerAnnotation.get().getAnnotationType().asElement(); expect.that(MoreElements.isType(annotationElement)).isTrue(); - expect.that(MoreElements.asType(annotationElement).getQualifiedName().toString()) + expect + .that(MoreElements.asType(annotationElement).getQualifiedName().toString()) .isEqualTo(InnerAnnotation.class.getCanonicalName()); } @@ -231,6 +287,7 @@ static void staticMethod() {} abstract String foo(); + @SuppressWarnings("unused") private void privateMethod() {} } @@ -259,7 +316,6 @@ void buh(int x, int y) {} @Test public void getLocalAndInheritedMethods_Old() { - Elements elements = compilation.getElements(); Types types = compilation.getTypes(); TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT); TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG); @@ -270,19 +326,20 @@ public void getLocalAndInheritedMethods_Old() { Set objectMethods = visibleMethodsFromObject(); assertThat(childTypeMethods).containsAtLeastElementsIn(objectMethods); Set nonObjectMethods = Sets.difference(childTypeMethods, objectMethods); - assertThat(nonObjectMethods).containsExactly( + assertThat(nonObjectMethods) + .containsExactly( getMethod(ParentInterface.class, "bar", longMirror), getMethod(ParentClass.class, "foo"), getMethod(Child.class, "bar"), getMethod(Child.class, "baz"), getMethod(Child.class, "buh", intMirror), getMethod(Child.class, "buh", intMirror, intMirror)) - .inOrder();; + .inOrder(); + ; } @Test public void getLocalAndInheritedMethods() { - Elements elements = compilation.getElements(); Types types = compilation.getTypes(); TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT); TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG); @@ -293,7 +350,8 @@ public void getLocalAndInheritedMethods() { Set objectMethods = visibleMethodsFromObject(); assertThat(childTypeMethods).containsAtLeastElementsIn(objectMethods); Set nonObjectMethods = Sets.difference(childTypeMethods, objectMethods); - assertThat(nonObjectMethods).containsExactly( + assertThat(nonObjectMethods) + .containsExactly( getMethod(ParentInterface.class, "bar", longMirror), getMethod(ParentClass.class, "foo"), getMethod(Child.class, "bar"), @@ -305,7 +363,6 @@ public void getLocalAndInheritedMethods() { @Test public void getAllMethods() { - Elements elements = compilation.getElements(); Types types = compilation.getTypes(); TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT); TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG); @@ -316,7 +373,8 @@ public void getAllMethods() { Set objectMethods = allMethodsFromObject(); assertThat(childTypeMethods).containsAtLeastElementsIn(objectMethods); Set nonObjectMethods = Sets.difference(childTypeMethods, objectMethods); - assertThat(nonObjectMethods).containsExactly( + assertThat(nonObjectMethods) + .containsExactly( getMethod(ParentInterface.class, "staticMethod"), getMethod(ParentInterface.class, "bar", longMirror), getMethod(ParentClass.class, "staticMethod"), @@ -330,41 +388,6 @@ public void getAllMethods() { .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() { - Elements elementUtils = compilation.getElements(); - TypeElement main = elementUtils.getTypeElement(Main.ParentComponent.class.getCanonicalName()); - Set methods = MoreElements.getLocalAndInheritedMethods( - main, compilation.getTypes(), elementUtils); - assertThat(methods).hasSize(1); - ExecutableElement method = methods.iterator().next(); - assertThat(method.getSimpleName().toString()).isEqualTo("injectable"); - assertThat(method.getParameters()).isEmpty(); - } - private Set visibleMethodsFromObject() { Types types = compilation.getTypes(); TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT); @@ -404,7 +427,7 @@ private Set allMethodsFromObject() { } private ExecutableElement getMethod(Class c, String methodName, TypeMirror... parameterTypes) { - TypeElement type = compilation.getElements().getTypeElement(c.getCanonicalName()); + TypeElement type = elements.getTypeElement(c.getCanonicalName()); Types types = compilation.getTypes(); ExecutableElement found = null; for (ExecutableElement method : ElementFilter.methodsIn(type.getEnclosedElements())) { @@ -423,7 +446,7 @@ private ExecutableElement getMethod(Class c, String methodName, TypeMirror... } } assertWithMessage(methodName + Arrays.toString(parameterTypes)).that(found).isNotNull(); - return found; + return requireNonNull(found); } private abstract static class AbstractAbstractList extends AbstractList {} @@ -458,8 +481,6 @@ private Set abstractMethodNamesFrom(Set methods) { // are implemented in AbstractList. @Test public void getLocalAndInheritedMethods_AbstractList() { - Elements elements = compilation.getElements(); - TypeElement abstractType = elements.getTypeElement(AbstractAbstractList.class.getCanonicalName()); Set abstractTypeMethods = diff --git a/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java b/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java index 05a0a1194e..ba8fccebdd 100644 --- a/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java +++ b/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java @@ -15,182 +15,172 @@ */ package com.google.auto.common; -import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.testing.compile.CompilationRule; -import javax.lang.model.element.Element; +import java.util.List; +import java.util.SortedMap; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** - * Tests {@link MoreTypes#isTypeOf}. - */ +/** Tests {@link MoreTypes#isTypeOf(Class, TypeMirror)}. */ @RunWith(JUnit4.class) public class MoreTypesIsTypeOfTest { @Rule public CompilationRule compilationRule = new CompilationRule(); - private Elements elements; - - @Before public void setUp() { - this.elements = compilationRule.getElements(); - } - - private interface TestType {} - - @Test public void isTypeOf_DeclaredType() { - assertTrue(MoreTypes.isType(typeElementFor(TestType.class).asType())); - assertWithMessage("mirror represents the TestType") - .that(MoreTypes.isTypeOf(TestType.class, typeElementFor(TestType.class).asType())) + private Elements elementUtils; + private Types typeUtils; + + @Before + public void setUp() { + this.elementUtils = compilationRule.getElements(); + this.typeUtils = compilationRule.getTypes(); + } + + @Test + public void isTypeOf_primitiveAndBoxedPrimitiveTypes() { + class PrimitiveTypeInfo { + final Class CLASS_TYPE; + final Class BOXED_CLASS_TYPE; + final TypeKind TYPE_KIND; + + PrimitiveTypeInfo(Class classType, Class boxedClassType, TypeKind typeKind) { + this.CLASS_TYPE = classType; + this.BOXED_CLASS_TYPE = boxedClassType; + this.TYPE_KIND = typeKind; + } + } + final List primitivesTypeInfo = + ImmutableList.of( + new PrimitiveTypeInfo(Byte.TYPE, Byte.class, TypeKind.BYTE), + new PrimitiveTypeInfo(Short.TYPE, Short.class, TypeKind.SHORT), + new PrimitiveTypeInfo(Integer.TYPE, Integer.class, TypeKind.INT), + new PrimitiveTypeInfo(Long.TYPE, Long.class, TypeKind.LONG), + new PrimitiveTypeInfo(Float.TYPE, Float.class, TypeKind.FLOAT), + new PrimitiveTypeInfo(Double.TYPE, Double.class, TypeKind.DOUBLE), + new PrimitiveTypeInfo(Boolean.TYPE, Boolean.class, TypeKind.BOOLEAN), + new PrimitiveTypeInfo(Character.TYPE, Character.class, TypeKind.CHAR)); + + for (boolean isBoxedI : new boolean[] {false, true}) { + for (int i = 0; i < primitivesTypeInfo.size(); i++) { // For the Class arg + Class clazz = + isBoxedI + ? primitivesTypeInfo.get(i).BOXED_CLASS_TYPE + : primitivesTypeInfo.get(i).CLASS_TYPE; + + for (boolean isBoxedJ : new boolean[] {false, true}) { + for (int j = 0; j < primitivesTypeInfo.size(); j++) { // For the TypeMirror arg + TypeKind typeKind = primitivesTypeInfo.get(j).TYPE_KIND; + TypeMirror typeMirror = + isBoxedJ + ? typeUtils.boxedClass(typeUtils.getPrimitiveType(typeKind)).asType() + : typeUtils.getPrimitiveType(typeKind); + + String message = + "Mirror:\t" + typeMirror.toString() + "\nClass:\t" + clazz.getCanonicalName(); + if (isBoxedI == isBoxedJ && i == j) { + assertWithMessage(message).that(MoreTypes.isTypeOf(clazz, typeMirror)).isTrue(); + } else { + assertWithMessage(message).that(MoreTypes.isTypeOf(clazz, typeMirror)).isFalse(); + } + } + } + } + } + } + + @Test + public void isTypeOf_voidAndPseudoVoidTypes() { + TypeMirror voidType = typeUtils.getNoType(TypeKind.VOID); + TypeMirror pseudoVoidType = getTypeElementFor(Void.class).asType(); + + assertWithMessage("Mirror:\t" + voidType + "\nClass:\t" + Void.TYPE.getCanonicalName()) + .that(MoreTypes.isTypeOf(Void.TYPE, voidType)) .isTrue(); - assertWithMessage("mirror does not represent a String") - .that(MoreTypes.isTypeOf(String.class, typeElementFor(TestType.class).asType())) + assertWithMessage("Mirror:\t" + pseudoVoidType + "\nClass:\t" + Void.TYPE.getCanonicalName()) + .that(MoreTypes.isTypeOf(Void.TYPE, pseudoVoidType)) .isFalse(); - } - private interface ArrayType { - String[] array(); + assertWithMessage("Mirror:\t" + voidType + "\nClass:\t" + Void.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(Void.class, voidType)) + .isFalse(); + assertWithMessage("Mirror:\t" + pseudoVoidType + "\nClass:\t" + Void.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(Void.class, pseudoVoidType)) + .isTrue(); } - @Test public void isTypeOf_ArrayType() { - assertTrue(MoreTypes.isType(typeElementFor(ArrayType.class).asType())); - TypeMirror type = extractReturnTypeFromHolder(typeElementFor(ArrayType.class)); - assertWithMessage("array mirror represents an array Class object") + @Test + public void isTypeOf_arrayType() { + TypeMirror type = typeUtils.getArrayType(getTypeElementFor(String.class).asType()); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + String[].class.getCanonicalName()) .that(MoreTypes.isTypeOf(String[].class, type)) .isTrue(); - } - - private interface PrimitiveBoolean { - boolean method(); - } - - @Test public void isTypeOf_PrimitiveBoolean() { - assertTrue(MoreTypes.isType(typeElementFor(PrimitiveBoolean.class).asType())); - TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveBoolean.class)); - assertWithMessage("mirror of a boolean").that(MoreTypes.isTypeOf(Boolean.TYPE, type)).isTrue(); - } - - private interface PrimitiveByte { - byte method(); - } - - @Test public void isTypeOf_PrimitiveByte() { - assertTrue(MoreTypes.isType(typeElementFor(PrimitiveByte.class).asType())); - TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveByte.class)); - assertWithMessage("mirror of a byte").that(MoreTypes.isTypeOf(Byte.TYPE, type)).isTrue(); - } - - private interface PrimitiveChar { - char method(); - } - - @Test public void isTypeOf_PrimitiveChar() { - assertTrue(MoreTypes.isType(typeElementFor(PrimitiveChar.class).asType())); - TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveChar.class)); - assertWithMessage("mirror of a char").that(MoreTypes.isTypeOf(Character.TYPE, type)).isTrue(); - } - - private interface PrimitiveDouble { - double method(); - } - - @Test public void isTypeOf_PrimitiveDouble() { - assertTrue(MoreTypes.isType(typeElementFor(PrimitiveDouble.class).asType())); - TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveDouble.class)); - assertWithMessage("mirror of a double").that(MoreTypes.isTypeOf(Double.TYPE, type)).isTrue(); - } - - private interface PrimitiveFloat { - float method(); - } - - @Test public void isTypeOf_PrimitiveFloat() { - assertTrue(MoreTypes.isType(typeElementFor(PrimitiveFloat.class).asType())); - TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveFloat.class)); - assertWithMessage("mirror of a float").that(MoreTypes.isTypeOf(Float.TYPE, type)).isTrue(); - } - - private interface PrimitiveInt { - int method(); - } - - @Test public void isTypeOf_PrimitiveInt() { - assertTrue(MoreTypes.isType(typeElementFor(PrimitiveInt.class).asType())); - TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveInt.class)); - assertWithMessage("mirror of a int").that(MoreTypes.isTypeOf(Integer.TYPE, type)).isTrue(); - } - - private interface PrimitiveLong { - long method(); - } - - @Test public void isTypeOf_PrimitiveLong() { - assertTrue(MoreTypes.isType(typeElementFor(PrimitiveLong.class).asType())); - TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveLong.class)); - assertWithMessage("mirror of a long").that(MoreTypes.isTypeOf(Long.TYPE, type)).isTrue(); - } - - private interface PrimitiveShort { - short method(); - } - - @Test public void isTypeOf_PrimitiveShort() { - assertTrue(MoreTypes.isType(typeElementFor(PrimitiveShort.class).asType())); - TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveShort.class)); - assertWithMessage("mirror of a short").that(MoreTypes.isTypeOf(Short.TYPE, type)).isTrue(); - } - - private interface PrimitiveVoid { - void method(); - } + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + Integer[].class.getCanonicalName()) + .that(MoreTypes.isTypeOf(Integer[].class, type)) + .isFalse(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + int[].class.getCanonicalName()) + .that(MoreTypes.isTypeOf(int[].class, type)) + .isFalse(); - @Test public void isTypeOf_void() { - assertTrue(MoreTypes.isType(typeElementFor(PrimitiveVoid.class).asType())); - TypeMirror primitive = extractReturnTypeFromHolder(typeElementFor(PrimitiveVoid.class)); - assertWithMessage("mirror of a void").that(MoreTypes.isTypeOf(Void.TYPE, primitive)).isTrue(); + type = typeUtils.getArrayType(typeUtils.getPrimitiveType(TypeKind.INT)); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + String[].class.getCanonicalName()) + .that(MoreTypes.isTypeOf(String[].class, type)) + .isFalse(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + Integer[].class.getCanonicalName()) + .that(MoreTypes.isTypeOf(Integer[].class, type)) + .isFalse(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + int[].class.getCanonicalName()) + .that(MoreTypes.isTypeOf(int[].class, type)) + .isTrue(); } - private interface DeclaredVoid { - Void method(); + private interface TestType { + @SuppressWarnings("unused") + > T method0(); } - @Test public void isTypeOf_Void() { - assertTrue(MoreTypes.isType(typeElementFor(DeclaredVoid.class).asType())); - TypeMirror declared = extractReturnTypeFromHolder(typeElementFor(DeclaredVoid.class)); - assertWithMessage("mirror of a void").that(MoreTypes.isTypeOf(Void.class, declared)).isTrue(); + @Test + public void isTypeOf_declaredType() { + TypeMirror TestTypeTypeMirror = getTypeElementFor(TestType.class).asType(); + assertTrue(MoreTypes.isType(TestTypeTypeMirror)); + assertWithMessage( + "Mirror:\t" + TestTypeTypeMirror + "\nClass:\t" + TestType.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(TestType.class, TestTypeTypeMirror)) + .isTrue(); + assertWithMessage( + "Mirror:\t" + TestTypeTypeMirror + "\nClass:\t" + String.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(String.class, TestTypeTypeMirror)) + .isFalse(); } - @Test public void isTypeOf_fail() { - assertFalse(MoreTypes.isType( - getOnlyElement(typeElementFor(DeclaredVoid.class).getEnclosedElements()).asType())); - TypeMirror method = - getOnlyElement(typeElementFor(DeclaredVoid.class).getEnclosedElements()).asType(); + @Test + public void isTypeOf_fail() { + TypeMirror methodType = + Iterables.getOnlyElement(getTypeElementFor(TestType.class).getEnclosedElements()).asType(); + assertFalse(MoreTypes.isType(methodType)); try { - MoreTypes.isTypeOf(String.class, method); + MoreTypes.isTypeOf(List.class, methodType); fail(); - } catch (IllegalArgumentException expected) {} - } - - // Utility methods for this test. - - private TypeMirror extractReturnTypeFromHolder(TypeElement typeElement) { - Element element = Iterables.getOnlyElement(typeElement.getEnclosedElements()); - TypeMirror arrayType = MoreElements.asExecutable(element).getReturnType(); - return arrayType; + } catch (IllegalArgumentException expected) { + } } - private TypeElement typeElementFor(Class clazz) { - return elements.getTypeElement(clazz.getCanonicalName()); + /* Utility method(s) */ + private TypeElement getTypeElementFor(Class clazz) { + return elementUtils.getTypeElement(clazz.getCanonicalName()); } } diff --git a/common/src/test/java/com/google/auto/common/MoreTypesTest.java b/common/src/test/java/com/google/auto/common/MoreTypesTest.java index ff33ffc1d9..b8e84e0859 100644 --- a/common/src/test/java/com/google/auto/common/MoreTypesTest.java +++ b/common/src/test/java/com/google/auto/common/MoreTypesTest.java @@ -16,12 +16,12 @@ package com.google.auto.common; import static com.google.common.truth.Truth.assertThat; +import static java.util.Objects.requireNonNull; import static javax.lang.model.type.TypeKind.NONE; import static javax.lang.model.type.TypeKind.VOID; import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; -import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; @@ -50,6 +50,7 @@ import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -91,48 +92,51 @@ public void equivalence() { DeclaredType containerOfString = types.getDeclaredType(container, stringType); TypeMirror containedInObject = types.asMemberOf(containerOfObject, contained); TypeMirror containedInString = types.asMemberOf(containerOfString, contained); - EquivalenceTester tester = EquivalenceTester.of(MoreTypes.equivalence()) - .addEquivalenceGroup(types.getNullType()) - .addEquivalenceGroup(types.getNoType(NONE)) - .addEquivalenceGroup(types.getNoType(VOID)) - .addEquivalenceGroup(objectType) - .addEquivalenceGroup(stringType) - .addEquivalenceGroup(containedInObject) - .addEquivalenceGroup(containedInString) - .addEquivalenceGroup(funkyBounds.asType()) - .addEquivalenceGroup(funkyBounds2.asType()) - .addEquivalenceGroup(funkierBounds.asType()) - .addEquivalenceGroup(funkyBoundsVar, funkyBounds2Var) - .addEquivalenceGroup(funkierBoundsVar) - // Enum> - .addEquivalenceGroup(enumElement.asType()) - // Map - .addEquivalenceGroup(mapType) - .addEquivalenceGroup(mapOfObjectToObjectType) - // Map - .addEquivalenceGroup(types.getDeclaredType(mapElement, wildcard, wildcard)) - // Map - .addEquivalenceGroup(types.erasure(mapType), types.erasure(mapOfObjectToObjectType)) - .addEquivalenceGroup(types.getDeclaredType(mapElement, objectType, stringType)) - .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType, objectType)) - .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType, stringType)) - .addEquivalenceGroup(setOfSetOfObject) - .addEquivalenceGroup(setOfSetOfString) - .addEquivalenceGroup(setOfSetOfSetOfObject) - .addEquivalenceGroup(setOfSetOfSetOfString) - .addEquivalenceGroup(wildcard) - // ? extends Object - .addEquivalenceGroup(types.getWildcardType(objectType, null)) - // ? extends String - .addEquivalenceGroup(types.getWildcardType(stringType, null)) - // ? super String - .addEquivalenceGroup(types.getWildcardType(null, stringType)) - // Map>> - .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType, - types.getDeclaredType(mapElement, stringType, - types.getDeclaredType(setElement, objectType)))) - .addEquivalenceGroup(FAKE_ERROR_TYPE) - ; + EquivalenceTester tester = + EquivalenceTester.of(MoreTypes.equivalence()) + .addEquivalenceGroup(types.getNullType()) + .addEquivalenceGroup(types.getNoType(NONE)) + .addEquivalenceGroup(types.getNoType(VOID)) + .addEquivalenceGroup(objectType) + .addEquivalenceGroup(stringType) + .addEquivalenceGroup(containedInObject) + .addEquivalenceGroup(containedInString) + .addEquivalenceGroup(funkyBounds.asType()) + .addEquivalenceGroup(funkyBounds2.asType()) + .addEquivalenceGroup(funkierBounds.asType()) + .addEquivalenceGroup(funkyBoundsVar, funkyBounds2Var) + .addEquivalenceGroup(funkierBoundsVar) + // Enum> + .addEquivalenceGroup(enumElement.asType()) + // Map + .addEquivalenceGroup(mapType) + .addEquivalenceGroup(mapOfObjectToObjectType) + // Map + .addEquivalenceGroup(types.getDeclaredType(mapElement, wildcard, wildcard)) + // Map + .addEquivalenceGroup(types.erasure(mapType), types.erasure(mapOfObjectToObjectType)) + .addEquivalenceGroup(types.getDeclaredType(mapElement, objectType, stringType)) + .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType, objectType)) + .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType, stringType)) + .addEquivalenceGroup(setOfSetOfObject) + .addEquivalenceGroup(setOfSetOfString) + .addEquivalenceGroup(setOfSetOfSetOfObject) + .addEquivalenceGroup(setOfSetOfSetOfString) + .addEquivalenceGroup(wildcard) + // ? extends Object + .addEquivalenceGroup(types.getWildcardType(objectType, null)) + // ? extends String + .addEquivalenceGroup(types.getWildcardType(stringType, null)) + // ? super String + .addEquivalenceGroup(types.getWildcardType(null, stringType)) + // Map>> + .addEquivalenceGroup( + types.getDeclaredType( + mapElement, + stringType, + types.getDeclaredType( + mapElement, stringType, types.getDeclaredType(setElement, objectType)))) + .addEquivalenceGroup(FAKE_ERROR_TYPE); for (TypeKind kind : TypeKind.values()) { if (kind.isPrimitive()) { @@ -145,20 +149,18 @@ public void equivalence() { } } - ImmutableSet> testClasses = ImmutableSet.of( - ExecutableElementsGroupA.class, - ExecutableElementsGroupB.class, - ExecutableElementsGroupC.class, - ExecutableElementsGroupD.class, - ExecutableElementsGroupE.class); + ImmutableSet> testClasses = + ImmutableSet.of( + ExecutableElementsGroupA.class, + ExecutableElementsGroupB.class, + ExecutableElementsGroupC.class, + ExecutableElementsGroupD.class, + ExecutableElementsGroupE.class); for (Class testClass : testClasses) { - ImmutableList equivalenceGroup = FluentIterable.from( - elements.getTypeElement(testClass.getCanonicalName()).getEnclosedElements()) - .transform(new Function() { - @Override public TypeMirror apply(Element input) { - return input.asType(); - } - }) + ImmutableList equivalenceGroup = + FluentIterable.from( + elements.getTypeElement(testClass.getCanonicalName()).getEnclosedElements()) + .transform(Element::asType) .toList(); tester.addEquivalenceGroup(equivalenceGroup); } @@ -169,35 +171,45 @@ public void equivalence() { @SuppressWarnings("unused") private static final class ExecutableElementsGroupA { ExecutableElementsGroupA() {} + void a() {} + public static void b() {} } @SuppressWarnings("unused") private static final class ExecutableElementsGroupB { ExecutableElementsGroupB(String s) {} + void a(String s) {} + public static void b(String s) {} } @SuppressWarnings("unused") private static final class ExecutableElementsGroupC { ExecutableElementsGroupC() throws Exception {} + void a() throws Exception {} + public static void b() throws Exception {} } @SuppressWarnings("unused") private static final class ExecutableElementsGroupD { ExecutableElementsGroupD() throws RuntimeException {} + void a() throws RuntimeException {} + public static void b() throws RuntimeException {} } @SuppressWarnings("unused") private static final class ExecutableElementsGroupE { ExecutableElementsGroupE() {} + void a() {} + public static void b() {} } @@ -215,53 +227,44 @@ private static final class FunkyBounds2> {} @SuppressWarnings("unused") private static final class FunkierBounds & Cloneable> {} - @Test public void testReferencedTypes() { + @Test + public void testReferencedTypes() { Elements elements = compilationRule.getElements(); - TypeElement testDataElement = elements - .getTypeElement(ReferencedTypesTestData.class.getCanonicalName()); + TypeElement testDataElement = + elements.getTypeElement(ReferencedTypesTestData.class.getCanonicalName()); ImmutableMap fieldIndex = FluentIterable.from(ElementFilter.fieldsIn(testDataElement.getEnclosedElements())) - .uniqueIndex(new Function() { - @Override public String apply(VariableElement input) { - return input.getSimpleName().toString(); - } - }); - - TypeElement objectElement = - elements.getTypeElement(Object.class.getCanonicalName()); - TypeElement stringElement = - elements.getTypeElement(String.class.getCanonicalName()); - TypeElement integerElement = - elements.getTypeElement(Integer.class.getCanonicalName()); - TypeElement setElement = - elements.getTypeElement(Set.class.getCanonicalName()); - TypeElement mapElement = - elements.getTypeElement(Map.class.getCanonicalName()); + .uniqueIndex(input -> input.getSimpleName().toString()); + + TypeElement objectElement = elements.getTypeElement(Object.class.getCanonicalName()); + TypeElement stringElement = elements.getTypeElement(String.class.getCanonicalName()); + TypeElement integerElement = elements.getTypeElement(Integer.class.getCanonicalName()); + TypeElement setElement = elements.getTypeElement(Set.class.getCanonicalName()); + TypeElement mapElement = elements.getTypeElement(Map.class.getCanonicalName()); TypeElement charSequenceElement = elements.getTypeElement(CharSequence.class.getCanonicalName()); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f1").asType())) - .containsExactly(objectElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f2").asType())) - .containsExactly(setElement, stringElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f3").asType())) + assertThat(referencedTypes(fieldIndex, "f1")).containsExactly(objectElement); + assertThat(referencedTypes(fieldIndex, "f2")).containsExactly(setElement, stringElement); + assertThat(referencedTypes(fieldIndex, "f3")) .containsExactly(mapElement, stringElement, objectElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f4").asType())) - .containsExactly(integerElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f5").asType())) - .containsExactly(setElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f6").asType())) - .containsExactly(setElement, charSequenceElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f7").asType())) + assertThat(referencedTypes(fieldIndex, "f4")).containsExactly(integerElement); + assertThat(referencedTypes(fieldIndex, "f5")).containsExactly(setElement); + assertThat(referencedTypes(fieldIndex, "f6")).containsExactly(setElement, charSequenceElement); + assertThat(referencedTypes(fieldIndex, "f7")) .containsExactly(mapElement, stringElement, setElement, charSequenceElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f8").asType())) - .containsExactly(stringElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f9").asType())) - .containsExactly(stringElement); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f10").asType())).isEmpty(); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f11").asType())).isEmpty(); - assertThat(MoreTypes.referencedTypes(fieldIndex.get("f12").asType())) - .containsExactly(setElement, stringElement); + assertThat(referencedTypes(fieldIndex, "f8")).containsExactly(stringElement); + assertThat(referencedTypes(fieldIndex, "f9")).containsExactly(stringElement); + assertThat(referencedTypes(fieldIndex, "f10")).isEmpty(); + assertThat(referencedTypes(fieldIndex, "f11")).isEmpty(); + assertThat(referencedTypes(fieldIndex, "f12")).containsExactly(setElement, stringElement); + } + + private static ImmutableSet referencedTypes( + ImmutableMap fieldIndex, String fieldName) { + VariableElement field = fieldIndex.get(fieldName); + requireNonNull(field, fieldName); + return MoreTypes.referencedTypes(field.asType()); } @SuppressWarnings("unused") // types used in compiler tests @@ -281,20 +284,23 @@ private static final class ReferencedTypesTestData { } private static class Parent {} + private static class ChildA extends Parent {} + private static class ChildB extends Parent {} + private static class GenericChild extends Parent {} + private interface InterfaceType {} @Test public void asElement_throws() { - TypeMirror javaDotLang = - compilationRule.getElements().getPackageElement("java.lang").asType(); + TypeMirror javaDotLang = compilationRule.getElements().getPackageElement("java.lang").asType(); try { MoreTypes.asElement(javaDotLang); fail(); - } catch (IllegalArgumentException expected) {} - + } catch (IllegalArgumentException expected) { + } } @Test @@ -302,8 +308,9 @@ public void asElement() { Elements elements = compilationRule.getElements(); TypeElement stringElement = elements.getTypeElement("java.lang.String"); assertThat(MoreTypes.asElement(stringElement.asType())).isEqualTo(stringElement); - TypeParameterElement setParameterElement = Iterables.getOnlyElement( - compilationRule.getElements().getTypeElement("java.util.Set").getTypeParameters()); + TypeParameterElement setParameterElement = + Iterables.getOnlyElement( + compilationRule.getElements().getTypeElement("java.util.Set").getTypeParameters()); assertThat(MoreTypes.asElement(setParameterElement.asType())).isEqualTo(setParameterElement); // we don't test error types because those are very hard to get predictably } @@ -321,8 +328,7 @@ public void testNonObjectSuperclass() { TypeElement genericChild = elements.getTypeElement(GenericChild.class.getCanonicalName()); TypeMirror genericChildOfNumber = types.getDeclaredType(genericChild, numberType); TypeMirror genericChildOfInteger = types.getDeclaredType(genericChild, integerType); - TypeMirror objectType = - elements.getTypeElement(Object.class.getCanonicalName()).asType(); + TypeMirror objectType = elements.getTypeElement(Object.class.getCanonicalName()).asType(); TypeMirror interfaceType = elements.getTypeElement(InterfaceType.class.getCanonicalName()).asType(); @@ -344,18 +350,20 @@ public void testNonObjectSuperclass() { Optional parentOfGenericChildOfInteger = MoreTypes.nonObjectSuperclass(types, elements, (DeclaredType) genericChildOfInteger); - EquivalenceTester tester = EquivalenceTester.of(MoreTypes.equivalence()) - .addEquivalenceGroup(parentOfChildA.get(), - types.getDeclaredType(parent, numberType), - parentOfGenericChildOfNumber.get()) - .addEquivalenceGroup(parentOfChildB.get(), types.getDeclaredType(parent, stringType)) - .addEquivalenceGroup(parentOfGenericChild.get(), parent.asType()) - .addEquivalenceGroup(parentOfGenericChildOfInteger.get(), - types.getDeclaredType(parent, integerType)); + EquivalenceTester tester = + EquivalenceTester.of(MoreTypes.equivalence()) + .addEquivalenceGroup( + parentOfChildA.get(), + types.getDeclaredType(parent, numberType), + parentOfGenericChildOfNumber.get()) + .addEquivalenceGroup(parentOfChildB.get(), types.getDeclaredType(parent, stringType)) + .addEquivalenceGroup(parentOfGenericChild.get(), parent.asType()) + .addEquivalenceGroup( + parentOfGenericChildOfInteger.get(), types.getDeclaredType(parent, integerType)); tester.test(); } - + @Test public void testAsMemberOf_variableElement() { Types types = compilationRule.getTypes(); @@ -365,11 +373,13 @@ public void testAsMemberOf_variableElement() { TypeMirror integerType = elements.getTypeElement(Integer.class.getCanonicalName()).asType(); TypeElement paramsElement = elements.getTypeElement(Params.class.getCanonicalName()); - VariableElement tParam = Iterables.getOnlyElement(Iterables.getOnlyElement( - ElementFilter.methodsIn(paramsElement.getEnclosedElements())).getParameters()); + VariableElement tParam = + Iterables.getOnlyElement( + Iterables.getOnlyElement(ElementFilter.methodsIn(paramsElement.getEnclosedElements())) + .getParameters()); VariableElement tField = - Iterables.getOnlyElement(ElementFilter.fieldsIn(paramsElement.getEnclosedElements())); - + Iterables.getOnlyElement(ElementFilter.fieldsIn(paramsElement.getEnclosedElements())); + DeclaredType numberParams = (DeclaredType) elements.getTypeElement(NumberParams.class.getCanonicalName()).asType(); DeclaredType stringParams = @@ -377,7 +387,7 @@ public void testAsMemberOf_variableElement() { TypeElement genericParams = elements.getTypeElement(GenericParams.class.getCanonicalName()); DeclaredType genericParamsOfNumber = types.getDeclaredType(genericParams, numberType); DeclaredType genericParamsOfInteger = types.getDeclaredType(genericParams, integerType); - + TypeMirror fieldOfNumberParams = MoreTypes.asMemberOf(types, numberParams, tField); TypeMirror paramOfNumberParams = MoreTypes.asMemberOf(types, numberParams, tParam); TypeMirror fieldOfStringParams = MoreTypes.asMemberOf(types, stringParams, tField); @@ -389,62 +399,76 @@ public void testAsMemberOf_variableElement() { TypeMirror paramOfGenericOfInteger = MoreTypes.asMemberOf(types, genericParamsOfInteger, tParam); - EquivalenceTester tester = EquivalenceTester.of(MoreTypes.equivalence()) - .addEquivalenceGroup(fieldOfNumberParams, paramOfNumberParams, fieldOfGenericOfNumber, - paramOfGenericOfNumber, numberType) - .addEquivalenceGroup(fieldOfStringParams, paramOfStringParams, stringType) - .addEquivalenceGroup(fieldOfGenericOfInteger, paramOfGenericOfInteger, integerType); + EquivalenceTester tester = + EquivalenceTester.of(MoreTypes.equivalence()) + .addEquivalenceGroup( + fieldOfNumberParams, + paramOfNumberParams, + fieldOfGenericOfNumber, + paramOfGenericOfNumber, + numberType) + .addEquivalenceGroup(fieldOfStringParams, paramOfStringParams, stringType) + .addEquivalenceGroup(fieldOfGenericOfInteger, paramOfGenericOfInteger, integerType); tester.test(); } - - private static class Params { - @SuppressWarnings("unused") T t; - @SuppressWarnings("unused") void add(T t) {} - } - private static class NumberParams extends Params {} - private static class StringParams extends Params {} - private static class GenericParams extends Params {} - - private static final ErrorType FAKE_ERROR_TYPE = new ErrorType() { - @Override - public TypeKind getKind() { - return TypeKind.ERROR; - } - @Override - public R accept(TypeVisitor v, P p) { - return v.visitError(this, p); - } - - @Override - public List getTypeArguments() { - return ImmutableList.of(); - } - - @Override - public TypeMirror getEnclosingType() { - return null; - } + private static class Params { + @SuppressWarnings("unused") + T t; - @Override - public Element asElement() { - return null; - } + @SuppressWarnings("unused") + void add(T t) {} + } - // JDK8 Compatibility: + private static class NumberParams extends Params {} - public A[] getAnnotationsByType(Class annotationType) { - return null; - } + private static class StringParams extends Params {} - public A getAnnotation(Class annotationType) { - return null; - } + private static class GenericParams extends Params {} - public List getAnnotationMirrors() { - return null; - } - }; + private static final ErrorType FAKE_ERROR_TYPE = + new ErrorType() { + @Override + public TypeKind getKind() { + return TypeKind.ERROR; + } + + @Override + public R accept(TypeVisitor v, P p) { + return v.visitError(this, p); + } + + @Override + public ImmutableList getTypeArguments() { + return ImmutableList.of(); + } + + @Override + public @Nullable TypeMirror getEnclosingType() { + return null; + } + + @Override + public @Nullable Element asElement() { + return null; + } + + @Override + public A @Nullable [] getAnnotationsByType(Class annotationType) { + return null; + } + + @Override + public @Nullable A getAnnotation(Class annotationType) { + return null; + } + + @Override + @SuppressWarnings("MutableMethodReturnType") + public List getAnnotationMirrors() { + return ImmutableList.of(); + } + }; @Test public void testIsConversionFromObjectUnchecked_yes() { 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 afb797606a..8d77fc7631 100644 --- a/common/src/test/java/com/google/auto/common/OverridesTest.java +++ b/common/src/test/java/com/google/auto/common/OverridesTest.java @@ -17,6 +17,7 @@ import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; import static javax.lang.model.util.ElementFilter.methodsIn; import com.google.common.base.Converter; @@ -57,6 +58,7 @@ import javax.tools.StandardJavaFileManager; import javax.tools.StandardLocation; import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -77,8 +79,8 @@ @RunWith(Parameterized.class) public class OverridesTest { @Parameterized.Parameters(name = "{0}") - public static ImmutableList data() { - return ImmutableList.of(CompilerType.JAVAC, CompilerType.ECJ); + public static CompilerType[] data() { + return CompilerType.values(); } @Rule public CompilationRule compilation = new CompilationRule(); @@ -103,6 +105,7 @@ void initUtils(OverridesTest test) { abstract void initUtils(OverridesTest test); } + private final CompilerType compilerType; private Types typeUtils; @@ -126,13 +129,20 @@ public void initializeTestElements() { static class TypesForInheritance { interface One { void m(); + void m(String x); + void n(); + + Number number(); } interface Two { void m(); + void m(int x); + + Integer number(); } static class Parent { @@ -142,28 +152,70 @@ public void m() {} static class ChildOfParent extends Parent {} static class ChildOfOne implements One { - @Override public void m() {} - @Override public void m(String x) {} - @Override public void n() {} + @Override + public void m() {} + + @Override + public void m(String x) {} + + @Override + public void n() {} + + @Override + public Number number() { + return 0; + } } static class ChildOfOneAndTwo implements One, Two { - @Override public void m() {} - @Override public void m(String x) {} - @Override public void m(int x) {} - @Override public void n() {} + @Override + public void m() {} + + @Override + public void m(String x) {} + + @Override + public void m(int x) {} + + @Override + public void n() {} + + @Override + public Integer number() { + return 0; + } } static class ChildOfParentAndOne extends Parent implements One { - @Override public void m() {} - @Override public void m(String x) {} - @Override public void n() {} + @Override + public void m() {} + + @Override + public void m(String x) {} + + @Override + public void n() {} + + @Override + public Number number() { + return 0; + } } static class ChildOfParentAndOneAndTwo extends Parent implements One, Two { - @Override public void m(String x) {} - @Override public void m(int x) {} - @Override public void n() {} + @Override + public void m(String x) {} + + @Override + public void m(int x) {} + + @Override + public void n() {} + + @Override + public Integer number() { + return 0; + } } abstract static class AbstractChildOfOne implements One {} @@ -171,6 +223,8 @@ abstract static class AbstractChildOfOne implements One {} abstract static class AbstractChildOfOneAndTwo implements One, Two {} abstract static class AbstractChildOfParentAndOneAndTwo extends Parent implements One, Two {} + + interface ExtendingOneAndTwo extends One, Two {} } static class MoreTypesForInheritance { @@ -194,14 +248,20 @@ interface HasContributionType { abstract static class BindingDeclaration implements HasKey { abstract Optional bindingElement(); + abstract Optional contributingModule(); } - abstract static class MultibindingDeclaration - extends BindingDeclaration implements HasBindingType, HasContributionType { - @Override public abstract Key key(); - @Override public abstract ContributionType contributionType(); - @Override public abstract BindingType bindingType(); + abstract static class MultibindingDeclaration extends BindingDeclaration + implements HasBindingType, HasContributionType { + @Override + public abstract Key key(); + + @Override + public abstract ContributionType contributionType(); + + @Override + public abstract BindingType bindingType(); } } @@ -221,16 +281,26 @@ static class Child extends PrivateParent {} } static class TypesForGenerics { - interface XCollection { + interface GCollection { boolean add(E x); } - interface XList extends XCollection { - @Override public boolean add(E x); + interface GList extends GCollection { + @Override + boolean add(E x); } - static class StringList implements XList { - @Override public boolean add(String x) { + static class StringList implements GList { + @Override + public boolean add(String x) { + return false; + } + } + + @SuppressWarnings("rawtypes") + static class RawList implements GList { + @Override + public boolean add(Object x) { return false; } } @@ -243,7 +313,8 @@ void frob(List x) {} } static class RawChildOfRaw extends RawParent { - @Override void frob(List x) {} + @Override + void frob(List x) {} } static class NonRawParent { @@ -251,7 +322,8 @@ void frob(List x) {} } static class RawChildOfNonRaw extends NonRawParent { - @Override void frob(List x) {} + @Override + void frob(List x) {} } } @@ -291,8 +363,9 @@ public void overridesRaw() { // since the two Es are not the same. @Test public void overridesDiamond() { - checkOverridesInSet(ImmutableSet.>of( - Collection.class, List.class, AbstractCollection.class, AbstractList.class)); + checkOverridesInSet( + ImmutableSet.>of( + Collection.class, List.class, AbstractCollection.class, AbstractList.class)); } private void checkOverridesInContainedClasses(Class container) { @@ -324,10 +397,13 @@ private void checkOverridesInSet(ImmutableSet> testClasses) { expect .withMessage( "%s.%s overrides %s.%s in %s: javac says %s, we say %s", - overrider.getEnclosingElement(), overrider, - overridden.getEnclosingElement(), overridden, + overrider.getEnclosingElement(), + overrider, + overridden.getEnclosingElement(), + overridden, in, - javacSays, weSay) + javacSays, + weSay) .fail(); } } @@ -355,7 +431,7 @@ private ExecutableElement getMethod(TypeElement in, String name, TypeKind... par } } assertThat(found).isNotNull(); - return found; + return requireNonNull(found); } // These skeletal parallels to the real collection classes ensure that the test is independent @@ -375,8 +451,8 @@ public boolean add(E e) { } } - private abstract static class XAbstractList - extends XAbstractCollection implements XList { + private abstract static class XAbstractList extends XAbstractCollection + implements XList { @Override public boolean add(E e) { return true; @@ -440,7 +516,7 @@ private abstract static class StringToRangeConverter> extends Converter> { @Override protected String doBackward(Range b) { - return null; + return ""; } } @@ -470,9 +546,8 @@ public void methodFromSuperclasses() { explicitOverrides.methodFromSuperclasses(xAbstractStringList, add); assertThat(addInAbstractStringList).isNull(); - ExecutableElement addInStringList = - explicitOverrides.methodFromSuperclasses(xStringList, add); - assertThat(addInStringList.getEnclosingElement()).isEqualTo(xAbstractList); + ExecutableElement addInStringList = explicitOverrides.methodFromSuperclasses(xStringList, add); + assertThat(requireNonNull(addInStringList).getEnclosingElement()).isEqualTo(xAbstractList); } @Test @@ -487,20 +562,21 @@ public void methodFromSuperinterfaces() { ExecutableElement addInAbstractStringList = explicitOverrides.methodFromSuperinterfaces(xAbstractStringList, add); - assertThat(addInAbstractStringList.getEnclosingElement()).isEqualTo(xCollection); + assertThat(requireNonNull(addInAbstractStringList).getEnclosingElement()) + .isEqualTo(xCollection); ExecutableElement addInNumberList = explicitOverrides.methodFromSuperinterfaces(xNumberList, add); - assertThat(addInNumberList.getEnclosingElement()).isEqualTo(xAbstractList); + assertThat(requireNonNull(addInNumberList).getEnclosingElement()).isEqualTo(xAbstractList); - ExecutableElement addInList = - explicitOverrides.methodFromSuperinterfaces(xList, add); - assertThat(addInList.getEnclosingElement()).isEqualTo(xCollection); + ExecutableElement addInList = explicitOverrides.methodFromSuperinterfaces(xList, add); + assertThat(requireNonNull(addInList).getEnclosingElement()).isEqualTo(xCollection); } - private void assertTypeListsEqual(List actual, List expected) { - assertThat(actual.size()).isEqualTo(expected.size()); - for (int i = 0; i < actual.size(); i++) { + private void assertTypeListsEqual(@Nullable List actual, List expected) { + requireNonNull(actual); + assertThat(actual).hasSize(expected.size()); + for (int i = 0; i < actual.size(); i++) { assertThat(typeUtils.isSameType(actual.get(i), expected.get(i))).isTrue(); } } @@ -552,10 +628,11 @@ private void evaluate(File dummySourceFile) throws Throwable { // it hard for ecj to find the boot class path. Elsewhere it is unnecessary but harmless. File rtJar = new File(StandardSystemProperty.JAVA_HOME.value() + "/lib/rt.jar"); if (rtJar.exists()) { - List bootClassPath = ImmutableList.builder() - .add(rtJar) - .addAll(fileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH)) - .build(); + List bootClassPath = + ImmutableList.builder() + .add(rtJar) + .addAll(fileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH)) + .build(); fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, bootClassPath); } Iterable sources = fileManager.getJavaFileObjects(dummySourceFile); @@ -583,8 +660,7 @@ public SourceVersion getSupportedSourceVersion() { } @Override - public boolean process( - Set annotations, RoundEnvironment roundEnv) { + public boolean process(Set annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver()) { ecjCompilation.elements = processingEnv.getElementUtils(); ecjCompilation.types = processingEnv.getTypeUtils(); @@ -643,24 +719,24 @@ private static String erasedToString(TypeMirror type) { private static final TypeVisitor ERASED_STRING_TYPE_VISITOR = new SimpleTypeVisitor6() { - @Override - protected String defaultAction(TypeMirror e, Void p) { - return e.toString(); - } + @Override + protected String defaultAction(TypeMirror e, Void p) { + return e.toString(); + } - @Override - public String visitArray(ArrayType t, Void p) { - return visit(t.getComponentType()) + "[]"; - } + @Override + public String visitArray(ArrayType t, Void p) { + return visit(t.getComponentType()) + "[]"; + } - @Override - public String visitDeclared(DeclaredType t, Void p) { - return MoreElements.asType(t.asElement()).getQualifiedName().toString(); - } + @Override + public String visitDeclared(DeclaredType t, Void p) { + return MoreElements.asType(t.asElement()).getQualifiedName().toString(); + } - @Override - public String visitTypeVariable(TypeVariable t, Void p) { - return visit(t.getUpperBound()); - } - }; + @Override + public String visitTypeVariable(TypeVariable t, Void p) { + return visit(t.getUpperBound()); + } + }; } diff --git a/common/src/test/java/com/google/auto/common/SimpleAnnotationMirrorTest.java b/common/src/test/java/com/google/auto/common/SimpleAnnotationMirrorTest.java index d73e1b6c59..0bad83db65 100644 --- a/common/src/test/java/com/google/auto/common/SimpleAnnotationMirrorTest.java +++ b/common/src/test/java/com/google/auto/common/SimpleAnnotationMirrorTest.java @@ -46,6 +46,7 @@ public class SimpleAnnotationMirrorTest { @interface MultipleValues { int value1(); + int value2(); } diff --git a/common/src/test/java/com/google/auto/common/SimpleTypeAnnotationValueTest.java b/common/src/test/java/com/google/auto/common/SimpleTypeAnnotationValueTest.java index 4fc61b51d6..ea85365bae 100644 --- a/common/src/test/java/com/google/auto/common/SimpleTypeAnnotationValueTest.java +++ b/common/src/test/java/com/google/auto/common/SimpleTypeAnnotationValueTest.java @@ -28,6 +28,7 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleAnnotationValueVisitor8; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -70,18 +71,21 @@ public void declaredType() { @Test public void visitorMethod() { - SimpleTypeAnnotationValue.of(objectType).accept(new SimpleAnnotationValueVisitor8(){ - @Override - public Void visitType(TypeMirror typeMirror, Void aVoid) { - // do nothing, expected case - return null; - } + SimpleTypeAnnotationValue.of(objectType) + .accept( + new SimpleAnnotationValueVisitor8<@Nullable Void, @Nullable Void>() { + @Override + public @Nullable Void visitType(TypeMirror typeMirror, @Nullable Void aVoid) { + // do nothing, expected case + return null; + } - @Override - protected Void defaultAction(Object o, Void aVoid) { - throw new AssertionError(); - } - }, null); + @Override + protected @Nullable Void defaultAction(Object o, @Nullable Void aVoid) { + throw new AssertionError(); + } + }, + null); } @Test diff --git a/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java b/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java index 15e54fff52..c9bcf77847 100644 --- a/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java +++ b/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java @@ -35,231 +35,263 @@ public class SuperficialValidationTest { @Test public void missingReturnType() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "abstract class TestClass {", - " abstract MissingType blah();", - "}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "abstract class TestClass {", + " abstract MissingType blah();", + "}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); + } + }) .failsToCompile(); } @Test public void missingGenericReturnType() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "abstract class TestClass {", - " abstract MissingType blah();", - "}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "abstract class TestClass {", + " abstract MissingType blah();", + "}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); + } + }) .failsToCompile(); } @Test public void missingReturnTypeTypeParameter() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "import java.util.Map;", - "import java.util.Set;", - "", - "abstract class TestClass {", - " abstract Map, MissingType> blah();", - "}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "import java.util.Map;", + "import java.util.Set;", + "", + "abstract class TestClass {", + " abstract Map, MissingType> blah();", + "}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); + } + }) .failsToCompile(); } @Test public void missingTypeParameter() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "class TestClass {}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", // + "package test;", + "", + "class TestClass {}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); + } + }) .failsToCompile(); } @Test public void missingParameterType() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "abstract class TestClass {", - " abstract void foo(MissingType x);", - "}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "abstract class TestClass {", + " abstract void foo(MissingType x);", + "}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); + } + }) .failsToCompile(); } @Test public void missingAnnotation() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "@MissingAnnotation", - "class TestClass {}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", // + "package test;", + "", + "@MissingAnnotation", + "class TestClass {}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); + } + }) .failsToCompile(); } @Test public void handlesRecursiveTypeParams() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "class TestClass> {}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", // + "package test;", + "", + "class TestClass> {}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue(); + } + }) .compilesWithoutError(); } @Test public void handlesRecursiveType() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "abstract class TestClass {", - " abstract TestClass foo(TestClass x);", - "}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "abstract class TestClass {", + " abstract TestClass foo(TestClass x);", + "}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue(); + } + }) .compilesWithoutError(); } @Test public void missingWildcardBound() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "import java.util.Set;", - "", - "class TestClass {", - " Set extendsTest() {", - " return null;", - " }", - "", - " Set superTest() {", - " return null;", - " }", - "}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "import java.util.Set;", + "", + "class TestClass {", + " Set extendsTest() {", + " return null;", + " }", + "", + " Set superTest() {", + " return null;", + " }", + "}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); + } + }) .failsToCompile(); } @Test public void missingIntersection() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( - "test.TestClass", - "package test;", - "", - "class TestClass {}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "class TestClass {}"); assertAbout(javaSource()) .that(javaFileObject) - .processedWith(new AssertingProcessor() { - @Override void runAssertions() { - TypeElement testClassElement = - processingEnv.getElementUtils().getTypeElement("test.TestClass"); - assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); - } - }) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions() { + TypeElement testClassElement = + processingEnv.getElementUtils().getTypeElement("test.TestClass"); + assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse(); + } + }) .failsToCompile(); } @Test public void invalidAnnotationValue() { - JavaFileObject javaFileObject = JavaFileObjects.forSourceLines("test.Outer", - "package test;", - "", - "final class Outer {", - " @interface TestAnnotation {", - " Class[] classes();", - " }", - "", - " @TestAnnotation(classes = Foo)", - " static class TestClass {}", - "}"); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.Outer", + "package test;", + "", + "final class Outer {", + " @interface TestAnnotation {", + " Class[] classes();", + " }", + "", + " @TestAnnotation(classes = Foo)", + " static class TestClass {}", + "}"); assertAbout(javaSource()) .that(javaFileObject) .processedWith( diff --git a/common/src/test/java/com/google/auto/common/VisibilityTest.java b/common/src/test/java/com/google/auto/common/VisibilityTest.java index 6a80b7af70..fc5e630b3d 100644 --- a/common/src/test/java/com/google/auto/common/VisibilityTest.java +++ b/common/src/test/java/com/google/auto/common/VisibilityTest.java @@ -39,9 +39,10 @@ public class VisibilityTest { public void packageVisibility() { assertThat(Visibility.ofElement(compilation.getElements().getPackageElement("java.lang"))) .isEqualTo(PUBLIC); - assertThat(Visibility.ofElement( - compilation.getElements().getPackageElement("com.google.auto.common"))) - .isEqualTo(PUBLIC); + assertThat( + Visibility.ofElement( + compilation.getElements().getPackageElement("com.google.auto.common"))) + .isEqualTo(PUBLIC); } @Test @@ -61,32 +62,44 @@ public void moduleVisibility() throws IllegalAccessException, InvocationTargetEx @SuppressWarnings("unused") public static class PublicClass { public static class NestedPublicClass {} + protected static class NestedProtectedClass {} + static class NestedDefaultClass {} + private static class NestedPrivateClass {} } @SuppressWarnings("unused") protected static class ProtectedClass { public static class NestedPublicClass {} + protected static class NestedProtectedClass {} + static class NestedDefaultClass {} + private static class NestedPrivateClass {} } @SuppressWarnings("unused") static class DefaultClass { public static class NestedPublicClass {} + protected static class NestedProtectedClass {} + static class NestedDefaultClass {} + private static class NestedPrivateClass {} } @SuppressWarnings("unused") private static class PrivateClass { public static class NestedPublicClass {} + protected static class NestedProtectedClass {} + static class NestedDefaultClass {} + private static class NestedPrivateClass {} } @@ -94,21 +107,25 @@ private static class NestedPrivateClass {} public void classVisibility() { assertThat(Visibility.ofElement(compilation.getElements().getTypeElement("java.util.Map"))) .isEqualTo(PUBLIC); - assertThat(Visibility.ofElement( - compilation.getElements().getTypeElement("java.util.Map.Entry"))) - .isEqualTo(PUBLIC); - assertThat(Visibility.ofElement( - compilation.getElements().getTypeElement(PublicClass.class.getCanonicalName()))) - .isEqualTo(PUBLIC); - assertThat(Visibility.ofElement( - compilation.getElements().getTypeElement(ProtectedClass.class.getCanonicalName()))) - .isEqualTo(PROTECTED); - assertThat(Visibility.ofElement( - compilation.getElements().getTypeElement(DefaultClass.class.getCanonicalName()))) - .isEqualTo(DEFAULT); - assertThat(Visibility.ofElement( - compilation.getElements().getTypeElement(PrivateClass.class.getCanonicalName()))) - .isEqualTo(PRIVATE); + assertThat( + Visibility.ofElement(compilation.getElements().getTypeElement("java.util.Map.Entry"))) + .isEqualTo(PUBLIC); + assertThat( + Visibility.ofElement( + compilation.getElements().getTypeElement(PublicClass.class.getCanonicalName()))) + .isEqualTo(PUBLIC); + assertThat( + Visibility.ofElement( + compilation.getElements().getTypeElement(ProtectedClass.class.getCanonicalName()))) + .isEqualTo(PROTECTED); + assertThat( + Visibility.ofElement( + compilation.getElements().getTypeElement(DefaultClass.class.getCanonicalName()))) + .isEqualTo(DEFAULT); + assertThat( + Visibility.ofElement( + compilation.getElements().getTypeElement(PrivateClass.class.getCanonicalName()))) + .isEqualTo(PRIVATE); } @Test @@ -118,14 +135,11 @@ public void effectiveClassVisibility() { assertThat(effectiveVisiblityOfClass(DefaultClass.class)).isEqualTo(DEFAULT); assertThat(effectiveVisiblityOfClass(PrivateClass.class)).isEqualTo(PRIVATE); - assertThat(effectiveVisiblityOfClass(PublicClass.NestedPublicClass.class)) - .isEqualTo(PUBLIC); + assertThat(effectiveVisiblityOfClass(PublicClass.NestedPublicClass.class)).isEqualTo(PUBLIC); assertThat(effectiveVisiblityOfClass(PublicClass.NestedProtectedClass.class)) .isEqualTo(PROTECTED); - assertThat(effectiveVisiblityOfClass(PublicClass.NestedDefaultClass.class)) - .isEqualTo(DEFAULT); - assertThat(effectiveVisiblityOfClass(PublicClass.NestedPrivateClass.class)) - .isEqualTo(PRIVATE); + assertThat(effectiveVisiblityOfClass(PublicClass.NestedDefaultClass.class)).isEqualTo(DEFAULT); + assertThat(effectiveVisiblityOfClass(PublicClass.NestedPrivateClass.class)).isEqualTo(PRIVATE); assertThat(effectiveVisiblityOfClass(ProtectedClass.NestedPublicClass.class)) .isEqualTo(PROTECTED); @@ -136,23 +150,17 @@ public void effectiveClassVisibility() { assertThat(effectiveVisiblityOfClass(ProtectedClass.NestedPrivateClass.class)) .isEqualTo(PRIVATE); - assertThat(effectiveVisiblityOfClass(DefaultClass.NestedPublicClass.class)) - .isEqualTo(DEFAULT); + assertThat(effectiveVisiblityOfClass(DefaultClass.NestedPublicClass.class)).isEqualTo(DEFAULT); assertThat(effectiveVisiblityOfClass(DefaultClass.NestedProtectedClass.class)) .isEqualTo(DEFAULT); - assertThat(effectiveVisiblityOfClass(DefaultClass.NestedDefaultClass.class)) - .isEqualTo(DEFAULT); - assertThat(effectiveVisiblityOfClass(DefaultClass.NestedPrivateClass.class)) - .isEqualTo(PRIVATE); + assertThat(effectiveVisiblityOfClass(DefaultClass.NestedDefaultClass.class)).isEqualTo(DEFAULT); + assertThat(effectiveVisiblityOfClass(DefaultClass.NestedPrivateClass.class)).isEqualTo(PRIVATE); - assertThat(effectiveVisiblityOfClass(PrivateClass.NestedPublicClass.class)) - .isEqualTo(PRIVATE); + assertThat(effectiveVisiblityOfClass(PrivateClass.NestedPublicClass.class)).isEqualTo(PRIVATE); assertThat(effectiveVisiblityOfClass(PrivateClass.NestedProtectedClass.class)) .isEqualTo(PRIVATE); - assertThat(effectiveVisiblityOfClass(PrivateClass.NestedDefaultClass.class)) - .isEqualTo(PRIVATE); - assertThat(effectiveVisiblityOfClass(PrivateClass.NestedPrivateClass.class)) - .isEqualTo(PRIVATE); + assertThat(effectiveVisiblityOfClass(PrivateClass.NestedDefaultClass.class)).isEqualTo(PRIVATE); + assertThat(effectiveVisiblityOfClass(PrivateClass.NestedPrivateClass.class)).isEqualTo(PRIVATE); } private Visibility effectiveVisiblityOfClass(Class clazz) { diff --git a/factory/README.md b/factory/README.md index f5874fe9bd..5eccd90781 100644 --- a/factory/README.md +++ b/factory/README.md @@ -10,7 +10,9 @@ AutoWhat‽ AutoFactory generates factories that can be used on their own or with [JSR-330](http://jcp.org/en/jsr/detail?id=330)-compatible [dependency injectors](http://en.wikipedia.org/wiki/Dependency_injection) from a simple annotation. Any combination of parameters can either be passed through factory methods or provided to the factory at construction time. They can implement interfaces or extend abstract classes. They're what you would have written, but without the bugs. -Save time. Save code. Save sanity. +[Dagger](https://dagger.dev/) users: Dagger's own +[assisted injection](https://dagger.dev/dev-guide/assisted-injection.html) is +now usually preferred to AutoFactory. Example ------- @@ -58,6 +60,20 @@ final class SomeClassFactory { > framework-specific annotations from Guice, Spring, etc are not > supported (though these all support JSR-330) +Mocking +------- + +By default, the factory class generated by AutoFactory is final, and thus cannot +be mocked. The generated factory class can be made mockable by setting +`allowSubclasses = true`, as follows: + +```java +@AutoFactory(allowSubclasses = true) +final class SomeClass { + // … +} +``` + Download -------- diff --git a/factory/pom.xml b/factory/pom.xml index 1003bb23dc..6e203abaf1 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"> 4.0.0 - - org.sonatype.oss - oss-parent - 7 - - com.google.auto.factory auto-factory HEAD-SNAPSHOT @@ -36,11 +30,11 @@ UTF-8 - 1.0 - 1.8 + 1.0.1 + 1.10.1 1.8 - 30.1.1-jre - 1.1.2 + 31.1-jre + 1.1.3 @@ -67,11 +61,24 @@ http://www.google.com + + + sonatype-nexus-snapshots + Sonatype Nexus Snapshots + https://oss.sonatype.org/content/repositories/snapshots/ + + + sonatype-nexus-staging + Nexus Release Repository + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + com.google.auto auto-common - 1.0 + 1.2.1 com.google.auto.value @@ -141,7 +148,7 @@ maven-compiler-plugin - 3.8.1 + 3.10.1 ${java.version} ${java.version} @@ -170,7 +177,7 @@ org.codehaus.plexus plexus-java - 1.0.7 + 1.1.1 @@ -184,11 +191,11 @@ maven-jar-plugin - 3.2.0 + 3.3.0 maven-invoker-plugin - 3.2.2 + 3.3.0 true ${project.build.directory}/it diff --git a/factory/src/it/functional/pom.xml b/factory/src/it/functional/pom.xml index a7a1853e6c..8a87a10f1e 100644 --- a/factory/src/it/functional/pom.xml +++ b/factory/src/it/functional/pom.xml @@ -45,22 +45,22 @@ com.google.guava guava - 29.0-jre + 31.1-jre com.google.inject guice - 4.1.0 + 5.1.0 com.google.dagger dagger - 2.13 + 2.42 com.google.dagger dagger-compiler - 2.13 + 2.42 true @@ -72,7 +72,7 @@ com.google.truth truth - 0.44 + 1.1.3 test diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java index 4c4a38af6a..f32e9f8d52 100644 --- a/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java +++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java @@ -16,44 +16,44 @@ package com.google.auto.factory; import com.google.auto.factory.otherpackage.OtherPackage; +import dagger.Binds; import dagger.Module; import dagger.Provides; @Module -final class DaggerModule { - @Provides Dependency provideDependency(DependencyImpl impl) { - return impl; - } +abstract class DaggerModule { + private DaggerModule() {} // no instances - @Provides + @Binds + abstract Dependency provideDependency(DependencyImpl impl); + + @Binds @Qualifier - Dependency provideQualifiedDependency(QualifiedDependencyImpl impl) { - return impl; - } + abstract Dependency provideQualifiedDependency(QualifiedDependencyImpl impl); @Provides - int providePrimitive() { + static int providePrimitive() { return 1; } @Provides @Qualifier - int provideQualifiedPrimitive() { + static int provideQualifiedPrimitive() { return 2; } @Provides - Number provideNumber() { + static Number provideNumber() { return 3; } @Provides - ReferencePackage provideReferencePackage(ReferencePackageFactory factory) { + static ReferencePackage provideReferencePackage(ReferencePackageFactory factory) { return factory.create(17); } @Provides - OtherPackage provideOtherPackage() { + static OtherPackage provideOtherPackage() { return new OtherPackage(null, 23); } } diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/DependencyImpl.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/DependencyImpl.java index 4c019ea61e..d94a3cf619 100644 --- a/factory/src/it/functional/src/main/java/com/google/auto/factory/DependencyImpl.java +++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/DependencyImpl.java @@ -18,5 +18,6 @@ import javax.inject.Inject; public class DependencyImpl implements Dependency { - @Inject DependencyImpl() {} + @Inject + DependencyImpl() {} } diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/GenericFoo.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/GenericFoo.java index f7c13b416e..31954a1938 100644 --- a/factory/src/it/functional/src/main/java/com/google/auto/factory/GenericFoo.java +++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/GenericFoo.java @@ -28,10 +28,7 @@ public class GenericFoo, C, E extends Enum> { private final E depE; GenericFoo( - @Provided Provider depA, - B depB, - D depD, - E depE) { + @Provided Provider depA, B depB, D depD, E depE) { this.depA = depA.get(); this.depB = depB; this.depDIntAccessor = depD; diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/GuiceModule.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/GuiceModule.java index 45d4d26e07..77346655d0 100644 --- a/factory/src/it/functional/src/main/java/com/google/auto/factory/GuiceModule.java +++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/GuiceModule.java @@ -18,7 +18,8 @@ import com.google.inject.AbstractModule; public class GuiceModule extends AbstractModule { - @Override protected void configure() { + @Override + protected void configure() { bind(Dependency.class).to(DependencyImpl.class); bind(Dependency.class).annotatedWith(Qualifier.class).to(QualifiedDependencyImpl.class); bind(Integer.class).toInstance(1); diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/ReferencePackage.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/ReferencePackage.java index 22aff65118..4f67c3b3bf 100755 --- a/factory/src/it/functional/src/main/java/com/google/auto/factory/ReferencePackage.java +++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/ReferencePackage.java @@ -25,9 +25,7 @@ public class ReferencePackage { private final int random; @Inject - public ReferencePackage( - @Provided OtherPackageFactory otherPackageFactory, - int random) { + ReferencePackage(@Provided OtherPackageFactory otherPackageFactory, int random) { this.otherPackageFactory = otherPackageFactory; this.random = random; } diff --git a/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java b/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java index e141211778..3caa5a540e 100644 --- a/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java +++ b/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java @@ -19,7 +19,8 @@ public class DependencyInjectionIntegrationTest { private static final IntAndStringAccessor INT_AND_STRING_ACCESSOR = new IntAndStringAccessor() {}; - @Test public void daggerInjectedFactory() { + @Test + public void daggerInjectedFactory() { FooFactory fooFactory = DaggerFactoryComponent.create().factory(); Foo one = fooFactory.create("A"); Foo two = fooFactory.create("B"); @@ -46,8 +47,9 @@ public void daggerInjectedGenericFactory() { genericFooFactory.create(ImmutableList.of(3L), INT_AND_STRING_ACCESSOR, DepE.VALUE_1); ArrayList intAndStringAccessorArrayList = new ArrayList<>(); intAndStringAccessorArrayList.add(4.0); - GenericFoo, Long, DepE> four = genericFooFactory.create( - intAndStringAccessorArrayList, INT_AND_STRING_ACCESSOR, DepE.VALUE_2); + GenericFoo, Long, DepE> four = + genericFooFactory.create( + intAndStringAccessorArrayList, INT_AND_STRING_ACCESSOR, DepE.VALUE_2); assertThat(three.getDepA()).isEqualTo(3); ImmutableList unusedLongList = three.getDepB(); assertThat(three.getDepB()).containsExactly(3L); @@ -75,7 +77,8 @@ public void daggerInjectedPackageSpanningFactory() { assertThat(otherPackage.random()).isEqualTo(5); } - @Test public void guiceInjectedFactory() { + @Test + public void guiceInjectedFactory() { FooFactory fooFactory = Guice.createInjector(new GuiceModule()).getInstance(FooFactory.class); Foo one = fooFactory.create("A"); Foo two = fooFactory.create("B"); @@ -103,8 +106,9 @@ public void guiceInjectedGenericFactory() { genericFooFactory.create(ImmutableList.of(3L), INT_AND_STRING_ACCESSOR, DepE.VALUE_1); ArrayList intAndStringAccessorArrayList = new ArrayList<>(); intAndStringAccessorArrayList.add(4.0); - GenericFoo, Long, DepE> four = genericFooFactory.create( - intAndStringAccessorArrayList, INT_AND_STRING_ACCESSOR, DepE.VALUE_2); + GenericFoo, Long, DepE> four = + genericFooFactory.create( + intAndStringAccessorArrayList, INT_AND_STRING_ACCESSOR, DepE.VALUE_2); assertThat(three.getDepA()).isEqualTo(3); ImmutableList unusedLongList = three.getDepB(); assertThat(three.getDepB()).containsExactly(3L); @@ -124,8 +128,7 @@ public void guiceInjectedGenericFactory() { @Test public void guiceInjectedPackageSpanningFactory() { ReferencePackageFactory referencePackageFactory = - Guice.createInjector(new GuiceModule()) - .getInstance(ReferencePackageFactory.class); + Guice.createInjector(new GuiceModule()).getInstance(ReferencePackageFactory.class); ReferencePackage referencePackage = referencePackageFactory.create(5); OtherPackage otherPackage = referencePackage.otherPackage(); assertThat(otherPackage.referencePackageFactory()).isNotSameInstanceAs(referencePackageFactory); diff --git a/factory/src/main/java/com/google/auto/factory/AutoFactory.java b/factory/src/main/java/com/google/auto/factory/AutoFactory.java index 2ef84cc32f..48a9aa889f 100644 --- a/factory/src/main/java/com/google/auto/factory/AutoFactory.java +++ b/factory/src/main/java/com/google/auto/factory/AutoFactory.java @@ -32,7 +32,7 @@ * * @author Gregory Kick */ -@Target({ TYPE, CONSTRUCTOR }) +@Target({TYPE, CONSTRUCTOR}) public @interface AutoFactory { /** * The simple name of the generated factory; the factory is always generated in the same @@ -50,10 +50,10 @@ /** * A list of interfaces that the generated factory is required to implement. */ - Class[] implementing() default { }; + Class[] implementing() default {}; /** - * The type that the generated factory is require to extend. + * The type that the generated factory is required to extend. */ Class extending() default Object.class; diff --git a/factory/src/main/java/com/google/auto/factory/Provided.java b/factory/src/main/java/com/google/auto/factory/Provided.java index e81e4aa664..226a16f48c 100644 --- a/factory/src/main/java/com/google/auto/factory/Provided.java +++ b/factory/src/main/java/com/google/auto/factory/Provided.java @@ -26,4 +26,4 @@ * @author Gregory Kick */ @Target(PARAMETER) -public @interface Provided { } +public @interface Provided {} diff --git a/factory/src/main/java/com/google/auto/factory/package-info.java b/factory/src/main/java/com/google/auto/factory/package-info.java new file mode 100644 index 0000000000..ea1ddd88d7 --- /dev/null +++ b/factory/src/main/java/com/google/auto/factory/package-info.java @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.auto.factory; + diff --git a/factory/src/main/java/com/google/auto/factory/processor/AnnotationValues.java b/factory/src/main/java/com/google/auto/factory/processor/AnnotationValues.java index b767c47f39..53d38a40a3 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/AnnotationValues.java +++ b/factory/src/main/java/com/google/auto/factory/processor/AnnotationValues.java @@ -32,24 +32,29 @@ private AnnotationValues() {} static boolean asBoolean(AnnotationValue value) { return value.accept( new SimpleAnnotationValueVisitor6() { - @Override protected Boolean defaultAction(Object o, Void p) { + @Override + protected Boolean defaultAction(Object o, Void p) { throw new IllegalArgumentException(); } - @Override public Boolean visitBoolean(boolean b, Void p) { + @Override + public Boolean visitBoolean(boolean b, Void p) { return b; } - }, null); + }, + null); } static TypeElement asType(AnnotationValue value) { return value.accept( new SimpleAnnotationValueVisitor6() { - @Override protected TypeElement defaultAction(Object o, Void p) { + @Override + protected TypeElement defaultAction(Object o, Void p) { throw new IllegalArgumentException(); } - @Override public TypeElement visitType(TypeMirror t, Void p) { + @Override + public TypeElement visitType(TypeMirror t, Void p) { return t.accept( new SimpleTypeVisitor6() { @Override @@ -59,12 +64,14 @@ protected TypeElement defaultAction(TypeMirror e, Void p) { @Override public TypeElement visitDeclared(DeclaredType t, Void p) { - return Iterables.getOnlyElement(ElementFilter.typesIn( - ImmutableList.of(t.asElement()))); + return Iterables.getOnlyElement( + ElementFilter.typesIn(ImmutableList.of(t.asElement()))); } - }, null); + }, + null); } - }, null); + }, + null); } static ImmutableList asList(AnnotationValue value) { @@ -80,6 +87,7 @@ public ImmutableList visitArray( List vals, Void p) { return ImmutableList.copyOf(vals); } - }, null); + }, + null); } } diff --git a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java index ec3642a938..889d8e41a5 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java +++ b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java @@ -21,6 +21,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.getOnlyElement; +import static java.util.Objects.requireNonNull; import static javax.lang.model.element.ElementKind.PACKAGE; import static javax.lang.model.util.ElementFilter.typesIn; import static javax.tools.Diagnostic.Kind.ERROR; @@ -54,12 +55,19 @@ @AutoValue abstract class AutoFactoryDeclaration { abstract TypeElement targetType(); + abstract Element target(); + abstract Optional className(); + abstract TypeElement extendingType(); + abstract ImmutableSet implementingTypes(); + abstract boolean allowSubclasses(); + abstract AnnotationMirror mirror(); + abstract ImmutableMap valuesMap(); PackageAndClass getFactoryName() { @@ -97,50 +105,69 @@ static final class Factory { Optional createIfValid(Element element) { checkNotNull(element); AnnotationMirror mirror = Mirrors.getAnnotationMirror(element, AutoFactory.class).get(); - checkArgument(Mirrors.getQualifiedName(mirror.getAnnotationType()). - contentEquals(AutoFactory.class.getName())); + checkArgument( + Mirrors.getQualifiedName(mirror.getAnnotationType()) + .contentEquals(AutoFactory.class.getName())); Map values = Mirrors.simplifyAnnotationValueMap(elements.getElementValuesWithDefaults(mirror)); checkState(values.size() == 4); - // className value is a string, so we can just call toString - AnnotationValue classNameValue = values.get("className"); + // className value is a string, so we can just call toString. We know values.get("className") + // is non-null because @AutoFactory has an annotation element of that name. + AnnotationValue classNameValue = requireNonNull(values.get("className")); String className = classNameValue.getValue().toString(); if (!className.isEmpty() && !isValidIdentifier(className)) { - messager.printMessage(ERROR, + messager.printMessage( + ERROR, String.format("\"%s\" is not a valid Java identifier", className), - element, mirror, classNameValue); + element, + mirror, + classNameValue); return Optional.empty(); } AnnotationValue extendingValue = checkNotNull(values.get("extending")); TypeElement extendingType = AnnotationValues.asType(extendingValue); if (extendingType == null) { - messager.printMessage(ERROR, "Unable to find the type: " + extendingValue.getValue(), - element, mirror, extendingValue); + messager.printMessage( + ERROR, + "Unable to find the type: " + extendingValue.getValue(), + element, + mirror, + extendingValue); return Optional.empty(); } else if (!isValidSupertypeForClass(extendingType)) { - messager.printMessage(ERROR, - String.format("%s is not a valid supertype for a factory. " - + "Supertypes must be non-final classes.", - extendingType.getQualifiedName()), - element, mirror, extendingValue); + messager.printMessage( + ERROR, + String.format( + "%s is not a valid supertype for a factory. " + + "Supertypes must be non-final classes.", + extendingType.getQualifiedName()), + element, + mirror, + extendingValue); return Optional.empty(); } ImmutableList noParameterConstructors = FluentIterable.from(ElementFilter.constructorsIn(extendingType.getEnclosedElements())) - .filter(new Predicate() { - @Override public boolean apply(ExecutableElement constructor) { - return constructor.getParameters().isEmpty(); - } - }) + .filter( + new Predicate() { + @Override + public boolean apply(ExecutableElement constructor) { + return constructor.getParameters().isEmpty(); + } + }) .toList(); if (noParameterConstructors.isEmpty()) { - messager.printMessage(ERROR, - String.format("%s is not a valid supertype for a factory. " - + "Factory supertypes must have a no-arg constructor.", - extendingType.getQualifiedName()), - element, mirror, extendingValue); + messager.printMessage( + ERROR, + String.format( + "%s is not a valid supertype for a factory. " + + "Factory supertypes must have a no-arg constructor.", + extendingType.getQualifiedName()), + element, + mirror, + extendingValue); return Optional.empty(); } else if (noParameterConstructors.size() > 1) { throw new IllegalStateException("Multiple constructors with no parameters??"); diff --git a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java index 91ad9462dc..d81d2253ba 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java +++ b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; @@ -83,8 +84,9 @@ public boolean process(Set annotations, RoundEnvironment try { doProcess(roundEnv); } catch (Throwable e) { - messager.printMessage(Kind.ERROR, "Failed to process @AutoFactory annotations:\n" - + Throwables.getStackTraceAsString(e)); + messager.printMessage( + Kind.ERROR, + "Failed to process @AutoFactory annotations:\n" + Throwables.getStackTraceAsString(e)); } return false; } @@ -98,7 +100,7 @@ private void doProcess(RoundEnvironment roundEnv) { ImmutableListMultimap.builder(); ImmutableSetMultimap.Builder implementationMethodDescriptorsBuilder = ImmutableSetMultimap.builder(); - // Iterate over the classes and methods that are annotated with @AutoFactory. + // Iterate over the classes and constructors that are annotated with @AutoFactory. for (Element element : roundEnv.getElementsAnnotatedWith(AutoFactory.class)) { Optional declaration = declarationFactory.createIfValid(element); if (declaration.isPresent()) { @@ -127,49 +129,58 @@ private void doProcess(RoundEnvironment roundEnv) { simpleNamesToNames(indexedMethods.keySet()); FactoryWriter factoryWriter = new FactoryWriter(processingEnv, factoriesBeingCreated); - indexedMethods.asMap().forEach( - (factoryName, methodDescriptors) -> { - // The sets of classes that are mentioned in the `extending` and `implementing` elements, - // respectively, of the @AutoFactory annotations for this factory. - ImmutableSet.Builder extending = newTypeSetBuilder(); - ImmutableSortedSet.Builder implementing = newTypeSetBuilder(); - boolean publicType = false; - Boolean allowSubclasses = null; - boolean skipCreation = false; - for (FactoryMethodDescriptor methodDescriptor : methodDescriptors) { - extending.add(methodDescriptor.declaration().extendingType().asType()); - for (TypeElement implementingType : - methodDescriptor.declaration().implementingTypes()) { - implementing.add(implementingType.asType()); - } - publicType |= methodDescriptor.publicMethod(); - if (allowSubclasses == null) { - allowSubclasses = methodDescriptor.declaration().allowSubclasses(); - } else if (!allowSubclasses.equals(methodDescriptor.declaration().allowSubclasses())) { - skipCreation = true; - messager.printMessage(Kind.ERROR, - "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.", - methodDescriptor.declaration().target(), - methodDescriptor.declaration().mirror(), - methodDescriptor.declaration().valuesMap().get("allowSubclasses")); - } - } - if (!skipCreation) { - try { - factoryWriter.writeFactory( - FactoryDescriptor.create( - factoryName, - Iterables.getOnlyElement(extending.build()), - implementing.build(), - publicType, - ImmutableSet.copyOf(methodDescriptors), - implementationMethodDescriptors.get(factoryName), - allowSubclasses)); - } catch (IOException e) { - messager.printMessage(Kind.ERROR, "failed: " + e); - } - } - }); + indexedMethods + .asMap() + .forEach( + (factoryName, methodDescriptors) -> { + if (methodDescriptors.isEmpty()) { + // This shouldn't happen, but check anyway to avoid an exception for + // methodDescriptors.iterator().next() below. + return; + } + // The sets of classes that are mentioned in the `extending` and `implementing` + // elements, respectively, of the @AutoFactory annotations for this factory. + ImmutableSet.Builder extending = newTypeSetBuilder(); + ImmutableSortedSet.Builder implementing = newTypeSetBuilder(); + boolean publicType = false; + Set allowSubclassesSet = new HashSet<>(); + boolean skipCreation = false; + for (FactoryMethodDescriptor methodDescriptor : methodDescriptors) { + extending.add(methodDescriptor.declaration().extendingType().asType()); + for (TypeElement implementingType : + methodDescriptor.declaration().implementingTypes()) { + implementing.add(implementingType.asType()); + } + publicType |= methodDescriptor.publicMethod(); + allowSubclassesSet.add(methodDescriptor.declaration().allowSubclasses()); + if (allowSubclassesSet.size() > 1) { + skipCreation = true; + messager.printMessage( + Kind.ERROR, + "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.", + methodDescriptor.declaration().target(), + methodDescriptor.declaration().mirror(), + methodDescriptor.declaration().valuesMap().get("allowSubclasses")); + } + } + // The set can't be empty because we eliminated methodDescriptors.isEmpty() above. + boolean allowSubclasses = allowSubclassesSet.iterator().next(); + if (!skipCreation) { + try { + factoryWriter.writeFactory( + FactoryDescriptor.create( + factoryName, + Iterables.getOnlyElement(extending.build()), + implementing.build(), + publicType, + ImmutableSet.copyOf(methodDescriptors), + implementationMethodDescriptors.get(factoryName), + allowSubclasses)); + } catch (IOException e) { + messager.printMessage(Kind.ERROR, "failed: " + e); + } + } + }); } private ImmutableSet implementationMethods( @@ -180,8 +191,7 @@ private ImmutableSet implementationMethods( ElementFilter.methodsIn(elements.getAllMembers(supertype))) { if (implementationMethod.getModifiers().contains(Modifier.ABSTRACT)) { ExecutableType methodType = - Elements2.getExecutableElementAsMemberOf( - types, implementationMethod, supertype); + Elements2.getExecutableElementAsMemberOf(types, implementationMethod, supertype); ImmutableSet passedParameters = Parameter.forParameterList( implementationMethod.getParameters(), methodType.getParameterTypes(), types); diff --git a/factory/src/main/java/com/google/auto/factory/processor/Elements2.java b/factory/src/main/java/com/google/auto/factory/processor/Elements2.java index fa00454e3c..3663f370e5 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/Elements2.java +++ b/factory/src/main/java/com/google/auto/factory/processor/Elements2.java @@ -33,7 +33,7 @@ import javax.lang.model.util.Types; final class Elements2 { - private Elements2() { } + private Elements2() {} static ImmutableSet getConstructors(TypeElement type) { checkNotNull(type); diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java index 670d8fba11..270188c426 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java +++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java @@ -37,7 +37,7 @@ */ @AutoValue abstract class FactoryDescriptor { - private static final CharMatcher invalidIdentifierCharacters = + private static final CharMatcher INVALID_IDENTIFIER_CHARACTERS = new CharMatcher() { @Override public boolean matches(char c) { @@ -46,16 +46,24 @@ public boolean matches(char c) { }; abstract PackageAndClass name(); + abstract TypeMirror extendingType(); + abstract ImmutableSet implementingTypes(); + abstract boolean publicType(); + abstract ImmutableSet methodDescriptors(); + abstract ImmutableSet implementationMethodDescriptors(); + abstract boolean allowSubclasses(); + abstract ImmutableMap providers(); final AutoFactoryDeclaration declaration() { - return Iterables.getFirst(methodDescriptors(), null).declaration(); + // There is always at least one method descriptor. + return methodDescriptors().iterator().next().declaration(); } private static class UniqueNameSet { @@ -111,7 +119,7 @@ static FactoryDescriptor create( default: String providerName = uniqueNames.getUniqueName( - invalidIdentifierCharacters.replaceFrom(key.toString(), '_') + INVALID_IDENTIFIER_CHARACTERS.replaceFrom(key.toString(), '_') + "Provider"); Optional nullable = parameters.stream() @@ -216,8 +224,7 @@ private static ImmutableSet getDeduplicatedMethodDescri * in the same order. */ private static boolean areDuplicateMethodDescriptors( - FactoryMethodDescriptor factory, - ImplementationMethodDescriptor implementation) { + FactoryMethodDescriptor factory, ImplementationMethodDescriptor implementation) { if (!factory.name().equals(implementation.name())) { return false; diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java index 0816aef409..70a21ea263 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java +++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java @@ -18,6 +18,7 @@ import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.partitioningBy; import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.PUBLIC; @@ -53,9 +54,7 @@ final class FactoryDescriptorGenerator { private final AutoFactoryDeclaration.Factory declarationFactory; FactoryDescriptorGenerator( - Messager messager, - Types types, - AutoFactoryDeclaration.Factory declarationFactory) { + Messager messager, Types types, AutoFactoryDeclaration.Factory declarationFactory) { this.messager = messager; this.types = types; this.declarationFactory = declarationFactory; @@ -67,65 +66,75 @@ ImmutableSet generateDescriptor(Element element) { if (!declaration.isPresent()) { return ImmutableSet.of(); } - return element.accept(new ElementKindVisitor6, Void>() { - @Override - protected ImmutableSet defaultAction(Element e, Void p) { - throw new AssertionError("@AutoFactory applied to an impossible element"); - } + return element.accept( + new ElementKindVisitor6, Void>() { + @Override + protected ImmutableSet defaultAction(Element e, Void p) { + throw new AssertionError("@AutoFactory applied to an impossible element"); + } - @Override - public ImmutableSet visitTypeAsClass(TypeElement type, Void p) { - if (type.getModifiers().contains(ABSTRACT)) { - // applied to an abstract factory - messager.printMessage(ERROR, - "Auto-factory doesn't support being applied to abstract classes.", type, mirror); - return ImmutableSet.of(); - } else { - // applied to the type to be created - ImmutableSet constructors = Elements2.getConstructors(type); - if (constructors.isEmpty()) { - return generateDescriptorForDefaultConstructor(declaration.get(), type); - } else { - return FluentIterable.from(constructors) - .transform(new Function() { - @Override public FactoryMethodDescriptor apply(ExecutableElement constructor) { - return generateDescriptorForConstructor(declaration.get(), constructor); - } - }) - .toSet(); + @Override + public ImmutableSet visitTypeAsClass(TypeElement type, Void p) { + if (type.getModifiers().contains(ABSTRACT)) { + // applied to an abstract factory + messager.printMessage( + ERROR, + "Auto-factory doesn't support being applied to abstract classes.", + type, + mirror); + return ImmutableSet.of(); + } else { + // applied to the type to be created + ImmutableSet constructors = Elements2.getConstructors(type); + if (constructors.isEmpty()) { + return generateDescriptorForDefaultConstructor(declaration.get(), type); + } else { + return FluentIterable.from(constructors) + .transform( + new Function() { + @Override + public FactoryMethodDescriptor apply(ExecutableElement constructor) { + return generateDescriptorForConstructor(declaration.get(), constructor); + } + }) + .toSet(); + } + } } - } - } - @Override - public ImmutableSet visitTypeAsInterface(TypeElement type, Void p) { - // applied to the factory interface - messager.printMessage(ERROR, - "Auto-factory doesn't support being applied to interfaces.", type, mirror); - return ImmutableSet.of(); - } + @Override + public ImmutableSet visitTypeAsInterface( + TypeElement type, Void p) { + // applied to the factory interface + messager.printMessage( + ERROR, "Auto-factory doesn't support being applied to interfaces.", type, mirror); + return ImmutableSet.of(); + } - @Override - public ImmutableSet visitExecutableAsConstructor( - ExecutableElement e, Void p) { - // applied to a constructor of a type to be created - return ImmutableSet.of(generateDescriptorForConstructor(declaration.get(), e)); - } - }, null); + @Override + public ImmutableSet visitExecutableAsConstructor( + ExecutableElement e, Void p) { + // applied to a constructor of a type to be created + return ImmutableSet.of(generateDescriptorForConstructor(declaration.get(), e)); + } + }, + null); } - FactoryMethodDescriptor generateDescriptorForConstructor(final AutoFactoryDeclaration declaration, - ExecutableElement constructor) { + FactoryMethodDescriptor generateDescriptorForConstructor( + final AutoFactoryDeclaration declaration, ExecutableElement constructor) { checkNotNull(constructor); checkArgument(constructor.getKind() == ElementKind.CONSTRUCTOR); TypeElement classElement = MoreElements.asType(constructor.getEnclosingElement()); Map> parameterMap = constructor.getParameters().stream() .collect(partitioningBy(parameter -> isAnnotationPresent(parameter, Provided.class))); + // The map returned by partitioningBy always has entries for both key values but our + // null-checker isn't yet smart enough to know that. ImmutableSet providedParameters = - Parameter.forParameterList(parameterMap.get(true), types); + Parameter.forParameterList(requireNonNull(parameterMap.get(true)), types); ImmutableSet passedParameters = - Parameter.forParameterList(parameterMap.get(false), types); + Parameter.forParameterList(requireNonNull(parameterMap.get(false)), types); return FactoryMethodDescriptor.builder(declaration) .name("create") .returnType(classElement.asType()) diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java index 5f4ad294f4..79cce538c6 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java +++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java @@ -31,15 +31,37 @@ @AutoValue abstract class FactoryMethodDescriptor { abstract AutoFactoryDeclaration declaration(); + abstract String name(); + abstract TypeMirror returnType(); + abstract boolean publicMethod(); + abstract boolean overridingMethod(); + + /** The parameters that are passed to the {@code create} method. */ abstract ImmutableSet passedParameters(); + + /** + * The factory constructor parameters that this factory method requires. When there is more than + * one AutoFactory constructor, each one can have its own {@code @Provided} parameters, or + * constructors can have {@code @Provided} parameters in common. The generated factory has a + * single constructor, which has one {@code @Injected} constructor parameter for each unique + * {@code @Provided} parameter in any constructor. + */ abstract ImmutableSet providedParameters(); + + /** + * The parameters of the constructor that this {@code create} method calls. This is the union of + * {@link #passedParameters()} and {@link #providedParameters()}. + */ abstract ImmutableSet creationParameters(); + abstract boolean isVarArgs(); + abstract ImmutableSet exceptions(); + abstract Builder toBuilder(); final PackageAndClass factoryName() { @@ -47,28 +69,39 @@ final PackageAndClass factoryName() { } static Builder builder(AutoFactoryDeclaration declaration) { - return new AutoValue_FactoryMethodDescriptor.Builder() - .declaration(checkNotNull(declaration)); + return new AutoValue_FactoryMethodDescriptor.Builder().declaration(checkNotNull(declaration)); } @AutoValue.Builder abstract static class Builder { abstract Builder declaration(AutoFactoryDeclaration declaration); + abstract Builder name(String name); + abstract Builder returnType(TypeMirror returnType); + abstract Builder publicMethod(boolean publicMethod); + abstract Builder overridingMethod(boolean overridingMethod); + abstract Builder passedParameters(Iterable passedParameters); + abstract Builder providedParameters(Iterable providedParameters); + abstract Builder creationParameters(Iterable creationParameters); + abstract Builder isVarArgs(boolean isVarargs); + abstract Builder exceptions(Iterable exceptions); + abstract FactoryMethodDescriptor buildImpl(); FactoryMethodDescriptor build() { FactoryMethodDescriptor descriptor = buildImpl(); - checkState(descriptor.creationParameters().equals( - Sets.union(descriptor.passedParameters(), descriptor.providedParameters()))); + checkState( + descriptor + .creationParameters() + .equals(Sets.union(descriptor.passedParameters(), descriptor.providedParameters()))); return descriptor; } } diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java index 32373915c3..7b5397aaef 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java +++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java @@ -16,9 +16,11 @@ package com.google.auto.factory.processor; import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec; +import static com.google.auto.common.MoreStreams.toImmutableList; import static com.squareup.javapoet.MethodSpec.constructorBuilder; import static com.squareup.javapoet.MethodSpec.methodBuilder; import static com.squareup.javapoet.TypeSpec.classBuilder; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static javax.lang.model.element.Modifier.FINAL; @@ -26,14 +28,12 @@ import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; -import com.google.auto.common.AnnotationMirrors; -import com.google.auto.common.AnnotationValues; +import com.google.auto.common.MoreTypes; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; -import com.google.common.collect.Streams; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; @@ -45,19 +45,14 @@ import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; import java.io.IOException; -import java.lang.annotation.Target; import java.util.Iterator; import java.util.List; -import java.util.Optional; -import java.util.stream.Stream; import javax.annotation.processing.Filer; import javax.annotation.processing.ProcessingEnvironment; import javax.inject.Inject; import javax.inject.Provider; import javax.lang.model.SourceVersion; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; @@ -79,12 +74,10 @@ final class FactoryWriter { this.factoriesBeingCreated = factoriesBeingCreated; } - void writeFactory(FactoryDescriptor descriptor) - throws IOException { + void writeFactory(FactoryDescriptor descriptor) throws IOException { String factoryName = descriptor.name().className(); TypeSpec.Builder factory = - classBuilder(factoryName) - .addOriginatingElement(descriptor.declaration().targetType()); + classBuilder(factoryName).addOriginatingElement(descriptor.declaration().targetType()); generatedAnnotationSpec( elements, sourceVersion, @@ -176,7 +169,7 @@ private void addFactoryMethods( checkNotNull = false; } } else { - ProviderField provider = descriptor.providers().get(parameter.key()); + ProviderField provider = requireNonNull(descriptor.providers().get(parameter.key())); argument = CodeBlock.of(provider.name()); if (parameter.isProvider()) { // Providers are checked for nullness in the Factory's constructor. @@ -198,8 +191,7 @@ private void addFactoryMethods( } } - private void addImplementationMethods( - TypeSpec.Builder factory, FactoryDescriptor descriptor) { + private void addImplementationMethods(TypeSpec.Builder factory, FactoryDescriptor descriptor) { for (ImplementationMethodDescriptor methodDescriptor : descriptor.implementationMethodDescriptors()) { MethodSpec.Builder implementationMethod = @@ -228,44 +220,17 @@ private ImmutableList parameters(Iterable parameters) ImmutableList.Builder builder = ImmutableList.builder(); for (Parameter parameter : parameters) { TypeName type = resolveTypeName(parameter.type().get()); - // Remove TYPE_USE annotations, since resolveTypeName will already have included those in - // the TypeName it returns. - List annotations = - Stream.of(parameter.nullable(), parameter.key().qualifier()) - .flatMap(Streams::stream) - .filter(a -> !isTypeUseAnnotation(a)) + ImmutableList annotations = + parameter.annotations().stream() .map(AnnotationSpec::get) - .collect(toList()); + .collect(toImmutableList()); ParameterSpec parameterSpec = - ParameterSpec.builder(type, parameter.name()) - .addAnnotations(annotations) - .build(); + ParameterSpec.builder(type, parameter.name()).addAnnotations(annotations).build(); builder.add(parameterSpec); } return builder.build(); } - private static boolean isTypeUseAnnotation(AnnotationMirror mirror) { - Element annotationElement = mirror.getAnnotationType().asElement(); - // This is basically equivalent to: - // Target target = annotationElement.getAnnotation(Target.class); - // return target != null - // && Arrays.asList(annotationElement.getAnnotation(Target.class)).contains(TYPE_USE); - // but that might blow up if the annotation is being compiled at the same time and has an - // undefined identifier in its @Target values. The rigmarole below avoids that problem. - Optional maybeTargetMirror = - Mirrors.getAnnotationMirror(annotationElement, Target.class); - return maybeTargetMirror - .map( - targetMirror -> - AnnotationValues.getEnums( - AnnotationMirrors.getAnnotationValue(targetMirror, "value")) - .stream() - .map(VariableElement::getSimpleName) - .anyMatch(name -> name.contentEquals("TYPE_USE"))) - .orElse(false); - } - private static void addCheckNotNullMethod( TypeSpec.Builder factory, FactoryDescriptor descriptor) { if (shouldGenerateCheckNotNull(descriptor)) { @@ -342,9 +307,28 @@ private static ImmutableSet getFactoryTypeVariables( for (ProviderField provider : descriptor.providers().values()) { typeVariables.addAll(getReferencedTypeParameterNames(provider.key().type().get())); } + // If a parent type has a type parameter, like FooFactory, then the generated factory needs + // to have the same parameter, like FooImplFactory extends FooFactory. 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 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 getMethodTypeVariables( FactoryMethodDescriptor methodDescriptor, ImmutableSet factoryTypeVariables) { diff --git a/factory/src/main/java/com/google/auto/factory/processor/ImplementationMethodDescriptor.java b/factory/src/main/java/com/google/auto/factory/processor/ImplementationMethodDescriptor.java index b5faa272dd..b2705b7ea7 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/ImplementationMethodDescriptor.java +++ b/factory/src/main/java/com/google/auto/factory/processor/ImplementationMethodDescriptor.java @@ -22,10 +22,15 @@ @AutoValue abstract class ImplementationMethodDescriptor { abstract String name(); + abstract TypeMirror returnType(); + abstract boolean publicMethod(); + abstract ImmutableSet passedParameters(); + abstract boolean isVarArgs(); + abstract ImmutableSet exceptions(); static Builder builder() { 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 12cfd927cc..6dc7644554 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 @@ -68,8 +68,10 @@ static Key create(TypeMirror type, Collection annotations, Typ // TODO(gak): check for only one qualifier rather than using the first Optional qualifier = annotations.stream() - .filter(annotation -> - isAnnotationPresent(annotation.getAnnotationType().asElement(), Qualifier.class)) + .filter( + annotation -> + isAnnotationPresent( + annotation.getAnnotationType().asElement(), Qualifier.class)) .findFirst(); TypeMirror keyType = @@ -95,7 +97,7 @@ private static TypeMirror boxedType(TypeMirror type, Types types) { 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/main/java/com/google/auto/factory/processor/Mirrors.java b/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java index 9e111875e8..313fc9eeef 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java +++ b/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java @@ -34,20 +34,23 @@ import javax.lang.model.util.SimpleElementVisitor6; final class Mirrors { - private Mirrors() { } + private Mirrors() {} static Name getQualifiedName(DeclaredType type) { - return type.asElement().accept(new SimpleElementVisitor6() { - @Override - protected Name defaultAction(Element e, Void p) { - throw new AssertionError("DeclaredTypes should be TypeElements"); - } + return type.asElement() + .accept( + new SimpleElementVisitor6() { + @Override + protected Name defaultAction(Element e, Void p) { + throw new AssertionError("DeclaredTypes should be TypeElements"); + } - @Override - public Name visitType(TypeElement e, Void p) { - return e.getQualifiedName(); - } - }, null); + @Override + public Name visitType(TypeElement e, Void p) { + return e.getQualifiedName(); + } + }, + null); } /** {@code true} if {@code type} is a {@link Provider}. */ @@ -62,8 +65,8 @@ static boolean isProvider(TypeMirror type) { static ImmutableMap simplifyAnnotationValueMap( Map annotationValueMap) { ImmutableMap.Builder builder = ImmutableMap.builder(); - for (Entry entry - : annotationValueMap.entrySet()) { + for (Entry entry : + annotationValueMap.entrySet()) { builder.put(entry.getKey().getSimpleName().toString(), entry.getValue()); } return builder.build(); @@ -73,12 +76,11 @@ static ImmutableMap simplifyAnnotationValueMap( * Get the {@link AnnotationMirror} for the type {@code annotationType} present on the given * {@link Element} if it exists. */ - static Optional getAnnotationMirror(Element element, - Class annotationType) { + static Optional getAnnotationMirror( + Element element, Class annotationType) { String annotationName = annotationType.getName(); return element.getAnnotationMirrors().stream() - .filter( - a -> getQualifiedName(a.getAnnotationType()).contentEquals(annotationName)) + .filter(a -> getQualifiedName(a.getAnnotationType()).contentEquals(annotationName)) .map(x -> x) // get rid of wildcard .findFirst(); } diff --git a/factory/src/main/java/com/google/auto/factory/processor/Parameter.java b/factory/src/main/java/com/google/auto/factory/processor/Parameter.java index 57660e0dd2..2a438661d3 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/Parameter.java +++ b/factory/src/main/java/com/google/auto/factory/processor/Parameter.java @@ -15,16 +15,17 @@ */ package com.google.auto.factory.processor; +import static com.google.auto.common.MoreStreams.toImmutableList; import static com.google.auto.factory.processor.Mirrors.unwrapOptionalEquivalence; import static com.google.auto.factory.processor.Mirrors.wrapOptionalInEquivalence; import static com.google.common.base.Preconditions.checkArgument; -import static java.util.stream.Collectors.toList; import com.google.auto.common.AnnotationMirrors; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.auto.value.AutoValue; import com.google.common.base.Equivalence; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -64,26 +65,38 @@ boolean isPrimitive() { abstract String name(); abstract Key key(); + + /** Annotations on the parameter (not its type). */ + abstract ImmutableList> annotationWrappers(); + + ImmutableList annotations() { + return annotationWrappers().stream().map(Equivalence.Wrapper::get).collect(toImmutableList()); + } + abstract Optional> nullableWrapper(); Optional nullable() { return unwrapOptionalEquivalence(nullableWrapper()); } - private static Parameter forVariableElement( VariableElement variable, TypeMirror type, Types types) { - List annotations = + ImmutableList annotations = Stream.of(variable.getAnnotationMirrors(), type.getAnnotationMirrors()) .flatMap(List::stream) - .collect(toList()); + .collect(toImmutableList()); Optional nullable = annotations.stream().filter(Parameter::isNullable).findFirst(); + ImmutableList> annotationWrappers = + variable.getAnnotationMirrors().stream() + .map(AnnotationMirrors.equivalence()::wrap) + .collect(toImmutableList()); Key key = Key.create(type, annotations, types); return new AutoValue_Parameter( MoreTypes.equivalence().wrap(type), variable.getSimpleName().toString(), key, + annotationWrappers, wrapOptionalInEquivalence(AnnotationMirrors.equivalence(), nullable)); } @@ -106,7 +119,7 @@ static ImmutableSet forParameterList( Set names = Sets.newHashSetWithExpectedSize(variables.size()); for (int i = 0; i < variables.size(); i++) { Parameter parameter = forVariableElement(variables.get(i), variableTypes.get(i), types); - checkArgument(names.add(parameter.name())); + checkArgument(names.add(parameter.name()), "Duplicate parameter name: %s", parameter.name()); builder.add(parameter); } ImmutableSet parameters = builder.build(); diff --git a/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java b/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java index fe4c1fd22c..bd88f83708 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java +++ b/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java @@ -26,6 +26,7 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.ElementKindVisitor6; +import org.checkerframework.checker.nullness.qual.Nullable; final class ProvidedChecker { private final Messager messager; @@ -35,41 +36,54 @@ final class ProvidedChecker { } void checkProvidedParameter(Element element) { - checkArgument(isAnnotationPresent(element, Provided.class), "%s not annoated with @Provided", - element); - element.accept(new ElementKindVisitor6() { - @Override - protected Void defaultAction(Element e, Void p) { - throw new AssertionError("Provided can only be applied to parameters"); - } - - @Override - public Void visitVariableAsParameter(final VariableElement providedParameter, Void p) { - providedParameter.getEnclosingElement().accept(new ElementKindVisitor6() { + checkArgument( + isAnnotationPresent(element, Provided.class), "%s not annoated with @Provided", element); + element.accept( + new ElementKindVisitor6<@Nullable Void, @Nullable Void>() { @Override - protected Void defaultAction(Element e, Void p) { - raiseError(providedParameter, "@%s may only be applied to constructor parameters"); - return null; + protected @Nullable Void defaultAction(Element e, @Nullable Void p) { + throw new AssertionError("Provided can only be applied to parameters"); } @Override - public Void visitExecutableAsConstructor(ExecutableElement constructor, Void p) { - if (!(annotatedWithAutoFactory(constructor) - || annotatedWithAutoFactory(constructor.getEnclosingElement()))) { - raiseError(providedParameter, - "@%s may only be applied to constructors requesting an auto-factory"); - } + public @Nullable Void visitVariableAsParameter( + VariableElement providedParameter, @Nullable Void p) { + providedParameter + .getEnclosingElement() + .accept( + new ElementKindVisitor6<@Nullable Void, @Nullable Void>() { + @Override + protected @Nullable Void defaultAction(Element e, @Nullable Void p) { + raiseError( + providedParameter, "@%s may only be applied to constructor parameters"); + return null; + } + + @Override + public @Nullable Void visitExecutableAsConstructor( + ExecutableElement constructor, @Nullable Void p) { + if (!(annotatedWithAutoFactory(constructor) + || annotatedWithAutoFactory(constructor.getEnclosingElement()))) { + raiseError( + providedParameter, + "@%s may only be applied to constructors requesting an auto-factory"); + } + return null; + } + }, + p); return null; } - }, p); - return null; - } - }, null); + }, + null); } private void raiseError(VariableElement providedParameter, String messageFormat) { - messager.printMessage(ERROR, String.format(messageFormat, Provided.class.getSimpleName()), - providedParameter, Mirrors.getAnnotationMirror(providedParameter, Provided.class).get()); + messager.printMessage( + ERROR, + String.format(messageFormat, Provided.class.getSimpleName()), + providedParameter, + Mirrors.getAnnotationMirror(providedParameter, Provided.class).get()); } private static boolean annotatedWithAutoFactory(Element e) { diff --git a/factory/src/main/java/com/google/auto/factory/processor/ProviderField.java b/factory/src/main/java/com/google/auto/factory/processor/ProviderField.java index f18def328e..99847ec0ad 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/ProviderField.java +++ b/factory/src/main/java/com/google/auto/factory/processor/ProviderField.java @@ -27,7 +27,9 @@ @AutoValue abstract class ProviderField { abstract String name(); + abstract Key key(); + abstract Optional> nullableWrapper(); Optional nullable() { diff --git a/factory/src/main/java/com/google/auto/factory/processor/TypeVariables.java b/factory/src/main/java/com/google/auto/factory/processor/TypeVariables.java index 1f894732c1..4bd546e8f7 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/TypeVariables.java +++ b/factory/src/main/java/com/google/auto/factory/processor/TypeVariables.java @@ -38,8 +38,8 @@ static ImmutableSet getReferencedTypeVariables(TypeMirror type) { return type.accept(ReferencedTypeVariables.INSTANCE, new HashSet<>()); } - private static final class ReferencedTypeVariables extends - SimpleTypeVisitor8, Set> { + private static final class ReferencedTypeVariables + extends SimpleTypeVisitor8, Set> { private static final ReferencedTypeVariables INSTANCE = new ReferencedTypeVariables(); @@ -53,8 +53,7 @@ public ImmutableSet visitArray(ArrayType t, Set visited) } @Override - public ImmutableSet visitDeclared( - DeclaredType t, Set visited) { + public ImmutableSet visitDeclared(DeclaredType t, Set visited) { if (!visited.add(t.asElement())) { return ImmutableSet.of(); } @@ -66,8 +65,7 @@ public ImmutableSet visitDeclared( } @Override - public ImmutableSet visitTypeVariable( - TypeVariable t, Set visited) { + public ImmutableSet visitTypeVariable(TypeVariable t, Set visited) { if (!visited.add(t.asElement())) { return ImmutableSet.of(); } @@ -79,8 +77,7 @@ public ImmutableSet visitTypeVariable( } @Override - public ImmutableSet visitUnion( - UnionType t, Set visited) { + public ImmutableSet visitUnion(UnionType t, Set visited) { ImmutableSet.Builder typeVariables = ImmutableSet.builder(); for (TypeMirror unionType : t.getAlternatives()) { typeVariables.addAll(unionType.accept(this, visited)); @@ -89,8 +86,7 @@ public ImmutableSet visitUnion( } @Override - public ImmutableSet visitIntersection( - IntersectionType t, Set visited) { + public ImmutableSet visitIntersection(IntersectionType t, Set visited) { ImmutableSet.Builder typeVariables = ImmutableSet.builder(); for (TypeMirror intersectionType : t.getBounds()) { typeVariables.addAll(intersectionType.accept(this, visited)); @@ -99,8 +95,7 @@ public ImmutableSet visitIntersection( } @Override - public ImmutableSet visitWildcard( - WildcardType t, Set visited) { + public ImmutableSet visitWildcard(WildcardType t, Set visited) { ImmutableSet.Builder typeVariables = ImmutableSet.builder(); TypeMirror extendsBound = t.getExtendsBound(); if (extendsBound != null) { diff --git a/factory/src/main/java/com/google/auto/factory/processor/package-info.java b/factory/src/main/java/com/google/auto/factory/processor/package-info.java index 7339020aff..4a7c4b1091 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/package-info.java +++ b/factory/src/main/java/com/google/auto/factory/processor/package-info.java @@ -15,4 +15,5 @@ * This package contains the annotation processor that implements the * {@link com.google.auto.factory.AutoFactory} API. */ -package com.google.auto.factory.processor; \ No newline at end of file +package com.google.auto.factory.processor; + diff --git a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryDeclarationTest.java b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryDeclarationTest.java index 1b23a65d4c..7bef233417 100644 --- a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryDeclarationTest.java +++ b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryDeclarationTest.java @@ -24,7 +24,8 @@ @RunWith(JUnit4.class) public class AutoFactoryDeclarationTest { - @Test public void identifiers() { + @Test + public void identifiers() { assertThat(isValidIdentifier("String")).isTrue(); assertThat(isValidIdentifier("9CantStartWithNumber")).isFalse(); assertThat(isValidIdentifier("enum")).isFalse(); diff --git a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java index 49a5357e6b..f783c5bc47 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 @@ -170,6 +170,7 @@ public void constructorWithThrowsClauseAnnotated() { .generatedSourceFile("tests.ConstructorAnnotatedThrowsFactory") .hasSourceEquivalentTo(loadExpectedFile("expected/ConstructorAnnotatedThrowsFactory.java")); } + @Test public void constructorAnnotatedNonFinal() { Compilation compilation = @@ -507,6 +508,32 @@ public void defaultPackage() { .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")); + } + + @Test + public void parameterAnnotations() { + JavaFileObject file = JavaFileObjects.forResource("good/ParameterAnnotations.java"); + Compilation compilation = javac.compile(file); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(compilation) + .generatedSourceFile("tests.ParameterAnnotationsFactory") + .hasSourceEquivalentTo(loadExpectedFile("expected/ParameterAnnotationsFactory.java")); + } + private JavaFileObject loadExpectedFile(String resourceName) { if (isJavaxAnnotationProcessingGeneratedAvailable()) { return JavaFileObjects.forResource(resourceName); diff --git a/factory/src/test/resources/bad/FactoryExtendingAbstractClassWithConstructorParams.java b/factory/src/test/resources/bad/FactoryExtendingAbstractClassWithConstructorParams.java index 98c5f667f7..7c7120bfae 100644 --- a/factory/src/test/resources/bad/FactoryExtendingAbstractClassWithConstructorParams.java +++ b/factory/src/test/resources/bad/FactoryExtendingAbstractClassWithConstructorParams.java @@ -20,9 +20,9 @@ @AutoFactory(extending = AbstractFactory.class) final class FactoryExtendingAbstractClassWithConstructorParams { - static abstract class AbstractFactory { + abstract static class AbstractFactory { protected AbstractFactory(Object obj) {} - + abstract FactoryExtendingAbstractClassWithConstructorParams newInstance(); } } diff --git a/factory/src/test/resources/bad/InvalidCustomName.java b/factory/src/test/resources/bad/InvalidCustomName.java index 5734ee7f53..6d0a2f917a 100644 --- a/factory/src/test/resources/bad/InvalidCustomName.java +++ b/factory/src/test/resources/bad/InvalidCustomName.java @@ -18,4 +18,4 @@ import com.google.auto.factory.AutoFactory; @AutoFactory(className = "SillyFactory!") -final class InvalidCustomName { } +final class InvalidCustomName {} diff --git a/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java b/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java index 8e4b1ea414..faa83971e5 100644 --- a/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java +++ b/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java @@ -23,9 +23,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" -) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class CheckerFrameworkNullableFactory { private final Provider java_lang_StringProvider; diff --git a/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java b/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java index b5bd89c159..8d889eed98 100644 --- a/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java +++ b/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java @@ -20,14 +20,15 @@ import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class ClassUsingQualifierWithArgsFactory { private final Provider providedDepAProvider; - @Inject ClassUsingQualifierWithArgsFactory( - @QualifierWithArgs(name="Fred", count=3) Provider providedDepAProvider) { + @Inject + ClassUsingQualifierWithArgsFactory( + @QualifierWithArgs(name = "Fred", count = 3) Provider providedDepAProvider) { this.providedDepAProvider = checkNotNull(providedDepAProvider, 1); } diff --git a/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java b/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java index 6e9d242e07..22349851de 100644 --- a/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java +++ b/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java @@ -20,13 +20,14 @@ import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class ConstructorAnnotatedFactory { private final Provider objProvider; - @Inject ConstructorAnnotatedFactory(Provider objProvider) { + @Inject + ConstructorAnnotatedFactory(Provider objProvider) { this.objProvider = checkNotNull(objProvider, 1); } diff --git a/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java b/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java index 528ea03f78..25ec894fbe 100644 --- a/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java +++ b/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java @@ -20,13 +20,14 @@ import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) class ConstructorAnnotatedNonFinalFactory { private final Provider objProvider; - - @Inject ConstructorAnnotatedNonFinalFactory(Provider objProvider) { + + @Inject + ConstructorAnnotatedNonFinalFactory(Provider objProvider) { this.objProvider = checkNotNull(objProvider, 1); } @@ -37,11 +38,11 @@ ConstructorAnnotatedNonFinal create() { ConstructorAnnotatedNonFinal create(String s) { return new ConstructorAnnotatedNonFinal(checkNotNull(s, 1)); } - + ConstructorAnnotatedNonFinal create(int i) { return new ConstructorAnnotatedNonFinal(checkNotNull(objProvider.get(), 1), i); } - + ConstructorAnnotatedNonFinal create(char c) { return new ConstructorAnnotatedNonFinal(checkNotNull(objProvider.get(), 1), c); } diff --git a/factory/src/test/resources/expected/ConstructorAnnotatedThrowsFactory.java b/factory/src/test/resources/expected/ConstructorAnnotatedThrowsFactory.java index 13f9e99412..05b30fdff4 100644 --- a/factory/src/test/resources/expected/ConstructorAnnotatedThrowsFactory.java +++ b/factory/src/test/resources/expected/ConstructorAnnotatedThrowsFactory.java @@ -21,13 +21,14 @@ import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class ConstructorAnnotatedThrowsFactory { private final Provider objProvider; - @Inject ConstructorAnnotatedThrowsFactory(Provider objProvider) { + @Inject + ConstructorAnnotatedThrowsFactory(Provider objProvider) { this.objProvider = checkNotNull(objProvider, 1); } diff --git a/factory/src/test/resources/expected/CustomNamedFactory.java b/factory/src/test/resources/expected/CustomNamedFactory.java index c388387c39..512c244c15 100644 --- a/factory/src/test/resources/expected/CustomNamedFactory.java +++ b/factory/src/test/resources/expected/CustomNamedFactory.java @@ -19,11 +19,12 @@ import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class CustomNamedFactory { - @Inject CustomNamedFactory() {} + @Inject + CustomNamedFactory() {} SimpleClassCustomName create() { return new SimpleClassCustomName(); diff --git a/factory/src/test/resources/expected/CustomNullableFactory.java b/factory/src/test/resources/expected/CustomNullableFactory.java index 31bb5bfa06..c8f2f28697 100644 --- a/factory/src/test/resources/expected/CustomNullableFactory.java +++ b/factory/src/test/resources/expected/CustomNullableFactory.java @@ -20,9 +20,9 @@ import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class CustomNullableFactory { private final Provider objectProvider; diff --git a/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java b/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java index c56afb0a46..f35b414e39 100644 --- a/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java +++ b/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java @@ -19,18 +19,20 @@ import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class FactoryExtendingAbstractClassFactory extends FactoryExtendingAbstractClass.AbstractFactory { - @Inject FactoryExtendingAbstractClassFactory() {} + @Inject + FactoryExtendingAbstractClassFactory() {} FactoryExtendingAbstractClass create() { return new FactoryExtendingAbstractClass(); } - @Override public FactoryExtendingAbstractClass newInstance() { + @Override + public FactoryExtendingAbstractClass newInstance() { return create(); } } diff --git a/factory/src/test/resources/expected/FactoryExtendingAbstractClassThrowsFactory.java b/factory/src/test/resources/expected/FactoryExtendingAbstractClassThrowsFactory.java index c2640f54b9..402f894679 100644 --- a/factory/src/test/resources/expected/FactoryExtendingAbstractClassThrowsFactory.java +++ b/factory/src/test/resources/expected/FactoryExtendingAbstractClassThrowsFactory.java @@ -20,18 +20,20 @@ import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class FactoryExtendingAbstractClassThrowsFactory extends FactoryExtendingAbstractClassThrows.AbstractFactory { - @Inject FactoryExtendingAbstractClassThrowsFactory() {} + @Inject + FactoryExtendingAbstractClassThrowsFactory() {} FactoryExtendingAbstractClassThrows create() throws IOException, InterruptedException { return new FactoryExtendingAbstractClassThrows(); } - @Override public FactoryExtendingAbstractClassThrows newInstance() throws Exception { + @Override + public FactoryExtendingAbstractClassThrows newInstance() throws Exception { return create(); } } diff --git a/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java b/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java index 1f3bb0e3ee..2d8a392db6 100644 --- a/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java +++ b/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java @@ -20,15 +20,15 @@ import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class FactoryImplementingCreateMethod_ConcreteClassFactory implements FactoryImplementingCreateMethod.FactoryInterfaceWithCreateMethod { @Inject FactoryImplementingCreateMethod_ConcreteClassFactory() {} - + @Override public FactoryImplementingCreateMethod.ConcreteClass create() { return new FactoryImplementingCreateMethod.ConcreteClass(); @@ -40,11 +40,12 @@ public FactoryImplementingCreateMethod.ConcreteClass create(int aDifferentArgume } @Override - public FactoryImplementingCreateMethod.ConcreteClass create(List genericWithDifferentArgumentName) { + public FactoryImplementingCreateMethod.ConcreteClass create( + List genericWithDifferentArgumentName) { return new FactoryImplementingCreateMethod.ConcreteClass( checkNotNull(genericWithDifferentArgumentName, 1)); } - + FactoryImplementingCreateMethod.ConcreteClass create(int a, boolean b) { return new FactoryImplementingCreateMethod.ConcreteClass(a, b); } diff --git a/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java b/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java index 3bfc526289..9f2bf0aea2 100644 --- a/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java +++ b/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java @@ -20,20 +20,23 @@ import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class FactoryImplementingGenericInterfaceExtensionFactory implements FactoryImplementingGenericInterfaceExtension.MyFactory { private final Provider sProvider; + @Inject FactoryImplementingGenericInterfaceExtensionFactory(Provider sProvider) { this.sProvider = checkNotNull(sProvider, 1); } + FactoryImplementingGenericInterfaceExtension create(Integer i) { return new FactoryImplementingGenericInterfaceExtension( checkNotNull(sProvider.get(), 1), checkNotNull(i, 2)); } + @Override public FactoryImplementingGenericInterfaceExtension make(Integer arg) { return create(arg); diff --git a/factory/src/test/resources/expected/Generics_ExplicitFooImplFactory.java b/factory/src/test/resources/expected/Generics_ExplicitFooImplFactory.java new file mode 100644 index 0000000000..00c6d92c48 --- /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 + implements Generics.FooFactory { + private final Provider unusedProvider; + + @Inject + Generics_ExplicitFooImplFactory(Provider unusedProvider) { + this.unusedProvider = checkNotNull(unusedProvider, 1); + } + + @Override + public Generics.ExplicitFooImpl create() { + return new Generics.ExplicitFooImpl(checkNotNull(unusedProvider.get(), 1)); + } + + private static 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 0000000000..2fb560a7cc --- /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 implements Generics.FooFactory { + @Inject + Generics_FooImplFactory() { + } + + @Override + public Generics.FooImpl create() { + return new Generics.FooImpl(); + } +} 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 0000000000..b338454fd1 --- /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 extends Generics.FooFactoryClass { + @Inject + Generics_FooImplWithClassFactory() { + } + + @Override + public Generics.FooImplWithClass create() { + return new Generics.FooImplWithClass(); + } +} diff --git a/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java b/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java index 19f2c1380b..ec4089b797 100644 --- a/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java +++ b/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java @@ -23,15 +23,18 @@ * @author Gregory Kick */ @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" -) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class MixedDepsImplementingInterfacesFactory - implements MixedDepsImplementingInterfaces.FromInt, MixedDepsImplementingInterfaces.FromObject, - MixedDepsImplementingInterfaces.MarkerA, MixedDepsImplementingInterfaces.MarkerB { + implements MixedDepsImplementingInterfaces.FromInt, + MixedDepsImplementingInterfaces.FromObject, + MixedDepsImplementingInterfaces.MarkerA, + MixedDepsImplementingInterfaces.MarkerB { private final Provider sProvider; - @Inject MixedDepsImplementingInterfacesFactory(Provider sProvider) { + @Inject + MixedDepsImplementingInterfacesFactory(Provider sProvider) { this.sProvider = checkNotNull(sProvider, 1); } @@ -43,11 +46,13 @@ MixedDepsImplementingInterfaces create(Object o) { return new MixedDepsImplementingInterfaces(checkNotNull(o, 1)); } - @Override public MixedDepsImplementingInterfaces fromInt(int i) { + @Override + public MixedDepsImplementingInterfaces fromInt(int i) { return create(i); } - @Override public MixedDepsImplementingInterfaces fromObject(Object o) { + @Override + public MixedDepsImplementingInterfaces fromObject(Object o) { return create(o); } diff --git a/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java b/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java index fac6e13a23..3eaf3afa51 100644 --- a/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java +++ b/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java @@ -22,7 +22,7 @@ @Generated( value = "com.google.auto.factory.processor.AutoFactoryProcessor", comments = "https://github.com/google/auto/tree/master/factory" -) + ) final class MultipleFactoriesConflictingParameterNamesFactory { private final Provider stringProvider; diff --git a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java index 5ee2b2fe93..6fcfb03699 100644 --- a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java +++ b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java @@ -19,9 +19,9 @@ import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class MultipleFactoriesImplementingInterface_ClassAFactory implements MultipleFactoriesImplementingInterface.Base.Factory { @Inject diff --git a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java index f6540683af..566468919d 100644 --- a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java +++ b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java @@ -19,9 +19,9 @@ import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class MultipleFactoriesImplementingInterface_ClassBFactory implements MultipleFactoriesImplementingInterface.Base.Factory { @Inject diff --git a/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java b/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java index de7bad7299..97cc8ac270 100644 --- a/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java +++ b/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java @@ -20,9 +20,9 @@ import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class MultipleProvidedParamsSameKeyFactory { private final Provider java_lang_StringProvider; diff --git a/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java b/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java index bf6a46819d..fe7aa1a9cb 100644 --- a/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java +++ b/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java @@ -19,11 +19,12 @@ import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class NestedClassCustomNamedFactory { - @Inject NestedClassCustomNamedFactory() {} + @Inject + NestedClassCustomNamedFactory() {} NestedClasses.SimpleNestedClassWithCustomFactory create() { return new NestedClasses.SimpleNestedClassWithCustomFactory(); diff --git a/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java b/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java index 041737feea..41ecc52e34 100644 --- a/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java +++ b/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java @@ -19,12 +19,13 @@ import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class NestedClasses_SimpleNestedClassFactory { - @Inject NestedClasses_SimpleNestedClassFactory() {} - + @Inject + NestedClasses_SimpleNestedClassFactory() {} + NestedClasses.SimpleNestedClass create() { return new NestedClasses.SimpleNestedClass(); } diff --git a/factory/src/test/resources/expected/OnlyPrimitivesFactory.java b/factory/src/test/resources/expected/OnlyPrimitivesFactory.java index ec60c58e51..b931a2223f 100644 --- a/factory/src/test/resources/expected/OnlyPrimitivesFactory.java +++ b/factory/src/test/resources/expected/OnlyPrimitivesFactory.java @@ -19,11 +19,12 @@ import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class OnlyPrimitivesFactory { - @Inject OnlyPrimitivesFactory() {} + @Inject + OnlyPrimitivesFactory() {} OnlyPrimitives create(int i, long l) { return new OnlyPrimitives(i, l); diff --git a/factory/src/test/resources/expected/ParameterAnnotationsFactory.java b/factory/src/test/resources/expected/ParameterAnnotationsFactory.java new file mode 100644 index 0000000000..9a4e76a23d --- /dev/null +++ b/factory/src/test/resources/expected/ParameterAnnotationsFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tests; + +import javax.annotation.processing.Generated; +import javax.inject.Inject; +import javax.inject.Provider; + +@Generated( + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) +final class ParameterAnnotationsFactory { + private final Provider<@ParameterAnnotations.NullableType String> fooProvider; + + @Inject + ParameterAnnotationsFactory(Provider<@ParameterAnnotations.NullableType String> fooProvider) { + this.fooProvider = checkNotNull(fooProvider, 1); + } + + ParameterAnnotations create(@ParameterAnnotations.NullableParameter Integer bar, @ParameterAnnotations.Nullable Long baz, @ParameterAnnotations.NullableType Thread buh) { + return new ParameterAnnotations(checkNotNull(fooProvider.get(), 1), checkNotNull(bar, 2), baz, checkNotNull(buh, 4)); + } + + private static 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/ProviderArgumentToCreateMethodFactory.java b/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java index 4d1a4cf53e..75a6291c8d 100644 --- a/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java +++ b/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java @@ -22,10 +22,11 @@ @Generated( value = "com.google.auto.factory.processor.AutoFactoryProcessor", comments = "https://github.com/google/auto/tree/master/factory" -) + ) final class ProviderArgumentToCreateMethodFactory - implements ProviderArgumentToCreateMethod.CustomCreator{ - @Inject ProviderArgumentToCreateMethodFactory() {} + implements ProviderArgumentToCreateMethod.CustomCreator { + @Inject + ProviderArgumentToCreateMethodFactory() {} ProviderArgumentToCreateMethod create(Provider stringProvider) { return new ProviderArgumentToCreateMethod(checkNotNull(stringProvider, 1)); diff --git a/factory/src/test/resources/expected/PublicClassFactory.java b/factory/src/test/resources/expected/PublicClassFactory.java index 06671dc6c7..9e5c113d1d 100644 --- a/factory/src/test/resources/expected/PublicClassFactory.java +++ b/factory/src/test/resources/expected/PublicClassFactory.java @@ -19,11 +19,12 @@ import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) public final class PublicClassFactory { - @Inject public PublicClassFactory() {} + @Inject + public PublicClassFactory() {} public PublicClass create() { return new PublicClass(); diff --git a/factory/src/test/resources/expected/SimpleClassFactory.java b/factory/src/test/resources/expected/SimpleClassFactory.java index 308d2cdc68..4741b752a6 100644 --- a/factory/src/test/resources/expected/SimpleClassFactory.java +++ b/factory/src/test/resources/expected/SimpleClassFactory.java @@ -19,11 +19,12 @@ import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassFactory { - @Inject SimpleClassFactory() {} + @Inject + SimpleClassFactory() {} SimpleClass create() { return new SimpleClass(); diff --git a/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java b/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java index 3fd72a2ff8..1770138786 100644 --- a/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java +++ b/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java @@ -20,12 +20,13 @@ import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassImplementingMarkerFactory implements RandomAccess { - @Inject SimpleClassImplementingMarkerFactory() {} - + @Inject + SimpleClassImplementingMarkerFactory() {} + SimpleClassImplementingMarker create() { return new SimpleClassImplementingMarker(); } diff --git a/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java b/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java index 1b13c2f005..7dd91bea8c 100644 --- a/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java +++ b/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java @@ -19,18 +19,20 @@ import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassImplementingSimpleInterfaceFactory implements SimpleClassImplementingSimpleInterface.SimpleInterface { - @Inject SimpleClassImplementingSimpleInterfaceFactory() {} - + @Inject + SimpleClassImplementingSimpleInterfaceFactory() {} + SimpleClassImplementingSimpleInterface create() { return new SimpleClassImplementingSimpleInterface(); } - @Override public SimpleClassImplementingSimpleInterface newInstance() { + @Override + public SimpleClassImplementingSimpleInterface newInstance() { return create(); } } diff --git a/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java b/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java index ccdea61c1a..b69ea32622 100644 --- a/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java +++ b/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java @@ -20,14 +20,14 @@ import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassMixedDepsFactory { private final Provider providedDepAProvider; - @Inject SimpleClassMixedDepsFactory( - @AQualifier Provider providedDepAProvider) { + @Inject + SimpleClassMixedDepsFactory(@AQualifier Provider providedDepAProvider) { this.providedDepAProvider = checkNotNull(providedDepAProvider, 1); } diff --git a/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java b/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java index d323812e4c..5ab9030688 100644 --- a/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java +++ b/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java @@ -19,11 +19,12 @@ import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) class SimpleClassNonFinalFactory { - @Inject SimpleClassNonFinalFactory() {} + @Inject + SimpleClassNonFinalFactory() {} SimpleClassNonFinal create() { return new SimpleClassNonFinal(); diff --git a/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java b/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java index e3540386eb..5b9559642f 100644 --- a/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java +++ b/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java @@ -21,9 +21,9 @@ import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassNullableParametersFactory { private final Provider providedNullableProvider; diff --git a/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java b/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java index 3260c36ec5..9cc8a166d8 100644 --- a/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java +++ b/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java @@ -19,11 +19,12 @@ import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassPassedDepsFactory { - @Inject SimpleClassPassedDepsFactory() {} + @Inject + SimpleClassPassedDepsFactory() {} SimpleClassPassedDeps create(String depA, String depB) { return new SimpleClassPassedDeps(checkNotNull(depA, 1), checkNotNull(depB, 2)); diff --git a/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java b/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java index 5376570040..52448aada6 100644 --- a/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java +++ b/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java @@ -20,15 +20,15 @@ import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassProvidedDepsFactory { private final Provider providedPrimitiveAProvider; private final Provider providedPrimitiveBProvider; private final Provider providedDepAProvider; private final Provider providedDepBProvider; - + @Inject SimpleClassProvidedDepsFactory( @AQualifier Provider providedPrimitiveAProvider, @@ -40,7 +40,7 @@ final class SimpleClassProvidedDepsFactory { this.providedDepAProvider = checkNotNull(providedDepAProvider, 3); this.providedDepBProvider = checkNotNull(providedDepBProvider, 4); } - + SimpleClassProvidedDeps create() { return new SimpleClassProvidedDeps( checkNotNull(providedPrimitiveAProvider.get(), 1), diff --git a/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java b/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java index aafdcec290..7bf2372cb2 100644 --- a/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java +++ b/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java @@ -20,9 +20,9 @@ import javax.inject.Provider; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassProvidedProviderDepsFactory { private final Provider providedDepAProvider; private final Provider providedDepBProvider; diff --git a/factory/src/test/resources/expected/SimpleClassThrowsFactory.java b/factory/src/test/resources/expected/SimpleClassThrowsFactory.java index d54dd528f9..eda503a4f9 100644 --- a/factory/src/test/resources/expected/SimpleClassThrowsFactory.java +++ b/factory/src/test/resources/expected/SimpleClassThrowsFactory.java @@ -20,11 +20,12 @@ import javax.inject.Inject; @Generated( - value = "com.google.auto.factory.processor.AutoFactoryProcessor", - comments = "https://github.com/google/auto/tree/master/factory" - ) + value = "com.google.auto.factory.processor.AutoFactoryProcessor", + comments = "https://github.com/google/auto/tree/master/factory" + ) final class SimpleClassThrowsFactory { - @Inject SimpleClassThrowsFactory() {} + @Inject + SimpleClassThrowsFactory() {} SimpleClassThrows create() throws IOException, InterruptedException { return new SimpleClassThrows(); diff --git a/factory/src/test/resources/expected/SimpleClassVarargsFactory.java b/factory/src/test/resources/expected/SimpleClassVarargsFactory.java index 51c7f466b8..ac7c4bdc6e 100644 --- a/factory/src/test/resources/expected/SimpleClassVarargsFactory.java +++ b/factory/src/test/resources/expected/SimpleClassVarargsFactory.java @@ -21,9 +21,10 @@ @Generated( value = "com.google.auto.factory.processor.AutoFactoryProcessor", comments = "https://github.com/google/auto/tree/master/factory" -) + ) final class SimpleClassVarargsFactory implements SimpleClassVarargs.InterfaceWithVarargs { - @Inject SimpleClassVarargsFactory() {} + @Inject + SimpleClassVarargsFactory() {} SimpleClassVarargs create(String... args) { return new SimpleClassVarargs(checkNotNull(args, 1)); diff --git a/factory/src/test/resources/good/ConstructorAnnotated.java b/factory/src/test/resources/good/ConstructorAnnotated.java index fdc02f3518..ddb154f4c1 100644 --- a/factory/src/test/resources/good/ConstructorAnnotated.java +++ b/factory/src/test/resources/good/ConstructorAnnotated.java @@ -19,9 +19,17 @@ import com.google.auto.factory.Provided; final class ConstructorAnnotated { - @AutoFactory ConstructorAnnotated() {} + @AutoFactory + ConstructorAnnotated() {} + ConstructorAnnotated(Object obj) {} - @AutoFactory ConstructorAnnotated(String s) {} - @AutoFactory ConstructorAnnotated(@Provided Object obj, int i) {} - @AutoFactory ConstructorAnnotated(@Provided Object obj, char c) {} + + @AutoFactory + ConstructorAnnotated(String s) {} + + @AutoFactory + ConstructorAnnotated(@Provided Object obj, int i) {} + + @AutoFactory + ConstructorAnnotated(@Provided Object obj, char c) {} } diff --git a/factory/src/test/resources/good/ConstructorAnnotatedNonFinal.java b/factory/src/test/resources/good/ConstructorAnnotatedNonFinal.java index 5bed1e603b..1b10e79e65 100644 --- a/factory/src/test/resources/good/ConstructorAnnotatedNonFinal.java +++ b/factory/src/test/resources/good/ConstructorAnnotatedNonFinal.java @@ -19,9 +19,17 @@ import com.google.auto.factory.Provided; final class ConstructorAnnotatedNonFinal { - @AutoFactory(allowSubclasses = true) ConstructorAnnotatedNonFinal() {} + @AutoFactory(allowSubclasses = true) + ConstructorAnnotatedNonFinal() {} + ConstructorAnnotatedNonFinal(Object obj) {} - @AutoFactory(allowSubclasses = true) ConstructorAnnotatedNonFinal(String s) {} - @AutoFactory(allowSubclasses = true) ConstructorAnnotatedNonFinal(@Provided Object obj, int i) {} - @AutoFactory(allowSubclasses = true) ConstructorAnnotatedNonFinal(@Provided Object obj, char c) {} + + @AutoFactory(allowSubclasses = true) + ConstructorAnnotatedNonFinal(String s) {} + + @AutoFactory(allowSubclasses = true) + ConstructorAnnotatedNonFinal(@Provided Object obj, int i) {} + + @AutoFactory(allowSubclasses = true) + ConstructorAnnotatedNonFinal(@Provided Object obj, char c) {} } diff --git a/factory/src/test/resources/good/ConstructorAnnotatedThrows.java b/factory/src/test/resources/good/ConstructorAnnotatedThrows.java index 1a22f56279..58a52d0b90 100644 --- a/factory/src/test/resources/good/ConstructorAnnotatedThrows.java +++ b/factory/src/test/resources/good/ConstructorAnnotatedThrows.java @@ -20,10 +20,17 @@ import java.io.IOException; final class ConstructorAnnotatedThrows { - @AutoFactory ConstructorAnnotatedThrows() throws IOException, InterruptedException {} + @AutoFactory + ConstructorAnnotatedThrows() throws IOException, InterruptedException {} + ConstructorAnnotatedThrows(Object obj) {} - @AutoFactory ConstructorAnnotatedThrows(String s) {} - @AutoFactory ConstructorAnnotatedThrows(@Provided Object obj, int i) throws IOException {} - @AutoFactory ConstructorAnnotatedThrows(@Provided Object obj, char c) - throws InterruptedException {} + + @AutoFactory + ConstructorAnnotatedThrows(String s) {} + + @AutoFactory + ConstructorAnnotatedThrows(@Provided Object obj, int i) throws IOException {} + + @AutoFactory + ConstructorAnnotatedThrows(@Provided Object obj, char c) throws InterruptedException {} } diff --git a/factory/src/test/resources/good/FactoryExtendingAbstractClass.java b/factory/src/test/resources/good/FactoryExtendingAbstractClass.java index 5511e99e5d..bd3a4dc723 100644 --- a/factory/src/test/resources/good/FactoryExtendingAbstractClass.java +++ b/factory/src/test/resources/good/FactoryExtendingAbstractClass.java @@ -20,7 +20,7 @@ @AutoFactory(extending = AbstractFactory.class) final class FactoryExtendingAbstractClass { - static abstract class AbstractFactory { + abstract static class AbstractFactory { abstract FactoryExtendingAbstractClass newInstance(); } } diff --git a/factory/src/test/resources/good/FactoryExtendingAbstractClassThrows.java b/factory/src/test/resources/good/FactoryExtendingAbstractClassThrows.java index 52ce2aaa84..2ac43f3f44 100644 --- a/factory/src/test/resources/good/FactoryExtendingAbstractClassThrows.java +++ b/factory/src/test/resources/good/FactoryExtendingAbstractClassThrows.java @@ -23,7 +23,7 @@ final class FactoryExtendingAbstractClassThrows { FactoryExtendingAbstractClassThrows() throws IOException, InterruptedException {} - static abstract class AbstractFactory { + abstract static class AbstractFactory { abstract FactoryExtendingAbstractClassThrows newInstance() throws Exception; } } diff --git a/factory/src/test/resources/good/FactoryExtendingAbstractClassWithMultipleConstructors.java b/factory/src/test/resources/good/FactoryExtendingAbstractClassWithMultipleConstructors.java index 43e94ce162..20e0b838c3 100644 --- a/factory/src/test/resources/good/FactoryExtendingAbstractClassWithMultipleConstructors.java +++ b/factory/src/test/resources/good/FactoryExtendingAbstractClassWithMultipleConstructors.java @@ -20,10 +20,11 @@ @AutoFactory(extending = AbstractFactory.class) final class FactoryExtendingAbstractClassWithMultipleConstructors { - static abstract class AbstractFactory { + abstract static class AbstractFactory { protected AbstractFactory(Object obj) {} + protected AbstractFactory() {} - + abstract FactoryExtendingAbstractClassWithMultipleConstructors newInstance(); } } diff --git a/factory/src/test/resources/good/FactoryImplementingCreateMethod.java b/factory/src/test/resources/good/FactoryImplementingCreateMethod.java index db15eefedb..d26596670e 100644 --- a/factory/src/test/resources/good/FactoryImplementingCreateMethod.java +++ b/factory/src/test/resources/good/FactoryImplementingCreateMethod.java @@ -26,7 +26,7 @@ interface FactoryInterfaceWithCreateMethod { Interface create(); Interface create(int a); - + Interface create(List generic); } diff --git a/factory/src/test/resources/good/Generics.java b/factory/src/test/resources/good/Generics.java new file mode 100644 index 0000000000..638302fe17 --- /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 {} + + interface FooFactory { + Foo create(); + } + + // The generated FooImplFactory should also have an type parameter, so we can + // have FooImplFactory implements FooFactory. + @AutoFactory(implementing = FooFactory.class) + static final class FooImpl implements Foo { + FooImpl() {} + } + + // The generated ExplicitFooImplFactory should have an type parameter, which + // serves both for FooFactory and for Provider in the constructor. + @AutoFactory(implementing = FooFactory.class) + static final class ExplicitFooImpl implements Foo { + ExplicitFooImpl(@Provided M unused) {} + } + + abstract static class FooFactoryClass { + abstract Foo create(); + } + + @AutoFactory(extending = FooFactoryClass.class) + static final class FooImplWithClass implements Foo {} +} diff --git a/factory/src/test/resources/good/MixedDepsImplementingInterfaces.java b/factory/src/test/resources/good/MixedDepsImplementingInterfaces.java index c7435edd1c..05ee0df9a0 100644 --- a/factory/src/test/resources/good/MixedDepsImplementingInterfaces.java +++ b/factory/src/test/resources/good/MixedDepsImplementingInterfaces.java @@ -24,18 +24,18 @@ final class MixedDepsImplementingInterfaces { @AutoFactory(implementing = {FromInt.class, MarkerA.class}) MixedDepsImplementingInterfaces(@Provided String s, int i) {} - + @AutoFactory(implementing = {FromObject.class, MarkerB.class}) MixedDepsImplementingInterfaces(Object o) {} interface FromInt { MixedDepsImplementingInterfaces fromInt(int i); } - + interface FromObject { MixedDepsImplementingInterfaces fromObject(Object o); } - + interface MarkerA {} interface MarkerB {} diff --git a/factory/src/test/resources/good/MultipleFactoriesImplementingInterface.java b/factory/src/test/resources/good/MultipleFactoriesImplementingInterface.java index 2eecf1ae82..f7709ec04b 100644 --- a/factory/src/test/resources/good/MultipleFactoriesImplementingInterface.java +++ b/factory/src/test/resources/good/MultipleFactoriesImplementingInterface.java @@ -17,7 +17,7 @@ import com.google.auto.factory.AutoFactory; -class MultipleFactoriesImplementingInterface { +class MultipleFactoriesImplementingInterface { static interface Base { static interface Factory { public abstract Base abstractNonDefaultCreate(); @@ -25,8 +25,8 @@ static interface Factory { } @AutoFactory(implementing = Base.Factory.class) - static class ClassA implements Base { } + static class ClassA implements Base {} @AutoFactory(implementing = Base.Factory.class) static class ClassB implements Base {} -} +} diff --git a/factory/src/test/resources/good/MultipleProvidedParamsSameKey.java b/factory/src/test/resources/good/MultipleProvidedParamsSameKey.java index 21da6c1eae..b338d3440e 100644 --- a/factory/src/test/resources/good/MultipleProvidedParamsSameKey.java +++ b/factory/src/test/resources/good/MultipleProvidedParamsSameKey.java @@ -28,7 +28,6 @@ final class MultipleProvidedParamsSameKey { private final Provider providerOne; private final Provider providerTwo; - public MultipleProvidedParamsSameKey( @Provided String one, @Provided String two, diff --git a/factory/src/test/resources/good/ParameterAnnotations.java b/factory/src/test/resources/good/ParameterAnnotations.java new file mode 100644 index 0000000000..778261cae0 --- /dev/null +++ b/factory/src/test/resources/good/ParameterAnnotations.java @@ -0,0 +1,47 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tests; + +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.auto.factory.AutoFactory; +import com.google.auto.factory.Provided; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@AutoFactory +final class ParameterAnnotations { + @Retention(RUNTIME) + @Target(PARAMETER) + @interface NullableParameter {} + + // We have special treatment of @Nullable; make sure it doesn't get copied twice. + @Retention(RUNTIME) + @Target(PARAMETER) + @interface Nullable {} + + @Retention(RUNTIME) + @Target(TYPE_USE) + @interface NullableType {} + + ParameterAnnotations( + @Provided @NullableParameter @NullableType String foo, + @NullableParameter Integer bar, + @Nullable Long baz, + @NullableType Thread buh) {} +} diff --git a/factory/src/test/resources/good/SimpleClassImplementingMarker.java b/factory/src/test/resources/good/SimpleClassImplementingMarker.java index 24e3abc035..52a1fd5a6f 100644 --- a/factory/src/test/resources/good/SimpleClassImplementingMarker.java +++ b/factory/src/test/resources/good/SimpleClassImplementingMarker.java @@ -1,11 +1,11 @@ /* * Copyright 2013 Google LLC - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -17,5 +17,4 @@ import java.util.RandomAccess; @AutoFactory(implementing = RandomAccess.class) -class SimpleClassImplementingMarker { -} +class SimpleClassImplementingMarker {} diff --git a/factory/src/test/resources/good/SimpleClassProvidedDeps.java b/factory/src/test/resources/good/SimpleClassProvidedDeps.java index ffcefd2afa..fa9e0c41c4 100644 --- a/factory/src/test/resources/good/SimpleClassProvidedDeps.java +++ b/factory/src/test/resources/good/SimpleClassProvidedDeps.java @@ -1,11 +1,11 @@ /* * Copyright 2013 Google LLC - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under diff --git a/factory/src/test/resources/support/QualifierWithArgs.java b/factory/src/test/resources/support/QualifierWithArgs.java index 81e3f84bf9..89f54eb690 100644 --- a/factory/src/test/resources/support/QualifierWithArgs.java +++ b/factory/src/test/resources/support/QualifierWithArgs.java @@ -29,5 +29,6 @@ @Retention(RUNTIME) @interface QualifierWithArgs { String name(); + int count(); } diff --git a/service/pom.xml b/service/pom.xml index b0afaaf76a..4b0ecb6e47 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -37,8 +37,8 @@ UTF-8 1.8 - 30.1.1-jre - 1.1.2 + 31.1-jre + 1.1.3 @@ -111,7 +111,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.10.1 ${java.version} ${java.version} @@ -123,14 +123,14 @@ org.codehaus.plexus plexus-java - 1.0.7 + 1.1.1 org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.3.0 diff --git a/service/processor/pom.xml b/service/processor/pom.xml index a342a254a0..cef19adeb6 100644 --- a/service/processor/pom.xml +++ b/service/processor/pom.xml @@ -49,7 +49,7 @@ com.google.auto auto-common - 1.0 + 1.2.1 com.google.guava 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 a396c9996b..85a24cb455 100644 --- a/service/processor/src/main/java/com/google/auto/service/processor/AutoServiceProcessor.java +++ b/service/processor/src/main/java/com/google/auto/service/processor/AutoServiceProcessor.java @@ -17,20 +17,23 @@ import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; import static com.google.auto.common.MoreElements.getAnnotationMirror; +import static com.google.auto.common.MoreStreams.toImmutableSet; import static com.google.common.base.Throwables.getStackTraceAsString; -import static com.google.common.collect.ImmutableSet.toImmutableSet; import com.google.auto.common.MoreElements; 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; @@ -59,15 +62,17 @@ *

* Processor Options:

    *
  • {@code -Adebug} - turns on debug statements
  • -
  • {@code -Averify=true} - turns on extra verification
  • + *
  • {@code -Averify=true} - turns on extra verification
  • *
*/ -@SupportedOptions({ "debug", "verify" }) +@SupportedOptions({"debug", "verify"}) public class AutoServiceProcessor extends AbstractProcessor { @VisibleForTesting static final String MISSING_SERVICES_ERROR = "No service interfaces provided for element!"; + private final List 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 boolean process(Set annotations, RoundEnvironment 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 exceptionStacks() { + return ImmutableList.copyOf(exceptionStacks); + } + private void processImpl(Set annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver()) { generateConfigFiles(); @@ -122,8 +133,8 @@ private void processImpl(Set annotations, RoundEnvironmen } } - private void processAnnotations(Set annotations, - RoundEnvironment roundEnv) { + private void processAnnotations( + Set annotations, RoundEnvironment roundEnv) { Set elements = roundEnv.getElementsAnnotatedWith(AutoService.class); @@ -148,9 +159,11 @@ private void processAnnotations(Set annotations, if (checkImplementer(providerImplementer, providerType, annotationMirror)) { providers.put(getBinaryName(providerType), getBinaryName(providerImplementer)); } else { - String message = "ServiceProviders must implement their service provider interface. " - + providerImplementer.getQualifiedName() + " does not implement " - + providerType.getQualifiedName(); + String message = + "ServiceProviders must implement their service provider interface. " + + providerImplementer.getQualifiedName() + + " does not implement " + + providerType.getQualifiedName(); error(message, e, annotationMirror); } } @@ -170,8 +183,8 @@ private void generateConfigFiles() { // before we attempt to get the resource in case the behavior // of filer.getResource does change to match the spec, but there's // no good way to resolve CLASS_OUTPUT without first getting a resource. - FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", - resourceFile); + FileObject existingFile = + filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile); log("Looking for existing resource file at " + existingFile.toUri()); Set oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream()); log("Existing service entries: " + oldServices); @@ -186,15 +199,14 @@ private void generateConfigFiles() { } Set newServices = new HashSet<>(providers.get(providerInterface)); - if (allServices.containsAll(newServices)) { + if (!allServices.addAll(newServices)) { log("No new service entries being added."); - return; + continue; } - allServices.addAll(newServices); log("New service file contents: " + allServices); - FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "", - resourceFile); + FileObject fileObject = + filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceFile); try (OutputStream out = fileObject.openOutputStream()) { ServicesFiles.writeServiceFile(allServices, out); } @@ -290,7 +302,7 @@ private String getBinaryNameImpl(TypeElement element, String className) { private ImmutableSet getValueFieldOfClasses(AnnotationMirror annotationMirror) { return getAnnotationValue(annotationMirror, "value") .accept( - new SimpleAnnotationValueVisitor8, Void>() { + new SimpleAnnotationValueVisitor8, Void>(ImmutableSet.of()) { @Override public ImmutableSet visitType(TypeMirror typeMirror, Void v) { // TODO(ronshapiro): class literals may not always be declared types, i.e. @@ -301,8 +313,7 @@ public ImmutableSet visitType(TypeMirror typeMirror, Void v) { @Override public ImmutableSet visitArray( List values, Void v) { - return values - .stream() + return values.stream() .flatMap(value -> value.accept(this, null).stream()) .collect(toImmutableSet()); } diff --git a/service/processor/src/main/java/com/google/auto/service/processor/ServicesFiles.java b/service/processor/src/main/java/com/google/auto/service/processor/ServicesFiles.java index c61b3ba2a1..75d6cca747 100644 --- a/service/processor/src/main/java/com/google/auto/service/processor/ServicesFiles.java +++ b/service/processor/src/main/java/com/google/auto/service/processor/ServicesFiles.java @@ -35,7 +35,7 @@ final class ServicesFiles { public static final String SERVICES_PATH = "META-INF/services"; - private ServicesFiles() { } + private ServicesFiles() {} /** * Returns an absolute path to a service file given the class @@ -96,4 +96,4 @@ static void writeServiceFile(Collection services, OutputStream output) } writer.flush(); } -} \ No newline at end of file +} diff --git a/service/processor/src/main/java/com/google/auto/service/processor/package-info.java b/service/processor/src/main/java/com/google/auto/service/processor/package-info.java index a9f0adb562..453c95e8f1 100644 --- a/service/processor/src/main/java/com/google/auto/service/processor/package-info.java +++ b/service/processor/src/main/java/com/google/auto/service/processor/package-info.java @@ -15,4 +15,4 @@ * This package contains the annotation processor that implements the * {@link com.google.auto.service.AutoService} API. */ -package com.google.auto.service.processor; \ No newline at end of file +package com.google.auto.service.processor; diff --git a/service/processor/src/test/java/com/google/auto/service/processor/AutoServiceProcessorTest.java b/service/processor/src/test/java/com/google/auto/service/processor/AutoServiceProcessorTest.java index 35615689c0..7a176dd93c 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 @@ 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 void nestedGenericWithVerifyOptionAndSuppressWarnings() { .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/AnotherService.java b/service/processor/src/test/resources/test/AnotherService.java index c096c223bf..de80f06d6f 100644 --- a/service/processor/src/test/resources/test/AnotherService.java +++ b/service/processor/src/test/resources/test/AnotherService.java @@ -15,4 +15,4 @@ */ package test; -interface AnotherService { } \ No newline at end of file +interface AnotherService {} diff --git a/service/processor/src/test/resources/test/AnotherServiceProvider.java b/service/processor/src/test/resources/test/AnotherServiceProvider.java index c5e5c1175e..2a023e4362 100644 --- a/service/processor/src/test/resources/test/AnotherServiceProvider.java +++ b/service/processor/src/test/resources/test/AnotherServiceProvider.java @@ -18,4 +18,4 @@ import com.google.auto.service.AutoService; @AutoService(AnotherService.class) -public class AnotherServiceProvider implements AnotherService { } \ No newline at end of file +public class AnotherServiceProvider implements AnotherService {} diff --git a/service/processor/src/test/resources/test/Enclosing.java b/service/processor/src/test/resources/test/Enclosing.java index 26dd5852e4..24202a59c5 100644 --- a/service/processor/src/test/resources/test/Enclosing.java +++ b/service/processor/src/test/resources/test/Enclosing.java @@ -19,5 +19,5 @@ public class Enclosing { @AutoService(SomeService.class) - public static class NestedSomeServiceProvider implements SomeService { } -} \ No newline at end of file + public static class NestedSomeServiceProvider implements SomeService {} +} diff --git a/service/processor/src/test/resources/test/GenericServiceProviderWithMissingServiceClass.java b/service/processor/src/test/resources/test/GenericServiceProviderWithMissingServiceClass.java new file mode 100644 index 0000000000..3ca344548d --- /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 implements MissingServiceClass {} diff --git a/service/processor/src/test/resources/test/SomeService.java b/service/processor/src/test/resources/test/SomeService.java index d29c409780..d81cac409c 100644 --- a/service/processor/src/test/resources/test/SomeService.java +++ b/service/processor/src/test/resources/test/SomeService.java @@ -15,4 +15,4 @@ */ package test; -interface SomeService { } \ No newline at end of file +interface SomeService {} diff --git a/service/processor/src/test/resources/test/SomeServiceProvider1.java b/service/processor/src/test/resources/test/SomeServiceProvider1.java index 008136bebd..fc2c843cf7 100644 --- a/service/processor/src/test/resources/test/SomeServiceProvider1.java +++ b/service/processor/src/test/resources/test/SomeServiceProvider1.java @@ -18,4 +18,4 @@ import com.google.auto.service.AutoService; @AutoService(SomeService.class) -public class SomeServiceProvider1 implements SomeService { } \ No newline at end of file +public class SomeServiceProvider1 implements SomeService {} diff --git a/service/processor/src/test/resources/test/SomeServiceProvider2.java b/service/processor/src/test/resources/test/SomeServiceProvider2.java index 5444996bb0..b7097d4155 100644 --- a/service/processor/src/test/resources/test/SomeServiceProvider2.java +++ b/service/processor/src/test/resources/test/SomeServiceProvider2.java @@ -18,4 +18,4 @@ import com.google.auto.service.AutoService; @AutoService(SomeService.class) -public class SomeServiceProvider2 implements SomeService { } \ No newline at end of file +public class SomeServiceProvider2 implements SomeService {} diff --git a/util/mvn-deploy.sh b/util/mvn-deploy.sh deleted file mode 100755 index e7160ebeca..0000000000 --- a/util/mvn-deploy.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -if [ $# -lt 1 ]; then - echo "usage $0 [ ...]" - exit 1; -fi -key=${1} -shift -params=${@} - -#validate key -keystatus=$(gpg --list-keys | grep ${key} | awk '{print $1}') -if [ "${keystatus}" != "pub" ]; then - echo "Could not find public key with label ${key}" - echo -n "Available keys from: " - gpg --list-keys | grep --invert-match '^sub' - - exit 1 -fi - -mvn ${params} clean site:jar -P sonatype-oss-release -Dgpg.keyname=${key} deploy diff --git a/value/README.md b/value/README.md index f6c00c30a1..bdf936c4af 100644 --- a/value/README.md +++ b/value/README.md @@ -21,5 +21,12 @@ less code and less room for error, while **not restricting your freedom** to code almost any aspect of your class exactly the way you want it. For more information, consult the -[detailed -documentation](userguide/index.md) +[detailed documentation](userguide/index.md). + +**Note:** If you are using Kotlin then its +[data classes](https://kotlinlang.org/docs/data-classes.html) are usually more +appropriate than AutoValue. Likewise, if you are using a version of Java that +has [records](https://docs.oracle.com/en/java/javase/16/language/records.html), +then those are usually more appropriate. You can still use +[AutoBuilder](userguide/autobuilder.md) +to make builders for data classes or records. diff --git a/value/annotations/pom.xml b/value/annotations/pom.xml index 3751e00462..4fc183b687 100644 --- a/value/annotations/pom.xml +++ b/value/annotations/pom.xml @@ -24,12 +24,11 @@ HEAD-SNAPSHOT - com.google.auto.value auto-value-annotations HEAD-SNAPSHOT AutoValue Annotations - Immutable value-type code generation for Java 1.6+. + Immutable value-type code generation for Java 1.7+. https://github.com/google/auto/tree/master/value diff --git a/value/pom.xml b/value/pom.xml index 8f516187b3..04a371567d 100644 --- a/value/pom.xml +++ b/value/pom.xml @@ -18,12 +18,6 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - - org.sonatype.oss - oss-parent - 7 - - com.google.auto.value auto-value-parent HEAD-SNAPSHOT @@ -37,8 +31,8 @@ UTF-8 1.8 - 30.1.1-jre - 1.1.2 + 31.1-jre + 1.1.3 @@ -65,6 +59,21 @@ http://www.google.com + + + eamonnmcmanus + Éamonn McManus + emcmanus@google.com + Google + http://www.google.com + + owner + developer + + -8 + + + annotations processor @@ -72,6 +81,19 @@ src/it/gwtserializer + + + sonatype-nexus-snapshots + Sonatype Nexus Snapshots + https://oss.sonatype.org/content/repositories/snapshots/ + + + sonatype-nexus-staging + Nexus Release Repository + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + @@ -124,11 +146,6 @@ junit 4.13.2
- - org.apache.velocity - velocity - 1.7 - @@ -138,7 +155,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.10.1 ${java.version} ${java.version} @@ -150,19 +167,19 @@ org.codehaus.plexus plexus-java - 1.0.7 + 1.1.1 org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.3.0 org.apache.maven.plugins maven-invoker-plugin - 3.2.2 + 3.3.0 true ${project.build.directory}/it @@ -189,4 +206,56 @@ + + + sonatype-oss-release + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.4.1 + + false + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.0.1 + + + sign-artifacts + verify + + sign + + + + + + + + diff --git a/value/processor/pom.xml b/value/processor/pom.xml index 9533facf8c..c98440daed 100644 --- a/value/processor/pom.xml +++ b/value/processor/pom.xml @@ -24,12 +24,11 @@ HEAD-SNAPSHOT - com.google.auto.value auto-value HEAD-SNAPSHOT AutoValue Processor - Immutable value-type code generation for Java 1.6+. + Immutable value-type code generation for Java 1.7+. https://github.com/google/auto/tree/master/value @@ -41,15 +40,15 @@
- 1.0 - 2.6.0 + 1.0.1 + 2.16 com.google.auto auto-common - 1.0 + 1.2.1 com.google.auto.service @@ -79,6 +78,16 @@ com.squareup javapoet + + org.jetbrains.kotlinx + kotlinx-metadata-jvm + 0.5.0 + + + org.ow2.asm + asm + 9.4 + com.google.auto.value @@ -92,11 +101,6 @@ ${errorprone.version} test - - org.apache.velocity - velocity - test - com.google.guava guava-testlib @@ -125,7 +129,7 @@ org.mockito mockito-core - 3.9.0 + 4.9.0 test @@ -212,8 +216,9 @@ maven-invoker-plugin - org.immutables.tools + org.apache.maven.plugins maven-shade-plugin + 3.4.1 package @@ -227,29 +232,45 @@ com.google.code.findbugs:jsr305 + + + + - - org.objectweb - autovalue.shaded.org.objectweb$ - com.google - autovalue.shaded.com.google$ + autovalue.shaded.com.google com.google.auto.value.** com.squareup.javapoet - autovalue.shaded.com.squareup.javapoet$ + autovalue.shaded.com.squareup.javapoet + + + kotlin + autovalue.shaded.kotlin + + + kotlinx + autovalue.shaded.kotlinx net.ltgt.gradle.incap - autovalue.shaded.net.ltgt.gradle.incap$ + autovalue.shaded.net.ltgt.gradle.incap org.checkerframework - autovalue.shaded.org.checkerframework$ + autovalue.shaded.org.checkerframework + + + org.jetbrains + autovalue.shaded.org.jetbrains + + + org.objectweb + autovalue.shaded.org.objectweb diff --git a/value/src/it/functional/pom.xml b/value/src/it/functional/pom.xml index 3cec480d65..f44d05cd7c 100644 --- a/value/src/it/functional/pom.xml +++ b/value/src/it/functional/pom.xml @@ -32,8 +32,7 @@ HEAD-SNAPSHOT Auto-Value Functional Integration Test - 1.4.32 - this-matches-nothing + 1.7.22 @@ -49,7 +48,7 @@ com.google.auto.service auto-service - 1.0 + 1.0.1 com.google.guava @@ -61,9 +60,9 @@ provided - com.google.gwt + org.gwtproject gwt-user - 2.9.0 + 2.10.0 junit @@ -93,20 +92,15 @@ dev.gradleplugins gradle-test-kit - 6.8.3 + 7.6 test org.mockito mockito-core - 3.9.0 + 4.9.0 test - - org.eclipse.jdt - ecj - 3.25.0 - com.google.escapevelocity escapevelocity @@ -124,7 +118,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.3.0 org.jetbrains.kotlin @@ -161,12 +155,12 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.10.1 org.codehaus.plexus plexus-java - 1.0.7 + 1.1.1 @@ -176,19 +170,16 @@ -Xlint:all -encoding utf8 - -Acom.google.auto.value.AutoBuilderIsUnstable true true - - ${exclude.tests} - + org.apache.maven.plugins maven-deploy-plugin - 2.8.2 + 3.0.0 true @@ -199,7 +190,6 @@ maven-surefire-plugin 2.22.2 - ${test.jvm.flags} ${project.version} @@ -216,12 +206,12 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.10.1 org.codehaus.plexus plexus-java - 1.0.7 + 1.1.1 @@ -234,32 +224,84 @@ true true + + + + + + + + + test-with-ecj + + [11,) + + + + + org.eclipse.jdt + ecj + 3.31.0 + test + + + + + + test-without-ecj + + (,11) + + + + + maven-compiler-plugin + - ${exclude.tests} + **/CompileWithEclipseTest.java + exclude-java8-tests (,1.7] - - **/AutoValueJava8Test.java - + + + + maven-compiler-plugin + + + **/AutoValueJava8Test.java + + + + + + open-modules [9,) - - --add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED - + + + + maven-surefire-plugin + + --add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + + + + + @@ -132,7 +132,7 @@ net.ltgt.gwt.maven gwt-maven-plugin - 1.0.0 + 1.0.1 @@ -144,7 +144,7 @@ org.apache.maven.plugins maven-deploy-plugin - 2.8.2 + 3.0.0 true 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 d36d8e285f..3360d80e74 100644 --- a/value/src/main/java/com/google/auto/value/AutoAnnotation.java +++ b/value/src/main/java/com/google/auto/value/AutoAnnotation.java @@ -71,8 +71,36 @@ * parameter corresponding to an array-valued annotation member, and the implementation of each such * member will also return a clone of the array. * + *

If your annotation has many elements, you may consider using {@code @AutoBuilder} instead of + * {@code @AutoAnnotation} to make it easier to construct instances. In that case, {@code default} + * values from the annotation will become default values for the values in the builder. For example: + * + *

+ * class Example {
+ *   {@code @interface} MyAnnotation {
+ *     String name() default "foo";
+ *     int number() default 23;
+ *   }
+ *
+ *   {@code @AutoBuilder(ofClass = MyAnnotation.class)}
+ *   interface MyAnnotationBuilder {
+ *     MyAnnotationBuilder name(String name);
+ *     MyAnnotationBuilder number(int number);
+ *     MyAnnotation build();
+ *   }
+ *
+ *   static MyAnnotationBuilder myAnnotationBuilder() {
+ *     return new AutoBuilder_Example_MyAnnotationBuilder();
+ *   }
+ * }
+ * 
+ * + * 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) */ +@Retention(RetentionPolicy.CLASS) @Target(ElementType.METHOD) -@Retention(RetentionPolicy.SOURCE) public @interface AutoAnnotation {} diff --git a/value/src/main/java/com/google/auto/value/AutoBuilder.java b/value/src/main/java/com/google/auto/value/AutoBuilder.java index b970900550..36107c1e86 100644 --- a/value/src/main/java/com/google/auto/value/AutoBuilder.java +++ b/value/src/main/java/com/google/auto/value/AutoBuilder.java @@ -22,7 +22,6 @@ /** * Specifies that the annotated interface or abstract class should be implemented as a builder. - * This is still unstable; uses outside Google may break. * *

A simple example: * diff --git a/value/src/main/java/com/google/auto/value/AutoValue.java b/value/src/main/java/com/google/auto/value/AutoValue.java index 45a677c960..d7541f6515 100644 --- a/value/src/main/java/com/google/auto/value/AutoValue.java +++ b/value/src/main/java/com/google/auto/value/AutoValue.java @@ -98,7 +98,7 @@ *

If you want to copy annotations from your {@literal @}AutoValue-annotated class's methods to * the generated fields in the AutoValue_... implementation, annotate your method * with {@literal @}AutoValue.CopyAnnotations. For example, if Example.java is:

-
+   *
    *   {@code @}Immutable
    *   {@code @}AutoValue
    *   abstract class Example {
diff --git a/value/src/main/java/com/google/auto/value/extension/memoized/Memoized.java b/value/src/main/java/com/google/auto/value/extension/memoized/Memoized.java
index ef16135a9d..cf24cb74f3 100644
--- a/value/src/main/java/com/google/auto/value/extension/memoized/Memoized.java
+++ b/value/src/main/java/com/google/auto/value/extension/memoized/Memoized.java
@@ -37,7 +37,7 @@
  * 
  *
  * 

If you want to memoize {@link #hashCode()} or {@link #toString()}, you can redeclare them, - * keeping them {@code abstract}, and annotate them with {@code @Memoize}. + * keeping them {@code abstract}, and annotate them with {@code @Memoized}. * *

If a {@code @Memoized} method is annotated with an annotation whose simple name is {@code * Nullable}, then {@code null} values will also be memoized. Otherwise, if the method returns diff --git a/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java index 50dab42981..acbe1c032c 100644 --- a/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java +++ b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java @@ -19,12 +19,12 @@ import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec; import static com.google.auto.common.MoreElements.getPackage; import static com.google.auto.common.MoreElements.isAnnotationPresent; +import static com.google.auto.common.MoreStreams.toImmutableList; +import static com.google.auto.common.MoreStreams.toImmutableSet; import static com.google.auto.value.extension.memoized.processor.ClassNames.MEMOIZED_NAME; import static com.google.auto.value.extension.memoized.processor.MemoizedValidator.getAnnotationMirror; import static com.google.common.base.Predicates.equalTo; import static com.google.common.base.Predicates.not; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Iterables.filter; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.Sets.union; @@ -597,9 +597,7 @@ private static boolean containsNullable(List annotat /** Translate a {@link TypeMirror} into a {@link TypeName}, including type annotations. */ private static TypeName annotatedType(TypeMirror type) { List annotations = - type.getAnnotationMirrors().stream() - .map(AnnotationSpec::get) - .collect(toList()); + type.getAnnotationMirrors().stream().map(AnnotationSpec::get).collect(toList()); return TypeName.get(type).annotated(annotations); } } diff --git a/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizedValidator.java b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizedValidator.java index 1aa5976c25..d250fbc622 100644 --- a/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizedValidator.java +++ b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizedValidator.java @@ -66,9 +66,7 @@ public SourceVersion getSupportedSourceVersion() { } private static boolean isAutoValue(Element element) { - return element - .getAnnotationMirrors() - .stream() + return element.getAnnotationMirrors().stream() .map(annotation -> MoreTypes.asTypeElement(annotation.getAnnotationType())) .anyMatch(type -> type.getQualifiedName().contentEquals("com.google.auto.value.AutoValue")); } diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/index.md b/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/index.md index 0282404b42..c74c4ac6d5 100644 --- a/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/index.md +++ b/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/index.md @@ -98,4 +98,4 @@ un-serializable types with [SerializerExtensions]. [`AutoValue`]: https://github.com/google/auto/tree/master/value [`SerializableAutoValue`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/SerializableAutoValue.java [`SerializableAutoValueExtension`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/extension/SerializableAutoValueExtension.java -[SerializerExtensions]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/userguide/serializer-extension.md +[SerializerExtensions]: serializer-extension diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java index 2f59e5d2d5..13752b6b4a 100644 --- a/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java +++ b/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java @@ -15,9 +15,9 @@ */ package com.google.auto.value.extension.serializable.processor; +import static com.google.auto.common.MoreStreams.toImmutableList; +import static com.google.auto.common.MoreStreams.toImmutableMap; import static com.google.auto.value.extension.serializable.processor.ClassNames.SERIALIZABLE_AUTO_VALUE_NAME; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableMap.toImmutableMap; import static java.util.stream.Collectors.joining; import com.google.auto.common.GeneratedAnnotationSpecs; @@ -114,7 +114,9 @@ private static final class Generator { .collect(toImmutableList()); TypeName classTypeName = - getClassTypeName(ClassName.get(context.packageName(), className), typeVariableNames); + getClassTypeName( + ClassName.get(context.packageName(), context.finalAutoValueClassName()), + typeVariableNames); this.proxyGenerator = new ProxyGenerator( classTypeName, typeVariableNames, propertyMirrors, buildSerializersMap()); diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java index 7ff4f19d90..a390adde2e 100644 --- a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java @@ -55,17 +55,22 @@ public Optional getSerializer( return Optional.empty(); } - return Optional.of(new ImmutableListSerializer(containedTypeSerializer, processingEnv)); + return Optional.of( + new ImmutableListSerializer(containedTypeSerializer, factory, processingEnv)); } private static class ImmutableListSerializer implements Serializer { private final Serializer containedTypeSerializer; + private final SerializerFactory factory; private final ProcessingEnvironment processingEnv; ImmutableListSerializer( - Serializer containedTypeSerializer, ProcessingEnvironment processingEnv) { + Serializer containedTypeSerializer, + SerializerFactory factory, + ProcessingEnvironment processingEnv) { this.containedTypeSerializer = containedTypeSerializer; + this.factory = factory; this.processingEnv = processingEnv; } @@ -81,7 +86,7 @@ public TypeMirror proxyFieldType() { @Override public CodeBlock toProxy(CodeBlock expression) { - CodeBlock element = CodeBlock.of("value$$"); + CodeBlock element = factory.newIdentifier("value"); return CodeBlock.of( "$L.stream().map($T.wrapper($L -> $L)).collect($T.toImmutableList())", expression, @@ -93,7 +98,7 @@ public CodeBlock toProxy(CodeBlock expression) { @Override public CodeBlock fromProxy(CodeBlock expression) { - CodeBlock element = CodeBlock.of("value$$"); + CodeBlock element = factory.newIdentifier("value"); return CodeBlock.of( "$L.stream().map($T.wrapper($L -> $L)).collect($T.toImmutableList())", expression, diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java index 9d571e3bb3..8d67e1033a 100644 --- a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java @@ -60,7 +60,7 @@ public Optional getSerializer( return Optional.of( new ImmutableMapSerializer( - keyType, valueType, keyTypeSerializer, valueTypeSerializer, processingEnv)); + keyType, valueType, keyTypeSerializer, valueTypeSerializer, factory, processingEnv)); } private static class ImmutableMapSerializer implements Serializer { @@ -71,6 +71,7 @@ private static class ImmutableMapSerializer implements Serializer { private final TypeMirror valueProxyType; private final Serializer keyTypeSerializer; private final Serializer valueTypeSerializer; + private final SerializerFactory factory; private final ProcessingEnvironment processingEnv; ImmutableMapSerializer( @@ -78,6 +79,7 @@ private static class ImmutableMapSerializer implements Serializer { TypeMirror valueType, Serializer keyTypeSerializer, Serializer valueTypeSerializer, + SerializerFactory factory, ProcessingEnvironment processingEnv) { this.keyType = keyType; this.valueType = valueType; @@ -85,6 +87,7 @@ private static class ImmutableMapSerializer implements Serializer { this.valueProxyType = valueTypeSerializer.proxyFieldType(); this.keyTypeSerializer = keyTypeSerializer; this.valueTypeSerializer = valueTypeSerializer; + this.factory = factory; this.processingEnv = processingEnv; } @@ -117,13 +120,15 @@ public CodeBlock fromProxy(CodeBlock expression) { generateValueMapFunction(valueProxyType, valueType, valueTypeSerializer::fromProxy)); } - private static CodeBlock generateKeyMapFunction( + private CodeBlock generateKeyMapFunction( TypeMirror originalType, TypeMirror transformedType, Function proxyMap) { - CodeBlock element = CodeBlock.of("element$$"); + CodeBlock element = factory.newIdentifier("element"); + CodeBlock value = factory.newIdentifier("value"); return CodeBlock.of( - "value$$ -> $T.<$T, $T>wrapper($L -> $L).apply(value$$.getKey())", + "$1L -> $2T.<$3T, $4T>wrapper($5L -> $6L).apply($1L.getKey())", + value, FunctionWithExceptions.class, originalType, transformedType, @@ -131,13 +136,15 @@ private static CodeBlock generateKeyMapFunction( proxyMap.apply(element)); } - private static CodeBlock generateValueMapFunction( + private CodeBlock generateValueMapFunction( TypeMirror originalType, TypeMirror transformedType, Function proxyMap) { - CodeBlock element = CodeBlock.of("element$$"); + CodeBlock element = factory.newIdentifier("element"); + CodeBlock value = factory.newIdentifier("value"); return CodeBlock.of( - "value$$ -> $T.<$T, $T>wrapper($L -> $L).apply(value$$.getValue())", + "$1L -> $2T.<$3T, $4T>wrapper($5L -> $6L).apply($1L.getValue())", + value, FunctionWithExceptions.class, originalType, transformedType, diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java index 57741f91e4..7c55289df2 100644 --- a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java @@ -19,7 +19,9 @@ import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension; import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory; import com.google.common.collect.ImmutableList; +import com.squareup.javapoet.CodeBlock; import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.type.TypeMirror; @@ -28,6 +30,7 @@ public final class SerializerFactoryImpl implements SerializerFactory { private final ImmutableList extensions; private final ProcessingEnvironment env; + private final AtomicInteger idCount = new AtomicInteger(); public SerializerFactoryImpl( ImmutableList extensions, ProcessingEnvironment env) { @@ -45,4 +48,9 @@ public Serializer getSerializer(TypeMirror typeMirror) { } return IdentitySerializerFactory.getSerializer(typeMirror); } + + @Override + public CodeBlock newIdentifier(String prefix) { + return CodeBlock.of("$L$$$L", prefix, idCount.incrementAndGet()); + } } diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java index d05c88b20c..2b54225136 100644 --- a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java @@ -15,6 +15,7 @@ */ package com.google.auto.value.extension.serializable.serializer.interfaces; +import com.squareup.javapoet.CodeBlock; import javax.lang.model.type.TypeMirror; /** @@ -26,4 +27,16 @@ public interface SerializerFactory { /** Returns a {@link Serializer} for the given {@link TypeMirror}. */ Serializer getSerializer(TypeMirror type); + + /** + * Returns an identifier beginning with the given prefix and that is distinct from any identifier + * returned by another call to this method. The returned identifier will contain a {@code $}, + * which should also mean it is distinct from identifiers in user code that are in scope. + * + *

The default implementation of this method throws {@link UnsupportedOperationException} for + * compatibility reasons. + */ + default CodeBlock newIdentifier(String prefix) { + throw new UnsupportedOperationException(); + } } diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java index 528126af76..e2381f7eff 100644 --- a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java +++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java @@ -20,9 +20,9 @@ import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec; import static com.google.auto.common.MoreElements.getPackage; import static com.google.auto.common.MoreElements.isAnnotationPresent; +import static com.google.auto.common.MoreStreams.toImmutableList; +import static com.google.auto.common.MoreStreams.toImmutableSet; import static com.google.auto.value.extension.toprettystring.processor.Annotations.getAnnotationMirror; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Sets.union; import static com.squareup.javapoet.MethodSpec.constructorBuilder; import static com.squareup.javapoet.TypeSpec.classBuilder; diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringCollectors.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringCollectors.java deleted file mode 100644 index 8fdc137392..0000000000 --- a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringCollectors.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.auto.value.extension.toprettystring.processor; - -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.toCollection; -import static java.util.stream.Collectors.toList; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import java.util.LinkedHashSet; -import java.util.stream.Collector; - -final class ToPrettyStringCollectors { - static Collector> toImmutableList() { - return collectingAndThen(toList(), ImmutableList::copyOf); - } - - static Collector> toImmutableSet() { - return collectingAndThen(toCollection(LinkedHashSet::new), ImmutableSet::copyOf); - } - - private ToPrettyStringCollectors() {} -} diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringExtension.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringExtension.java index 968e7b750a..134fcac410 100644 --- a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringExtension.java +++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringExtension.java @@ -17,9 +17,9 @@ package com.google.auto.value.extension.toprettystring.processor; import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods; +import static com.google.auto.common.MoreStreams.toImmutableList; import static com.google.auto.common.MoreTypes.asTypeElement; import static com.google.auto.value.extension.toprettystring.processor.ExtensionClassTypeSpecBuilder.extensionClassTypeSpecBuilder; -import static com.google.auto.value.extension.toprettystring.processor.ToPrettyStringCollectors.toImmutableList; import static com.google.auto.value.extension.toprettystring.processor.ToPrettyStringMethods.toPrettyStringMethod; import static com.google.auto.value.extension.toprettystring.processor.ToPrettyStringMethods.toPrettyStringMethods; import static com.google.common.collect.Iterables.getLast; diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringMethods.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringMethods.java index b5b1242b68..041a16d664 100644 --- a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringMethods.java +++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringMethods.java @@ -17,9 +17,9 @@ package com.google.auto.value.extension.toprettystring.processor; import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods; +import static com.google.auto.common.MoreStreams.toImmutableList; +import static com.google.auto.common.MoreStreams.toImmutableSet; import static com.google.auto.value.extension.toprettystring.processor.Annotations.toPrettyStringAnnotation; -import static com.google.auto.value.extension.toprettystring.processor.ToPrettyStringCollectors.toImmutableList; -import static com.google.auto.value.extension.toprettystring.processor.ToPrettyStringCollectors.toImmutableSet; import static com.google.common.collect.MoreCollectors.toOptional; import com.google.auto.value.extension.AutoValueExtension.Context; 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 ed986ab7b6..ed6abaa6fc 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.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 @@ public Void visitType(TypeMirror classConstant, StringBuilder sb) { 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 @@ public Void visitAnnotation(AnnotationMirror a, StringBuilder sb) { "@AutoAnnotation cannot yet supply a default value for annotation-valued member '" + memberName + "'", - context); + errorContext); sb.append("null"); return null; } @@ -209,9 +213,9 @@ static String sourceFormForInitializer( 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 @@ static String sourceFormForInitializer( * 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 "" 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 ""} 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 here + List values = (List) 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 eb87287bba..e3aa826cfa 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 @@ -27,6 +27,7 @@ import com.google.auto.service.AutoService; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.hash.Hashing; @@ -46,11 +47,11 @@ import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; @@ -79,11 +80,28 @@ public class AutoAnnotationProcessor extends AbstractProcessor { public AutoAnnotationProcessor() {} + private Elements elementUtils; + private Types typeUtils; + private TypeMirror javaLangObject; + @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } + @Override + public ImmutableSet getSupportedOptions() { + return ImmutableSet.of(Nullables.NULLABLE_OPTION); + } + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + this.elementUtils = processingEnv.getElementUtils(); + this.typeUtils = processingEnv.getTypeUtils(); + this.javaLangObject = elementUtils.getTypeElement("java.lang.Object").asType(); + } + /** * Issue a compilation error. This method does not throw an exception, since we want to continue * processing and perhaps report other errors. @@ -104,13 +122,8 @@ private AbortProcessingException abortWithError(Element e, String msg, Object... return new AbortProcessingException(); } - private Elements elementUtils; - private Types typeUtils; - @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { - elementUtils = processingEnv.getElementUtils(); - typeUtils = processingEnv.getTypeUtils(); process(roundEnv); return false; } @@ -137,14 +150,9 @@ private void process(RoundEnvironment roundEnv) { } private void processMethod(ExecutableElement method) { - if (!method.getModifiers().contains(Modifier.STATIC)) { - throw abortWithError(method, "@AutoAnnotation method must be static"); - } - TypeElement annotationElement = getAnnotationReturnType(method); - Set> wrapperTypesUsedInCollections = wrapperTypesUsedInCollections(method); - + ImmutableSet> wrapperTypesUsedInCollections = wrapperTypesUsedInCollections(method); ImmutableMap memberMethods = getMemberMethods(annotationElement); TypeElement methodClass = MoreElements.asType(method.getEnclosingElement()); String pkg = TypeSimplifier.packageNameOf(methodClass); @@ -163,6 +171,7 @@ private void processMethod(ExecutableElement method) { vars.generated = getGeneratedTypeName(); vars.members = members; vars.params = parameters; + vars.equalsParameterType = equalsParameterType(); vars.pkg = pkg; vars.wrapperTypesUsedInCollections = wrapperTypesUsedInCollections; vars.gwtCompatible = isGwtCompatible(annotationElement); @@ -186,6 +195,18 @@ private String getGeneratedTypeName() { .orElse(""); } + private String equalsParameterType() { + // Unlike AutoValue, we don't currently try to guess a @Nullable based on the methods in your + // class. It's the default one or nothing. + ImmutableList equalsParameterAnnotations = + Nullables.fromMethods(processingEnv, ImmutableList.of()) + .nullableTypeAnnotation() + .map(ImmutableList::of) + .orElse(ImmutableList.of()); + return TypeEncoder.encodeWithAnnotations( + javaLangObject, equalsParameterAnnotations, ImmutableSet.of()); + } + /** * Returns the hashCode of the given AnnotationValue, if that hashCode is guaranteed to be always * the same. The hashCode of a String or primitive type never changes. The hashCode of a Class or @@ -258,7 +279,7 @@ private boolean methodsAreOverloaded(List methods) { 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; } @@ -432,9 +453,7 @@ private TypeMirror getTypeMirror(Class c) { } private static boolean isGwtCompatible(TypeElement annotationElement) { - return annotationElement - .getAnnotationMirrors() - .stream() + return annotationElement.getAnnotationMirrors().stream() .map(mirror -> mirror.getAnnotationType().asElement()) .anyMatch(element -> element.getSimpleName().contentEquals("GwtCompatible")); } diff --git a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java index dc39e06b34..11bc896c47 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java @@ -35,6 +35,12 @@ class AutoAnnotationTemplateVars extends TemplateVars { */ Map params; + /** + * A string representing the parameter type declaration of the equals(Object) method, including + * any annotations. + */ + String equalsParameterType; + /** The encoded form of the {@code Generated} class, or empty if it is not available. */ String generated; diff --git a/value/src/main/java/com/google/auto/value/processor/AutoBuilderAnnotationTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoBuilderAnnotationTemplateVars.java new file mode 100644 index 0000000000..68df14edb1 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/processor/AutoBuilderAnnotationTemplateVars.java @@ -0,0 +1,57 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.auto.value.processor; + +import com.google.auto.value.processor.AutoValueishProcessor.Property; +import com.google.common.collect.ImmutableSet; +import com.google.escapevelocity.Template; + +/** The variables to substitute into the autobuilderannotation.vm template. */ +class AutoBuilderAnnotationTemplateVars extends TemplateVars { + private static final Template TEMPLATE = parsedTemplateForResource("autobuilderannotation.vm"); + + /** Package of generated class. */ + String pkg; + + /** The encoding of the {@code Generated} class. Empty if the class is not available. */ + String generated; + + /** The name of the class to generate. */ + String className; + + /** + * The {@linkplain TypeEncoder#encode encoded} name of the annotation type that the generated code + * will build. + */ + String annotationType; + + /** + * The {@linkplain TypeEncoder#encode encoded} name of the {@code @AutoBuilder} type that users + * will call to build this annotation. + */ + String autoBuilderType; + + /** + * The "properties" that the builder will build. These are really just names and types, being the + * names and types of the annotation elements. + */ + ImmutableSet props; + + @Override + Template parsedTemplate() { + return TEMPLATE; + } +} diff --git a/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java index 7d0ef2e685..511356c4da 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java @@ -15,14 +15,18 @@ */ package com.google.auto.value.processor; +import static com.google.auto.common.GeneratedAnnotations.generatedAnnotation; import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods; import static com.google.auto.common.MoreElements.getPackage; +import static com.google.auto.common.MoreStreams.toImmutableList; +import static com.google.auto.common.MoreStreams.toImmutableMap; +import static com.google.auto.common.MoreStreams.toImmutableSet; +import static com.google.auto.common.MoreTypes.asTypeElement; import static com.google.auto.value.processor.AutoValueProcessor.OMIT_IDENTIFIERS_OPTION; +import static com.google.auto.value.processor.ClassNames.AUTO_ANNOTATION_NAME; import static com.google.auto.value.processor.ClassNames.AUTO_BUILDER_NAME; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.auto.value.processor.ClassNames.KOTLIN_METADATA_NAME; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toMap; import static javax.lang.model.util.ElementFilter.constructorsIn; import static javax.lang.model.util.ElementFilter.methodsIn; @@ -33,19 +37,24 @@ import com.google.auto.common.MoreTypes; import com.google.auto.common.Visibility; import com.google.auto.service.AutoService; -import com.google.auto.value.processor.BuilderSpec.PropertyGetter; import com.google.auto.value.processor.MissingTypes.MissingTypeException; import com.google.common.base.Ascii; -import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Maps; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UncheckedIOException; import java.lang.reflect.Field; +import java.util.AbstractMap.SimpleEntry; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.NavigableSet; import java.util.Optional; import java.util.Set; +import java.util.TreeMap; import java.util.TreeSet; import java.util.stream.Stream; import javax.annotation.processing.ProcessingEnvironment; @@ -60,7 +69,15 @@ 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 javax.tools.JavaFileObject; +import kotlinx.metadata.Flag; +import kotlinx.metadata.KmClass; +import kotlinx.metadata.KmConstructor; +import kotlinx.metadata.KmValueParameter; +import kotlinx.metadata.jvm.KotlinClassHeader; +import kotlinx.metadata.jvm.KotlinClassMetadata; import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; @@ -75,9 +92,10 @@ @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING) public class AutoBuilderProcessor extends AutoValueishProcessor { private static final String ALLOW_OPTION = "com.google.auto.value.AutoBuilderIsUnstable"; + private static final String AUTO_ANNOTATION_CLASS_PREFIX = "AutoBuilderAnnotation_"; public AutoBuilderProcessor() { - super(AUTO_BUILDER_NAME); + super(AUTO_BUILDER_NAME, /* appliesToInterfaces= */ true); } @Override @@ -93,93 +111,328 @@ public synchronized void init(ProcessingEnvironment processingEnv) { javaLangVoid = elementUtils().getTypeElement("java.lang.Void").asType(); } + // The handling of @AutoBuilder to generate annotation implementations needs some explanation. + // Suppose we have this: + // + // public class Annotations { + // @interface MyAnnot {...} + // + // @AutoBuilder(ofClass = MyAnnot.class) + // public interface MyAnnotBuilder { + // ... + // MyAnnot build(); + // } + // + // public static MyAnnotBuilder myAnnotBuilder() { + // return new AutoBuilder_Annotations_MyAnnotBuilder(); + // } + // } + // + // Then we will detect that the ofClass type is an annotation. Since annotations can have neither + // constructors nor static methods, we know this isn't a regular @AutoBuilder. We want to + // generate an implementation of the MyAnnot annotation, and we know we can do that if we have a + // suitable @AutoAnnotation method. So we generate: + // + // class AutoBuilderAnnotation_Annotations_MyAnnotBuilder { + // @AutoAnnotation + // static MyAnnot newAnnotation(...) { + // return new AutoAnnotation_AutoBuilderAnnotation_Annotations_MyAnnotBuilder_newAnnotation( + // ...); + // } + // } + // + // We also "defer" MyAnnotBuilder so that it will be considered again on the next round. At that + // point the method AutoBuilderAnnotation_Annotations_MyAnnotBuilder.newAnnotation will exist, and + // we just need to tweak the handling of MyAnnotBuilder so that it behaves as if it were: + // + // @AutoBuilder( + // callMethod = newAnnotation, + // ofClass = AutoBuilderAnnotation_Annotations_MyAnnotBuilder.class) + // interface MyAnnotBuilder {...} + // + // Using AutoAnnotation and AutoBuilder together you'd write + // + // @AutoAnnotation static MyAnnot newAnnotation(...) { ... } + // + // @AutoBuilder(callMethod = "newAnnotation", ofClass = Some.class) + // interface MyAnnotBuilder { ... } + // + // If you set ofClass to an annotation class, AutoBuilder generates the @AutoAnnotation method for + // you and then acts as if your @AutoBuilder annotation pointed to it. + @Override void processType(TypeElement autoBuilderType) { - if (!processingEnv.getOptions().containsKey(ALLOW_OPTION)) { - 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(); TypeElement ofClass = getOfClass(autoBuilderType, autoBuilderAnnotation); checkModifiersIfNested(ofClass, autoBuilderType, "AutoBuilder ofClass"); String callMethod = findCallMethodValue(autoBuilderAnnotation); + if (ofClass.getKind() == ElementKind.ANNOTATION_TYPE) { + buildAnnotation(autoBuilderType, ofClass, callMethod); + } else { + processType(autoBuilderType, ofClass, callMethod); + } + } + + private void processType(TypeElement autoBuilderType, TypeElement ofClass, String callMethod) { ImmutableSet methods = abstractMethodsIn( getLocalAndInheritedMethods(autoBuilderType, typeUtils(), elementUtils())); - ExecutableElement executable = findExecutable(ofClass, callMethod, autoBuilderType, methods); + Executable executable = findExecutable(ofClass, callMethod, autoBuilderType, methods); BuilderSpec builderSpec = new BuilderSpec(ofClass, processingEnv, errorReporter()); BuilderSpec.Builder builder = builderSpec.new Builder(autoBuilderType); - TypeMirror builtType = builtType(executable); + TypeMirror builtType = executable.builtType(); + ImmutableMap propertyInitializers = + propertyInitializers(autoBuilderType, executable); Optional> maybeClassifier = BuilderMethodClassifierForAutoBuilder.classify( - methods, errorReporter(), processingEnv, executable, builtType, autoBuilderType); - if (!maybeClassifier.isPresent()) { + methods, + errorReporter(), + processingEnv, + executable, + builtType, + autoBuilderType, + propertyInitializers.keySet()); + if (!maybeClassifier.isPresent() || errorReporter().errorCount() > 0) { // We've already output one or more error messages. return; } BuilderMethodClassifier classifier = maybeClassifier.get(); - Map propertyToGetterName = - Maps.transformValues(classifier.builderGetters(), PropertyGetter::getName); + ImmutableMap propertyToGetterName = + propertyToGetterName(executable, autoBuilderType); AutoBuilderTemplateVars vars = new AutoBuilderTemplateVars(); - vars.props = propertySet(executable, propertyToGetterName); + Nullables nullables = Nullables.fromMethods(processingEnv, methods); + vars.props = + propertySet( + executable, + propertyToGetterName, + propertyInitializers, + nullables); builder.defineVars(vars, classifier); vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION); String generatedClassName = generatedClassName(autoBuilderType, "AutoBuilder_"); vars.builderName = TypeSimplifier.simpleNameOf(generatedClassName); vars.builtType = TypeEncoder.encode(builtType); - vars.build = build(executable); - vars.types = typeUtils(); - vars.toBuilderConstructor = false; - defineSharedVarsForType(ofClass, ImmutableSet.of(), vars); + vars.builderAnnotations = copiedClassAnnotations(autoBuilderType); + Optional forwardingClassName = maybeForwardingClass(autoBuilderType, executable); + vars.build = + forwardingClassName + .map(n -> TypeSimplifier.simpleNameOf(n) + ".of") + .orElseGet(executable::invoke); + vars.toBuilderConstructor = !propertyToGetterName.isEmpty(); + vars.toBuilderMethods = ImmutableList.of(); + defineSharedVarsForType( + autoBuilderType, ImmutableSet.of(), nullables, vars); String text = vars.toText(); text = TypeEncoder.decode(text, processingEnv, vars.pkg, autoBuilderType.asType()); text = Reformatter.fixup(text); writeSourceFile(generatedClassName, text, autoBuilderType); + forwardingClassName.ifPresent( + n -> generateForwardingClass(n, executable, builtType, autoBuilderType)); + } + + /** + * Generates a class that will call the synthetic Kotlin constructor that is used to specify which + * optional parameters are defaulted. Because it is synthetic, it can't be called from Java source + * code. Instead, Java source code calls the {@code of} method in the class we generate here. + */ + private void generateForwardingClass( + String forwardingClassName, + Executable executable, + TypeMirror builtType, + TypeElement autoBuilderType) { + // The synthetic constructor has the same parameters as the user-written constructor, plus as + // many `int` bitmasks as are needed to have one bit for each of those parameters, plus a dummy + // parameter of type kotlin.jvm.internal.DefaultConstructorMarker to avoid confusion with a + // constructor that might have its own `int` parameters where the bitmasks are. + // This ABI is not publicly specified (as far as we know) but JetBrains has confirmed orally + // that it unlikely to change, and if it does it will be in a backward-compatible way. + ImmutableList.Builder constructorParameters = ImmutableList.builder(); + executable.parameters().stream() + .map(Element::asType) + .map(typeUtils()::erasure) + .forEach(constructorParameters::add); + int bitmaskCount = (executable.optionalParameterCount() + 31) / 32; + constructorParameters.addAll( + Collections.nCopies(bitmaskCount, typeUtils().getPrimitiveType(TypeKind.INT))); + String marker = "kot".concat("lin.jvm.internal.DefaultConstructorMarker"); // defeat shading + constructorParameters.add(elementUtils().getTypeElement(marker).asType()); + byte[] classBytes = + ForwardingClassGenerator.makeConstructorForwarder( + forwardingClassName, builtType, constructorParameters.build()); + try { + JavaFileObject trampoline = + processingEnv.getFiler().createClassFile(forwardingClassName, autoBuilderType); + try (OutputStream out = trampoline.openOutputStream()) { + out.write(classBytes); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private Optional maybeForwardingClass( + TypeElement autoBuilderType, Executable executable) { + return executable.optionalParameterCount() == 0 + ? Optional.empty() + : Optional.of(generatedClassName(autoBuilderType, "AutoBuilderBridge_")); } private ImmutableSet propertySet( - ExecutableElement executable, Map propertyToGetterName) { + Executable executable, + Map propertyToGetterName, + ImmutableMap builderInitializers, + Nullables nullables) { // Fix any parameter names that are reserved words in Java. Java source code can't have // such parameter names, but Kotlin code might, for example. Map identifiers = - executable.getParameters().stream() + executable.parameters().stream() .collect(toMap(v -> v, v -> v.getSimpleName().toString())); fixReservedIdentifiers(identifiers); - return executable.getParameters().stream() + return executable.parameters().stream() .map( - v -> - 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)), + executable.isOptional(name), + nullables); + }) .collect(toImmutableSet()); } - private Property newProperty(VariableElement var, String identifier, String getterName) { + private Property newProperty( + VariableElement var, + String identifier, + String getterName, + Optional builderInitializer, + boolean hasDefault, + Nullables nullables) { String name = var.getSimpleName().toString(); TypeMirror type = var.asType(); Optional nullableAnnotation = nullableAnnotationFor(var, var.asType()); return new Property( - name, identifier, TypeEncoder.encode(type), type, nullableAnnotation, getterName); + name, + identifier, + TypeEncoder.encode(type), + type, + nullableAnnotation, + nullables, + getterName, + builderInitializer, + hasDefault); + } + + private ImmutableMap propertyInitializers( + TypeElement autoBuilderType, Executable executable) { + boolean autoAnnotation = + MoreElements.getAnnotationMirror(executable.executableElement(), AUTO_ANNOTATION_NAME) + .isPresent(); + if (!autoAnnotation) { + return ImmutableMap.of(); + } + // We expect the return type of an @AutoAnnotation method to be an annotation type. If it isn't, + // AutoAnnotation will presumably complain, so we don't need to complain further. + TypeMirror returnType = executable.builtType(); + 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 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( + /** + * Returns a map from property names to the corresponding getters in the built type. The built + * type is the return type of the given {@code executable}, and the property names are the names + * of its parameters. If the return type is a {@link DeclaredType} {@code Foo} and if every + * property name {@code bar} matches a method {@code bar()} or {@code getBar()} in {@code Foo}, + * then the method returns a map where {@code bar} maps to {@code bar} or {@code getBar}. If these + * conditions are not met then the method returns an empty map. + * + *

The method name match is case-insensitive, so we will also accept {@code baR()} or {@code + * getbar()}. For a property of type {@code boolean}, we also accept {@code isBar()} (or {@code + * isbar()} etc). + * + *

The return type of each getter method must match the type of the corresponding parameter + * exactly. This will always be true for our principal use cases, Java records and Kotlin data + * classes. For other use cases, we may in the future accept getters where we know how to convert, + * for example if the getter has type {@code ImmutableList} and the parameter has type + * {@code Baz[]}. We already have similar logic for the parameter types of builder setters. + */ + private ImmutableMap propertyToGetterName( + Executable executable, TypeElement autoBuilderType) { + TypeMirror builtType = executable.builtType(); + if (builtType.getKind() != TypeKind.DECLARED) { + return ImmutableMap.of(); + } + TypeElement type = MoreTypes.asTypeElement(builtType); + Map noArgInstanceMethods = + MoreElements.getLocalAndInheritedMethods(type, typeUtils(), elementUtils()).stream() + .filter(m -> m.getParameters().isEmpty()) + .filter(m -> !m.getModifiers().contains(Modifier.STATIC)) + .filter(m -> visibleFrom(autoBuilderType, getPackage(autoBuilderType))) + .collect( + toMap( + m -> m.getSimpleName().toString(), + m -> m, + (a, b) -> a, + () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER))); + ImmutableMap propertyToGetterName = + executable.parameters().stream() + .map( + param -> { + String name = param.getSimpleName().toString(); + // Parameter name is `bar`; we look for `bar()` and `getBar()` (or `getbar()` etc) + // in that order. If `bar` is boolean we also look for `isBar()`. + ExecutableElement getter = noArgInstanceMethods.get(name); + if (getter == null) { + getter = noArgInstanceMethods.get("get" + name); + if (getter == null && param.asType().getKind() == TypeKind.BOOLEAN) { + getter = noArgInstanceMethods.get("is" + name); + } + } + if (getter != null + && !MoreTypes.equivalence() + .equivalent(getter.getReturnType(), param.asType())) { + getter = null; + } + return new SimpleEntry<>(name, getter); + }) + .filter(entry -> entry.getValue() != null) + .collect( + toImmutableMap( + Map.Entry::getKey, entry -> entry.getValue().getSimpleName().toString())); + return (propertyToGetterName.size() == executable.parameters().size()) + ? propertyToGetterName + : ImmutableMap.of(); + } + + private Executable findExecutable( TypeElement ofClass, String callMethod, TypeElement autoBuilderType, - ImmutableSet methods) { - List executables = + ImmutableSet methodsInAutoBuilderType) { + ImmutableList executables = findRelevantExecutables(ofClass, callMethod, autoBuilderType); String description = callMethod.isEmpty() ? "constructor" : "static method named \"" + callMethod + "\""; @@ -194,28 +447,33 @@ private ExecutableElement findExecutable( case 1: return executables.get(0); default: - return matchingExecutable(autoBuilderType, executables, methods, description); + return matchingExecutable( + autoBuilderType, executables, methodsInAutoBuilderType, description); } } - private ImmutableList findRelevantExecutables( + private ImmutableList findRelevantExecutables( TypeElement ofClass, String callMethod, TypeElement autoBuilderType) { + Optional kotlinMetadata = kotlinMetadataAnnotation(ofClass); List elements = ofClass.getEnclosedElements(); - Stream relevantExecutables = + Stream relevantExecutables = callMethod.isEmpty() - ? constructorsIn(elements).stream() + ? kotlinMetadata + .map(a -> kotlinConstructorsIn(a, ofClass).stream()) + .orElseGet(() -> constructorsIn(elements).stream().map(Executable::of)) : methodsIn(elements).stream() .filter(m -> m.getSimpleName().contentEquals(callMethod)) - .filter(m -> m.getModifiers().contains(Modifier.STATIC)); + .filter(m -> m.getModifiers().contains(Modifier.STATIC)) + .map(Executable::of); return relevantExecutables - .filter(c -> visibleFrom(c, getPackage(autoBuilderType))) + .filter(e -> visibleFrom(e.executableElement(), getPackage(autoBuilderType))) .collect(toImmutableList()); } - private ExecutableElement matchingExecutable( + private Executable matchingExecutable( TypeElement autoBuilderType, - List executables, - ImmutableSet methods, + List executables, + ImmutableSet methodsInAutoBuilderType, String description) { // There's more than one visible executable (constructor or method). We try to find the one that // corresponds to the methods in the @AutoBuilder interface. This is a bit approximate. We're @@ -226,8 +484,10 @@ private ExecutableElement matchingExecutable( // more parameters, then this is indeed the one we want. If we later get errors when we try to // analyze the interface in detail, those are probably legitimate errors and not because we // picked the wrong executable. - ImmutableList matches = - executables.stream().filter(x -> executableMatches(x, methods)).collect(toImmutableList()); + ImmutableList matches = + executables.stream() + .filter(x -> executableMatches(x, methodsInAutoBuilderType)) + .collect(toImmutableList()); switch (matches.size()) { case 0: throw errorReporter() @@ -242,9 +502,9 @@ private ExecutableElement matchingExecutable( default: // More than one match, let's see if we can find the best one. } - int max = matches.stream().mapToInt(c -> c.getParameters().size()).max().getAsInt(); - ImmutableList maxMatches = - matches.stream().filter(c -> c.getParameters().size() == max).collect(toImmutableList()); + int max = matches.stream().mapToInt(e -> e.parameters().size()).max().getAsInt(); + ImmutableList maxMatches = + matches.stream().filter(c -> c.parameters().size() == max).collect(toImmutableList()); if (maxMatches.size() > 1) { throw errorReporter() .abortWithError( @@ -256,25 +516,14 @@ private ExecutableElement matchingExecutable( return maxMatches.get(0); } - private String executableListString(List executables) { + private String executableListString(List executables) { return executables.stream() - .map(AutoBuilderProcessor::executableString) + .map(Object::toString) .collect(joining("\n ", " ", "")); } - static String executableString(ExecutableElement executable) { - Element nameSource = - executable.getKind() == ElementKind.CONSTRUCTOR - ? executable.getEnclosingElement() - : executable; - return nameSource.getSimpleName() - + executable.getParameters().stream() - .map(v -> v.asType() + " " + v.getSimpleName()) - .collect(joining(", ", "(", ")")); - } - private boolean executableMatches( - ExecutableElement executable, ImmutableSet methods) { + Executable executable, ImmutableSet methodsInAutoBuilderType) { // Start with the complete set of parameter names and remove them one by one as we find // corresponding methods. We ignore case, under the assumption that it is unlikely that a case // difference is going to allow a candidate to match when another one is better. @@ -286,11 +535,9 @@ private boolean executableMatches( // There are further constraints, including on the types X and Y, that will later be imposed by // BuilderMethodClassifier, but here we just require that there be at least one method with // one of these shapes for foo. - NavigableSet parameterNames = - executable.getParameters().stream() - .map(v -> v.getSimpleName().toString()) - .collect(toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER))); - for (ExecutableElement method : methods) { + NavigableSet parameterNames = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + parameterNames.addAll(executable.parameterNames()); + for (ExecutableElement method : methodsInAutoBuilderType) { String name = method.getSimpleName().toString(); if (name.endsWith("Builder")) { String property = name.substring(0, name.length() - "Builder".length()); @@ -325,30 +572,90 @@ private boolean visibleFrom(Element element, PackageElement fromPackage) { } } - private TypeMirror builtType(ExecutableElement executable) { - switch (executable.getKind()) { - case CONSTRUCTOR: - return executable.getEnclosingElement().asType(); - case METHOD: - return executable.getReturnType(); - default: - throw new VerifyException("Unexpected executable kind " + executable.getKind()); - } + private Optional kotlinMetadataAnnotation(Element element) { + // It would be MUCH simpler if we could just use ofClass.getAnnotation(Metadata.class). + // However that would be unsound. We want to shade the Kotlin runtime, including + // kotlin.Metadata, so as not to interfere with other things on the annotation classpath that + // might have a different version of the runtime. That means that if we referenced + // kotlin.Metadata.class here we would actually be referencing + // autovalue.shaded.kotlin.Metadata.class. Obviously the Kotlin class doesn't have that + // annotation. + return element.getAnnotationMirrors().stream() + .filter( + a -> + asTypeElement(a.getAnnotationType()) + .getQualifiedName() + .contentEquals(KOTLIN_METADATA_NAME)) + .map(a -> a) // get rid of that stupid wildcard + .findFirst(); } - private String build(ExecutableElement executable) { - TypeElement enclosing = MoreElements.asType(executable.getEnclosingElement()); - String type = TypeEncoder.encodeRaw(enclosing.asType()); - switch (executable.getKind()) { - case CONSTRUCTOR: - boolean generic = !enclosing.getTypeParameters().isEmpty(); - String typeParams = generic ? "<>" : ""; - return "new " + type + typeParams; - case METHOD: - return type + "." + executable.getSimpleName(); - default: - throw new VerifyException("Unexpected executable kind " + executable.getKind()); + /** + * Use Kotlin reflection to build {@link Executable} instances for the constructors in {@code + * ofClass} that include information about which parameters have default values. + */ + private ImmutableList kotlinConstructorsIn( + AnnotationMirror metadata, TypeElement ofClass) { + ImmutableMap annotationValues = + AnnotationMirrors.getAnnotationValuesWithDefaults(metadata).entrySet().stream() + .map(e -> new SimpleEntry<>(e.getKey().getSimpleName().toString(), e.getValue())) + .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); + // We match the KmConstructor instances with the ExecutableElement instances based on the + // parameter names. We could possibly just assume that the constructors are in the same order. + Map, ExecutableElement> map = + constructorsIn(ofClass.getEnclosedElements()).stream() + .collect(toMap(c -> parameterNames(c), c -> c, (a, b) -> a, LinkedHashMap::new)); + ImmutableMap, ExecutableElement> paramNamesToConstructor = + ImmutableMap.copyOf(map); + KotlinClassHeader header = + new KotlinClassHeader( + (Integer) annotationValues.get("k").getValue(), + intArrayValue(annotationValues.get("mv")), + stringArrayValue(annotationValues.get("d1")), + stringArrayValue(annotationValues.get("d2")), + (String) annotationValues.get("xs").getValue(), + (String) annotationValues.get("pn").getValue(), + (Integer) annotationValues.get("xi").getValue()); + KotlinClassMetadata.Class classMetadata = + (KotlinClassMetadata.Class) KotlinClassMetadata.read(header); + KmClass kmClass = classMetadata.toKmClass(); + ImmutableList.Builder kotlinConstructorsBuilder = ImmutableList.builder(); + for (KmConstructor constructor : kmClass.getConstructors()) { + ImmutableSet.Builder allBuilder = ImmutableSet.builder(); + ImmutableSet.Builder optionalBuilder = ImmutableSet.builder(); + for (KmValueParameter param : constructor.getValueParameters()) { + String name = param.getName(); + allBuilder.add(name); + if (Flag.ValueParameter.DECLARES_DEFAULT_VALUE.invoke(param.getFlags())) { + optionalBuilder.add(name); + } + } + ImmutableSet optional = optionalBuilder.build(); + ImmutableSet all = allBuilder.build(); + ExecutableElement javaConstructor = paramNamesToConstructor.get(all); + if (javaConstructor != null) { + kotlinConstructorsBuilder.add(Executable.of(javaConstructor, optional)); + } } + return kotlinConstructorsBuilder.build(); + } + + private static int[] intArrayValue(AnnotationValue value) { + @SuppressWarnings("unchecked") + List list = (List) value.getValue(); + return list.stream().mapToInt(v -> (int) v.getValue()).toArray(); + } + + private static String[] stringArrayValue(AnnotationValue value) { + @SuppressWarnings("unchecked") + List list = (List) value.getValue(); + return list.stream().map(AnnotationValue::getValue).toArray(String[]::new); + } + + private static ImmutableSet parameterNames(ExecutableElement executableElement) { + return executableElement.getParameters().stream() + .map(v -> v.getSimpleName().toString()) + .collect(toImmutableSet()); } private static final ElementKind ELEMENT_KIND_RECORD = elementKindRecord(); @@ -413,4 +720,70 @@ Optional nullableAnnotationForMethod(ExecutableElement propertyMethod) { // TODO(b/183005059): implement return Optional.empty(); } + + private void buildAnnotation( + TypeElement autoBuilderType, TypeElement annotationType, String callMethod) { + if (!callMethod.isEmpty()) { + errorReporter() + .abortWithError( + autoBuilderType, + "[AutoBuilderAnnotationMethod] @AutoBuilder for an annotation must have an empty" + + " callMethod, not \"%s\"", + callMethod); + } + String autoAnnotationClassName = + generatedClassName(autoBuilderType, AUTO_ANNOTATION_CLASS_PREFIX); + TypeElement autoAnnotationClass = elementUtils().getTypeElement(autoAnnotationClassName); + if (autoAnnotationClass != null) { + processType(autoBuilderType, autoAnnotationClass, "newAnnotation"); + return; + } + AutoBuilderAnnotationTemplateVars vars = new AutoBuilderAnnotationTemplateVars(); + vars.autoBuilderType = TypeEncoder.encode(autoBuilderType.asType()); + vars.props = annotationBuilderPropertySet(annotationType); + vars.pkg = TypeSimplifier.packageNameOf(autoBuilderType); + vars.generated = + generatedAnnotation(elementUtils(), processingEnv.getSourceVersion()) + .map(annotation -> TypeEncoder.encode(annotation.asType())) + .orElse(""); + vars.className = TypeSimplifier.simpleNameOf(autoAnnotationClassName); + vars.annotationType = TypeEncoder.encode(annotationType.asType()); + String text = vars.toText(); + text = TypeEncoder.decode(text, processingEnv, vars.pkg, /* baseType= */ javaLangVoid); + text = Reformatter.fixup(text); + writeSourceFile(autoAnnotationClassName, text, autoBuilderType); + addDeferredType(autoBuilderType); + } + + private ImmutableSet annotationBuilderPropertySet(TypeElement annotationType) { + // Annotation methods can't have their own annotations so there's nowhere for us to discover + // a user @Nullable. We can only use our default @Nullable type annotation. + Nullables nullables = Nullables.fromMethods(processingEnv, ImmutableList.of()); + // Translate the annotation elements into fake Property instances. We're really only interested + // in the name and type, so we can use them to declare a parameter of the generated + // @AutoAnnotation method. We'll generate a parameter for every element, even elements that + // don't have setters in the builder. The generated builder implementation will pass the default + // value from the annotation to those parameters. + return methodsIn(annotationType.getEnclosedElements()).stream() + .filter(m -> m.getParameters().isEmpty() && !m.getModifiers().contains(Modifier.STATIC)) + .map(method -> annotationBuilderProperty(method, nullables)) + .collect(toImmutableSet()); + } + + private static Property annotationBuilderProperty( + ExecutableElement annotationMethod, + Nullables nullables) { + String name = annotationMethod.getSimpleName().toString(); + TypeMirror type = annotationMethod.getReturnType(); + return new Property( + name, + name, + TypeEncoder.encode(type), + type, + /* nullableAnnotation= */ Optional.empty(), + nullables, + /* getter= */ "", + /* maybeBuilderInitializer= */ Optional.empty(), + /* hasDefault= */ false); + } } diff --git a/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java index b48d2ea4e9..d04f26f647 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 @@ @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING) public class AutoOneOfProcessor extends AutoValueishProcessor { public AutoOneOfProcessor() { - super(AUTO_ONE_OF_NAME); + super(AUTO_ONE_OF_NAME, /* appliesToInterfaces= */ false); } @Override @@ -68,15 +68,13 @@ boolean propertiesCanBeVoid() { return true; } + @Override + public ImmutableSet getSupportedOptions() { + return ImmutableSet.of(Nullables.NULLABLE_OPTION); + } + @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. @@ -113,8 +111,9 @@ void processType(TypeElement autoOneOfType) { AutoOneOfTemplateVars vars = new AutoOneOfTemplateVars(); vars.generatedClass = TypeSimplifier.simpleNameOf(subclass); vars.propertyToKind = propertyToKind; - defineSharedVarsForType(autoOneOfType, methods, vars); - defineVarsForType(autoOneOfType, vars, propertyMethodsAndTypes, kindGetter); + Nullables nullables = Nullables.fromMethods(processingEnv, methods); + defineSharedVarsForType(autoOneOfType, methods, nullables, vars); + defineVarsForType(autoOneOfType, vars, propertyMethodsAndTypes, kindGetter, nullables); String text = vars.toText(); text = TypeEncoder.decode(text, processingEnv, vars.pkg, autoOneOfType.asType()); @@ -124,10 +123,8 @@ void processType(TypeElement autoOneOfType) { private DeclaredType mirrorForKindType(TypeElement autoOneOfType) { // The annotation is guaranteed to be present by the contract of Processor#process - AnnotationMirror oneOfAnnotation = - getAnnotationMirror(autoOneOfType, AUTO_ONE_OF_NAME).get(); - AnnotationValue kindValue = - AnnotationMirrors.getAnnotationValue(oneOfAnnotation, "value"); + AnnotationMirror oneOfAnnotation = getAnnotationMirror(autoOneOfType, AUTO_ONE_OF_NAME).get(); + AnnotationValue kindValue = AnnotationMirrors.getAnnotationValue(oneOfAnnotation, "value"); Object value = kindValue.getValue(); if (value instanceof TypeMirror) { TypeMirror kindType = (TypeMirror) value; @@ -154,9 +151,7 @@ private ImmutableMap propertyToKindMap( Map transformedPropertyNames = propertyNames.stream().collect(toMap(this::transformName, s -> s)); Map transformedEnumConstants = - kindElement - .getEnclosedElements() - .stream() + kindElement.getEnclosedElements().stream() .filter(e -> e.getKind().equals(ElementKind.ENUM_CONSTANT)) .collect(toMap(e -> transformName(e.getSimpleName().toString()), e -> e)); @@ -207,8 +202,7 @@ private ExecutableElement findKindGetterOrAbort( TypeMirror kindMirror, ImmutableSet abstractMethods) { Set kindGetters = - abstractMethods - .stream() + abstractMethods.stream() .filter(e -> sameType(kindMirror, e.getReturnType())) .filter(e -> e.getParameters().isEmpty()) .collect(toSet()); @@ -264,15 +258,20 @@ private void defineVarsForType( TypeElement type, AutoOneOfTemplateVars vars, ImmutableMap propertyMethodsAndTypes, - ExecutableElement kindGetter) { - vars.props = propertySet( - propertyMethodsAndTypes, ImmutableListMultimap.of(), ImmutableListMultimap.of()); + ExecutableElement kindGetter, + Nullables nullables) { + vars.props = + propertySet( + propertyMethodsAndTypes, + /* annotatedPropertyFields= */ ImmutableListMultimap.of(), + /* annotatedPropertyMethods= */ ImmutableListMultimap.of(), + nullables); vars.kindGetter = kindGetter.getSimpleName().toString(); vars.kindType = TypeEncoder.encode(kindGetter.getReturnType()); TypeElement javaIoSerializable = elementUtils().getTypeElement("java.io.Serializable"); vars.serializable = - javaIoSerializable != null // just in case - && typeUtils().isAssignable(type.asType(), javaIoSerializable.asType()); + javaIoSerializable != null // just in case + && typeUtils().isAssignable(type.asType(), javaIoSerializable.asType()); } @Override diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java index 83aab3cec4..db711857bd 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java @@ -22,8 +22,6 @@ import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import java.util.Optional; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.util.Types; /** * Variables to substitute into the autovalue.vm or builder.vm template. @@ -109,11 +107,12 @@ abstract class AutoValueOrBuilderTemplateVars extends AutoValueishTemplateVars { * *

    *
  • it is {@code @Nullable} (in which case it defaults to null); - *
  • it is {@code Optional} (in which case it defaults to empty); + *
  • it has a builder initializer (for example it is {@code Optional}, which will have an + * initializer of {@code Optional.empty()}); *
  • it has a property-builder method (in which case it defaults to empty). *
*/ - ImmutableSet builderRequiredProperties = ImmutableSet.of(); + BuilderRequiredProperties builderRequiredProperties = BuilderRequiredProperties.EMPTY; /** * A map from property names to information about the associated property getter. A property @@ -125,11 +124,17 @@ abstract class AutoValueOrBuilderTemplateVars extends AutoValueishTemplateVars { /** * True if the generated builder should have a second constructor with a parameter of the built - * class. The constructor produces a new builder that starts off with the values from the + * type. The constructor produces a new builder that starts off with the values from the * parameter. */ Boolean toBuilderConstructor; + /** + * Any {@code toBuilder()} methods, that is methods that return the builder type. AutoBuilder does + * not support this, but it's included in these shared variables to simplify the template. + */ + ImmutableList toBuilderMethods; + /** * Whether to include identifiers in strings in the generated code. If false, exception messages * will not mention properties by name, and {@code toString()} will include neither property names @@ -142,7 +147,4 @@ abstract class AutoValueOrBuilderTemplateVars extends AutoValueishTemplateVars { * subclasses) */ Boolean isFinal = false; - - /** The type utilities returned by {@link ProcessingEnvironment#getTypeUtils()}. */ - Types types; } diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java index ef1f3098f7..821db9a8f0 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java @@ -16,12 +16,13 @@ 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; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; import com.google.auto.service.AutoService; import com.google.auto.value.extension.AutoValueExtension; @@ -33,7 +34,6 @@ import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; @@ -46,7 +46,6 @@ 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; @@ -80,37 +79,40 @@ public AutoValueProcessor() { @VisibleForTesting AutoValueProcessor(ClassLoader loaderForExtensions) { - super(AUTO_VALUE_NAME); - this.extensions = null; - this.loaderForExtensions = loaderForExtensions; + this(ImmutableList.of(), loaderForExtensions); } @VisibleForTesting - public AutoValueProcessor(Iterable extensions) { - super(AUTO_VALUE_NAME); - this.extensions = ImmutableList.copyOf(extensions); - this.loaderForExtensions = null; + public AutoValueProcessor(Iterable testExtensions) { + this(testExtensions, null); + } + + private AutoValueProcessor( + Iterable 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 extensions; private final ClassLoader loaderForExtensions; @VisibleForTesting static ImmutableList extensionsFromLoader(ClassLoader loader) { - return ImmutableList.copyOf( - Iterables.filter( - SimpleServiceLoader.load(AutoValueExtension.class, loader), - ext -> !ext.getClass().getName().equals(OLD_MEMOIZE_EXTENSION))); + return SimpleServiceLoader.load(AutoValueExtension.class, loader).stream() + .filter(ext -> !ext.getClass().getName().equals(OLD_MEMOIZE_EXTENSION)) + .collect(toImmutableList()); } @Override 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) { @@ -131,14 +133,17 @@ public synchronized void init(ProcessingEnvironment processingEnv) { } @Override - public Set getSupportedOptions() { + public ImmutableSet getSupportedOptions() { ImmutableSet.Builder builder = ImmutableSet.builder(); AutoValueExtension.IncrementalExtensionType incrementalType = extensions.stream() .map(e -> e.incrementalType(processingEnv)) .min(naturalOrder()) .orElse(AutoValueExtension.IncrementalExtensionType.ISOLATING); - builder.add(OMIT_IDENTIFIERS_OPTION).addAll(optionsFor(incrementalType)); + builder + .add(OMIT_IDENTIFIERS_OPTION) + .add(Nullables.NULLABLE_OPTION) + .addAll(optionsFor(incrementalType)); for (AutoValueExtension extension : extensions) { builder.addAll(extension.getSupportedOptions()); } @@ -164,10 +169,6 @@ static String generatedSubclassName(TypeElement type, int depth) { @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"); @@ -177,9 +178,8 @@ void processType(TypeElement type) { .abortWithError( type, "[AutoValueImplAnnotation] @AutoValue may not be used to implement an annotation" - + " interface; try using @AutoAnnotation instead"); + + " interface; try using @AutoAnnotation or @AutoBuilder 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. @@ -217,8 +217,9 @@ void processType(TypeElement type) { ImmutableMap properties = propertyNameToMethodMap(propertyMethodsAndTypes.keySet()); - ExtensionContext context = new ExtensionContext( - processingEnv, type, properties, propertyMethodsAndTypes, abstractMethods); + ExtensionContext context = + new ExtensionContext( + processingEnv, type, properties, propertyMethodsAndTypes, abstractMethods); ImmutableList applicableExtensions = applicableExtensions(type, context); ImmutableSet consumedMethods = methodsConsumedByExtensions( @@ -231,8 +232,9 @@ void processType(TypeElement type) { propertyMethodsAndTypes = propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods), type); properties = propertyNameToMethodMap(propertyMethodsAndTypes.keySet()); - context = new ExtensionContext( - processingEnv, type, properties, propertyMethodsAndTypes, allAbstractMethods); + context = + new ExtensionContext( + processingEnv, type, properties, propertyMethodsAndTypes, allAbstractMethods); } ImmutableSet propertyMethods = propertyMethodsAndTypes.keySet(); @@ -241,10 +243,16 @@ void processType(TypeElement type) { String finalSubclass = TypeSimplifier.simpleNameOf(generatedSubclassName(type, 0)); AutoValueTemplateVars vars = new AutoValueTemplateVars(); - vars.types = processingEnv.getTypeUtils(); vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION); - defineSharedVarsForType(type, methods, vars); - defineVarsForType(type, vars, toBuilderMethods, propertyMethodsAndTypes, builder); + Nullables nullables = Nullables.fromMethods(processingEnv, methods); + defineSharedVarsForType(type, methods, nullables, vars); + defineVarsForType( + type, + vars, + toBuilderMethods, + propertyMethodsAndTypes, + builder, + nullables); vars.builtType = vars.origClass + vars.actualTypes; vars.build = "new " + finalSubclass + vars.actualTypes; @@ -421,18 +429,22 @@ private void defineVarsForType( AutoValueTemplateVars vars, ImmutableSet toBuilderMethods, ImmutableMap propertyMethodsAndTypes, - Optional maybeBuilder) { + Optional maybeBuilder, + Nullables nullables) { ImmutableSet propertyMethods = propertyMethodsAndTypes.keySet(); - // We can't use ImmutableList.toImmutableList() for obscure Google-internal reasons. vars.toBuilderMethods = - ImmutableList.copyOf(toBuilderMethods.stream().map(SimpleMethod::new).collect(toList())); + toBuilderMethods.stream().map(SimpleMethod::new).collect(toImmutableList()); vars.toBuilderConstructor = !vars.toBuilderMethods.isEmpty(); ImmutableListMultimap annotatedPropertyFields = propertyFieldAnnotationMap(type, propertyMethods); ImmutableListMultimap annotatedPropertyMethods = propertyMethodAnnotationMap(type, propertyMethods); vars.props = - propertySet(propertyMethodsAndTypes, annotatedPropertyFields, annotatedPropertyMethods); + propertySet( + propertyMethodsAndTypes, + annotatedPropertyFields, + annotatedPropertyMethods, + nullables); // Check for @AutoValue.Builder and add appropriate variables if it is present. maybeBuilder.ifPresent( builder -> { @@ -492,7 +504,7 @@ private static ImmutableSet immutableSetDifference(ImmutableSet a, Imm if (Collections.disjoint(a, b)) { return a; } else { - return ImmutableSet.copyOf(difference(a, b)); + return difference(a, b).immutableCopy(); } } } diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java index d68466a45f..e53b92e930 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java @@ -15,7 +15,6 @@ */ package com.google.auto.value.processor; -import com.google.common.collect.ImmutableList; import com.google.escapevelocity.Template; /** @@ -42,9 +41,6 @@ class AutoValueTemplateVars extends AutoValueOrBuilderTemplateVars { */ String modifiers; - /** Any {@code toBuilder()} methods, that is methods that return the builder type. */ - ImmutableList toBuilderMethods; - private static final Template TEMPLATE = parsedTemplateForResource("autovalue.vm"); @Override 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 a494b9a661..0e4940cd93 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 @@ -19,15 +19,17 @@ import static com.google.auto.common.GeneratedAnnotations.generatedAnnotation; import static com.google.auto.common.MoreElements.getPackage; import static com.google.auto.common.MoreElements.isAnnotationPresent; +import static com.google.auto.common.MoreStreams.toImmutableList; +import static com.google.auto.common.MoreStreams.toImmutableSet; import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_PACKAGE_NAME; import static com.google.auto.value.processor.ClassNames.COPY_ANNOTATIONS_NAME; -import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.Sets.union; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; +import static javax.lang.model.util.ElementFilter.constructorsIn; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; @@ -91,19 +93,22 @@ */ 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 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; @@ -114,6 +119,10 @@ abstract class AutoValueishProcessor extends AbstractProcessor { public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); errorReporter = new ErrorReporter(processingEnv); + annotationType = elementUtils().getTypeElement(annotationClassName); + if (annotationType != null) { + simpleAnnotationName = annotationType.getSimpleName().toString(); + } } final ErrorReporter errorReporter() { @@ -129,9 +138,9 @@ final Elements elementUtils() { } /** - * 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 deferredTypeNames() { return ImmutableList.copyOf(deferredTypeNames); @@ -143,7 +152,7 @@ public final SourceVersion getSupportedSourceVersion() { } /** - * A property of an {@code @AutoValue} or {@code @AutoOneOf} class, defined by one of its abstract + * A property of an {@code @AutoValue} (etc) class, defined by one of its abstract * methods. An instance of this class is made available to the Velocity template engine for each * property. The public methods of this class define JavaBeans-style properties that are * accessible from templates. For example {@link #getType()} means we can write {@code $p.type} @@ -155,8 +164,11 @@ public static class Property { private final String type; private final TypeMirror typeMirror; private final Optional nullableAnnotation; + private final Optional availableNullableTypeAnnotation; private final Optionalish optional; private final String getter; + private final String builderInitializer; // empty, or with initial ` = `. + private final boolean hasDefault; Property( String name, @@ -164,14 +176,69 @@ public static class Property { String type, TypeMirror typeMirror, Optional nullableAnnotation, - String getter) { + Nullables nullables, + String getter, + Optional maybeBuilderInitializer, + boolean hasDefault) { this.name = name; this.identifier = identifier; this.type = type; this.typeMirror = typeMirror; this.nullableAnnotation = nullableAnnotation; + this.availableNullableTypeAnnotation = nullables.nullableTypeAnnotation(); this.optional = Optionalish.createIfOptional(typeMirror); + this.builderInitializer = + maybeBuilderInitializer.isPresent() + ? " = " + maybeBuilderInitializer.get() + : builderInitializer(typeMirror, nullableAnnotation); this.getter = getter; + this.hasDefault = hasDefault; + } + + /** + * Returns the appropriate initializer for a builder property. The caller of the {@code + * Property} constructor may have supplied an initializer, but otherwise we supply one only if + * this property is an {@code Optional} and is not {@code @Nullable}. In that case the + * initializer sets it to {@code Optional.empty()}. + */ + private static String builderInitializer( + TypeMirror typeMirror, Optional nullableAnnotation) { + if (nullableAnnotation.isPresent()) { + return ""; + } + Optionalish optional = Optionalish.createIfOptional(typeMirror); + if (optional == null) { + return ""; + } + return " = " + optional.getEmpty(); + } + + /** + * Returns the appropriate type for a builder field that will eventually be assigned to this + * property. This is the same as the final property type, except that it may have an additional + * {@code @Nullable} annotation. Some builder fields start off null and then acquire a value + * when the corresponding setter is called. Builder fields should have an extra + * {@code @Nullable} if all of the following conditions are met: + * + *
    + *
  • the property is not primitive; + *
  • the property is not already nullable; + *
  • there is no explicit initializer (for example {@code Optional} properties start off as + * {@code Optional.empty()}); + *
  • we have found a {@code @Nullable} type annotation that can be applied. + *
+ */ + public String getBuilderFieldType() { + if (typeMirror.getKind().isPrimitive() + || nullableAnnotation.isPresent() + || !builderInitializer.isEmpty() + || !availableNullableTypeAnnotation.isPresent()) { + return type; + } + return TypeEncoder.encodeWithAnnotations( + typeMirror, + ImmutableList.of(availableNullableTypeAnnotation.get()), + /* excludedAnnotationTypes= */ ImmutableSet.of()); } /** @@ -195,7 +262,7 @@ public String getName() { return name; } - public TypeMirror getTypeMirror() { + TypeMirror getTypeMirror() { return typeMirror; } @@ -215,6 +282,14 @@ public Optionalish getOptional() { return optional; } + /** + * 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 @@ -235,12 +310,16 @@ public boolean isNullable() { /** * Returns the name of the getter method for this property as defined by the {@code @AutoValue} * or {@code @AutoBuilder} class. For property {@code foo}, this will be {@code foo} or {@code - * getFoo} or {@code isFoo}. For AutoValue, this will also be the name of a getter method in a - * builder; in the case of AutoBuilder it will only be that and may be null. + * getFoo} or {@code isFoo}. For AutoBuilder, the getter in question is the one that will be + * called on the built type to derive the value of the property, in the copy constructor. */ public String getGetter() { return getter; } + + boolean hasDefault() { + return hasDefault; + } } /** A {@link Property} that corresponds to an abstract getter method in the source. */ @@ -253,17 +332,22 @@ public static class GetterProperty extends Property { String name, String identifier, ExecutableElement method, - String type, + TypeMirror typeMirror, + String typeString, ImmutableList fieldAnnotations, ImmutableList methodAnnotations, - Optional nullableAnnotation) { + Optional nullableAnnotation, + Nullables nullables) { super( name, identifier, - type, - method.getReturnType(), + typeString, + typeMirror, nullableAnnotation, - method.getSimpleName().toString()); + nullables, + method.getSimpleName().toString(), + Optional.empty(), + /* hasDefault= */ false); this.method = method; this.fieldAnnotations = fieldAnnotations; this.methodAnnotations = methodAnnotations; @@ -291,8 +375,7 @@ public String getAccess() { @Override public boolean equals(Object obj) { - return obj instanceof GetterProperty - && ((GetterProperty) obj).method.equals(method); + return obj instanceof GetterProperty && ((GetterProperty) obj).method.equals(method); } @Override @@ -301,9 +384,15 @@ public int hashCode() { } } + void addDeferredType(TypeElement type) { + // We save the name of the type rather + // than its TypeElement because it is not guaranteed that it will be represented by + // the same TypeElement on the next round. + deferredTypeNames.add(type.getQualifiedName().toString()); + } + @Override public final boolean process(Set 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? @@ -316,10 +405,8 @@ public final boolean process(Set annotations, RoundEnviro + " because the annotation class was not found"); return false; } - simpleAnnotationName = annotationType.getSimpleName().toString(); List deferredTypes = - deferredTypeNames - .stream() + deferredTypeNames.stream() .map(name -> elementUtils().getTypeElement(name)) .collect(toList()); if (roundEnv.processingOver()) { @@ -329,9 +416,10 @@ public final boolean process(Set annotations, RoundEnviro 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; @@ -346,6 +434,7 @@ public final boolean process(Set annotations, RoundEnviro deferredTypeNames.clear(); for (TypeElement type : types) { try { + validateType(type); processType(type); } catch (AbortProcessingException e) { // We abandoned this type; continue with the next. @@ -353,15 +442,14 @@ public final boolean process(Set annotations, RoundEnviro // We abandoned this type, but only because we needed another type that it references and // that other type was missing. It is possible that the missing type will be generated by // further annotation processing, so we will try again on the next round (perhaps failing - // again and adding it back to the list). We save the name of the @AutoValue type rather - // than its TypeElement because it is not guaranteed that it will be represented by - // the same TypeElement on the next round. - deferredTypeNames.add(type.getQualifiedName().toString()); + // again and adding it back to the list). + addDeferredType(type); } catch (RuntimeException e) { 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; @@ -371,8 +459,44 @@ public final boolean process(Set annotations, RoundEnviro } /** - * 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. */ @@ -401,7 +525,8 @@ public final boolean process(Set annotations, RoundEnviro final ImmutableSet propertySet( ImmutableMap propertyMethodsAndTypes, ImmutableListMultimap annotatedPropertyFields, - ImmutableListMultimap annotatedPropertyMethods) { + ImmutableListMultimap annotatedPropertyMethods, + Nullables nullables) { ImmutableBiMap methodToPropertyName = propertyNameToMethodMap(propertyMethodsAndTypes.keySet()).inverse(); Map methodToIdentifier = new LinkedHashMap<>(methodToPropertyName); @@ -410,7 +535,7 @@ final ImmutableSet propertySet( ImmutableSet.Builder props = ImmutableSet.builder(); propertyMethodsAndTypes.forEach( (propertyMethod, returnType) -> { - String propertyType = + String propertyTypeString = TypeEncoder.encodeWithAnnotations( returnType, ImmutableList.of(), getExcludedAnnotationTypes(propertyMethod)); String propertyName = methodToPropertyName.get(propertyMethod); @@ -426,15 +551,19 @@ final ImmutableSet propertySet( propertyName, identifier, propertyMethod, - propertyType, + returnType, + propertyTypeString, fieldAnnotations, methodAnnotations, - nullableAnnotation); + nullableAnnotation, + nullables); props.add(p); 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(); @@ -444,6 +573,7 @@ final ImmutableSet propertySet( final void defineSharedVarsForType( TypeElement type, ImmutableSet methods, + Nullables nullables, AutoValueishTemplateVars vars) { vars.pkg = TypeSimplifier.packageNameOf(type); vars.origClass = TypeSimplifier.classNameOf(type); @@ -461,30 +591,30 @@ final void defineSharedVarsForType( vars.toString = methodsToGenerate.containsKey(ObjectMethod.TO_STRING); vars.equals = methodsToGenerate.containsKey(ObjectMethod.EQUALS); vars.hashCode = methodsToGenerate.containsKey(ObjectMethod.HASH_CODE); - Optional nullable = Nullables.nullableMentionedInMethods(methods); - vars.equalsParameterType = equalsParameterType(methodsToGenerate, nullable); + vars.equalsParameterType = + equalsParameterType(methodsToGenerate, nullables); vars.serialVersionUID = getSerialVersionUID(type); } /** Returns the spelling to be used in the generated code for the given list of annotations. */ static ImmutableList annotationStrings(List annotations) { - // TODO(b/68008628): use ImmutableList.toImmutableList() when that works. - return ImmutableList.copyOf( - annotations.stream().map(AnnotationOutput::sourceFormForAnnotation).collect(toList())); + return annotations.stream() + .map(AnnotationOutput::sourceFormForAnnotation) + .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 +685,8 @@ final ImmutableBiMap propertyNameToMethodMap( 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 +720,9 @@ static Optional nullableAnnotationFor(Element element, TypeMirror elemen List elementAnnotations = element.getAnnotationMirrors(); OptionalInt nullableAnnotationIndex = nullableAnnotationIndex(elementAnnotations); if (nullableAnnotationIndex.isPresent()) { - ImmutableList 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(); } @@ -725,8 +857,8 @@ private static Map determineObjectMethodsToGene ObjectMethod override = objectMethodToOverride(method); boolean canGenerate = method.getModifiers().contains(Modifier.ABSTRACT) - || isJavaLangObject(MoreElements.asType(method.getEnclosingElement())); - if (!override.equals(ObjectMethod.NONE) && canGenerate) { + || isJavaLangObject(MoreElements.asType(method.getEnclosingElement())); + if (!override.equals(ObjectMethod.NONE) && canGenerate) { methodsToGenerate.put(override, method); } } @@ -742,7 +874,7 @@ private static Map determineObjectMethodsToGene * @param nullable the type of a {@code @Nullable} type annotation that we have found, if any */ static String equalsParameterType( - Map methodsToGenerate, Optional nullable) { + Map methodsToGenerate, Nullables nullables) { ExecutableElement equals = methodsToGenerate.get(ObjectMethod.EQUALS); if (equals == null) { return ""; // this will not be referenced because no equals method will be generated @@ -751,11 +883,14 @@ static String equalsParameterType( // Add @Nullable if we know one and the parameter doesn't already have one. // The @Nullable we add will be a type annotation, but if the parameter already has @Nullable // then that might be a type annotation or an annotation on the parameter. + Optional nullableTypeAnnotation = nullables.nullableTypeAnnotation(); ImmutableList extraAnnotations = - nullable.isPresent() && !nullableAnnotationFor(equals, parameterType).isPresent() - ? ImmutableList.of(nullable.get()) + nullableTypeAnnotation.isPresent() + && !nullableAnnotationFor(equals, parameterType).isPresent() + ? ImmutableList.of(nullableTypeAnnotation.get()) : ImmutableList.of(); - return TypeEncoder.encodeWithAnnotations(parameterType, extraAnnotations, ImmutableSet.of()); + return TypeEncoder.encodeWithAnnotations( + parameterType, extraAnnotations, /* excludedAnnotationTypes= */ ImmutableSet.of()); } /** @@ -794,8 +929,7 @@ static ImmutableSet abstractMethodsIn(Iterable propertyMethodsIn( - Set abstractMethods, - TypeElement autoValueOrOneOfType) { + Set abstractMethods, TypeElement autoValueOrOneOfType) { DeclaredType declaredType = MoreTypes.asDeclared(autoValueOrOneOfType.asType()); ImmutableSet.Builder properties = ImmutableSet.builder(); for (ExecutableElement method : abstractMethods) { @@ -821,7 +955,7 @@ final void checkReturnType(TypeElement autoValueClass, ExecutableElement getter) TypeMirror type = getter.getReturnType(); if (type.getKind() == TypeKind.ARRAY) { TypeMirror componentType = MoreTypes.asArray(type).getComponentType(); - if (componentType.getKind().isPrimitive()) { + if (componentType.getKind().isPrimitive()) { warnAboutPrimitiveArrays(autoValueClass, getter); } else { errorReporter.reportError( @@ -872,7 +1006,7 @@ private static class ContainsMutableVisitor extends SimpleAnnotationValueVisitor @Override public Boolean visitArray(List list, Void p) { return list.stream().map(AnnotationValue::getValue).anyMatch("mutable"::equals); - } + } } /** @@ -933,7 +1067,25 @@ ImmutableList copiedClassAnnotations(TypeElement type) { // Only copy annotations from a class if it has @AutoValue.CopyAnnotations. if (hasAnnotationMirror(type, COPY_ANNOTATIONS_NAME)) { Set excludedAnnotations = - union(getExcludedAnnotationClassNames(type), getAnnotationsMarkedWithInherited(type)); + ImmutableSet.builder() + .addAll(getExcludedAnnotationClassNames(type)) + .addAll(getAnnotationsMarkedWithInherited(type)) + // + // Kotlin classes have an intrinsic @Metadata annotation generated + // onto them by kotlinc. This annotation is specific to the annotated + // class and should not be implicitly copied. Doing so can mislead + // static analysis or metaprogramming tooling that reads the data + // contained in these annotations. + // + // It may be surprising to see AutoValue classes written in Kotlin + // when they could be written as Kotlin data classes, but this can + // come up in cases where consumers rely on AutoValue features or + // extensions that are not available in data classes. + // + // See: https://github.com/google/auto/issues/1087 + // + .add(ClassNames.KOTLIN_METADATA_NAME) + .build(); return copyAnnotations(type, type, excludedAnnotations); } else { @@ -963,8 +1115,7 @@ private Set getExcludedAnnotationTypes(Element element) { @SuppressWarnings("unchecked") List excludedClasses = (List) getAnnotationValue(maybeAnnotation.get(), "exclude").getValue(); - return excludedClasses - .stream() + return excludedClasses.stream() .map(annotationValue -> (DeclaredType) annotationValue.getValue()) .collect(toCollection(TypeMirrorSet::new)); } @@ -974,17 +1125,14 @@ private Set getExcludedAnnotationTypes(Element element) { * strings that are fully-qualified class names. */ private Set getExcludedAnnotationClassNames(Element element) { - return getExcludedAnnotationTypes(element) - .stream() + return getExcludedAnnotationTypes(element).stream() .map(MoreTypes::asTypeElement) .map(typeElement -> typeElement.getQualifiedName().toString()) .collect(toSet()); } private static Set getAnnotationsMarkedWithInherited(Element element) { - return element - .getAnnotationMirrors() - .stream() + return element.getAnnotationMirrors().stream() .filter(a -> isAnnotationPresent(a.getAnnotationType().asElement(), Inherited.class)) .map(a -> getAnnotationFqName(a)) .collect(toSet()); @@ -1051,9 +1199,7 @@ private ImmutableList propertyFieldAnnotations( Set returnTypeAnnotations = getReturnTypeAnnotations(method, this::annotationAppliesToFields); Set nonFieldAnnotations = - method - .getAnnotationMirrors() - .stream() + method.getAnnotationMirrors().stream() .map(a -> a.getAnnotationType().asElement()) .map(MoreElements::asType) .filter(a -> !annotationAppliesToFields(a)) @@ -1071,10 +1217,7 @@ private ImmutableList propertyFieldAnnotations( private Set getReturnTypeAnnotations( ExecutableElement method, Predicate typeFilter) { - return method - .getReturnType() - .getAnnotationMirrors() - .stream() + return method.getReturnType().getAnnotationMirrors().stream() .map(a -> a.getAnnotationType().asElement()) .map(MoreElements::asType) .filter(typeFilter) @@ -1144,6 +1287,14 @@ static boolean hasAnnotationMirror(Element element, String annotationName) { 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 0bcaa6827d..e6ec640c7f 100644 --- a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java +++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java @@ -69,6 +69,7 @@ abstract class BuilderMethodClassifier { private final Elements elementUtils; private final TypeMirror builtType; private final TypeElement builderType; + private final ImmutableSet propertiesWithDefaults; /** * Property types, rewritten to refer to type variables in the builder. For example, suppose you @@ -101,13 +102,15 @@ abstract class BuilderMethodClassifier { ProcessingEnvironment processingEnv, TypeMirror builtType, TypeElement builderType, - ImmutableMap rewrittenPropertyTypes) { + ImmutableMap rewrittenPropertyTypes, + ImmutableSet propertiesWithDefaults) { this.errorReporter = errorReporter; this.typeUtils = processingEnv.getTypeUtils(); this.elementUtils = processingEnv.getElementUtils(); this.builtType = builtType; this.builderType = builderType; this.rewrittenPropertyTypes = rewrittenPropertyTypes; + this.propertiesWithDefaults = propertiesWithDefaults; this.eclipseHack = new EclipseHack(processingEnv); } @@ -193,7 +196,7 @@ boolean classifyMethods(Iterable methods, boolean autoValueHa propertyBuilder.getBuilderTypeMirror(), propertyType); } - } else if (!hasSetter) { + } else if (!hasSetter && !propertiesWithDefaults.contains(property)) { // We have neither barBuilder() nor setBar(Bar), so we should complain. String setterName = settersPrefixed ? prefixWithSet(property) : property; errorReporter.reportError( @@ -244,8 +247,15 @@ private void classifyMethodNoArgs(ExecutableElement method) { TypeMirror returnType = builderMethodReturnType(method); if (methodName.endsWith("Builder")) { - String property = methodName.substring(0, methodName.length() - "Builder".length()); - if (rewrittenPropertyTypes.containsKey(property)) { + String prefix = methodName.substring(0, methodName.length() - "Builder".length()); + String property = + rewrittenPropertyTypes.containsKey(prefix) + ? prefix + : rewrittenPropertyTypes.keySet().stream() + .filter(p -> PropertyNames.decapitalizeNormally(p).equals(prefix)) + .findFirst() + .orElse(null); + if (property != null) { PropertyBuilderClassifier propertyBuilderClassifier = new PropertyBuilderClassifier( errorReporter, @@ -374,8 +384,9 @@ private void classifyMethodOneArg(ExecutableElement method) { // propertyNameToSetters can't be null when we call put on it below. errorReporter.reportError( method, - "[%sBuilderWhatProp] Method does not correspond to %s", + "[%sBuilderWhatProp] Method %s does not correspond to %s", autoWhat(), + methodName, getterMustMatch()); checkForFailedJavaBean(method); return; @@ -385,14 +396,17 @@ private void classifyMethodOneArg(ExecutableElement method) { 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/BuilderMethodClassifierForAutoBuilder.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoBuilder.java index 94b258f152..7ecfd67467 100644 --- a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoBuilder.java +++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoBuilder.java @@ -15,16 +15,15 @@ */ package com.google.auto.value.processor; -import static com.google.common.collect.ImmutableBiMap.toImmutableBiMap; -import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.auto.common.MoreStreams.toImmutableBiMap; +import static com.google.auto.common.MoreStreams.toImmutableMap; -import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.common.base.Equivalence; -import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -41,18 +40,25 @@ import javax.lang.model.util.Types; class BuilderMethodClassifierForAutoBuilder extends BuilderMethodClassifier { - private final ExecutableElement executable; + private final Executable executable; private final ImmutableBiMap paramToPropertyName; private BuilderMethodClassifierForAutoBuilder( ErrorReporter errorReporter, ProcessingEnvironment processingEnv, - ExecutableElement executable, + Executable executable, TypeMirror builtType, TypeElement builderType, ImmutableBiMap paramToPropertyName, - ImmutableMap rewrittenPropertyTypes) { - super(errorReporter, processingEnv, builtType, builderType, rewrittenPropertyTypes); + ImmutableMap rewrittenPropertyTypes, + ImmutableSet propertiesWithDefaults) { + super( + errorReporter, + processingEnv, + builtType, + builderType, + rewrittenPropertyTypes, + propertiesWithDefaults); this.executable = executable; this.paramToPropertyName = paramToPropertyName; } @@ -66,6 +72,8 @@ private BuilderMethodClassifierForAutoBuilder( * @param executable the constructor or static method that AutoBuilder will call. * @param builtType the type to be built. * @param builderType the builder class or interface within {@code ofClass}. + * @param propertiesWithDefaults properties that have a default value, so it is not an error for + * them not to have a setter. * @return an {@code Optional} that contains the results of the classification if it was * successful or nothing if it was not. */ @@ -73,11 +81,12 @@ static Optional> classify( Iterable methods, ErrorReporter errorReporter, ProcessingEnvironment processingEnv, - ExecutableElement executable, + Executable executable, TypeMirror builtType, - TypeElement builderType) { + TypeElement builderType, + ImmutableSet propertiesWithDefaults) { ImmutableBiMap paramToPropertyName = - executable.getParameters().stream() + executable.parameters().stream() .collect(toImmutableBiMap(v -> v, v -> v.getSimpleName().toString())); ImmutableMap rewrittenPropertyTypes = rewriteParameterTypes(executable, builderType, errorReporter, processingEnv.getTypeUtils()); @@ -89,7 +98,8 @@ static Optional> classify( builtType, builderType, paramToPropertyName, - rewrittenPropertyTypes); + rewrittenPropertyTypes, + propertiesWithDefaults); if (classifier.classifyMethods(methods, false)) { return Optional.of(classifier); } else { @@ -134,11 +144,11 @@ static Optional> classify( // MoreTypes.equivalence to compare those, and that returns true for distinct type variables if // they have the same name and bounds. private static ImmutableMap rewriteParameterTypes( - ExecutableElement executable, + Executable executable, TypeElement builderType, ErrorReporter errorReporter, Types typeUtils) { - ImmutableList executableTypeParams = executableTypeParams(executable); + ImmutableList executableTypeParams = executable.typeParameters(); List builderTypeParams = builderType.getTypeParameters(); if (!BuilderSpec.sameTypeParameters(executableTypeParams, builderTypeParams)) { errorReporter.abortWithError( @@ -146,12 +156,12 @@ private static ImmutableMap rewriteParameterTypes( "[AutoBuilderTypeParams] Builder type parameters %s must match type parameters %s of %s", TypeEncoder.typeParametersString(builderTypeParams), TypeEncoder.typeParametersString(executableTypeParams), - AutoBuilderProcessor.executableString(executable)); + executable); } if (executableTypeParams.isEmpty()) { // Optimization for a common case. No point in doing all that type visiting if we have no // variables to substitute. - return executable.getParameters().stream() + return executable.parameters().stream() .collect(toImmutableMap(v -> v.getSimpleName().toString(), Element::asType)); } Map, TypeMirror> typeVariables = new LinkedHashMap<>(); @@ -162,32 +172,13 @@ private static ImmutableMap rewriteParameterTypes( } Function substitute = v -> typeVariables.get(MoreTypes.equivalence().wrap(v)); - return executable.getParameters().stream() + return executable.parameters().stream() .collect( toImmutableMap( v -> v.getSimpleName().toString(), v -> TypeVariables.substituteTypeVariables(v.asType(), substitute, typeUtils))); } - private static ImmutableList executableTypeParams( - ExecutableElement executable) { - switch (executable.getKind()) { - case CONSTRUCTOR: - // A constructor can have its own type parameters, in addition to any that its containing - // class has. That's pretty unusual, but we allow it, requiring the builder to have type - // parameters that are the concatenation of the class's and the constructor's. - TypeElement container = MoreElements.asType(executable.getEnclosingElement()); - return ImmutableList.builder() - .addAll(container.getTypeParameters()) - .addAll(executable.getTypeParameters()) - .build(); - case METHOD: - return ImmutableList.copyOf(executable.getTypeParameters()); - default: - throw new VerifyException("Unexpected executable kind " + executable.getKind()); - } - } - @Override Optional propertyForBuilderGetter(ExecutableElement method) { String methodName = method.getSimpleName().toString(); @@ -224,10 +215,7 @@ TypeMirror originalPropertyType(VariableElement propertyElement) { @Override String propertyString(VariableElement propertyElement) { - return "parameter \"" - + propertyElement.getSimpleName() - + "\" of " - + AutoBuilderProcessor.executableString(executable); + return "parameter \"" + propertyElement.getSimpleName() + "\" of " + executable; } @Override @@ -237,7 +225,7 @@ String autoWhat() { @Override String getterMustMatch() { - return "a parameter of " + AutoBuilderProcessor.executableString(executable); + return "a parameter of " + executable; } @Override diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java index dde449bb5f..0c2a8c5b3b 100644 --- a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java +++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java @@ -41,7 +41,13 @@ private BuilderMethodClassifierForAutoValue( TypeElement builderType, ImmutableBiMap getterToPropertyName, ImmutableMap rewrittenPropertyTypes) { - super(errorReporter, processingEnv, builtType, builderType, rewrittenPropertyTypes); + super( + errorReporter, + processingEnv, + builtType, + builderType, + rewrittenPropertyTypes, + ImmutableSet.of()); this.errorReporter = errorReporter; this.getterToPropertyName = getterToPropertyName; this.getterNameToGetter = diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderRequiredProperties.java b/value/src/main/java/com/google/auto/value/processor/BuilderRequiredProperties.java new file mode 100644 index 0000000000..e99f8fd859 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/processor/BuilderRequiredProperties.java @@ -0,0 +1,401 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.auto.value.processor; + +import static com.google.auto.common.MoreStreams.toImmutableList; +import static com.google.auto.common.MoreStreams.toImmutableMap; +import static java.lang.Math.min; +import static java.util.stream.Collectors.joining; + +import com.google.auto.value.processor.AutoValueishProcessor.Property; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +/** + * Code generation to track which properties have been set in a builder. + * + *

Every property in an {@code @AutoValue} or {@code @AutoBuilder} builder must be set before the + * {@code build()} method is called, with a few exceptions like {@code @Nullable} and {@code + * Optional} properties. That means we must keep track of which ones have in fact been set. We do + * that in two ways: for reference (non-primitive) types, we use {@code null} to indicate that the + * value has not been set, while for primitive types we use a bitmask where each bit indicates + * whether a certain primitive property has been set. + * + *

Additionally, for Kotlin constructors with default parameters, we track exactly which + * properties have been set so we can invoke the constructor thas has a bitmask indicating the + * properties to be defaulted. + * + *

The public methods in this class are accessed reflectively from the {@code builder.vm} + * template. In that template, {@code $builderRequiredProperties} references an instance of this + * class corresponding to the builder being generated. A reference like {@code + * $builderRequiredProperties.markAsSet($p)} calls the method {@link #markAsSet} with the given + * parameter. A reference like {@code $builderRequiredProperties.requiredProperties} is shorthand + * for {@link #getRequiredProperties() $builderRequiredProperties.getProperties()}. + */ +public abstract class BuilderRequiredProperties { + static final BuilderRequiredProperties EMPTY = of(ImmutableSet.of(), ImmutableSet.of()); + + // Bitmasks are a bit fiddly because we use them in a couple of ways. The first way is where + // we are just using the bitmasks to track which primitive properties have been set. Then if + // we have three primitive properties we can just check that the bitmask is (1 << 3) - 1, the + // all-ones bitmask, to see that they have all been set. The second way is when we are also + // handling optional Kotlin parameters. Then the bitmasks are different: we have one bit for every + // property, primitive or not, optional or not. To check that the required primitive properties + // have been set, we need to check specific bits. For example if properties 1 and 3 are primitive + // then we need to check (~set$0 & ((1 << 1) | (1 << 3))) == 0. That tests that bits 1 and 3 are + // set, since if either of them is 0 then it will be 1 in ~set$0 and will survive the AND. We can + // also isolate the bits representing optional Kotlin parameters similarly, and pass those to the + // special Kotlin constructor that handles default parameters. Kotlin uses bitmasks for that too: + // they have one bit per parameter, optional or not, but only the bits for optional parameters + // matter. We isolate those bits with `&` operations similar to what was described for primitive + // properties. We also need the all-ones bitmask to implement a "copy constructor" builder, which + // starts out with all properties set. + + /** All required properties. */ + final ImmutableSet requiredProperties; + + /** + * The bit index for each tracked property. Properties are tracked if they are primitive, or if + * this is a Kotlin constructor with default parameters. Non-tracked properties do not appear in + * this map. + */ + final ImmutableMap trackedPropertyToIndex; + + /** + * The integer fields that store the bitmask. In the usual case, where there are ≤32 tracked + * properties, we can pack the bitmask into one integer field. Its type is the smallest one that + * fits the required number of bits, for example {@code byte} if there are ≤8 tracked properties. + * + *

If there are {@literal >32} tracked properties, we will pack them into as few integer fields + * as possible. For example if there are 75 tracked properties (this can happen) then we will put + * numbers 0 to 31 in an {@code int}, 32 to 63 in a second {@code int}, and 64 to 75 in a {@code + * short}. + * + *

When there are {@literal >32} tracked properties, we could potentially pack them better if + * we used {@code long}. But sometimes AutoValue code gets translated into JavaScript, which + * doesn't handle long values natively. By the time you have that many properties you are probably + * not going to notice the difference between 5 ints or 2 longs plus an int. + */ + final ImmutableList bitmaskFields; + + /** + * Represents a field in which we will record which tracked properties from a certain set have + * been given a value. + */ + private static class BitmaskField { + final Class type; + final String name; + + /** + * The source representation of the value this field has when all properties have been given a + * value. + */ + final String allSetBitmask; + + /** + * The source representation of the value this field has when all required properties have been + * given a value. + */ + final String allRequiredBitmask; + + BitmaskField(Class type, String name, String allSetBitmask, String allRequiredBitmask) { + this.type = type; + this.name = name; + this.allSetBitmask = allSetBitmask; + this.allRequiredBitmask = allRequiredBitmask; + } + } + + static BuilderRequiredProperties of( + ImmutableSet allProperties, ImmutableSet requiredProperties) { + boolean hasDefaults = allProperties.stream().anyMatch(Property::hasDefault); + return hasDefaults + ? new WithDefaults(allProperties, requiredProperties) + : new NoDefaults(requiredProperties); + } + + private BuilderRequiredProperties( + ImmutableSet requiredProperties, ImmutableList trackedProperties) { + this.requiredProperties = requiredProperties; + + int trackedCount = trackedProperties.size(); + this.trackedPropertyToIndex = + IntStream.range(0, trackedCount) + .boxed() + .collect(toImmutableMap(trackedProperties::get, i -> i)); + + this.bitmaskFields = + IntStream.range(0, (trackedCount + 31) / 32) + .mapToObj( + i -> { + int bitBase = i * 32; + int remainingBits = trackedCount - bitBase; + Class type = classForBits(remainingBits); + String name = "set$" + i; + String allSetBitmask = + (remainingBits >= 32) ? "-1" : hex((1 << remainingBits) - 1); + String allRequiredBitmask = + allRequiredBitmask(trackedProperties, bitBase, remainingBits); + return new BitmaskField(type, name, allSetBitmask, allRequiredBitmask); + }) + .collect(toImmutableList()); + } + + abstract String allRequiredBitmask( + ImmutableList trackedProperties, int bitBase, int remainingBits); + + public ImmutableSet getRequiredProperties() { + return requiredProperties; + } + + /** + * Returns code to declare any fields needed to track which properties have been set. Each line in + * the returned list should appear on a line of its own. + */ + public ImmutableList getFieldDeclarations() { + return bitmaskFields.stream() + .map(field -> "private " + field.type + " " + field.name + ";") + .collect(toImmutableList()); + } + + /** + * Returns code to indicate that all tracked properties have received a value. This is needed in + * the {@code toBuilder()} constructor, since it assigns to the corresponding fields directly + * without going through their setters. + */ + public ImmutableList getInitToAllSet() { + return bitmaskFields.stream() + .map(field -> field.name + " = " + cast(field.type, field.allSetBitmask) + ";") + .collect(toImmutableList()); + } + + /** + * Returns code to indicate that the given property has been set, if assigning to the property + * field is not enough. For reference (non-primitive) properties, assignment is enough, but + * for primitive properties we also need to set a bit in the bitmask. + */ + public String markAsSet(Property p) { + Integer index = trackedPropertyToIndex.get(p); + if (index == null) { + return ""; + } + BitmaskField field = bitmaskFields.get(index / 32); + // This use-case is why Java reduces int shift amounts mod 32. :-) + return field.name + " |= " + cast(field.type, hex(1 << index)) + ";"; + } + + /** + * Returns an expression that is true if the given property is required but has not been set. + * Returns null if the property is not required. + */ + public String missingRequiredProperty(Property p) { + return requiredProperties.contains(p) ? propertyNotSet(p) : null; + } + + /** + * Returns an expression that is true if the given property has not been given a value. That's + * only different from {@link #missingRequiredProperty} if the property has a Kotlin default. If + * so, we don't require it to be set at build time (because Kotlin will supply the default), but + * we do require it to be set if it is accessed with a getter on the builder. We don't have access + * to Kotlin parameter defaults so we can't arrange for the builder field to have the same default + * value. Rather than returning a bogus zero value we say the value is unset. + */ + public String noValueToGet(Property p) { + return (requiredProperties.contains(p) || p.hasDefault()) ? propertyNotSet(p) : null; + } + + private String propertyNotSet(Property p) { + Integer index = trackedPropertyToIndex.get(p); + if (index == null) { + return "this." + p + " == null"; + } + BitmaskField field = bitmaskFields.get(index / 32); + return "(" + field.name + " & " + hex(1 << index) + ") == 0"; + } + + /** + * Returns an expression that is true if any required properties have not been set. Should not be + * called if there are no required properties. + */ + public abstract String getAnyMissing(); + + /** + * Returns additional constructor parameters to indicate what properties have been defaulted, or + * an empty string if there are none. + */ + public abstract String getDefaultedBitmaskParameters(); + + /** + * The smallest primitive integer type that has at least this many bits, or {@code int} if the + * number of bits is more than 32. + */ + private static Class classForBits(int bits) { + return bits <= 8 ? byte.class : bits <= 16 ? short.class : int.class; + } + + private static String cast(Class type, String number) { + return (type == int.class) ? number : ("(" + type + ") " + number); + } + + @VisibleForTesting + static String hex(int number) { + if (number >= 0) { + if (number < 10) { + return Integer.toHexString(number); + } + if (number <= 0xffff) { + return "0x" + Integer.toHexString(number); + } + } + // It's harder to tell 0x7fffffff from 0x7ffffff than to tell 0x7fff_ffff from 0x7ff_ffff. + String lowNybble = Integer.toHexString(number & 0xffff); + String pad = "000".substring(lowNybble.length() - 1); + return "0x" + Integer.toHexString(number >>> 16) + "_" + pad + lowNybble; + } + + /** Subclass for when there are no Kotlin default properties. */ + private static final class NoDefaults extends BuilderRequiredProperties { + NoDefaults(ImmutableSet requiredProperties) { + super(requiredProperties, primitivePropertiesIn(requiredProperties)); + } + + private static ImmutableList primitivePropertiesIn( + ImmutableSet properties) { + return properties.stream().filter(p -> p.getKind().isPrimitive()).collect(toImmutableList()); + } + + @Override + String allRequiredBitmask( + ImmutableList trackedProperties, int bitBase, int remainingBits) { + // We have to be a bit careful with sign-extension. If we're using a byte and + // the mask is 0xff, then we'll write -1 instead. The comparison set$0 == 0xff + // would always fail since the byte value gets sign-extended to 0xffff_ffff. + // We should also write -1 if this is not the last field. + boolean minusOne = remainingBits >= 32 || remainingBits == 16 || remainingBits == 8; + return minusOne ? "-1" : hex((1 << remainingBits) - 1); + } + + /** + * {@inheritDoc} + * + *

We check the bitmask for primitive properties, and null checks for non-primitive ones. + */ + @Override + public String getAnyMissing() { + Stream primitiveConditions = + bitmaskFields.stream().map(field -> field.name + " != " + field.allRequiredBitmask); + Stream nonPrimitiveConditions = + requiredProperties.stream() + .filter(p -> !trackedPropertyToIndex.containsKey(p)) + .map(this::missingRequiredProperty); + return Stream.concat(primitiveConditions, nonPrimitiveConditions).collect(joining("\n|| ")); + } + + @Override + public String getDefaultedBitmaskParameters() { + return ""; + } + } + + /** Subclass for when there are Kotlin default properties. */ + private static final class WithDefaults extends BuilderRequiredProperties { + private final ImmutableList allProperties; + + WithDefaults(ImmutableSet allProperties, ImmutableSet requiredProperties) { + super(requiredProperties, allProperties.asList()); + this.allProperties = allProperties.asList(); + } + + @Override + String allRequiredBitmask( + ImmutableList trackedProperties, int bitBase, int remainingBits) { + int requiredBits = 0; + for (int bit = 0; bit < remainingBits; bit++) { + Property p = trackedProperties.get(bitBase + bit); + if (requiredProperties.contains(p)) { + requiredBits |= 1 << bit; + } + } + return hex(requiredBits); + } + + /** + * {@inheritDoc} + * + *

Everything can be checked with bitmask operations. If bit i represents a required + * property then it must be 1 in the bitmask field. So if we invert it we must get 0, and if we + * do that for the field as a whole and AND with a bitmask selecting only required properties we + * should get 0. + */ + @Override + public String getAnyMissing() { + return bitmaskFields.stream() + .filter(field -> !field.allRequiredBitmask.equals("0")) + .map(field -> "(~" + field.name + " & " + field.allRequiredBitmask + ") != 0") + .collect(joining("\n|| ")); + } + + /** + * {@inheritDoc} + * + *

When there are default parameters, we're calling the special constructor that has one or + * more bitmask parameters at the end. Bit i is set if parameter i (zero-origin) + * has its default value, and then the actual value passed for that parameter is ignored. Our + * bitmask field has a 1 for any parameter that has been set, meaning it has a 0 for any + * parameter that has been defaulted. So we need to invert it, and we also want to AND it with a + * bitmask that selects just the bits for parameters with defaults. (The AND probably isn't + * strictly necessary, since the constructor code doesn't actually look at those other bits, but + * it seems cleaner.) If the bitmask for parameters with defaults is 0 then we can just use 0 + * for that bitmask, and if it is ~0 (all 1 bits) then we can skip the AND. + * + *

That special constructor has an additional dummy parameter of type {@code + * DefaultConstructorMarker}. We just pass {@code null} to that parameter. + */ + @Override + public String getDefaultedBitmaskParameters() { + ImmutableList.Builder defaultedBitmasksBuilder = ImmutableList.builder(); + for (int bitBase = 0; bitBase < allProperties.size(); bitBase += 32) { + int bitCount = min(32, allProperties.size() - bitBase); + int defaultedBitmask = 0; + for (int i = 0; i < bitCount; i++) { + if (allProperties.get(bitBase + i).hasDefault()) { + defaultedBitmask |= 1 << i; + } + } + defaultedBitmasksBuilder.add(defaultedBitmask); + } + ImmutableList defaultedBitmasks = defaultedBitmasksBuilder.build(); + return IntStream.range(0, bitmaskFields.size()) + .mapToObj( + i -> { + int defaultedBitmask = defaultedBitmasks.get(i); + switch (defaultedBitmask) { + case 0: + return "0"; + case ~0: + return "~" + bitmaskFields.get(i).name; + default: + return "~" + bitmaskFields.get(i).name + " & " + hex(defaultedBitmask); + } + }) + .collect(joining(",\n", ",\n", ",\nnull")); + } + } +} diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java index 017a5ff5e3..425636de70 100644 --- a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java +++ b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java @@ -16,7 +16,9 @@ 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; @@ -35,7 +37,6 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -86,16 +87,9 @@ Optional getBuilder() { Optional 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 @@ Optional getBuilder() { } } + /** Finds why this {@code @AutoValue.Builder} class is bad, if it is bad. */ + private Optional 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; @@ -315,6 +327,7 @@ void defineVars(AutoValueOrBuilderTemplateVars vars, BuilderMethodClassifier autoValueClass, typeParamsString()); } + errorReporter.abortIfAnyError(); return; } this.buildMethod = Iterables.getOnlyElement(buildMethods); @@ -330,15 +343,15 @@ void defineVars(AutoValueOrBuilderTemplateVars vars, BuilderMethodClassifier vars.builderPropertyBuilders = ImmutableMap.copyOf(classifier.propertyNameToPropertyBuilder()); - Set required = new LinkedHashSet<>(vars.props); - for (Property property : vars.props) { - if (property.isNullable() - || property.getOptional() != null - || vars.builderPropertyBuilders.containsKey(property.getName())) { - required.remove(property); - } - } - vars.builderRequiredProperties = ImmutableSet.copyOf(required); + ImmutableSet requiredProperties = + vars.props.stream() + .filter(p -> !p.isNullable()) + .filter(p -> p.getBuilderInitializer().isEmpty()) + .filter(p -> !p.hasDefault()) + .filter(p -> !vars.builderPropertyBuilders.containsKey(p.getName())) + .collect(toImmutableSet()); + vars.builderRequiredProperties = + BuilderRequiredProperties.of(vars.props, requiredProperties); } } @@ -378,8 +391,7 @@ public static class PropertyGetter { this.optional = optional; } - // Not accessed from templates so doesn't have to be public. - String getName() { + public String getName() { return name; } diff --git a/value/src/main/java/com/google/auto/value/processor/ClassNames.java b/value/src/main/java/com/google/auto/value/processor/ClassNames.java index e033c77d6f..dea20ea61d 100644 --- a/value/src/main/java/com/google/auto/value/processor/ClassNames.java +++ b/value/src/main/java/com/google/auto/value/processor/ClassNames.java @@ -30,4 +30,5 @@ private ClassNames() {} static final String AUTO_VALUE_BUILDER_NAME = AUTO_VALUE_NAME + ".Builder"; static final String AUTO_BUILDER_NAME = AUTO_VALUE_PACKAGE_NAME + "AutoBuilder"; static final String COPY_ANNOTATIONS_NAME = AUTO_VALUE_NAME + ".CopyAnnotations"; + static final String KOTLIN_METADATA_NAME = "kot".concat("lin.Metadata"); // defeat shading } diff --git a/value/src/main/java/com/google/auto/value/processor/Executable.java b/value/src/main/java/com/google/auto/value/processor/Executable.java new file mode 100644 index 0000000000..aeaada9d05 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/processor/Executable.java @@ -0,0 +1,144 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.auto.value.processor; + +import static com.google.auto.common.MoreStreams.toImmutableList; +import static java.util.stream.Collectors.joining; + +import com.google.auto.common.MoreElements; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +/** + * A wrapper for an {@link ExecutableElement}, representing a static method or a constructor. This + * wrapper then allows us to attach additional information, such as which parameters have Kotlin + * defaults. + */ +class Executable { + private final ExecutableElement executableElement; + + private final ImmutableList parameters; + private final ImmutableSet optionalParameters; + private final ImmutableList typeParameters; + + private Executable(ExecutableElement executableElement, ImmutableSet optionalParameters) { + this.executableElement = executableElement; + this.parameters = ImmutableList.copyOf(executableElement.getParameters()); + this.optionalParameters = optionalParameters; + + switch (executableElement.getKind()) { + case CONSTRUCTOR: + // A constructor can have its own type parameters, in addition to any that its containing + // class has. That's pretty unusual, but we allow it, requiring the builder to have type + // parameters that are the concatenation of the class's and the constructor's. + TypeElement container = MoreElements.asType(executableElement.getEnclosingElement()); + this.typeParameters = ImmutableList.builder() + .addAll(container.getTypeParameters()) + .addAll(executableElement.getTypeParameters()) + .build(); + break; + case METHOD: + this.typeParameters = ImmutableList.copyOf(executableElement.getTypeParameters()); + break; + default: + throw new VerifyException("Unexpected executable kind " + executableElement.getKind()); + } + } + + static Executable of(ExecutableElement executableElement) { + return of(executableElement, ImmutableSet.of()); + } + + static Executable of( + ExecutableElement executableElement, ImmutableSet optionalParameters) { + return new Executable(executableElement, optionalParameters); + } + + ExecutableElement executableElement() { + return executableElement; + } + + ImmutableList parameters() { + return parameters; + } + + ImmutableList parameterNames() { + return parameters.stream().map(v -> v.getSimpleName().toString()).collect(toImmutableList()); + } + + boolean isOptional(String parameterName) { + return optionalParameters.contains(parameterName); + } + + int optionalParameterCount() { + return optionalParameters.size(); + } + + ImmutableList typeParameters() { + return typeParameters; + } + + TypeMirror builtType() { + switch (executableElement.getKind()) { + case CONSTRUCTOR: + return executableElement.getEnclosingElement().asType(); + case METHOD: + return executableElement.getReturnType(); + default: + throw new VerifyException("Unexpected executable kind " + executableElement.getKind()); + } + } + + /** + * The Java code to invoke this constructor or method, up to just before the opening {@code (}. + */ + String invoke() { + TypeElement enclosing = MoreElements.asType(executableElement.getEnclosingElement()); + String type = TypeEncoder.encodeRaw(enclosing.asType()); + switch (executableElement.getKind()) { + case CONSTRUCTOR: + boolean generic = !enclosing.getTypeParameters().isEmpty(); + String typeParams = generic ? "<>" : ""; + return "new " + type + typeParams; + case METHOD: + return type + "." + executableElement.getSimpleName(); + default: + throw new VerifyException("Unexpected executable kind " + executableElement.getKind()); + } + } + + // Used in error messages, for example if more than one constructor matches your setters. + @Override + public String toString() { + ExecutableElement executable = executableElement; + Element nameSource = + executable.getKind() == ElementKind.CONSTRUCTOR + ? executable.getEnclosingElement() + : executable; + return nameSource.getSimpleName() + + executable.getParameters().stream() + .map(v -> v.asType() + " " + v.getSimpleName()) + .collect(joining(", ", "(", ")")); + } +} diff --git a/value/src/main/java/com/google/auto/value/processor/ForwardingClassGenerator.java b/value/src/main/java/com/google/auto/value/processor/ForwardingClassGenerator.java new file mode 100644 index 0000000000..71f035d0a3 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/processor/ForwardingClassGenerator.java @@ -0,0 +1,229 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.auto.value.processor; + +import static com.google.auto.common.MoreTypes.asArray; +import static com.google.auto.common.MoreTypes.asTypeElement; +import static java.util.stream.Collectors.joining; +import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS; +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.ACC_STATIC; +import static org.objectweb.asm.Opcodes.ACC_SUPER; +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.ARETURN; +import static org.objectweb.asm.Opcodes.DLOAD; +import static org.objectweb.asm.Opcodes.DUP; +import static org.objectweb.asm.Opcodes.FLOAD; +import static org.objectweb.asm.Opcodes.ILOAD; +import static org.objectweb.asm.Opcodes.INVOKESPECIAL; +import static org.objectweb.asm.Opcodes.LLOAD; +import static org.objectweb.asm.Opcodes.NEW; +import static org.objectweb.asm.Opcodes.V1_7; + +import com.google.auto.common.MoreElements; +import com.google.common.collect.ImmutableList; +import javax.lang.model.element.NestingKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; + +/** + * Generates a class that invokes the constructor of another class. + * + *

The point here is that the constructor might be synthetic, in which case it can't be called + * directly from Java source code. Say we want to call the constructor {@code ConstructMe(int, + * String, long)} with parameters {@code 1, "2", 3L}. If the constructor is synthetic, then Java + * source code can't just do {@code new ConstructMe(1, "2", 3L)}. So this class allows you to + * generate a class file, say {@code Forwarder}, that is basically what you would get if you could + * compile this: + * + *

+ * final class Forwarder {
+ *   private Forwarder() {}
+ *
+ *   static ConstructMe of(int a, String b, long c) {
+ *     return new ConstructMe(a, b, c);
+ *   }
+ * }
+ * 
+ * + *

Because the class file is assembled directly, rather than being produced by the Java compiler, + * it can call the synthetic constructor. Then regular Java source code can do {@code + * Forwarder.of(1, "2", 3L)} to call the constructor. + */ +final class ForwardingClassGenerator { + /** + * Assembles a class with a static method {@code of} that calls the constructor of another class + * with the same parameters. + * + *

It would be simpler if we could just pass in an {@code ExecutableElement} representing the + * constructor, but if it is synthetic then it won't be visible to the {@code javax.lang.model} + * APIs. So we have to pass the constructed type and the constructor parameter types separately. + * + * @param forwardingClassName the fully-qualified name of the class to generate + * @param classToConstruct the type whose constructor will be invoked ({@code ConstructMe} in the + * example above) + * @param constructorParameters the erased types of the constructor parameters, which will also be + * the types of the generated {@code of} method. We require the types to be erased so as not + * to require an instance of the {@code Types} interface to erase them here. Having to deal + * with generics would complicate things unnecessarily. + * @return a byte array making up the new class file + */ + static byte[] makeConstructorForwarder( + String forwardingClassName, + TypeMirror classToConstruct, + ImmutableList constructorParameters) { + + ClassWriter classWriter = new ClassWriter(COMPUTE_MAXS); + classWriter.visit( + V1_7, + ACC_FINAL | ACC_SUPER, + internalName(forwardingClassName), + null, + "java/lang/Object", + null); + classWriter.visitSource(forwardingClassName, null); + + // Generate the `of` method. + // TODO(emcmanus): cleaner generics. If we're constructing Foo then we should + // generate a generic signature for the `of` method, as if the Java declaration were this: + // static Foo of(...) + // Currently we just generate: + // static Foo of(...) + // which returns the raw Foo type. + String parameterSignature = + constructorParameters.stream() + .map(ForwardingClassGenerator::signatureEncoding) + .collect(joining("")); + String internalClassToConstruct = internalName(asTypeElement(classToConstruct)); + String ofMethodSignature = "(" + parameterSignature + ")L" + internalClassToConstruct + ";"; + MethodVisitor ofMethodVisitor = + classWriter.visitMethod(ACC_STATIC, "of", ofMethodSignature, null, null); + ofMethodVisitor.visitCode(); + + // The remaining instructions are basically what ASMifier generates for a class like the + // `Forwarder` class in the example above. + ofMethodVisitor.visitTypeInsn(NEW, internalClassToConstruct); + ofMethodVisitor.visitInsn(DUP); + + int local = 0; + for (TypeMirror type : constructorParameters) { + ofMethodVisitor.visitVarInsn(loadInstruction(type), local); + local += localSize(type); + } + String constructorToCallSignature = "(" + parameterSignature + ")V"; + ofMethodVisitor.visitMethodInsn( + INVOKESPECIAL, + internalClassToConstruct, + "", + constructorToCallSignature, + /* isInterface= */ false); + + ofMethodVisitor.visitInsn(ARETURN); + ofMethodVisitor.visitMaxs(0, 0); + ofMethodVisitor.visitEnd(); + classWriter.visitEnd(); + return classWriter.toByteArray(); + } + + /** The bytecode instruction that copies a parameter of the given type onto the JVM stack. */ + private static int loadInstruction(TypeMirror type) { + switch (type.getKind()) { + case DECLARED: + case ARRAY: + return ALOAD; + case LONG: + return LLOAD; + case FLOAT: + return FLOAD; + case DOUBLE: + return DLOAD; + case BYTE: + case SHORT: + case CHAR: + case INT: + case BOOLEAN: + // These are all represented as int local variables. + return ILOAD; + default: + // We expect the caller to have erased the parameters so we shouldn't be seeing type + // variables or whatever. + throw new IllegalArgumentException("Unexpected type " + type); + } + } + + /** + * The size in the local variable array of a value of the given type. A quirk of the JVM means + * that long and double variables each take up two consecutive slots in the local variable array. + * (The first n local variables are the parameters, so we need to know their sizes when iterating + * over them.) + */ + private static int localSize(TypeMirror type) { + switch (type.getKind()) { + case LONG: + case DOUBLE: + return 2; + default: + return 1; + } + } + + private static String internalName(String className) { + return className.replace('.', '/'); + } + + /** + * Given a class like {@code foo.bar.Outer.Inner}, produces a string like {@code + * "foo/bar/Outer$Inner"}, which is the way the class is referenced in the JVM. + */ + private static String internalName(TypeElement typeElement) { + if (typeElement.getNestingKind().equals(NestingKind.MEMBER)) { + TypeElement enclosing = MoreElements.asType(typeElement.getEnclosingElement()); + return internalName(enclosing) + "$" + typeElement.getSimpleName(); + } + return internalName(typeElement.getQualifiedName().toString()); + } + + private static String signatureEncoding(TypeMirror type) { + switch (type.getKind()) { + case ARRAY: + return "[" + signatureEncoding(asArray(type).getComponentType()); + case BYTE: + return "B"; + case SHORT: + return "S"; + case INT: + return "I"; + case LONG: + return "J"; + case FLOAT: + return "F"; + case DOUBLE: + return "D"; + case CHAR: + return "C"; + case BOOLEAN: + return "Z"; + case DECLARED: + return "L" + internalName(asTypeElement(type)) + ";"; + default: + throw new AssertionError("Bad signature type " + type); + } + } + + private ForwardingClassGenerator() {} +} diff --git a/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java b/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java index 722075419b..35fcbbf000 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,31 +40,7 @@ Optional gwtCompatibleAnnotation() { return gwtCompatibleAnnotation; } - // Get rid of the misconceived - // in the return type of getElementValues(). - static Map getElementValues(AnnotationMirror annotation) { - return Collections.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 8cd79433b4..8673d3db36 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 @@ 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 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 @@ private boolean shouldWriteGwtSerializer() { Optional optionalGwtCompatible = gwtCompatibility.gwtCompatibleAnnotation(); if (optionalGwtCompatible.isPresent()) { AnnotationMirror gwtCompatible = optionalGwtCompatible.get(); - for (Map.Entry 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; } @@ -99,9 +95,7 @@ void maybeWriteGwtSerializer(AutoValueTemplateVars autoVars, String finalSubclas (vars.pkg.isEmpty() ? "" : vars.pkg + ".") + vars.subclass + "_CustomFieldSerializer"; vars.serializerClass = TypeSimplifier.simpleNameOf(className); vars.props = - autoVars.props.stream() - .map(p -> new Property((GetterProperty) p)) - .collect(toList()); + autoVars.props.stream().map(p -> new Property((GetterProperty) p)).collect(toList()); vars.classHashString = computeClassHash(autoVars.props, vars.pkg); String text = vars.toText(); text = TypeEncoder.decode(text, processingEnv, vars.pkg, type.asType()); @@ -110,11 +104,11 @@ void maybeWriteGwtSerializer(AutoValueTemplateVars autoVars, String finalSubclas } public static class Property { - private final GetterProperty property; - private final boolean isCastingUnchecked; + private final GetterProperty property; + private final boolean isCastingUnchecked; - Property(GetterProperty property) { - this.property = property; + Property(GetterProperty property) { + this.property = property; this.isCastingUnchecked = TypeSimplifier.isCastingUnchecked(property.getTypeMirror()); } @@ -255,8 +249,8 @@ private void writeSourceFile(String className, String text, TypeElement originat // Compute a hash that is guaranteed to change if the names, types, or order of the fields // change. We use TypeEncoder so that we can get a defined string for types, since // TypeMirror.toString() isn't guaranteed to remain the same. - private String computeClassHash(Iterable props, String pkg) { - CRC32 crc = new CRC32(); + private String computeClassHash(Iterable props, String pkg) { + CRC32 crc = new CRC32(); String encodedType = TypeEncoder.encode(type.asType()) + ":"; String decodedType = TypeEncoder.decode(encodedType, processingEnv, "", null); if (!decodedType.startsWith(pkg)) { @@ -265,8 +259,8 @@ private String computeClassHash(Iterable props, decodedType = pkg + "." + decodedType; } crc.update(decodedType.getBytes(UTF_8)); - for (AutoValueishProcessor.Property prop : props) { - String encodedProp = prop + ":" + TypeEncoder.encode(prop.getTypeMirror()) + ";"; + for (AutoValueishProcessor.Property prop : props) { + String encodedProp = prop + ":" + TypeEncoder.encode(prop.getTypeMirror()) + ";"; String decodedProp = TypeEncoder.decode(encodedProp, processingEnv, pkg, null); crc.update(decodedProp.getBytes(UTF_8)); } diff --git a/value/src/main/java/com/google/auto/value/processor/Nullables.java b/value/src/main/java/com/google/auto/value/processor/Nullables.java index 3df2322261..f7925ea4d4 100644 --- a/value/src/main/java/com/google/auto/value/processor/Nullables.java +++ b/value/src/main/java/com/google/auto/value/processor/Nullables.java @@ -17,13 +17,19 @@ import static java.util.stream.Collectors.toList; +import com.google.auto.common.MoreTypes; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.ArrayType; @@ -36,21 +42,80 @@ class Nullables { /** - * Returns the type of a {@code @Nullable} type-annotation, if one is found anywhere in the - * signatures of the given methods. + * If set to a non-empty string, defines which {@code @Nullable} type annotation should be used by + * default. If set to an empty string, does not insert {@code @Nullable} unless it is referenced + * in the {@code @AutoValue} methods. If unset, defaults to {@value #DEFAULT_NULLABLE}. */ - static Optional nullableMentionedInMethods( - Collection methods) { - return methods.stream() - .flatMap( - method -> - Stream.concat( - Stream.of(method.getReturnType()), - method.getParameters().stream().map(Element::asType))) - .map(Nullables::nullableIn) - .filter(Optional::isPresent) - .findFirst() - .orElse(Optional.empty()); + static final String NULLABLE_OPTION = "com.google.auto.value.NullableTypeAnnotation"; + + // We write this using .concat in order to hide it from rewriting rules. + private static final String DEFAULT_NULLABLE = "org".concat(".jspecify.nullness.Nullable"); + + private final Optional nullableTypeAnnotation; + + private Nullables(Optional nullableTypeAnnotation) { + this.nullableTypeAnnotation = nullableTypeAnnotation; + } + + /** + * Make an instance where the default {@code @Nullable} type annotation is discovered by looking + * for methods whose parameter or return types have such an annotation. If there are none, use a + * default {@code @Nullable} type annotation if it is available. + * + * @param methods the methods to examine + * @param processingEnv the {@link ProcessingEnvironment}, or null if one is unavailable + * (typically in tests) + */ + static Nullables fromMethods( + /* @Nullable */ ProcessingEnvironment processingEnv, Collection methods) { + Optional nullableTypeAnnotation = + methods.stream() + .flatMap( + method -> + Stream.concat( + Stream.of(method.getReturnType()), + method.getParameters().stream().map(Element::asType))) + .map(Nullables::nullableIn) + .filter(Optional::isPresent) + .findFirst() + .orElseGet(() -> defaultNullableTypeAnnotation(processingEnv)); + return new Nullables(nullableTypeAnnotation); + } + + /** Returns an appropriate {@code @Nullable} type-annotation, if one is known. */ + Optional nullableTypeAnnotation() { + return nullableTypeAnnotation; + } + + private static Optional defaultNullableTypeAnnotation( + /* @Nullable */ ProcessingEnvironment processingEnv) { + if (processingEnv == null) { + return Optional.empty(); + } + // -Afoo without `=` sets "foo" to null in the getOptions() map. + String nullableOption = + Strings.nullToEmpty( + processingEnv.getOptions().getOrDefault(NULLABLE_OPTION, DEFAULT_NULLABLE)); + return (!nullableOption.isEmpty() + && processingEnv.getSourceVersion().ordinal() >= SourceVersion.RELEASE_8.ordinal()) + ? Optional.ofNullable(processingEnv.getElementUtils().getTypeElement(nullableOption)) + .map(t -> annotationMirrorOf(MoreTypes.asDeclared(t.asType()))) + : Optional.empty(); + } + + private static AnnotationMirror annotationMirrorOf(DeclaredType annotationType) { + return new AnnotationMirror() { + @Override + public DeclaredType getAnnotationType() { + return annotationType; + } + + @Override + public ImmutableMap + getElementValues() { + return ImmutableMap.of(); + } + }; } private static Optional nullableIn(TypeMirror type) { @@ -61,7 +126,7 @@ private static Optional nullableIn( List annotations) { return annotations.stream() .filter(a -> a.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable")) - .map(a -> (AnnotationMirror) a) // get rid of the pesky wildcard + .map(a -> (AnnotationMirror) a) // get rid of the pesky wildcard .findFirst(); } @@ -128,6 +193,4 @@ private Optional visitAll(List types) { .orElse(Optional.empty()); } } - - private Nullables() {} } diff --git a/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java b/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java index 5d0168e465..23423bb42d 100644 --- a/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java +++ b/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java @@ -86,6 +86,7 @@ public static class PropertyBuilder { private final String name; private final String builderType; private final TypeMirror builderTypeMirror; + private final String build; private final String initializer; private final String beforeInitDefault; private final String initDefault; @@ -96,6 +97,7 @@ public static class PropertyBuilder { ExecutableElement propertyBuilderMethod, String builderType, TypeMirror builderTypeMirror, + String build, String initializer, String beforeInitDefault, String initDefault, @@ -105,6 +107,7 @@ public static class PropertyBuilder { this.name = propertyBuilderMethod.getSimpleName() + "$"; this.builderType = builderType; this.builderTypeMirror = builderTypeMirror; + this.build = build; this.initializer = initializer; this.beforeInitDefault = beforeInitDefault; this.initDefault = initDefault; @@ -117,6 +120,11 @@ public ExecutableElement getPropertyBuilderMethod() { return propertyBuilderMethod; } + /** The name of the property builder method. */ + public String getMethodName() { + return propertyBuilderMethod.getSimpleName().toString(); + } + /** The property builder method parameters, for example {@code Comparator comparator} */ public String getPropertyBuilderMethodParameters() { return propertyBuilderMethod.getParameters().stream() @@ -143,6 +151,11 @@ TypeMirror getBuilderTypeMirror() { return builderTypeMirror; } + /** The name of the build method, {@code build} or {@code buildOrThrow}. */ + public String getBuild() { + return build; + } + /** An initializer for the builder field, for example {@code ImmutableSet.builder()}. */ public String getInitializer() { return initializer; @@ -192,8 +205,9 @@ public String getCopyAll() { // doesn't have to be the name of `Bar` with `Builder` stuck on the end), but `barBuilder()` does // have to be the name of the property with `Builder` stuck on the end. The requirements for the // `BarBuilder` type are: - // (1) It must have an instance method called `build()` that returns `Bar`. If the type of - // `bar()` is `Bar` then the type of `build()` must be `Bar`. + // (1) It must have an instance method called `build()` or `buildOrThrow() that returns `Bar`. If + // the type of `bar()` is `Bar` then the type of the build method must be + // `Bar`. // (2) `BarBuilder` must have a public no-arg constructor, or `Bar` must have a static method // `naturalOrder(), `builder()`, or `newBuilder()` that returns `BarBuilder`. The // `naturalOrder()` case is specifically for ImmutableSortedSet and ImmutableSortedMap. @@ -234,27 +248,32 @@ Optional makePropertyBuilder(ExecutableElement method, String p TypeElement barTypeElement = MoreTypes.asTypeElement(barTypeMirror); Map barNoArgMethods = noArgMethodsOf(barTypeElement); - // Condition (1), must have build() method returning Bar. - ExecutableElement build = barBuilderNoArgMethods.get("build"); + // Condition (1), must have build() or buildOrThrow() method returning Bar. + ExecutableElement build = barBuilderNoArgMethods.get("buildOrThrow"); + if (build == null) { + build = barBuilderNoArgMethods.get("build"); + } if (build == null || build.getModifiers().contains(Modifier.STATIC)) { errorReporter.reportError( method, "[AutoValueBuilderNotBuildable] Method looks like a property builder, but it returns %s" - + " which does not have a non-static build() method", + + " which does not have a non-static build() or buildOrThrow() method", barBuilderTypeElement); return Optional.empty(); } - // We've determined that `BarBuilder` has a method `build()`. But it must return `Bar`. - // And if the type of `bar()` is Bar then `BarBuilder.build()` must return Bar. + // We've determined that `BarBuilder` has a method `build()` or `buildOrThrow(). But it must + // return `Bar`. And if the type of `bar()` is Bar then `BarBuilder.build()` must return + // Bar. TypeMirror buildType = eclipseHack.methodReturnType(build, barBuilderDeclaredType); if (!MoreTypes.equivalence().equivalent(barTypeMirror, buildType)) { errorReporter.reportError( method, - "[AutoValueBuilderWrongType] Property builder for %s has type %s whose build() method" + "[AutoValueBuilderWrongType] Property builder for %s has type %s whose %s() method" + " returns %s instead of %s", property, barBuilderTypeElement, + build.getSimpleName(), buildType, barTypeMirror); return Optional.empty(); @@ -323,7 +342,7 @@ Optional makePropertyBuilder(ExecutableElement method, String p } else { String localBuilder = property + "$builder"; beforeInitDefault = barBuilderType + " " + localBuilder + " = " + initializer + ";"; - initDefault = localBuilder + ".build()"; + initDefault = localBuilder + "." + build.getSimpleName() + "()"; } PropertyBuilder propertyBuilder = @@ -331,6 +350,7 @@ Optional makePropertyBuilder(ExecutableElement method, String p method, barBuilderType, barBuilderTypeMirror, + build.getSimpleName().toString(), initializer, beforeInitDefault, initDefault, diff --git a/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java b/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java index 290e59e093..e7bab77486 100644 --- a/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java +++ b/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java @@ -140,8 +140,7 @@ static String actualTypeParametersString(TypeElement type) { if (typeParameters.isEmpty()) { return ""; } else { - return typeParameters - .stream() + return typeParameters.stream() .map(e -> e.getSimpleName().toString()) .collect(joining(", ", "<", ">")); } @@ -264,8 +263,7 @@ private static String javaLangSpelling( * {@code Map.Entry} everywhere rather than {@code Entry}. */ private static Set topLevelTypes(Types typeUtil, Set types) { - return types - .stream() + return types.stream() .map(typeMirror -> MoreElements.asType(typeUtil.asElement(typeMirror))) .map(typeElement -> topLevelType(typeElement).asType()) .collect(toCollection(TypeMirrorSet::new)); diff --git a/value/src/main/java/com/google/auto/value/processor/TypeVariables.java b/value/src/main/java/com/google/auto/value/processor/TypeVariables.java index 75186f11f2..ae27f91b46 100644 --- a/value/src/main/java/com/google/auto/value/processor/TypeVariables.java +++ b/value/src/main/java/com/google/auto/value/processor/TypeVariables.java @@ -15,7 +15,7 @@ */ package com.google.auto.value.processor; -import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.auto.common.MoreStreams.toImmutableMap; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; diff --git a/value/src/main/java/com/google/auto/value/processor/autoannotation.vm b/value/src/main/java/com/google/auto/value/processor/autoannotation.vm index 590992eed2..5cdad62948 100644 --- a/value/src/main/java/com/google/auto/value/processor/autoannotation.vm +++ b/value/src/main/java/com/google/auto/value/processor/autoannotation.vm @@ -213,7 +213,7 @@ final class $className implements $annotationName, `java.io.Serializable` { #end @`java.lang.Override` - public boolean equals(Object o) { + public boolean equals($equalsParameterType o) { if (o == this) { return true; } diff --git a/value/src/main/java/com/google/auto/value/processor/autobuilder.vm b/value/src/main/java/com/google/auto/value/processor/autobuilder.vm index fd8ef09d3e..0aa0c19e87 100644 --- a/value/src/main/java/com/google/auto/value/processor/autobuilder.vm +++ b/value/src/main/java/com/google/auto/value/processor/autobuilder.vm @@ -12,9 +12,9 @@ ## See the License for the specific language governing permissions and ## limitations under the License. -## Template for each generated AutoValue_Foo class. +## Template for each generated AutoBuilder_Foo class. ## This template uses the Apache Velocity Template Language (VTL). -## The variables ($pkg, $props, and so on) are defined by the fields of AutoValueTemplateVars. +## The variables ($pkg, $props, and so on) are defined by the fields of AutoBuilderTemplateVars. ## ## Comments, like this one, begin with ##. The comment text extends up to and including the newline ## character at the end of the line. So comments also serve to join a line to the next one. diff --git a/value/src/main/java/com/google/auto/value/processor/autobuilderannotation.vm b/value/src/main/java/com/google/auto/value/processor/autobuilderannotation.vm new file mode 100644 index 0000000000..427957cc32 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/processor/autobuilderannotation.vm @@ -0,0 +1,54 @@ +## Copyright 2022 Google LLC +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. + +## Template for each generated AutoBuilderAnnotation_Foo_Bar class. +## This template uses the Apache Velocity Template Language (VTL). +## The variables ($pkg, $props, and so on) are defined by the fields of +## AutoBuilderAnnotationTemplateVars. +## +## Comments, like this one, begin with ##. The comment text extends up to and including the newline +## character at the end of the line. So comments also serve to join a line to the next one. +## Velocity deletes a newline after a directive (#if, #foreach, #end etc) so ## is not needed there. +## That does mean that we sometimes need an extra blank line after such a directive. +## +## Post-processing will remove unwanted spaces and blank lines, but will not join two lines. +## It will also replace classes spelled as (e.g.) `java.util.Arrays`, with the backquotes, to +## use just Arrays if that class can be imported unambiguously, or java.util.Arrays if not. + +#if (!$pkg.empty) +package $pkg; +#end + +## The following line will be replaced by the required imports during post-processing. +`import` + +#if (!$generated.empty) +@${generated}("com.google.auto.value.processor.AutoBuilderProcessor") +#else +// Generated by com.google.auto.value.processor.AutoBuilderProcessor +#end +class $className { + @`com.google.auto.value.AutoAnnotation` + static ${annotationType} newAnnotation( +#foreach ($p in $props) + $p.type $p #if ($foreach.hasNext) , #end +#end + ) { + return new AutoAnnotation_${className}_newAnnotation( +#foreach ($p in $props) + $p #if ($foreach.hasNext) , #end +#end + ); + } +} diff --git a/value/src/main/java/com/google/auto/value/processor/autovalue.vm b/value/src/main/java/com/google/auto/value/processor/autovalue.vm index 86cfe493fe..18ca827aea 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 5b29108bdf..8a106b87b7 100644 --- a/value/src/main/java/com/google/auto/value/processor/builder.vm +++ b/value/src/main/java/com/google/auto/value/processor/builder.vm @@ -37,14 +37,7 @@ class ${builderName}${builderFormalTypes} ## ${builderTypeName}${builderActualTypes} { #foreach ($p in $props) - - #if ($p.kind.primitive) - - private $types.boxedClass($p.typeMirror).simpleName $p; - - #else - - #if ($builderPropertyBuilders[$p.name]) + #if ($builderPropertyBuilders[$p.name]) ## If you have ImmutableList.Builder stringsBuilder() then we define two fields: ## private ImmutableList.Builder stringsBuilder$; ## private ImmutableList strings; @@ -54,9 +47,12 @@ class ${builderName}${builderFormalTypes} ## #end - private $p.type $p #if ($p.optional && !$p.nullable) = $p.optional.empty #end ; + private $p.builderFieldType $p $p.builderInitializer; - #end +#end + +#foreach ($decl in $builderRequiredProperties.fieldDeclarations) + $decl #end ${builderName}() { @@ -64,7 +60,8 @@ class ${builderName}${builderFormalTypes} ## #if ($toBuilderConstructor) - private ${builderName}(${origClass}${actualTypes} source) { + #if (!$autoBuilder) private #end## + ${builderName}($builtType source) { #foreach ($p in $props) @@ -72,6 +69,10 @@ class ${builderName}${builderFormalTypes} ## #end + #foreach ($init in $builderRequiredProperties.initToAllSet) + $init + #end + } #end @@ -94,15 +95,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 @@ -116,6 +113,9 @@ class ${builderName}${builderFormalTypes} ## #end this.$p = ${setter.copy($p)}; + + $builderRequiredProperties.markAsSet($p) + return this; } @@ -124,24 +124,27 @@ class ${builderName}${builderFormalTypes} ## #if ($propertyBuilder) @`java.lang.Override` - ${propertyBuilder.access}$propertyBuilder.builderType ${p.name}Builder($propertyBuilder.propertyBuilderMethodParameters) { + ${propertyBuilder.access}$propertyBuilder.builderType ## + ${propertyBuilder.methodName}($propertyBuilder.propertyBuilderMethodParameters) { if (${propertyBuilder.name} == null) { ## This is the first time someone has asked for the builder. If the property it sets already - ## has a value (because it came from a toBuilder() call on the AutoValue class, or because - ## there is also a setter for this property) then we copy that value into the builder. + ## has a value (because it came from the copy constructor, or because there is also a setter + ## for this property) then we copy that value into the builder. ## Otherwise the builder starts out empty. ## If we have neither a setter nor a toBuilder() method, then the builder always starts ## off empty. - #if ($builderSetters[$p.name].empty && $toBuilderMethods.empty) + #if ($builderSetters[$p.name].empty && !$toBuilderConstructor) ${propertyBuilder.name} = ${propertyBuilder.initializer}; + $builderRequiredProperties.markAsSet($p) #else if ($p == null) { ${propertyBuilder.name} = ${propertyBuilder.initializer}; + $builderRequiredProperties.markAsSet($p) } else { #if (${propertyBuilder.builtToBuilder}) @@ -177,31 +180,36 @@ class ${builderName}${builderFormalTypes} ## ## Getter - #if ($builderGetters[$p.name]) + #set ($builderGetter = $builderGetters[$p.name]) + #if ($builderGetter) @`java.lang.Override` - ${p.nullableAnnotation}${builderGetters[$p.name].access}$builderGetters[$p.name].type ${p.getter}() { - #if ($builderGetters[$p.name].optional) + ${p.nullableAnnotation}${builderGetter.access}$builderGetter.type ${builderGetter.name}() { + #set ($noValueToGetCondition = $builderRequiredProperties.noValueToGet($p)) + #if ($builderGetters[$p.name].optional) + #if ($noValueToGetCondition) + if ($noValueToGetCondition) { + return $builderGetter.optional.empty; + } + #else if ($p == null) { - return $builderGetters[$p.name].optional.empty; - } else { - return ${builderGetters[$p.name].optional.rawType}.of($p); + return $builderGetter.optional.empty; } + #end + return ${builderGetter.optional.rawType}.of($p); #else - #if ($builderRequiredProperties.contains($p)) - - if ($p == null) { + #if ($noValueToGetCondition) + if ($noValueToGetCondition) { throw new IllegalStateException(#if ($identifiers)"Property \"$p.name\" has not been set"#end); } - #end #if ($propertyBuilder) if (${propertyBuilder.name} != null) { - return ${propertyBuilder.name}.build(); + return ${propertyBuilder.name}.${propertyBuilder.build}(); } if ($p == null) { ${propertyBuilder.beforeInitDefault} @@ -229,7 +237,7 @@ class ${builderName}${builderFormalTypes} ## #if ($propertyBuilder) if (${propertyBuilder.name} != null) { - this.$p = ${propertyBuilder.name}.build(); + this.$p = ${propertyBuilder.name}.${propertyBuilder.build}(); } else if (this.$p == null) { ${propertyBuilder.beforeInitDefault} this.$p = ${propertyBuilder.initDefault}; @@ -238,38 +246,42 @@ class ${builderName}${builderFormalTypes} ## #end #end -#if (!$builderRequiredProperties.empty) +#if (!$builderRequiredProperties.requiredProperties.empty) + if ($builderRequiredProperties.anyMissing) { + #if ($identifiers) ## build a friendly message showing all missing properties + #if ($builderRequiredProperties.requiredProperties.size() == 1) + + `java.lang.String` missing = " $builderRequiredProperties.requiredProperties.iterator().next()"; - `java.lang.String` missing = ""; + #else - #foreach ($p in $builderRequiredProperties) + `java.lang.StringBuilder` missing = new `java.lang.StringBuilder`(); - if (this.$p == null) { - missing += " $p.name"; + #foreach ($p in $builderRequiredProperties.requiredProperties) + if ($builderRequiredProperties.missingRequiredProperty($p)) { + missing.append(" $p.name"); } - + #end #end - if (!missing.isEmpty()) { - throw new IllegalStateException("Missing required properties:" + missing); - } + throw new IllegalStateException("Missing required properties:" + missing); #else ## just throw an exception if anything is missing - if (#foreach ($p in $builderRequiredProperties)## - this.$p == null## - #if ($foreach.hasNext) || #end - #end) { - throw new IllegalStateException(); - } + throw new IllegalStateException(); + #end + + } + #end #if ($builtType != "void") return #end ${build}( #foreach ($p in $props) this.$p #if ($foreach.hasNext) , #end -#end ); +#end + $builderRequiredProperties.defaultedBitmaskParameters ); } } diff --git a/value/src/main/java/com/google/auto/value/processor/equalshashcode.vm b/value/src/main/java/com/google/auto/value/processor/equalshashcode.vm index 03e25c2b80..0afcdb6349 100644 --- a/value/src/main/java/com/google/auto/value/processor/equalshashcode.vm +++ b/value/src/main/java/com/google/auto/value/processor/equalshashcode.vm @@ -42,9 +42,9 @@ ## A reminder that trailing ## here serves to delete the newline, which we don't want in the output. #macro (equalsThatExpression $p $subclass) #if ($p.kind == "FLOAT") - Float.floatToIntBits(this.$p) == Float.floatToIntBits(that.${p.getter}()) ## + `java.lang.Float`.floatToIntBits(this.$p) == `java.lang.Float`.floatToIntBits(that.${p.getter}()) ## #elseif ($p.kind == "DOUBLE") - Double.doubleToLongBits(this.$p) == Double.doubleToLongBits(that.${p.getter}()) ## + `java.lang.Double`.doubleToLongBits(this.$p) == `java.lang.Double`.doubleToLongBits(that.${p.getter}()) ## #elseif ($p.kind.primitive) this.$p == that.${p.getter}() ## #elseif ($p.kind == "ARRAY") @@ -65,9 +65,9 @@ #if ($p.kind == "LONG") (int) (($p >>> 32) ^ $p) ## #elseif ($p.kind == "FLOAT") - Float.floatToIntBits($p) ## + `java.lang.Float`.floatToIntBits($p) ## #elseif ($p.kind == "DOUBLE") - (int) ((Double.doubleToLongBits($p) >>> 32) ^ Double.doubleToLongBits($p)) ## + (int) ((`java.lang.Double`.doubleToLongBits($p) >>> 32) ^ `java.lang.Double`.doubleToLongBits($p)) ## #elseif ($p.kind == "BOOLEAN") $p ? 1231 : 1237 ## #elseif ($p.kind.primitive) diff --git a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubject.java b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubject.java index 18c773682c..5d1462d0ad 100644 --- a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubject.java +++ b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubject.java @@ -51,9 +51,6 @@ void hasError(String error) { javac() .withProcessors(new AutoValueProcessor(ImmutableList.of(new MemoizeExtension()))) .compile(file); - assertThat(compilation) - .hadErrorContaining(error) - .inFile(file) - .onLineContaining(actual); + assertThat(compilation).hadErrorContaining(error).inFile(file).onLineContaining(actual); } } diff --git a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java index 0bedcf8d64..5beb686b32 100644 --- a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java +++ b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java @@ -125,7 +125,9 @@ String returnsNull() { @org.checkerframework.checker.nullness.qual.Nullable String nullableWithTypeAnnotation() { nullableWithTypeAnnotationCount++; - return "nullable derived " + stringWithTypeAnnotation() + " " + return "nullable derived " + + stringWithTypeAnnotation() + + " " + nullableWithTypeAnnotationCount; } @@ -236,8 +238,9 @@ public final boolean equals(Object that) { @Before public void setUp() { - value = new AutoValue_MemoizedTest_Value( - "string", "stringWithTypeAnnotation", new HashCodeAndToStringCounter()); + value = + new AutoValue_MemoizedTest_Value( + "string", "stringWithTypeAnnotation", new HashCodeAndToStringCounter()); listValue = new AutoValue_MemoizedTest_ListValue(0, "hello"); } @@ -377,8 +380,9 @@ public void nullableWithTypeAnnotationHasAnnotation() throws ReflectiveOperation Method nullable = AutoValue_MemoizedTest_Value.class.getDeclaredMethod("nullableWithTypeAnnotation"); AnnotatedType returnType = nullable.getAnnotatedReturnType(); - assertThat(returnType.isAnnotationPresent( - org.checkerframework.checker.nullness.qual.Nullable.class)) + assertThat( + returnType.isAnnotationPresent( + org.checkerframework.checker.nullness.qual.Nullable.class)) .isTrue(); } @@ -389,11 +393,13 @@ public void nullableConstructorParameter() throws ReflectiveOperationException { // [1] @org.checkerframework.checker.nullness.qual.Nullable String stringWithTypeAnnotation, // [2] HashCodeAndToStringCounter counter // We don't currently copy @javax.annotation.Nullable because it is not a TYPE_USE annotation. - Constructor constructor = AutoValue_MemoizedTest_Value.class.getDeclaredConstructor( - String.class, String.class, HashCodeAndToStringCounter.class); + Constructor constructor = + AutoValue_MemoizedTest_Value.class.getDeclaredConstructor( + String.class, String.class, HashCodeAndToStringCounter.class); AnnotatedType paramType = constructor.getAnnotatedParameterTypes()[1]; - assertThat(paramType.isAnnotationPresent( - org.checkerframework.checker.nullness.qual.Nullable.class)) + assertThat( + paramType.isAnnotationPresent( + org.checkerframework.checker.nullness.qual.Nullable.class)) .isTrue(); } @@ -450,8 +456,8 @@ public void hashCodeEqualsOptimization_offWhenEqualsIsFinal() { assertThat(memoizedHashCodeAndFinalEqualsMethod.equals(second)).isTrue(); assertThat(memoizedHashCodeAndFinalEqualsMethod.hashCodeCount).isEqualTo(0); - memoizedHashCodeAndFinalEqualsMethod.hashCode(); - memoizedHashCodeAndFinalEqualsMethod.hashCode(); + int unused1 = memoizedHashCodeAndFinalEqualsMethod.hashCode(); + int unused2 = memoizedHashCodeAndFinalEqualsMethod.hashCode(); assertThat(memoizedHashCodeAndFinalEqualsMethod.hashCodeCount).isEqualTo(1); } @@ -467,8 +473,7 @@ abstract static class AbstractTypePath { @AutoValue abstract static class ResourceUriPath extends AbstractTypePath { - static ResourceUriPath create( - TypeEdgeIterable edges) { + static ResourceUriPath create(TypeEdgeIterable edges) { return new AutoValue_MemoizedTest_ResourceUriPath<>(edges); } diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java index 1879d1e5fe..cbfdcafd45 100644 --- a/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java +++ b/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertThrows; import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.memoized.Memoized; import com.google.auto.value.extension.serializable.SerializableAutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -415,4 +416,82 @@ public void multiplePropertiesSameType_allFieldsSerialized() { assertThat(actualAutoValue).isEqualTo(autoValue); } + + /** + * Type that may result in nested lambdas in the generated code. Including this allows us to + * verify that we handle those correctly, in particular not reusing a lambda parameter name in + * another lambda nested inside the first one. + */ + @SerializableAutoValue + @AutoValue + abstract static class ComplexType implements Serializable { + abstract ImmutableMap>> a(); + + static ComplexType.Builder builder() { + return new AutoValue_SerializableAutoValueExtensionTest_ComplexType.Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + abstract ComplexType.Builder setA( + ImmutableMap>> a); + + abstract ComplexType build(); + } + } + + @Test + public void complexType() { + ImmutableMap>> map = + ImmutableMap.of("foo", ImmutableMap.of("bar", Optional.of("baz"))); + ComplexType complexType = ComplexType.builder().setA(map).build(); + + ComplexType reserialized = SerializableTester.reserialize(complexType); + + assertThat(reserialized).isEqualTo(complexType); + } + + /** + * Type that uses both {@code @SerializableAutoValue} and {@code @Memoized}, showing that the two + * extensions work correctly together. + */ + @SerializableAutoValue + @AutoValue + abstract static class SerializeMemoize implements Serializable { + abstract Optional number(); + private transient int methodCount; + + @Memoized + Optional negate() { + methodCount++; + return number().map(n -> -n); + } + + static SerializeMemoize.Builder builder() { + return new AutoValue_SerializableAutoValueExtensionTest_SerializeMemoize.Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setNumber(Optional number); + abstract SerializeMemoize build(); + } + } + + @Test + public void serializeMemoize() { + SerializeMemoize instance1 = SerializeMemoize.builder().setNumber(Optional.of(17)).build(); + assertThat(instance1.methodCount).isEqualTo(0); + assertThat(instance1.negate()).hasValue(-17); + assertThat(instance1.methodCount).isEqualTo(1); + assertThat(instance1.negate()).hasValue(-17); + assertThat(instance1.methodCount).isEqualTo(1); + SerializeMemoize instance2 = SerializableTester.reserialize(instance1); + assertThat(instance2.methodCount).isEqualTo(0); + assertThat(instance2.negate()).hasValue(-17); + assertThat(instance2.methodCount).isEqualTo(1); + assertThat(instance2.negate()).hasValue(-17); + assertThat(instance2.methodCount).isEqualTo(1); + + } } diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java index 388977fb9f..162d7cf6ee 100644 --- a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java +++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java @@ -42,6 +42,15 @@ public Serializer getSerializer(TypeMirror type) { return new FakeIdentitySerializer(type, isIdentity); } + // This doesn't follow the contract, and always returns the same string for a given prefix. + // That means it will be wrong if two identifiers with the same prefix are in the same scope in + // the generated code, but for our purposes in this fake it is OK, and means we don't have to + // hardwire knowledge of the uniqueness algorithm into golden text in tests. + @Override + public CodeBlock newIdentifier(String prefix) { + return CodeBlock.of("$L$$", prefix); + } + private static class FakeIdentitySerializer implements Serializer { private final TypeMirror typeMirror; diff --git a/value/src/test/java/com/google/auto/value/processor/AutoAnnotationCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationCompilationTest.java index f80702454a..1f79a074b5 100644 --- a/value/src/test/java/com/google/auto/value/processor/AutoAnnotationCompilationTest.java +++ b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationCompilationTest.java @@ -131,6 +131,7 @@ public void testSimple() { Compilation compilation = javac() .withProcessors(new AutoAnnotationProcessor()) + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") .compile(annotationFactoryJavaFile, myAnnotationJavaFile, myEnumJavaFile); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -195,6 +196,7 @@ public void testEmptyPackage() { Compilation compilation = javac() .withProcessors(new AutoAnnotationProcessor()) + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") .compile(annotationFactoryJavaFile, myAnnotationJavaFile); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -294,6 +296,7 @@ public void testGwtSimple() { Compilation compilation = javac() .withProcessors(new AutoAnnotationProcessor()) + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") .compile(annotationFactoryJavaFile, myAnnotationJavaFile, gwtCompatibleJavaFile); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -434,6 +437,7 @@ public void testCollectionsForArrays() { Compilation compilation = javac() .withProcessors(new AutoAnnotationProcessor()) + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") .compile(annotationFactoryJavaFile, myEnumJavaFile, myAnnotationJavaFile); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -465,9 +469,7 @@ public void testMissingClass() { " @NotAutoAnnotation Empty notNewEmpty() {}", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(erroneousJavaFileObject); + javac().withProcessors(new AutoAnnotationProcessor()).compile(erroneousJavaFileObject); assertThat(compilation) .hadErrorContaining("NotAutoAnnotation") .inFile(erroneousJavaFileObject) diff --git a/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java index 55a4c5db75..fa4854bb8b 100644 --- a/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java +++ b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java @@ -57,37 +57,10 @@ public void testCorrect() { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(TEST_ANNOTATION, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource); assertThat(compilation).succeededWithoutWarnings(); } - @Test - public void testNotStatic() { - JavaFileObject testSource = - JavaFileObjects.forSourceLines( - "com.foo.Test", - "package com.foo;", - "", - "import com.example.TestAnnotation;", - "import com.google.auto.value.AutoAnnotation;", - "", - "class Test {", - " @AutoAnnotation TestAnnotation newTestAnnotation(int value) {", - " return new AutoAnnotation_Test_newTestAnnotation(value);", - " }", - "}"); - Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(TEST_ANNOTATION, testSource); - assertThat(compilation) - .hadErrorContaining("must be static") - .inFile(testSource) - .onLineContaining("TestAnnotation newTestAnnotation(int value)"); - } - @Test public void testDoesNotReturnAnnotation() { JavaFileObject testSource = @@ -103,9 +76,7 @@ public void testDoesNotReturnAnnotation() { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(TEST_ANNOTATION, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource); assertThat(compilation) .hadErrorContaining("must be an annotation type, not java.lang.String") .inFile(testSource) @@ -132,9 +103,7 @@ public void testOverload() { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(TEST_ANNOTATION, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource); assertThat(compilation) .hadErrorContaining("@AutoAnnotation methods cannot be overloaded") .inFile(testSource) @@ -196,9 +165,7 @@ public void testWrongName() { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(TEST_ANNOTATION, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource); assertThat(compilation) .hadErrorContaining("method parameter 'fred' must have the same name") .inFile(testSource) @@ -221,9 +188,7 @@ public void testWrongType() { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(TEST_ANNOTATION, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource); assertThat(compilation) .hadErrorContaining( "method parameter 'value' has type java.lang.String " @@ -267,9 +232,7 @@ public void testWrongTypeCollection() { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(testSource, testAnnotation); + javac().withProcessors(new AutoAnnotationProcessor()).compile(testSource, testAnnotation); assertThat(compilation) .hadErrorContaining( "method parameter 'value' has type " @@ -296,9 +259,7 @@ public void testExtraParameters() { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(TEST_ANNOTATION, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource); assertThat(compilation) .hadErrorContaining( "method parameter 'other' must have the same name as a member of " @@ -323,9 +284,7 @@ public void testMissingParameters() { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(TEST_ANNOTATION, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource); assertThat(compilation) .hadErrorContaining("method needs a parameter with name 'value' and type int") .inFile(testSource) @@ -357,9 +316,7 @@ public void testAnnotationValuedDefaultsNotSupportedYet() { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(annotationSource, testSource); + javac().withProcessors(new AutoAnnotationProcessor()).compile(annotationSource, testSource); assertThat(compilation) .hadErrorContaining( "@AutoAnnotation cannot yet supply a default value for annotation-valued member " @@ -399,10 +356,7 @@ public void testAnnotationMemberNameConflictWithGeneratedLocal() { " }", "}"); Compilation compilation = - javac() - .withProcessors(new AutoAnnotationProcessor()) - .compile(annotationSource, testSource); - assertThat(compilation) - .hadErrorContaining("variable value$ is already defined in constructor"); + javac().withProcessors(new AutoAnnotationProcessor()).compile(annotationSource, testSource); + assertThat(compilation).hadErrorContaining("variable value$ is already defined in constructor"); } } diff --git a/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java index ce83b226a8..77ec4c75ab 100644 --- a/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java +++ b/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java @@ -19,6 +19,8 @@ import static com.google.common.truth.TruthJUnit.assume; import static com.google.testing.compile.CompilationSubject.assertThat; import static com.google.testing.compile.Compiler.javac; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.joining; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; @@ -34,17 +36,27 @@ public final class AutoBuilderCompilationTest { "foo.bar.AutoBuilder_Baz_Builder", "package foo.bar;", "", - GeneratedImport.importGeneratedAnnotationType(), + sorted( + GeneratedImport.importGeneratedAnnotationType(), + "import org.checkerframework.checker.nullness.qual.Nullable;"), "", "@Generated(\"" + AutoBuilderProcessor.class.getName() + "\")", "class AutoBuilder_Baz_Builder implements Baz.Builder {", - " private Integer anInt;", - " private String aString;", + " private int anInt;", + " private @Nullable String aString;", + " private byte set$0;", "", " AutoBuilder_Baz_Builder() {}", "", + " AutoBuilder_Baz_Builder(Baz source) {", + " this.anInt = source.anInt();", + " this.aString = source.aString();", + " set$0 = (byte) 1;", + " }", + "", " @Override public Baz.Builder setAnInt(int anInt) {", " this.anInt = anInt;", + " set$0 |= (byte) 0x1;", " return this;", " }", "", @@ -58,14 +70,15 @@ public final class AutoBuilderCompilationTest { "", " @Override", " public Baz build() {", - " String missing = \"\";", - " if (this.anInt == null) {", - " missing += \" anInt\";", - " }", - " if (this.aString == null) {", - " missing += \" aString\";", - " }", - " if (!missing.isEmpty()) {", + " if (set$0 != 0x1", + " || this.aString == null) {", + " StringBuilder missing = new StringBuilder();", + " if ((set$0 & 0x1) == 0) {", + " missing.append(\" anInt\");", + " }", + " if (this.aString == null) {", + " missing.append(\" aString\");", + " }", " throw new IllegalStateException(\"Missing required properties:\" + missing);", " }", " return new Baz(", @@ -114,7 +127,7 @@ public void simpleSuccess() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=org.checkerframework.checker.nullness.qual.Nullable") .compile(javaFileObject); assertThat(compilation) .generatedSourceFile("foo.bar.AutoBuilder_Baz_Builder") @@ -147,13 +160,54 @@ public void simpleRecord() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation) .generatedSourceFile("foo.bar.AutoBuilder_Baz_Builder") .hasSourceEquivalentTo(EXPECTED_SIMPLE_OUTPUT); } + @Test + public void buildOtherPackage() { + JavaFileObject built = + JavaFileObjects.forSourceLines( + "com.example.Built", + "package com.example;", + "", + "public class Built {", + " private final int anInt;", + " private final String aString;", + "", + " public Built(int anInt, String aString) {", + " this.anInt = anInt;", + " this.aString = aString;", + " }", + "}"); + JavaFileObject builder = + JavaFileObjects.forSourceLines( + "foo.bar.Builder", + "package foo.bar;", + "", + "import com.example.Built;", + "import com.google.auto.value.AutoBuilder;", + "", + "@AutoBuilder(ofClass = Built.class)", + "public interface Builder {", + " public static Builder builder() {", + " return new AutoBuilder_Builder();", + " }", + "", + " Builder setAnInt(int x);", + " Builder setAString(String x);", + " Built build();", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoBuilderProcessor()) + .compile(built, builder); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(compilation).generatedSourceFile("foo.bar.AutoBuilder_Builder"); + } + @Test public void autoBuilderOnEnum() { JavaFileObject javaFileObject = @@ -170,7 +224,6 @@ public void autoBuilderOnEnum() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -198,7 +251,6 @@ public void autoBuilderPrivate() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -207,6 +259,113 @@ public void autoBuilderPrivate() { .onLineContaining("interface Builder"); } + @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 autoBuilderMissingBuildMethod() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "public class Baz {", + " private final int anInt;", + " private final String aString;", + "", + " public Baz(int anInt, String aString) {", + " this.anInt = anInt;", + " this.aString = aString;", + " }", + "", + " public int anInt() {", + " return anInt;", + " }", + "", + " public String aString() {", + " return aString;", + " }", + "", + " public static Builder builder() {", + " return new AutoBuilder_Baz_Builder();", + " }", + "", + " @AutoBuilder", + " public interface Builder {", + " Builder setAnInt(int x);", + " Builder setAString(String x);", + " }", + "}"); + Compilation compilation = + javac().withProcessors(new AutoBuilderProcessor()).compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoValueBuilderBuild] Builder must have a single no-argument method, typically" + + " called build(), that returns foo.bar.Baz") + .inFile(javaFileObject) + .onLineContaining("interface Builder"); + } + @Test public void autoBuilderNestedInPrivate() { JavaFileObject javaFileObject = @@ -227,7 +386,6 @@ public void autoBuilderNestedInPrivate() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -255,7 +413,6 @@ public void autoBuilderInner() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -284,7 +441,6 @@ public void innerConstructor() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -311,7 +467,6 @@ public void noVisibleConstructor() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -342,7 +497,6 @@ public void noVisibleMethod() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -374,7 +528,6 @@ public void methodNotStatic() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -407,7 +560,6 @@ public void noMatchingConstructor() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -445,7 +597,6 @@ public void twoMatchingConstructors() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -475,7 +626,6 @@ public void constructInterface() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -509,7 +659,6 @@ public void inconsistentSetPrefix() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -540,7 +689,6 @@ public void missingSetter() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -574,7 +722,6 @@ public void tooManyArgs() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -606,7 +753,6 @@ public void alienNoArgMethod() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -642,13 +788,12 @@ public void alienOneArgMethod() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( - "[AutoBuilderBuilderWhatProp] Method does not correspond to a parameter of Baz(int one," - + " int two)") + "[AutoBuilderBuilderWhatProp] Method three does not correspond to " + + "a parameter of Baz(int one, int two)") .inFile(javaFileObject) .onLineContaining("three(int x)"); } @@ -675,12 +820,11 @@ public void setterReturnType() { 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)"); } @@ -693,7 +837,7 @@ public void nullableSetterForNonNullableParameter() { "package foo.bar;", "", "import com.google.auto.value.AutoBuilder;", - "import org.jspecify.nullness.Nullable;", + "import org.checkerframework.checker.nullness.qual.Nullable;", "", "class Baz {", " Baz(String thing) {}", @@ -706,7 +850,7 @@ public void nullableSetterForNonNullableParameter() { "}"); JavaFileObject nullableFileObject = JavaFileObjects.forSourceLines( - "org.jspecify.nullness.Nullable", + "org.checkerframework.checker.nullness.qual.Nullable", "package org.jspecify.nullness;", "", "import java.lang.annotation.ElementType;", @@ -717,7 +861,6 @@ public void nullableSetterForNonNullableParameter() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject, nullableFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -750,7 +893,6 @@ public void setterWrongType() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -783,7 +925,6 @@ public void setterWrongTypeEvenWithConversion() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -818,7 +959,6 @@ public void typeParamMismatch() { Compilation compilation = javac() .withProcessors(new AutoBuilderProcessor()) - .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable") .compile(javaFileObject); assertThat(compilation).failed(); assertThat(compilation) @@ -828,4 +968,39 @@ public void typeParamMismatch() { .inFile(javaFileObject) .onLineContaining("interface Builder"); } + + @Test + public void annotationWithCallMethod() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoBuilder;", + "", + "class Baz {", + " @interface MyAnnot {", + " boolean broken();", + " }", + "", + " @AutoBuilder(callMethod = \"annotationType\", ofClass = MyAnnot.class)", + " interface Builder {", + " abstract Builder broken(boolean x);", + " abstract MyAnnot build();", + " }", + "}"); + Compilation compilation = + javac().withProcessors(new AutoBuilderProcessor()).compile(javaFileObject); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "[AutoBuilderAnnotationMethod] @AutoBuilder for an annotation must have an empty" + + " callMethod, not \"annotationType\"") + .inFile(javaFileObject) + .onLineContaining("interface Builder"); + } + + private static String sorted(String... imports) { + return stream(imports).sorted().collect(joining("\n")); + } } diff --git a/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java index 2ae98b7edb..a55b74d0f2 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 @@ -237,7 +237,8 @@ public void success() { Compilation compilation = javac() .withProcessors(new AutoOneOfProcessor()) - .withOptions("-Xlint:-processing", "-implicit:none") + .withOptions( + "-Xlint:-processing", "-implicit:none", "-A" + Nullables.NULLABLE_OPTION + "=") .compile(javaFileObject); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -321,7 +322,8 @@ public void voidInstanceWithoutGenericTypeParameters() { Compilation compilation = javac() .withProcessors(new AutoOneOfProcessor()) - .withOptions("-Xlint:-processing", "-implicit:none") + .withOptions( + "-Xlint:-processing", "-implicit:none", "-A" + Nullables.NULLABLE_OPTION + "=") .compile(javaFileObject); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -460,6 +462,33 @@ public void enumExtraCase() { .onLineContaining("GERBIL"); } + @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 = @@ -488,4 +517,62 @@ public void cantBeNullable() { .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 546bb576bf..c651769385 100644 --- a/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java +++ b/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java @@ -15,12 +15,16 @@ */ package com.google.auto.value.processor; +import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.TruthJUnit.assume; import static com.google.testing.compile.CompilationSubject.assertThat; import static com.google.testing.compile.CompilationSubject.compilations; import static com.google.testing.compile.Compiler.javac; +import static java.util.Arrays.stream; import static java.util.stream.Collectors.joining; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.truth.Expect; @@ -32,7 +36,6 @@ import java.io.Writer; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; @@ -118,7 +121,10 @@ public void simpleSuccess() { " }", "}"); Compilation compilation = - javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject); + javac() + .withProcessors(new AutoValueProcessor()) + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") + .compile(javaFileObject); assertThat(compilation) .generatedSourceFile("foo.bar.AutoValue_Baz") .hasSourceEquivalentTo(expectedOutput); @@ -216,7 +222,10 @@ public void importTwoWays() { " }", "}"); Compilation compilation = - javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject); + javac() + .withProcessors(new AutoValueProcessor()) + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") + .compile(javaFileObject); assertThat(compilation) .generatedSourceFile("foo.bar.AutoValue_Baz") .hasSourceEquivalentTo(expectedOutput); @@ -347,7 +356,8 @@ public void testNestedParameterizedTypesWithTypeAnnotations() { Compilation compilation = javac() .withProcessors(new AutoValueProcessor()) - .withOptions("-Xlint:-processing", "-implicit:none") + .withOptions( + "-Xlint:-processing", "-implicit:none", "-A" + Nullables.NULLABLE_OPTION + "=") .compile(annotFileObject, outerFileObject, nestyFileObject); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -547,6 +557,50 @@ public void testPrimitiveArrayWarningSuppressed() { assertThat(compilation).succeededWithoutWarnings(); } + @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 = @@ -574,7 +628,7 @@ public void autoValueMustBeStatic() { } @Test - public void autoValueMustBeNotBePrivate() { + public void autoValueMustNotBePrivate() { JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( "foo.bar.Baz", @@ -627,6 +681,52 @@ public void autoValueMustBeNotBeNestedInPrivate() { .onLineContaining("class Nested"); } + @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 = @@ -965,10 +1065,11 @@ public void correctBuilder() { "", "import com.google.auto.value.AutoValue;", "import com.google.common.base.Optional;", - "import com.google.common.collect.ImmutableList;", + "import com.google.common.collect.ImmutableMap;", "", "import java.util.ArrayList;", "import java.util.List;", + "import java.util.Map;", "import javax.annotation.Nullable;", "", "@AutoValue", @@ -979,7 +1080,7 @@ public void correctBuilder() { " @SuppressWarnings(\"mutable\")", " @Nullable public abstract int[] aNullableIntArray();", " public abstract List aList();", - " public abstract ImmutableList anImmutableList();", + " public abstract ImmutableMap anImmutableMap();", " public abstract Optional anOptionalString();", " public abstract NestedAutoValue aNestedAutoValue();", "", @@ -991,8 +1092,8 @@ public void correctBuilder() { " public abstract Builder aByteArray(byte[] x);", " public abstract Builder aNullableIntArray(@Nullable int[] x);", " public abstract Builder aList(List x);", - " public abstract Builder anImmutableList(List x);", - " public abstract ImmutableList.Builder anImmutableListBuilder();", + " public abstract Builder anImmutableMap(Map x);", + " public abstract ImmutableMap.Builder anImmutableMapBuilder();", " public abstract Builder anOptionalString(Optional s);", " public abstract Builder anOptionalString(String s);", " public abstract NestedAutoValue.Builder aNestedAutoValueBuilder();", @@ -1004,7 +1105,7 @@ public void correctBuilder() { "", " public abstract Optional anInt();", " public abstract List aList();", - " public abstract ImmutableList anImmutableList();", + " public abstract ImmutableMap anImmutableMap();", "", " public abstract Baz build();", " }", @@ -1042,9 +1143,10 @@ public void correctBuilder() { "package foo.bar;", "", "import com.google.common.base.Optional;", - "import com.google.common.collect.ImmutableList;", + "import com.google.common.collect.ImmutableMap;", "import java.util.Arrays;", "import java.util.List;", + "import java.util.Map;", sorted( GeneratedImport.importGeneratedAnnotationType(), "import javax.annotation.Nullable;"), @@ -1055,7 +1157,7 @@ public void correctBuilder() { " private final byte[] aByteArray;", " private final int[] aNullableIntArray;", " private final List aList;", - " private final ImmutableList anImmutableList;", + " private final ImmutableMap anImmutableMap;", " private final Optional anOptionalString;", " private final NestedAutoValue aNestedAutoValue;", "", @@ -1064,14 +1166,14 @@ public void correctBuilder() { " byte[] aByteArray,", " @Nullable int[] aNullableIntArray,", " List aList,", - " ImmutableList anImmutableList,", + " ImmutableMap anImmutableMap,", " Optional anOptionalString,", " NestedAutoValue aNestedAutoValue) {", " this.anInt = anInt;", " this.aByteArray = aByteArray;", " this.aNullableIntArray = aNullableIntArray;", " this.aList = aList;", - " this.anImmutableList = anImmutableList;", + " this.anImmutableMap = anImmutableMap;", " this.anOptionalString = anOptionalString;", " this.aNestedAutoValue = aNestedAutoValue;", " }", @@ -1095,8 +1197,8 @@ public void correctBuilder() { " return aList;", " }", "", - " @Override public ImmutableList anImmutableList() {", - " return anImmutableList;", + " @Override public ImmutableMap anImmutableMap() {", + " return anImmutableMap;", " }", "", " @Override public Optional anOptionalString() {", @@ -1113,7 +1215,7 @@ public void correctBuilder() { " + \"aByteArray=\" + Arrays.toString(aByteArray) + \", \"", " + \"aNullableIntArray=\" + Arrays.toString(aNullableIntArray) + \", \"", " + \"aList=\" + aList + \", \"", - " + \"anImmutableList=\" + anImmutableList + \", \"", + " + \"anImmutableMap=\" + anImmutableMap + \", \"", " + \"anOptionalString=\" + anOptionalString + \", \"", " + \"aNestedAutoValue=\" + aNestedAutoValue", " + \"}\";", @@ -1133,7 +1235,7 @@ public void correctBuilder() { + "(that instanceof AutoValue_Baz) " + "? ((AutoValue_Baz) that).aNullableIntArray : that.aNullableIntArray())", " && this.aList.equals(that.aList())", - " && this.anImmutableList.equals(that.anImmutableList())", + " && this.anImmutableMap.equals(that.anImmutableMap())", " && this.anOptionalString.equals(that.anOptionalString())", " && this.aNestedAutoValue.equals(that.aNestedAutoValue());", " }", @@ -1151,7 +1253,7 @@ public void correctBuilder() { " h$ *= 1000003;", " h$ ^= aList.hashCode();", " h$ *= 1000003;", - " h$ ^= anImmutableList.hashCode();", + " h$ ^= anImmutableMap.hashCode();", " h$ *= 1000003;", " h$ ^= anOptionalString.hashCode();", " h$ *= 1000003;", @@ -1164,15 +1266,16 @@ public void correctBuilder() { " }", "", " static final class Builder extends Baz.Builder {", - " private Integer anInt;", + " private int anInt;", " private byte[] aByteArray;", " private int[] aNullableIntArray;", " private List aList;", - " private ImmutableList.Builder anImmutableListBuilder$;", - " private ImmutableList anImmutableList;", + " private ImmutableMap.Builder anImmutableMapBuilder$;", + " private ImmutableMap anImmutableMap;", " private Optional anOptionalString = Optional.absent();", " private NestedAutoValue.Builder aNestedAutoValueBuilder$;", " private NestedAutoValue aNestedAutoValue;", + " private byte set$0;", "", " Builder() {", " }", @@ -1182,24 +1285,25 @@ public void correctBuilder() { " this.aByteArray = source.aByteArray();", " this.aNullableIntArray = source.aNullableIntArray();", " this.aList = source.aList();", - " this.anImmutableList = source.anImmutableList();", + " this.anImmutableMap = source.anImmutableMap();", " this.anOptionalString = source.anOptionalString();", " this.aNestedAutoValue = source.aNestedAutoValue();", + " set$0 = (byte) 1;", " }", "", " @Override", " public Baz.Builder anInt(int anInt) {", " this.anInt = anInt;", + " set$0 |= (byte) 1;", " return this;", " }", "", " @Override", " public Optional anInt() {", - " if (anInt == null) {", + " if ((set$0 & 1) == 0) {", " return Optional.absent();", - " } else {", - " return Optional.of(anInt);", " }", + " return Optional.of(anInt);", " }", "", " @Override", @@ -1228,45 +1332,45 @@ public void correctBuilder() { "", " @Override", " public List aList() {", - " if (aList == null) {", + " if (this.aList == null) {", " throw new IllegalStateException(\"Property \\\"aList\\\" has not been set\");", " }", " return aList;", " }", "", " @Override", - " public Baz.Builder anImmutableList(List anImmutableList) {", - " if (anImmutableListBuilder$ != null) {", + " public Baz.Builder anImmutableMap(Map anImmutableMap) {", + " if (anImmutableMapBuilder$ != null) {", " throw new IllegalStateException(" - + "\"Cannot set anImmutableList after calling anImmutableListBuilder()\");", + + "\"Cannot set anImmutableMap after calling anImmutableMapBuilder()\");", " }", - " this.anImmutableList = ImmutableList.copyOf(anImmutableList);", + " this.anImmutableMap = ImmutableMap.copyOf(anImmutableMap);", " return this;", " }", "", " @Override", - " public ImmutableList.Builder anImmutableListBuilder() {", - " if (anImmutableListBuilder$ == null) {", - " if (anImmutableList == null) {", - " anImmutableListBuilder$ = ImmutableList.builder();", + " public ImmutableMap.Builder anImmutableMapBuilder() {", + " if (anImmutableMapBuilder$ == null) {", + " if (anImmutableMap == null) {", + " anImmutableMapBuilder$ = ImmutableMap.builder();", " } else {", - " anImmutableListBuilder$ = ImmutableList.builder();", - " anImmutableListBuilder$.addAll(anImmutableList);", - " anImmutableList = null;", + " anImmutableMapBuilder$ = ImmutableMap.builder();", + " anImmutableMapBuilder$.putAll(anImmutableMap);", + " anImmutableMap = null;", " }", " }", - " return anImmutableListBuilder$;", + " return anImmutableMapBuilder$;", " }", "", " @Override", - " public ImmutableList anImmutableList() {", - " if (anImmutableListBuilder$ != null) {", - " return anImmutableListBuilder$.build();", + " public ImmutableMap anImmutableMap() {", + " if (anImmutableMapBuilder$ != null) {", + " return anImmutableMapBuilder$.buildOrThrow();", " }", - " if (anImmutableList == null) {", - " anImmutableList = ImmutableList.of();", + " if (anImmutableMap == null) {", + " anImmutableMap = ImmutableMap.of();", " }", - " return anImmutableList;", + " return anImmutableMap;", " }", "", " @Override", @@ -1299,10 +1403,10 @@ public void correctBuilder() { "", " @Override", " public Baz build() {", - " if (anImmutableListBuilder$ != null) {", - " this.anImmutableList = anImmutableListBuilder$.build();", - " } else if (this.anImmutableList == null) {", - " this.anImmutableList = ImmutableList.of();", + " if (anImmutableMapBuilder$ != null) {", + " this.anImmutableMap = anImmutableMapBuilder$.buildOrThrow();", + " } else if (this.anImmutableMap == null) {", + " this.anImmutableMap = ImmutableMap.of();", " }", " if (aNestedAutoValueBuilder$ != null) {", " this.aNestedAutoValue = aNestedAutoValueBuilder$.build();", @@ -1311,17 +1415,19 @@ public void correctBuilder() { + "NestedAutoValue.builder();", " this.aNestedAutoValue = aNestedAutoValue$builder.build();", " }", - " String missing = \"\";", - " if (this.anInt == null) {", - " missing += \" anInt\";", - " }", - " if (this.aByteArray == null) {", - " missing += \" aByteArray\";", - " }", - " if (this.aList == null) {", - " missing += \" aList\";", - " }", - " if (!missing.isEmpty()) {", + " if (set$0 != 1", + " || this.aByteArray == null", + " || this.aList == null) {", + " StringBuilder missing = new StringBuilder();", + " if ((set$0 & 1) == 0) {", + " missing.append(\" anInt\");", + " }", + " if (this.aByteArray == null) {", + " missing.append(\" aByteArray\");", + " }", + " if (this.aList == null) {", + " missing.append(\" aList\");", + " }", " throw new IllegalStateException(\"Missing required properties:\" + missing);", " }", " return new AutoValue_Baz(", @@ -1329,7 +1435,7 @@ public void correctBuilder() { " this.aByteArray,", " this.aNullableIntArray,", " this.aList,", - " this.anImmutableList,", + " this.anImmutableMap,", " this.anOptionalString,", " this.aNestedAutoValue);", " }", @@ -1338,7 +1444,8 @@ public void correctBuilder() { Compilation compilation = javac() .withProcessors(new AutoValueProcessor()) - .withOptions("-Xlint:-processing", "-implicit:none") + .withOptions( + "-Xlint:-processing", "-implicit:none", "-A" + Nullables.NULLABLE_OPTION + "=") .compile(javaFileObject, nestedJavaFileObject); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -1346,6 +1453,304 @@ public void correctBuilder() { .hasSourceEquivalentTo(expectedOutput); } + @Test + public void builderWithNullableTypeAnnotation() { + // Sadly we can't rely on JDK 8 to handle type annotations correctly. + // Some versions do, some don't. So skip the test unless we are on at least JDK 9. + double javaVersion = Double.parseDouble(JAVA_SPECIFICATION_VERSION.value()); + assume().that(javaVersion).isAtLeast(9.0); + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "import com.google.common.base.Optional;", + "import com.google.common.collect.ImmutableMap;", + "", + "import java.util.ArrayList;", + "import java.util.List;", + "import java.util.Map;", + "import org.checkerframework.checker.nullness.qual.Nullable;", + "", + "@AutoValue", + "public abstract class Baz {", + " public abstract int anInt();", + " @SuppressWarnings(\"mutable\")", + " public abstract byte[] aByteArray();", + " @SuppressWarnings(\"mutable\")", + " public abstract int @Nullable [] aNullableIntArray();", + " public abstract List aList();", + " public abstract ImmutableMap anImmutableMap();", + " public abstract Optional anOptionalString();", + "", + " public abstract Builder toBuilder();", + "", + " @AutoValue.Builder", + " public abstract static class Builder {", + " public abstract Builder anInt(int x);", + " public abstract Builder aByteArray(byte[] x);", + " public abstract Builder aNullableIntArray(int @Nullable [] x);", + " public abstract Builder aList(List x);", + " public abstract Builder anImmutableMap(Map x);", + " public abstract ImmutableMap.Builder anImmutableMapBuilder();", + " public abstract Builder anOptionalString(Optional s);", + " public abstract Baz build();", + " }", + "", + " public static Builder builder() {", + " return AutoValue_Baz.builder();", + " }", + "}"); + JavaFileObject expectedOutput = + JavaFileObjects.forSourceLines( + "foo.bar.AutoValue_Baz", + "package foo.bar;", + "", + "import com.google.common.base.Optional;", + "import com.google.common.collect.ImmutableMap;", + "import java.util.Arrays;", + "import java.util.List;", + "import java.util.Map;", + sorted( + GeneratedImport.importGeneratedAnnotationType(), + "import org.checkerframework.checker.nullness.qual.Nullable;"), + "", + "@Generated(\"" + AutoValueProcessor.class.getName() + "\")", + "final class AutoValue_Baz extends Baz {", + " private final int anInt;", + " private final byte[] aByteArray;", + " private final int @Nullable [] aNullableIntArray;", + " private final List aList;", + " private final ImmutableMap anImmutableMap;", + " private final Optional anOptionalString;", + "", + " private AutoValue_Baz(", + " int anInt,", + " byte[] aByteArray,", + " int @Nullable [] aNullableIntArray,", + " List aList,", + " ImmutableMap anImmutableMap,", + " Optional anOptionalString) {", + " this.anInt = anInt;", + " this.aByteArray = aByteArray;", + " this.aNullableIntArray = aNullableIntArray;", + " this.aList = aList;", + " this.anImmutableMap = anImmutableMap;", + " this.anOptionalString = anOptionalString;", + " }", + "", + " @Override public int anInt() {", + " return anInt;", + " }", + "", + " @SuppressWarnings(\"mutable\")", + " @Override public byte[] aByteArray() {", + " return aByteArray;", + " }", + "", + " @SuppressWarnings(\"mutable\")", + " @Override public int @Nullable [] aNullableIntArray() {", + " return aNullableIntArray;", + " }", + "", + " @Override public List aList() {", + " return aList;", + " }", + "", + " @Override public ImmutableMap anImmutableMap() {", + " return anImmutableMap;", + " }", + "", + " @Override public Optional anOptionalString() {", + " return anOptionalString;", + " }", + "", + " @Override public String toString() {", + " return \"Baz{\"", + " + \"anInt=\" + anInt + \", \"", + " + \"aByteArray=\" + Arrays.toString(aByteArray) + \", \"", + " + \"aNullableIntArray=\" + Arrays.toString(aNullableIntArray) + \", \"", + " + \"aList=\" + aList + \", \"", + " + \"anImmutableMap=\" + anImmutableMap + \", \"", + " + \"anOptionalString=\" + anOptionalString", + " + \"}\";", + " }", + "", + " @Override public boolean equals(@Nullable Object o) {", + " if (o == this) {", + " return true;", + " }", + " if (o instanceof Baz) {", + " Baz that = (Baz) o;", + " return this.anInt == that.anInt()", + " && Arrays.equals(this.aByteArray, " + + "(that instanceof AutoValue_Baz) " + + "? ((AutoValue_Baz) that).aByteArray : that.aByteArray())", + " && Arrays.equals(this.aNullableIntArray, " + + "(that instanceof AutoValue_Baz) " + + "? ((AutoValue_Baz) that).aNullableIntArray : that.aNullableIntArray())", + " && this.aList.equals(that.aList())", + " && this.anImmutableMap.equals(that.anImmutableMap())", + " && this.anOptionalString.equals(that.anOptionalString());", + " }", + " return false;", + " }", + "", + " @Override public int hashCode() {", + " int h$ = 1;", + " h$ *= 1000003;", + " h$ ^= anInt;", + " h$ *= 1000003;", + " h$ ^= Arrays.hashCode(aByteArray);", + " h$ *= 1000003;", + " h$ ^= Arrays.hashCode(aNullableIntArray);", + " h$ *= 1000003;", + " h$ ^= aList.hashCode();", + " h$ *= 1000003;", + " h$ ^= anImmutableMap.hashCode();", + " h$ *= 1000003;", + " h$ ^= anOptionalString.hashCode();", + " return h$;", + " }", + "", + " @Override public Baz.Builder toBuilder() {", + " return new Builder(this);", + " }", + "", + " static final class Builder extends Baz.Builder {", + " private int anInt;", + " private byte @Nullable [] aByteArray;", + " private int @Nullable [] aNullableIntArray;", + " private @Nullable List aList;", + " private ImmutableMap.@Nullable Builder anImmutableMapBuilder$;", + " private @Nullable ImmutableMap anImmutableMap;", + " private Optional anOptionalString = Optional.absent();", + " private byte set$0;", + "", + " Builder() {", + " }", + "", + " private Builder(Baz source) {", + " this.anInt = source.anInt();", + " this.aByteArray = source.aByteArray();", + " this.aNullableIntArray = source.aNullableIntArray();", + " this.aList = source.aList();", + " this.anImmutableMap = source.anImmutableMap();", + " this.anOptionalString = source.anOptionalString();", + " set$0 = (byte) 1;", + " }", + "", + " @Override", + " public Baz.Builder anInt(int anInt) {", + " this.anInt = anInt;", + " set$0 |= (byte) 1;", + " return this;", + " }", + "", + " @Override", + " public Baz.Builder aByteArray(byte[] aByteArray) {", + " if (aByteArray == null) {", + " throw new NullPointerException(\"Null aByteArray\");", + " }", + " this.aByteArray = aByteArray;", + " return this;", + " }", + "", + " @Override", + " public Baz.Builder aNullableIntArray(int @Nullable [] aNullableIntArray) {", + " this.aNullableIntArray = aNullableIntArray;", + " return this;", + " }", + "", + " @Override", + " public Baz.Builder aList(List aList) {", + " if (aList == null) {", + " throw new NullPointerException(\"Null aList\");", + " }", + " this.aList = aList;", + " return this;", + " }", + "", + " @Override", + " public Baz.Builder anImmutableMap(Map anImmutableMap) {", + " if (anImmutableMapBuilder$ != null) {", + " throw new IllegalStateException(" + + "\"Cannot set anImmutableMap after calling anImmutableMapBuilder()\");", + " }", + " this.anImmutableMap = ImmutableMap.copyOf(anImmutableMap);", + " return this;", + " }", + "", + " @Override", + " public ImmutableMap.Builder anImmutableMapBuilder() {", + " if (anImmutableMapBuilder$ == null) {", + " if (anImmutableMap == null) {", + " anImmutableMapBuilder$ = ImmutableMap.builder();", + " } else {", + " anImmutableMapBuilder$ = ImmutableMap.builder();", + " anImmutableMapBuilder$.putAll(anImmutableMap);", + " anImmutableMap = null;", + " }", + " }", + " return anImmutableMapBuilder$;", + " }", + "", + " @Override", + " public Baz.Builder anOptionalString(Optional anOptionalString) {", + " if (anOptionalString == null) {", + " throw new NullPointerException(\"Null anOptionalString\");", + " }", + " this.anOptionalString = anOptionalString;", + " return this;", + " }", + "", + " @Override", + " public Baz build() {", + " if (anImmutableMapBuilder$ != null) {", + " this.anImmutableMap = anImmutableMapBuilder$.buildOrThrow();", + " } else if (this.anImmutableMap == null) {", + " this.anImmutableMap = ImmutableMap.of();", + " }", + " if (set$0 != 1", + " || this.aByteArray == null", + " || this.aList == null) {", + " StringBuilder missing = new StringBuilder();", + " if ((set$0 & 1) == 0) {", + " missing.append(\" anInt\");", + " }", + " if (this.aByteArray == null) {", + " missing.append(\" aByteArray\");", + " }", + " if (this.aList == null) {", + " missing.append(\" aList\");", + " }", + " throw new IllegalStateException(\"Missing required properties:\" + missing);", + " }", + " return new AutoValue_Baz(", + " this.anInt,", + " this.aByteArray,", + " this.aNullableIntArray,", + " this.aList,", + " this.anImmutableMap,", + " this.anOptionalString);", + " }", + " }", + "}"); + Compilation compilation = + javac() + .withProcessors(new AutoValueProcessor()) + .withOptions( + "-Xlint:-processing", + "-implicit:none", + "-A" + Nullables.NULLABLE_OPTION + "=org.checkerframework.checker.nullness.qual.Nullable") + .compile(javaFileObject); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(compilation) + .generatedSourceFile("foo.bar.AutoValue_Baz") + .hasSourceEquivalentTo(expectedOutput); + } + @Test public void autoValueBuilderOnTopLevelClass() { JavaFileObject javaFileObject = @@ -1437,6 +1842,42 @@ public void autoValueBuilderNotStatic() { .onLineContaining("abstract class Builder"); } + @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 = @@ -1767,7 +2208,8 @@ public void autoValueBuilderBeansConfusion() { .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor()) .compile(javaFileObject); assertThat(compilation) - .hadErrorContaining("Method does not correspond to a property method of foo.bar.Item") + .hadErrorContaining( + "Method setTitle does not correspond to a property method of foo.bar.Item") .inFile(javaFileObject) .onLineContaining("Builder setTitle(String title)"); assertThat(compilation) @@ -1801,7 +2243,7 @@ public void autoValueBuilderExtraSetter() { .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor()) .compile(javaFileObject); assertThat(compilation) - .hadErrorContaining("Method does not correspond to a property method of foo.bar.Baz") + .hadErrorContaining("Method blim does not correspond to a property method of foo.bar.Baz") .inFile(javaFileObject) .onLineContaining("Builder blim(int x)"); } @@ -1839,6 +2281,8 @@ public void autoValueBuilderSetPrefixAndNoSetPrefix() { @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", @@ -1852,7 +2296,7 @@ public void autoValueBuilderSetterReturnType() { "", " @AutoValue.Builder", " public interface Builder {", - " void blim(int x);", + " Object blim(int x);", " Baz build();", " }", "}"); @@ -1863,7 +2307,7 @@ public void autoValueBuilderSetterReturnType() { assertThat(compilation) .hadErrorContaining("Setter methods must return foo.bar.Baz.Builder") .inFile(javaFileObject) - .onLineContaining("void blim(int x)"); + .onLineContaining("Object blim(int x)"); } @Test @@ -1931,7 +2375,7 @@ public void autoValueBuilderPropertyBuilderInvalidType() { assertThat(compilation) .hadErrorContaining( "Method looks like a property builder, but it returns java.lang.StringBuilder which " - + "does not have a non-static build() method") + + "does not have a non-static build() or buildOrThrow() method") .inFile(javaFileObject) .onLineContaining("StringBuilder blimBuilder()"); } @@ -2125,7 +2569,7 @@ public void autoValueBuilderPropertyBuilderHasNoBuild() { assertThat(compilation) .hadErrorContaining( "Method looks like a property builder, but it returns java.lang.StringBuilder which " - + "does not have a non-static build() method") + + "does not have a non-static build() or buildOrThrow() method") .inFile(javaFileObject) .onLineContaining("StringBuilder blimBuilder()"); } @@ -2163,7 +2607,7 @@ public void autoValueBuilderPropertyBuilderHasStaticBuild() { assertThat(compilation) .hadErrorContaining( "Method looks like a property builder, but it returns foo.bar.Baz.StringFactory which " - + "does not have a non-static build() method") + + "does not have a non-static build() or buildOrThrow() method") .inFile(javaFileObject) .onLineContaining("StringFactory blimBuilder()"); } @@ -2514,7 +2958,7 @@ public void autoValueBuilderAlienMethod1() { .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor()) .compile(javaFileObject); assertThat(compilation) - .hadErrorContaining("Method does not correspond to a property method of foo.bar.Baz") + .hadErrorContaining("Method whut does not correspond to a property method of foo.bar.Baz") .inFile(javaFileObject) .onLineContaining("void whut(String x)"); } @@ -2902,8 +3346,6 @@ public void referencingGeneratedClass() { "foo.bar.Bar", "package foo.bar;", "", - "import com.google.auto.value.AutoValue;", - "", "@" + Foo.class.getCanonicalName(), "public abstract class Bar {", " public abstract BarFoo barFoo();", @@ -2916,6 +3358,73 @@ public void referencingGeneratedClass() { assertThat(compilation).succeededWithoutWarnings(); } + @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 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) @@ -3040,6 +3549,63 @@ public void visibleProtectedAnnotationFromOtherPackage() { .containsMatch("(?s:@Parent.ProtectedAnnotation\\s*@Override\\s*public String foo\\(\\))"); } + @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 = @@ -3371,7 +3937,41 @@ public SourceVersion getSupportedSourceVersion() { } } - private String sorted(String... imports) { - return Arrays.stream(imports).sorted().collect(joining("\n")); + // This is a regression test for the problem described in + // https://github.com/google/auto/issues/1087. + @Test + public void kotlinMetadataAnnotationsAreImplicitlyExcludedFromCopying() { + JavaFileObject metadata = + JavaFileObjects.forSourceLines( + "kotlin.Metadata", "package kotlin;", "", "public @interface Metadata {", "}"); + JavaFileObject test = + JavaFileObjects.forSourceLines( + "foo.bar.Test", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "import kotlin.Metadata;", + "", + "@AutoValue.CopyAnnotations", + "@Metadata", + "@AutoValue", + "public abstract class Test {", + " public abstract String string();", + "}"); + AutoValueProcessor autoValueProcessor = new AutoValueProcessor(); + Compilation compilation = + javac() + .withProcessors(autoValueProcessor) + .withOptions("-Xlint:-processing", "-implicit:none") + .compile(test, metadata); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(compilation) + .generatedSourceFile("foo.bar.AutoValue_Test") + .contentsAsUtf8String() + .doesNotContain("kotlin.Metadata"); + } + + private static String sorted(String... imports) { + return stream(imports).sorted().collect(joining("\n")); } } diff --git a/value/src/test/java/com/google/auto/value/processor/BuilderRequiredPropertiesTest.java b/value/src/test/java/com/google/auto/value/processor/BuilderRequiredPropertiesTest.java new file mode 100644 index 0000000000..fe7ad321ca --- /dev/null +++ b/value/src/test/java/com/google/auto/value/processor/BuilderRequiredPropertiesTest.java @@ -0,0 +1,351 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.auto.value.processor; + +import static com.google.auto.common.MoreStreams.toImmutableList; +import static com.google.auto.common.MoreStreams.toImmutableSet; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.auto.value.processor.AutoValueishProcessor.Property; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.testing.compile.CompilationRule; +import java.util.Optional; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link BuilderRequiredProperties}. */ +@RunWith(JUnit4.class) +public final class BuilderRequiredPropertiesTest { + @ClassRule public static final CompilationRule compilationRule = new CompilationRule(); + + private TypeMirror intType; + private TypeMirror stringType; + + @Before + public void initTypes() { + intType = compilationRule.getTypes().getPrimitiveType(TypeKind.INT); + stringType = compilationRule.getElements().getTypeElement("java.lang.String").asType(); + } + + @Test + public void fieldDeclarations() { + assertThat(fieldDeclarations(0)).isEmpty(); + assertThat(fieldDeclarations(1)).containsExactly("private byte set$0;"); + assertThat(fieldDeclarations(8)).containsExactly("private byte set$0;"); + assertThat(fieldDeclarations(9)).containsExactly("private short set$0;"); + assertThat(fieldDeclarations(16)).containsExactly("private short set$0;"); + assertThat(fieldDeclarations(17)).containsExactly("private int set$0;"); + assertThat(fieldDeclarations(32)).containsExactly("private int set$0;"); + assertThat(fieldDeclarations(33)).containsExactly("private int set$0;", "private byte set$1;"); + assertThat(fieldDeclarations(40)).containsExactly("private int set$0;", "private byte set$1;"); + assertThat(fieldDeclarations(41)).containsExactly("private int set$0;", "private short set$1;"); + assertThat(fieldDeclarations(48)).containsExactly("private int set$0;", "private short set$1;"); + assertThat(fieldDeclarations(49)).containsExactly("private int set$0;", "private int set$1;"); + assertThat(fieldDeclarations(64)).containsExactly("private int set$0;", "private int set$1;"); + assertThat(fieldDeclarations(65)) + .containsExactly("private int set$0;", "private int set$1;", "private byte set$2;"); + assertThat(fieldDeclarations(144)) + .containsExactly( + "private int set$0;", + "private int set$1;", + "private int set$2;", + "private int set$3;", + "private short set$4;"); + } + + private ImmutableList fieldDeclarations(int size) { + return builderRequiredProperties(size).getFieldDeclarations(); + } + + @Test + public void initToAllSet() { + assertThat(initToAllSet(0)).isEmpty(); + assertThat(initToAllSet(1)).containsExactly("set$0 = (byte) 1;"); + assertThat(initToAllSet(8)).containsExactly("set$0 = (byte) 0xff;"); + assertThat(initToAllSet(9)).containsExactly("set$0 = (short) 0x1ff;"); + assertThat(initToAllSet(16)).containsExactly("set$0 = (short) 0xffff;"); + assertThat(initToAllSet(17)).containsExactly("set$0 = 0x1_ffff;"); + assertThat(initToAllSet(31)).containsExactly("set$0 = 0x7fff_ffff;"); + assertThat(initToAllSet(32)).containsExactly("set$0 = -1;"); + assertThat(initToAllSet(33)).containsExactly("set$0 = -1;", "set$1 = (byte) 1;"); + assertThat(initToAllSet(63)).containsExactly("set$0 = -1;", "set$1 = 0x7fff_ffff;"); + assertThat(initToAllSet(64)).containsExactly("set$0 = -1;", "set$1 = -1;"); + assertThat(initToAllSet(144)) + .containsExactly( + "set$0 = -1;", "set$1 = -1;", "set$2 = -1;", "set$3 = -1;", "set$4 = (short) 0xffff;"); + } + + private ImmutableList initToAllSet(int size) { + return builderRequiredProperties(size).getInitToAllSet(); + } + + @Test + public void markAsSet_reference() { + BuilderRequiredProperties onlyString = builderRequiredProperties(0); + Property stringProperty = Iterables.getOnlyElement(onlyString.getRequiredProperties()); + assertThat(onlyString.markAsSet(stringProperty)).isEmpty(); + } + + @Test + public void markAsSet_byte() { + BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(8); + ImmutableList primitives = requiredPrimitiveProperties(builderRequiredProperties); + assertThat(primitives).hasSize(8); + assertThat(builderRequiredProperties.markAsSet(primitives.get(0))) + .isEqualTo("set$0 |= (byte) 1;"); + assertThat(builderRequiredProperties.markAsSet(primitives.get(7))) + .isEqualTo("set$0 |= (byte) 0x80;"); + } + + @Test + public void markAsSet_short() { + BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(16); + ImmutableList primitives = requiredPrimitiveProperties(builderRequiredProperties); + assertThat(primitives).hasSize(16); + assertThat(builderRequiredProperties.markAsSet(primitives.get(0))) + .isEqualTo("set$0 |= (short) 1;"); + assertThat(builderRequiredProperties.markAsSet(primitives.get(7))) + .isEqualTo("set$0 |= (short) 0x80;"); + assertThat(builderRequiredProperties.markAsSet(primitives.get(15))) + .isEqualTo("set$0 |= (short) 0x8000;"); + } + + @Test + public void markAsSet_int() { + BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(32); + ImmutableList primitives = requiredPrimitiveProperties(builderRequiredProperties); + assertThat(primitives).hasSize(32); + assertThat(builderRequiredProperties.markAsSet(primitives.get(0))).isEqualTo("set$0 |= 1;"); + assertThat(builderRequiredProperties.markAsSet(primitives.get(31))) + .isEqualTo("set$0 |= 0x8000_0000;"); + } + + @Test + public void markAsSet_intPlusByte() { + BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(34); + ImmutableList primitives = requiredPrimitiveProperties(builderRequiredProperties); + assertThat(primitives).hasSize(34); + assertThat(builderRequiredProperties.markAsSet(primitives.get(0))).isEqualTo("set$0 |= 1;"); + assertThat(builderRequiredProperties.markAsSet(primitives.get(31))) + .isEqualTo("set$0 |= 0x8000_0000;"); + assertThat(builderRequiredProperties.markAsSet(primitives.get(32))) + .isEqualTo("set$1 |= (byte) 1;"); + assertThat(builderRequiredProperties.markAsSet(primitives.get(33))) + .isEqualTo("set$1 |= (byte) 2;"); + } + + @Test + public void missingRequiredProperty_reference() { + BuilderRequiredProperties onlyString = builderRequiredProperties(0); + Property stringProperty = Iterables.getOnlyElement(onlyString.getRequiredProperties()); + assertThat(onlyString.missingRequiredProperty(stringProperty)).isEqualTo("this.string == null"); + } + + @Test + public void missingRequiredProperty_byte() { + BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(8); + ImmutableList primitives = requiredPrimitiveProperties(builderRequiredProperties); + assertThat(primitives).hasSize(8); + assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(0))) + .isEqualTo("(set$0 & 1) == 0"); + assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(7))) + .isEqualTo("(set$0 & 0x80) == 0"); + } + + @Test + public void missingRequiredProperty_short() { + BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(16); + ImmutableList primitives = requiredPrimitiveProperties(builderRequiredProperties); + assertThat(primitives).hasSize(16); + assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(0))) + .isEqualTo("(set$0 & 1) == 0"); + assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(7))) + .isEqualTo("(set$0 & 0x80) == 0"); + assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(15))) + .isEqualTo("(set$0 & 0x8000) == 0"); + } + + @Test + public void missingRequiredProperty_int() { + BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(32); + ImmutableList primitives = requiredPrimitiveProperties(builderRequiredProperties); + assertThat(primitives).hasSize(32); + assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(0))) + .isEqualTo("(set$0 & 1) == 0"); + assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(31))) + .isEqualTo("(set$0 & 0x8000_0000) == 0"); + } + + @Test + public void missingRequiredProperty_intPlusByte() { + BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(34); + ImmutableList primitives = requiredPrimitiveProperties(builderRequiredProperties); + assertThat(primitives).hasSize(34); + assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(0))) + .isEqualTo("(set$0 & 1) == 0"); + assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(31))) + .isEqualTo("(set$0 & 0x8000_0000) == 0"); + assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(32))) + .isEqualTo("(set$1 & 1) == 0"); + assertThat(builderRequiredProperties.missingRequiredProperty(primitives.get(33))) + .isEqualTo("(set$1 & 2) == 0"); + } + + @Test + public void noValueToGet_noDefaults() { + BuilderRequiredProperties builderRequiredProperties = builderRequiredProperties(34); + ImmutableList primitives = requiredPrimitiveProperties(builderRequiredProperties); + assertThat(primitives).hasSize(34); + for (Property property : primitives) { + assertWithMessage("For property %s", property) + .that(builderRequiredProperties.noValueToGet(property)) + .isEqualTo(builderRequiredProperties.missingRequiredProperty(property)); + } + } + + @Test + public void noValueToGet_withDefaults() { + ImmutableSet allProperties = fakePropertiesWithDefaults(0); + BuilderRequiredProperties builderRequiredProperties = + BuilderRequiredProperties.of(allProperties, /* requiredProperties= */ ImmutableSet.of()); + ImmutableList allPropertiesList = allProperties.asList(); + assertThat(allPropertiesList.get(0).hasDefault()).isFalse(); + assertThat(allPropertiesList.get(1).hasDefault()).isTrue(); + assertThat(allPropertiesList.get(2).hasDefault()).isTrue(); + assertThat(builderRequiredProperties.noValueToGet(allPropertiesList.get(0))).isNull(); + assertThat(builderRequiredProperties.noValueToGet(allPropertiesList.get(1))) + .isEqualTo("(set$0 & 2) == 0"); + assertThat(builderRequiredProperties.noValueToGet(allPropertiesList.get(2))) + .isEqualTo("(set$0 & 4) == 0"); + } + + @Test + public void getAnyMissing() { + assertThat(builderRequiredProperties(0).getAnyMissing()).isEqualTo("this.string == null"); + assertThat(builderRequiredProperties(1).getAnyMissing()) + .isEqualTo("set$0 != 1\n|| this.string == null"); + assertThat(builderRequiredProperties(16).getAnyMissing()) + .isEqualTo("set$0 != -1\n|| this.string == null"); + assertThat(builderRequiredProperties(17).getAnyMissing()) + .isEqualTo("set$0 != 0x1_ffff\n|| this.string == null"); + assertThat(builderRequiredProperties(31).getAnyMissing()) + .isEqualTo("set$0 != 0x7fff_ffff\n|| this.string == null"); + assertThat(builderRequiredProperties(32).getAnyMissing()) + .isEqualTo("set$0 != -1\n|| this.string == null"); + assertThat(builderRequiredProperties(33).getAnyMissing()) + .isEqualTo("set$0 != -1\n|| set$1 != 1\n|| this.string == null"); + assertThat(builderRequiredProperties(64).getAnyMissing()) + .isEqualTo("set$0 != -1\n|| set$1 != -1\n|| this.string == null"); + } + + @Test + public void getAnyMissing_withDefaults() { + assertThat(builderRequiredPropertiesWithDefaults(0).getAnyMissing()) + .isEqualTo("(~set$0 & 1) != 0"); + assertThat(builderRequiredPropertiesWithDefaults(1).getAnyMissing()) + .isEqualTo("(~set$0 & 3) != 0"); + assertThat(builderRequiredPropertiesWithDefaults(15).getAnyMissing()) + .isEqualTo("(~set$0 & 0xffff) != 0"); + assertThat(builderRequiredPropertiesWithDefaults(16).getAnyMissing()) + .isEqualTo("(~set$0 & 0x1_ffff) != 0"); + assertThat(builderRequiredPropertiesWithDefaults(17).getAnyMissing()) + .isEqualTo("(~set$0 & 0x3_ffff) != 0"); + + // TODO(emcmanus): remove the no-op `& 0xfff_ffff` + assertThat(builderRequiredPropertiesWithDefaults(31).getAnyMissing()) + .isEqualTo("(~set$0 & 0xffff_ffff) != 0"); + assertThat(builderRequiredPropertiesWithDefaults(32).getAnyMissing()) + .isEqualTo("(~set$0 & 0xffff_ffff) != 0\n|| (~set$1 & 1) != 0"); + assertThat(builderRequiredPropertiesWithDefaults(33).getAnyMissing()) + .isEqualTo("(~set$0 & 0xffff_ffff) != 0\n|| (~set$1 & 3) != 0"); + assertThat(builderRequiredPropertiesWithDefaults(63).getAnyMissing()) + .isEqualTo("(~set$0 & 0xffff_ffff) != 0\n|| (~set$1 & 0xffff_ffff) != 0"); + assertThat(builderRequiredPropertiesWithDefaults(64).getAnyMissing()) + .isEqualTo( + "(~set$0 & 0xffff_ffff) != 0\n|| (~set$1 & 0xffff_ffff) != 0\n|| (~set$2 & 1) != 0"); + } + + @Test + public void hex() { + assertThat(BuilderRequiredProperties.hex(0x0)).isEqualTo("0"); + assertThat(BuilderRequiredProperties.hex(0x1)).isEqualTo("1"); + assertThat(BuilderRequiredProperties.hex(0x9)).isEqualTo("9"); + assertThat(BuilderRequiredProperties.hex(0xa)).isEqualTo("0xa"); + assertThat(BuilderRequiredProperties.hex(0xffff)).isEqualTo("0xffff"); + assertThat(BuilderRequiredProperties.hex(0x1_0000)).isEqualTo("0x1_0000"); + assertThat(BuilderRequiredProperties.hex(0x7fff_ffff)).isEqualTo("0x7fff_ffff"); + assertThat(BuilderRequiredProperties.hex(0xffff_ffff)).isEqualTo("0xffff_ffff"); + } + + private ImmutableList requiredPrimitiveProperties( + BuilderRequiredProperties builderRequiredProperties) { + return builderRequiredProperties.getRequiredProperties().stream() + .filter(p -> p.getTypeMirror().getKind().isPrimitive()) + .collect(toImmutableList()); + } + + private BuilderRequiredProperties builderRequiredProperties(int primitiveCount) { + ImmutableSet properties = fakeProperties(primitiveCount); + return BuilderRequiredProperties.of(properties, properties); + } + + private BuilderRequiredProperties builderRequiredPropertiesWithDefaults(int primitiveCount) { + ImmutableSet allProperties = fakePropertiesWithDefaults(primitiveCount); + ImmutableSet requiredProperties = + allProperties.stream().filter(p -> !p.hasDefault()).collect(toImmutableSet()); + return BuilderRequiredProperties.of(allProperties, requiredProperties); + } + + private ImmutableSet fakeProperties(int primitiveCount) { + return Stream.concat( + Stream.of(fakeProperty("string", stringType, /* hasDefault= */ false)), + IntStream.range(0, primitiveCount) + .mapToObj(i -> fakeProperty("x" + i, intType, /* hasDefault= */ false))) + .collect(toImmutableSet()); + } + + private ImmutableSet fakePropertiesWithDefaults(int primitiveCount) { + ImmutableSet requiredProperties = fakeProperties(primitiveCount); + return ImmutableSet.builder() + .addAll(requiredProperties) + .add(fakeProperty("stringWithDefault", stringType, /* hasDefault= */ true)) + .add(fakeProperty("intWithDefault", intType, /* hasDefault= */ true)) + .build(); + } + + private Property fakeProperty(String name, TypeMirror type, boolean hasDefault) { + return new Property( + /* name= */ name, + /* identifier= */ name, + /* type= */ type.toString(), + /* typeMirror= */ type, + /* nullableAnnotation= */ Optional.empty(), + /* nullables= */ Nullables.fromMethods(null, ImmutableList.of()), + /* getter= */ name, + /* maybeBuilderInitializer= */ Optional.empty(), + /* hasDefault= */ hasDefault); + } +} diff --git a/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java b/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java index 8f74d5df1c..56eaad25f3 100644 --- a/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java +++ b/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java @@ -172,6 +172,7 @@ public void testExtensionConsumesProperties() { Compilation compilation = javac() .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension()))) + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") .compile(javaFileObject); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) @@ -250,9 +251,7 @@ class AlsoConsumeDizzle extends ConsumeDizzle {} " abstract String dizzle();", "}"); Compilation compilation = - javac() - .withProcessors(new AutoValueProcessor(ImmutableList.of(ext1, ext2))) - .compile(impl); + javac().withProcessors(new AutoValueProcessor(ImmutableList.of(ext1, ext2))).compile(impl); assertThat(compilation) .hadErrorContaining("wants to consume a method that was already consumed") .inFile(impl) @@ -596,10 +595,9 @@ public void testUnconsumedMethod() { "}"); Compilation compilation = javac() - .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension()))) - .compile(javaFileObject); - assertThat(compilation) - .hadErrorContaining("writeToParcel"); + .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension()))) + .compile(javaFileObject); + assertThat(compilation).hadErrorContaining("writeToParcel"); assertThat(compilation) .hadWarningContaining( "Abstract method is neither a property getter nor a Builder converter, " @@ -647,13 +645,12 @@ private void doTestBadJarDoesntBlowUp(File badJar) throws IOException { "public abstract class Baz {", "}"); Compilation compilation = - javac() - .withProcessors(new AutoValueProcessor(badJarLoader)) - .compile(javaFileObject); + javac().withProcessors(new AutoValueProcessor(badJarLoader)).compile(javaFileObject); assertThat(compilation).succeeded(); - assertThat(compilation).hadWarningContaining( - "This may be due to a corrupt jar file in the compiler's classpath.\n " - + ServiceConfigurationError.class.getName()); + assertThat(compilation) + .hadWarningContaining( + "This may be due to a corrupt jar file in the compiler's classpath.\n " + + ServiceConfigurationError.class.getName()); assertThat(compilation).generatedSourceFile("foo.bar.AutoValue_Baz"); } @@ -857,8 +854,12 @@ public String generateClass( String sideClassName = "Side_" + context.autoValueClass().getSimpleName(); String sideClass = "" // - + "package " + context.packageName() + ";\n" - + "class " + sideClassName + " {}\n"; + + "package " + + context.packageName() + + ";\n" + + "class " + + sideClassName + + " {}\n"; Filer filer = context.processingEnvironment().getFiler(); try { String sideClassFqName = context.packageName() + "." + sideClassName; @@ -912,25 +913,27 @@ String extraText(Context context) { @Test public void propertyTypes() { - JavaFileObject parent = JavaFileObjects.forSourceLines( - "foo.bar.Parent", - "package foo.bar;", - "", - "import java.util.List;", - "", - "interface Parent {", - " T thing();", - " List list();", - "}"); - JavaFileObject autoValueClass = JavaFileObjects.forSourceLines( - "foo.bar.Baz", - "package foo.bar;", - "", - "import com.google.auto.value.AutoValue;", - "", - "@AutoValue", - "abstract class Baz implements Parent {", - "}"); + JavaFileObject parent = + JavaFileObjects.forSourceLines( + "foo.bar.Parent", + "package foo.bar;", + "", + "import java.util.List;", + "", + "interface Parent {", + " T thing();", + " List list();", + "}"); + JavaFileObject autoValueClass = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "", + "@AutoValue", + "abstract class Baz implements Parent {", + "}"); ContextChecker checker = context -> { assertThat(context.builder()).isEmpty(); @@ -955,15 +958,16 @@ public void propertyTypes() { @Test public void finalAutoValueClassName() { - JavaFileObject autoValueClass = JavaFileObjects.forSourceLines( - "foo.bar.Baz", - "package foo.bar;", - "", - "import com.google.auto.value.AutoValue;", - "", - "@AutoValue", - "abstract class Baz {", - "}"); + JavaFileObject autoValueClass = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "", + "@AutoValue", + "abstract class Baz {", + "}"); ContextChecker checker = context -> { assertThat(context.finalAutoValueClassName()).isEqualTo("foo.bar.AutoValue_Baz"); @@ -982,43 +986,45 @@ public void finalAutoValueClassName() { @Test public void builderContext() { - JavaFileObject parent = JavaFileObjects.forSourceLines( - "foo.bar.Parent", - "package foo.bar;", - "", - "import com.google.common.collect.ImmutableList;", - "", - "interface Parent {", - " T thing();", - " ImmutableList list();", - "}"); - JavaFileObject autoValueClass = JavaFileObjects.forSourceLines( - "foo.bar.Baz", - "package foo.bar;", - "", - "import com.google.auto.value.AutoValue;", - "import com.google.common.collect.ImmutableList;", - "", - "@AutoValue", - "abstract class Baz implements Parent {", - " static Builder builder() {", - " return new AutoValue_Baz.Builder();", - " }", - "", - " abstract Builder toBuilder();", - "", - " @AutoValue.Builder", - " abstract static class Builder {", - " abstract Builder setThing(String x);", - " abstract Builder setList(Iterable x);", - " abstract Builder setList(ImmutableList x);", - " abstract ImmutableList.Builder listBuilder();", - " abstract Baz autoBuild();", - " Baz build() {", - " return autoBuild();", - " }", - " }", - "}"); + JavaFileObject parent = + JavaFileObjects.forSourceLines( + "foo.bar.Parent", + "package foo.bar;", + "", + "import com.google.common.collect.ImmutableList;", + "", + "interface Parent {", + " T thing();", + " ImmutableList list();", + "}"); + JavaFileObject autoValueClass = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "import com.google.common.collect.ImmutableList;", + "", + "@AutoValue", + "abstract class Baz implements Parent {", + " static Builder builder() {", + " return new AutoValue_Baz.Builder();", + " }", + "", + " abstract Builder toBuilder();", + "", + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setThing(String x);", + " abstract Builder setList(Iterable x);", + " abstract Builder setList(ImmutableList x);", + " abstract ImmutableList.Builder listBuilder();", + " abstract Baz autoBuild();", + " Baz build() {", + " return autoBuild();", + " }", + " }", + "}"); ContextChecker checker = context -> { assertThat(context.builder()).isPresent(); @@ -1076,34 +1082,36 @@ public void builderContext() { @Test public void builderContextWithInheritance() { - JavaFileObject parent = JavaFileObjects.forSourceLines( - "foo.bar.Parent", - "package foo.bar;", - "", - "interface Parent {", - " BuilderT toBuilder();", - " interface Builder {", - " BuilderT setThing(T x);", - " BuiltT build();", - " }", - "}"); - JavaFileObject autoValueClass = JavaFileObjects.forSourceLines( - "foo.bar.Baz", - "package foo.bar;", - "", - "import com.google.auto.value.AutoValue;", - "", - "@AutoValue", - "abstract class Baz implements Parent> {", - " abstract T thing();", - " static Builder builder() {", - " return new AutoValue_Baz.Builder<>();", - " }", - "", - " @AutoValue.Builder", - " abstract static class Builder implements Parent.Builder, Baz> {", - " }", - "}"); + JavaFileObject parent = + JavaFileObjects.forSourceLines( + "foo.bar.Parent", + "package foo.bar;", + "", + "interface Parent {", + " BuilderT toBuilder();", + " interface Builder {", + " BuilderT setThing(T x);", + " BuiltT build();", + " }", + "}"); + JavaFileObject autoValueClass = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "", + "@AutoValue", + "abstract class Baz implements Parent> {", + " abstract T thing();", + " static Builder builder() {", + " return new AutoValue_Baz.Builder<>();", + " }", + "", + " @AutoValue.Builder", + " abstract static class Builder implements Parent.Builder, Baz> {", + " }", + "}"); ContextChecker checker = context -> { assertThat(context.builder()).isPresent(); @@ -1148,26 +1156,27 @@ public void builderContextWithInheritance() { @Test public void oddBuilderContext() { - JavaFileObject autoValueClass = JavaFileObjects.forSourceLines( - "foo.bar.Baz", - "package foo.bar;", - "", - "import com.google.auto.value.AutoValue;", - "import com.google.common.collect.ImmutableList;", - "", - "@AutoValue", - "abstract class Baz {", - " abstract String string();", - "", - " @AutoValue.Builder", - " abstract static class Builder {", - " abstract Builder setString(String x);", - " abstract Baz oddBuild();", - " Baz build(int butNotReallyBecauseOfThisParameter) {", - " return null;", - " }", - " }", - "}"); + JavaFileObject autoValueClass = + JavaFileObjects.forSourceLines( + "foo.bar.Baz", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "import com.google.common.collect.ImmutableList;", + "", + "@AutoValue", + "abstract class Baz {", + " abstract String string();", + "", + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setString(String x);", + " abstract Baz oddBuild();", + " Baz build(int butNotReallyBecauseOfThisParameter) {", + " return null;", + " }", + " }", + "}"); ContextChecker checker = context -> { assertThat(context.builder()).isPresent(); @@ -1198,25 +1207,26 @@ public void oddBuilderContext() { // https://github.com/google/auto/issues/809 @Test public void propertyErrorShouldNotCrash() { - JavaFileObject autoValueClass = JavaFileObjects.forSourceLines( - "test.Test", - "package test;", - "import com.google.auto.value.AutoValue;", - "import java.util.List;", - "", - "@AutoValue", - "public abstract class Test {", - " abstract Integer property();", - " abstract List listProperty();", - "", - " @AutoValue.Builder", - " public interface Builder {", - " Builder property(Integer property);", - " Builder listProperty(List listProperty);", - " Builder listProperty(Integer listPropertyValues);", - " Test build();", - " }", - "}"); + JavaFileObject autoValueClass = + JavaFileObjects.forSourceLines( + "test.Test", + "package test;", + "import com.google.auto.value.AutoValue;", + "import java.util.List;", + "", + "@AutoValue", + "public abstract class Test {", + " abstract Integer property();", + " abstract List listProperty();", + "", + " @AutoValue.Builder", + " public interface Builder {", + " Builder property(Integer property);", + " Builder listProperty(List listProperty);", + " Builder listProperty(Integer listPropertyValues);", + " Test build();", + " }", + "}"); // We don't actually expect the extension to be invoked. Previously it was, and that led to a // NullPointerException when calling .setters() in the checker. ContextChecker checker = diff --git a/value/src/test/java/com/google/auto/value/processor/ForwardingClassGeneratorTest.java b/value/src/test/java/com/google/auto/value/processor/ForwardingClassGeneratorTest.java new file mode 100644 index 0000000000..383c46b261 --- /dev/null +++ b/value/src/test/java/com/google/auto/value/processor/ForwardingClassGeneratorTest.java @@ -0,0 +1,189 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.auto.value.processor; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.truth.Truth.assertThat; +import static java.util.Arrays.stream; +import static javax.lang.model.util.ElementFilter.constructorsIn; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.testing.compile.CompilationRule; +import java.lang.reflect.Method; +import java.util.function.Supplier; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class ForwardingClassGeneratorTest { + @Rule public final CompilationRule compilationRule = new CompilationRule(); + + public static class Simple implements Supplier> { + final int anInt; + + public Simple(int anInt) { + this.anInt = anInt; + } + + @Override + public ImmutableList get() { + return ImmutableList.of(anInt); + } + } + + @Test + public void simple() throws Exception { + testClass(Simple.class, ImmutableList.of(23)); + } + + public static class Outer { + public static class Inner {} + } + + public static class KitchenSink implements Supplier> { + final byte aByte; + final short aShort; + final int anInt; + final long aLong; + final float aFloat; + final double aDouble; + final char aChar; + final boolean aBoolean; + final String aString; + final ImmutableList aStringList; + final String[] aStringArray; + final byte[] aByteArray; + final Outer.Inner anInner; + + public KitchenSink( + byte aByte, + short aShort, + int anInt, + long aLong, + float aFloat, + double aDouble, + char aChar, + boolean aBoolean, + String aString, + ImmutableList aStringList, + String[] aStringArray, + byte[] aByteArray, + Outer.Inner anInner) { + this.aByte = aByte; + this.aShort = aShort; + this.anInt = anInt; + this.aLong = aLong; + this.aFloat = aFloat; + this.aDouble = aDouble; + this.aChar = aChar; + this.aBoolean = aBoolean; + this.aString = aString; + this.aStringList = aStringList; + this.aStringArray = aStringArray; + this.aByteArray = aByteArray; + this.anInner = anInner; + } + + @Override + public ImmutableList get() { + return ImmutableList.of( + aByte, + aShort, + anInt, + aLong, + aFloat, + aDouble, + aChar, + aBoolean, + aString, + aStringList, + aStringArray, + aByteArray, + anInner); + } + } + + @Test + public void kitchenSink() throws Exception { + testClass( + KitchenSink.class, + ImmutableList.of( + (byte) 1, + (short) 2, + 3, + 4L, + 5f, + 6d, + '7', + true, + "9", + ImmutableList.of("10"), + new String[] {"11"}, + new byte[] {12}, + new Outer.Inner())); + } + + /** + * Tests that we can successfully generate a forwarding class that calls the constructor of the + * given class. We'll then load the created class and call the forwarding method, checking that it + * does indeed call the constructor. + */ + private void testClass( + Class>> c, + ImmutableList constructorParameters) + throws ReflectiveOperationException { + TypeElement typeElement = compilationRule.getElements().getTypeElement(c.getCanonicalName()); + ExecutableElement constructorExecutable = + Iterables.getOnlyElement(constructorsIn(typeElement.getEnclosedElements())); + ImmutableList parameterTypeMirrors = + constructorExecutable.getParameters().stream() + .map(Element::asType) + .map(compilationRule.getTypes()::erasure) + .collect(toImmutableList()); + String className = "com.example.Forwarder"; + byte[] bytes = + ForwardingClassGenerator.makeConstructorForwarder( + className, typeElement.asType(), parameterTypeMirrors); + // Now load the class we just generated, and use reflection to call its forwarding method. + // That should give us an instance of the target class `c`, obtained by the call to its + // constructor from the forwarding method. + ClassLoader loader = + new ClassLoader() { + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (name.equals(className)) { + return defineClass(className, bytes, 0, bytes.length); + } + throw new ClassNotFoundException(name); + } + }; + Class forwardingClass = Class.forName(className, true, loader); + Method ofMethod = stream(forwardingClass.getDeclaredMethods()).findFirst().get(); + assertThat(ofMethod.getName()).isEqualTo("of"); + ofMethod.setAccessible(true); + Supplier> constructed = + c.cast(ofMethod.invoke(null, constructorParameters.toArray())); + ImmutableList retrievedParameters = constructed.get(); + assertThat(retrievedParameters).isEqualTo(constructorParameters); + } +} diff --git a/value/src/test/java/com/google/auto/value/processor/GeneratedDoesNotExistTest.java b/value/src/test/java/com/google/auto/value/processor/GeneratedDoesNotExistTest.java index f3d3d6110b..18cca5e410 100644 --- a/value/src/test/java/com/google/auto/value/processor/GeneratedDoesNotExistTest.java +++ b/value/src/test/java/com/google/auto/value/processor/GeneratedDoesNotExistTest.java @@ -48,6 +48,8 @@ */ @RunWith(Parameterized.class) public class GeneratedDoesNotExistTest { + private static final ImmutableList STANDARD_OPTIONS = + ImmutableList.of("-A" + Nullables.NULLABLE_OPTION + "="); @Parameters(name = "{0}") public static Collection data() { @@ -57,12 +59,16 @@ public static Collection data() { // TODO(b/72513371): use --release 8 once compile-testing supports that params.add( new Object[] { - ImmutableList.of(), "javax.annotation.processing.Generated", + STANDARD_OPTIONS, "javax.annotation.processing.Generated", }); } params.add( new Object[] { - ImmutableList.of("-source", "8", "-target", "8"), "javax.annotation.Generated", + ImmutableList.builder() + .addAll(STANDARD_OPTIONS) + .add("-source", "8", "-target", "8") + .build(), + "javax.annotation.Generated", }); return params.build(); } @@ -223,9 +229,9 @@ public void test() { Processor noGeneratedProcessor = partialProxy(Processor.class, handler); Compilation compilation = javac() - .withOptions(javacOptions) - .withProcessors(noGeneratedProcessor) - .compile(javaFileObject); + .withOptions(javacOptions) + .withProcessors(noGeneratedProcessor) + .compile(javaFileObject); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) .generatedSourceFile("foo.bar.AutoValue_Baz") diff --git a/value/src/test/java/com/google/auto/value/processor/IncrementalExtensionTest.java b/value/src/test/java/com/google/auto/value/processor/IncrementalExtensionTest.java index 4f526edbba..472f62db43 100644 --- a/value/src/test/java/com/google/auto/value/processor/IncrementalExtensionTest.java +++ b/value/src/test/java/com/google/auto/value/processor/IncrementalExtensionTest.java @@ -62,10 +62,12 @@ public void customExtensionsAreNotIsolatingByDefault() { AutoValueExtension nonIsolatingExtension = new NonIsolatingExtension(); assertThat(nonIsolatingExtension.incrementalType((ProcessingEnvironment) null)) .isEqualTo(IncrementalExtensionType.UNKNOWN); - ImmutableList extensions = ImmutableList.builder() - .addAll(AutoValueProcessor.extensionsFromLoader(AutoValueProcessor.class.getClassLoader())) - .add(nonIsolatingExtension) - .build(); + ImmutableList extensions = + ImmutableList.builder() + .addAll( + AutoValueProcessor.extensionsFromLoader(AutoValueProcessor.class.getClassLoader())) + .add(nonIsolatingExtension) + .build(); AutoValueProcessor processor = new AutoValueProcessor(extensions); assertThat(processor.getSupportedOptions()) @@ -77,10 +79,12 @@ public void customExtensionsCanBeIsolating() { AutoValueExtension isolatingExtension = new IsolatingExtension(); assertThat(isolatingExtension.incrementalType((ProcessingEnvironment) null)) .isEqualTo(IncrementalExtensionType.ISOLATING); - ImmutableList extensions = ImmutableList.builder() - .addAll(AutoValueProcessor.extensionsFromLoader(AutoValueProcessor.class.getClassLoader())) - .add(isolatingExtension) - .build(); + ImmutableList extensions = + ImmutableList.builder() + .addAll( + AutoValueProcessor.extensionsFromLoader(AutoValueProcessor.class.getClassLoader())) + .add(isolatingExtension) + .build(); AutoValueProcessor processor = new AutoValueProcessor(extensions); assertThat(processor.getSupportedOptions()) diff --git a/value/src/test/java/com/google/auto/value/processor/NullablesTest.java b/value/src/test/java/com/google/auto/value/processor/NullablesTest.java index 9e345f53f2..4d542625ab 100644 --- a/value/src/test/java/com/google/auto/value/processor/NullablesTest.java +++ b/value/src/test/java/com/google/auto/value/processor/NullablesTest.java @@ -148,7 +148,7 @@ public boolean process(Set annotations, RoundEnvironment expect .about(optionals()) - .that(Nullables.nullableMentionedInMethods(notNullableMethods)) + .that(Nullables.fromMethods(null, notNullableMethods).nullableTypeAnnotation()) .isEmpty(); TypeElement nullableElement = @@ -167,7 +167,8 @@ public boolean process(Set annotations, RoundEnvironment .withMessage("method %s should have @Nullable", nullableMethod) .about(optionals()) .that( - Nullables.nullableMentionedInMethods(notNullablePlusNullable) + Nullables.fromMethods(null, notNullablePlusNullable) + .nullableTypeAnnotation() .map(AnnotationMirror::getAnnotationType)) .hasValue(nullableType); } 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 2c3bea0d8b..1d7e89f50b 100644 --- a/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java +++ b/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java @@ -15,12 +15,13 @@ */ package com.google.auto.value.processor; -import static com.google.common.truth.Truth.assertAbout; -import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; +import static com.google.testing.compile.CompilationSubject.assertThat; +import static com.google.testing.compile.Compiler.javac; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; +import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; @@ -40,8 +41,7 @@ */ @RunWith(JUnit4.class) public class PropertyAnnotationsTest { - private static final String TEST_ANNOTATION = - "@PropertyAnnotationsTest.TestAnnotation"; + private static final String TEST_ANNOTATION = "@PropertyAnnotationsTest.TestAnnotation"; private static final String TEST_ARRAY_ANNOTATION = "@PropertyAnnotationsTest.TestArrayAnnotation"; @@ -272,12 +272,15 @@ private void assertGeneratedMatches( .addMethodAnnotations(expectedMethodAnnotations) .build(); - assertAbout(javaSource()) - .that(javaFileObject) - .processedWith(new AutoValueProcessor()) - .compilesWithoutError() - .and() - .generatesSources(expectedOutput); + Compilation compilation = + javac() + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") + .withProcessors(new AutoValueProcessor()) + .compile(javaFileObject); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("foo.bar.AutoValue_Baz") + .hasSourceEquivalentTo(expectedOutput); } @Test @@ -445,10 +448,12 @@ public void testEnumArrayAnnotation() { assertGeneratedMatches( getImports(PropertyAnnotationsTest.class), ImmutableList.of( - TEST_ARRAY_ANNOTATION + "(testEnums = {PropertyAnnotationsTest.TestEnum.A," + TEST_ARRAY_ANNOTATION + + "(testEnums = {PropertyAnnotationsTest.TestEnum.A," + " PropertyAnnotationsTest.TestEnum.B})"), ImmutableList.of( - TEST_ARRAY_ANNOTATION + "(testEnums = {PropertyAnnotationsTest.TestEnum.A," + TEST_ARRAY_ANNOTATION + + "(testEnums = {PropertyAnnotationsTest.TestEnum.A," + " PropertyAnnotationsTest.TestEnum.B})")); } @@ -505,19 +510,25 @@ public void testCopyingMethodAnnotations() { "@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(); - assertAbout(javaSource()) - .that(inputFile) - .processedWith(new AutoValueProcessor()) - .compilesWithoutError() - .and() - .generatesSources(outputFile); + Compilation compilation = + javac() + .withOptions("-A" + Nullables.NULLABLE_OPTION) + .withProcessors(new AutoValueProcessor()) + .compile(inputFile); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("foo.bar.AutoValue_Baz") + .hasSourceEquivalentTo(outputFile); } /** @@ -540,21 +551,26 @@ public void testCopyingMethodAnnotationsToGeneratedFields() { .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(); - assertAbout(javaSource()) - .that(inputFile) - .processedWith(new AutoValueProcessor()) - .compilesWithoutError() - .and() - .generatesSources(outputFile); + Compilation compilation = + javac() + .withOptions("-A" + Nullables.NULLABLE_OPTION + "=") + .withProcessors(new AutoValueProcessor()) + .compile(inputFile); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("foo.bar.AutoValue_Baz") + .hasSourceEquivalentTo(outputFile); } } diff --git a/value/src/test/java/com/google/auto/value/processor/PropertyNamesTest.java b/value/src/test/java/com/google/auto/value/processor/PropertyNamesTest.java index 7af240c6ac..d488e599de 100644 --- a/value/src/test/java/com/google/auto/value/processor/PropertyNamesTest.java +++ b/value/src/test/java/com/google/auto/value/processor/PropertyNamesTest.java @@ -34,14 +34,12 @@ public class PropertyNamesTest { .put("x", "x") .put("", "") .build(); - + @Test public void decapitalizeLikeJavaBeans() { - NORMAL_CASES - .forEach( - (input, output) -> { - expect.that(PropertyNames.decapitalizeLikeJavaBeans(input)).isEqualTo(output); - }); + NORMAL_CASES.forEach( + (input, output) -> + expect.that(PropertyNames.decapitalizeLikeJavaBeans(input)).isEqualTo(output)); expect.that(PropertyNames.decapitalizeLikeJavaBeans(null)).isNull(); expect.that(PropertyNames.decapitalizeLikeJavaBeans("HTMLPage")).isEqualTo("HTMLPage"); expect.that(PropertyNames.decapitalizeLikeJavaBeans("OAuth")).isEqualTo("OAuth"); @@ -49,11 +47,9 @@ public void decapitalizeLikeJavaBeans() { @Test public void decapitalizeNormally() { - NORMAL_CASES - .forEach( - (input, output) -> { - expect.that(PropertyNames.decapitalizeNormally(input)).isEqualTo(output); - }); + NORMAL_CASES.forEach( + (input, output) -> + expect.that(PropertyNames.decapitalizeNormally(input)).isEqualTo(output)); expect.that(PropertyNames.decapitalizeNormally(null)).isNull(); expect.that(PropertyNames.decapitalizeNormally("HTMLPage")).isEqualTo("hTMLPage"); expect.that(PropertyNames.decapitalizeNormally("OAuth")).isEqualTo("oAuth"); diff --git a/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java b/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java index 39e2dc0e99..7bc6779031 100644 --- a/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java +++ b/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java @@ -157,8 +157,7 @@ public boolean process(Set annotations, RoundEnvironment void testTypeSpellings(TypeElement testClass) { ExecutableElement witness = - ElementFilter.methodsIn(testClass.getEnclosedElements()) - .stream() + ElementFilter.methodsIn(testClass.getEnclosedElements()).stream() .filter(m -> m.getSimpleName().contentEquals("witness")) .collect(onlyElement()); if (witness.getReturnType().getAnnotationMirrors().isEmpty()) { diff --git a/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java b/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java index 095865e451..83951e0a58 100644 --- a/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java +++ b/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java @@ -306,6 +306,7 @@ public void testSimplifyMultipleBounds() { @SuppressWarnings("ClassCanBeStatic") static class Outer { class InnerWithoutTypeParam {} + class Middle { class InnerWithTypeParam {} } diff --git a/value/src/test/java/com/google/auto/value/processor/TypeVariablesTest.java b/value/src/test/java/com/google/auto/value/processor/TypeVariablesTest.java index 078ef513ff..895bed2535 100644 --- a/value/src/test/java/com/google/auto/value/processor/TypeVariablesTest.java +++ b/value/src/test/java/com/google/auto/value/processor/TypeVariablesTest.java @@ -115,10 +115,12 @@ public void hairyTypeParameters() { abstract static class Outer { abstract Map getFoo(); + abstract List getBar(); abstract static class Inner { abstract void setFoo(Map foo); + abstract void setBar(List bar); } } @@ -165,13 +167,15 @@ public void canAssignStaticMethodResult() { List immutableMapMethods = ElementFilter.methodsIn(immutableMap.getEnclosedElements()); ExecutableElement copyOf = methodNamed(immutableMapMethods, "copyOf", erasedMap); - expect.that( - TypeVariables.canAssignStaticMethodResult( - copyOf, immutableMapStringInteger, immutableMapStringNumber, typeUtils)) + expect + .that( + TypeVariables.canAssignStaticMethodResult( + copyOf, immutableMapStringInteger, immutableMapStringNumber, typeUtils)) .isTrue(); - expect.that( - TypeVariables.canAssignStaticMethodResult( - copyOf, immutableMapStringNumber, immutableMapStringInteger, typeUtils)) + expect + .that( + TypeVariables.canAssignStaticMethodResult( + copyOf, immutableMapStringNumber, immutableMapStringInteger, typeUtils)) .isFalse(); } @@ -184,7 +188,9 @@ private static ExecutableElement methodNamed( return methods.stream() .filter(m -> m.getSimpleName().contentEquals(name)) .filter(m -> m.getParameters().size() == 1) - .filter(m -> typeUtils.isSameType( + .filter( + m -> + typeUtils.isSameType( erasedParameterType, typeUtils.erasure(m.getParameters().get(0).asType()))) .findFirst() .get(); diff --git a/value/userguide/autobuilder.md b/value/userguide/autobuilder.md index ccd191ca9a..9de222f7a0 100644 --- a/value/userguide/autobuilder.md +++ b/value/userguide/autobuilder.md @@ -13,14 +13,11 @@ 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: -``` +```java @AutoBuilder(ofClass = Person.class) abstract class PersonBuilder { static PersonBuilder personBuilder() { @@ -35,13 +32,13 @@ abstract class PersonBuilder { It might be used like this: -``` +```java Person p = PersonBuilder.personBuilder().setName("Priz").setId(6).build(); ``` That would have the same effect as this: -``` +```java Person p = new Person("Priz", 6); ``` @@ -54,7 +51,7 @@ and likewise with `setId`. There is also a `build()` method. Calling that method invokes the `Person` constructor with the parameters that were previously set. -## Example: calling a Kotlin constructor +## Example: calling a Kotlin constructor Kotlin has named arguments and default arguments for constructors and functions, which means there is not much need for anything like AutoBuilder there. But if @@ -63,21 +60,22 @@ AutoBuilder can help. Given this trivial Kotlin data class: -``` -class KotlinData(val int: Int, val string: String?) +```kotlin +class KotlinData(val int: Int, val string: String?, val id: Long = -1L) ``` You might make a builder for it like this: -``` +```java @AutoBuilder(ofClass = KotlinData.class) public abstract class KotlinDataBuilder { public static KotlinDataBuilder kotlinDataBuilder() { return new AutoBuilder_KotlinDataBuilder(); } - public abstract setInt(int x); - public abstract setString(@Nullable String x); + public abstract KotlinDataBuilder setInt(int x); + public abstract KotlinDataBuilder setString(@Nullable String x); + public abstract KotlinDataBuilder setId(long x); public abstract KotlinData build(); } ``` @@ -86,6 +84,41 @@ The Kotlin type `String?` corresponds to `@Nullable String` in the AutoBuilder class, where `@Nullable` is any annotation with that name, such as `org.jetbrains.annotations.Nullable`. +The `id` parameter has a default value of `-1L`, which means that if `setId` is +not called then the `id` field of the built `KotlinData` will be `-1L`. + +If you are using [kapt](https://kotlinlang.org/docs/kapt.html) then you can also +define the builder in the data class itself: + +```kotlin +class KotlinData(val int: Int, val string: String?, val id: Long = -1L) { + @AutoBuilder // we don't need ofClass: by default it is the containing class + interface Builder { + fun setInt(x: Int): Builder + fun setString(x: String?): Builder + fun setId(x: Long): Builder + fun build(): KotlinData + } + + fun toBuilder(): Builder = AutoBuilder_KotlinData_Builder(this) + + companion object { + @JvmStatic fun builder(): Builder = AutoBuilder_KotlinData_Builder() + } +} +``` + +This example uses an interface rather than an abstract class for the builder, +but both are possible. Java code would then construct instances like this: + +```java +KotlinData k = KotlinData.builder().setInt(23).build(); +``` + +The example also implements a `toBuilder()` method to get a builder that starts +out with values from the given instance. See [below](#to_builder) for more +details on that. + ## The generated subclass Like `@AutoValue.Builder`, compiling an `@AutoBuilder` class will generate a @@ -97,7 +130,7 @@ will typically be the only reference to the generated class. If the `@AutoBuilder` type is nested then the name of the generated class reflects that nesting. For example: -``` +```java class Outer { static class Inner { @AutoBuilder @@ -126,7 +159,7 @@ it is nested then it must be static. ### Both `callMethod` and `ofClass` -``` +```java @AutoBuilder(callMethod = "of", ofClass = LocalTime.class) interface LocalTimeBuilder { ... @@ -136,7 +169,7 @@ interface LocalTimeBuilder { ### Only `ofClass` -``` +```java @AutoBuilder(ofClass = Thread.class) interface ThreadBuilder { ... @@ -146,7 +179,7 @@ interface ThreadBuilder { ### Only `callMethod` -``` +```java class Foo { static String concat(String first, String middle, String last) {...} @@ -163,7 +196,7 @@ Notice in this example that the static method returns `String`. The implicit ### Neither `callMethod` nor `ofClass` -``` +```java class Person { Person(String name, int id) {...} @@ -188,7 +221,7 @@ the return type just described and that does not correspond to a parameter name. The following example uses the name `call()` since that more accurately reflects what it does: -``` +```java public class LogUtil { public static void log(Level severity, String message, Object... params) {...} @@ -201,6 +234,44 @@ public class LogUtil { } ``` +## Making a builder from a built instance + +It is not always possible to map back from the result of a constructor or method +call to a builder that might have produced it. But in one important case, it +*is* possible. That's when every parameter in the constructor or method +corresponds to a "getter method" in the built type. This will always be true +when building a Java record or a Kotlin data class (provided its getters are +visible to the builder). In this case, the generated builder class will have a +second constructor that takes an object of the built type as a parameter and +produces a builder that starts out with values from that object. That can then +be used to produce a new object that may differ from the first one in just one +or two properties. (This is very similar to AutoValue's +[`toBuilder()`](builders-howto.md#to_builder) feature.) + +If the constructor or method has a parameter `String bar` then the built type +must have a visible method `String bar()` or `String getBar()`. (Java records +have the first and Kotlin data classes have the second.) If there is a +similar corresponding method for every parameter then the second constructor is +generated. + +If you are able to change the built type, the most convenient way to use this is +to add a `toBuilder()` instance method that calls `new AutoBuilder_Foo(this)`. +We saw this in the [Kotlin example](#kotlin) earlier. Otherwise, you can have +a second static `builder` method, like this: + +```java +@AutoBuilder(ofClass = Person.class) +abstract class PersonBuilder { + static PersonBuilder personBuilder() { + return new AutoBuilder_PersonBuilder(); + } + static PersonBuilder personBuilder(Person person) { + return new AutoBuilder_PersonBuilder(person); + } + ... +} +``` + ## Overloaded constructors or methods There might be more than one constructor or static method that matches the @@ -215,7 +286,7 @@ one such method or constructor. If the builder calls the constructor of a generic type, then it must have the same type parameters as that type, as in this example: -``` +```java class NumberPair { NumberPair(T first, T second) {...} @@ -231,7 +302,7 @@ class NumberPair { If the builder calls a static method with type parameters, then it must have the same type parameters, as in this example: -``` +```java class Utils { static Map singletonNumberMap(K key, V value) {...} @@ -249,7 +320,7 @@ separately from any that its containing class might have. A builder that calls a constructor like that must have the type parameters of the class followed by the type parameters of the constructor: -``` +```java class CheckedSet implements Set { CheckedSet(Class type) {...} @@ -265,13 +336,14 @@ class CheckedSet implements Set { Parameters that are annotated `@Nullable` are null by default. Parameters of type `Optional`, `OptionalInt`, `OptionalLong`, and `OptionalDouble` are empty +by default. Kotlin constructor parameters with default values get those values by default. Every other parameter is _required_, meaning that the build method will throw `IllegalStateException` if any are omitted. To establish default values for parameters, set them in the `builder()` method before returning the builder. -``` +```java class Foo { Foo(String bar, @Nullable String baz, String buh) {...} @@ -315,7 +387,7 @@ exception in the first case or return an empty `Optional` in the second. In this example, the `nickname` parameter defaults to the same value as the `name` parameter but can also be set to a different value: -``` +```java public class Named { Named(String name, String nickname) {...} @@ -345,6 +417,13 @@ The builder in the example is an abstract class rather than an interface. An abstract class allows us to distinguish between public methods for users of the builder to call, and package-private methods that the builder's own logic uses. +## Building annotation instances + +AutoBuilder can build instances of annotation interfaces. When the annotation +has no elements (methods in the annotation), or only one, then AutoAnnotation is +simpler to use. But when there are several elements, a builder is helpful. See +[here](howto.md#annotation) for examples of both. + ## Naming conventions A setter method for the parameter `foo` can be called either `setFoo` or `foo`. @@ -363,7 +442,7 @@ If class `Foo` has a nested `@AutoBuilder` that builds instances of `Foo`, then conventionally that type is called `Builder`, and instances of it are obtained by calling a static `Foo.builder()` method: -``` +```java Foo foo1 = Foo.builder().setBar(bar).setBaz(baz).build(); Foo.Builder fooBuilder = Foo.builder(); ``` @@ -373,7 +452,7 @@ typically be called `FooBuilder` and it will have a static `fooBuilder()` method that returns an instance of `FooBuilder`. That way callers can statically import `FooBuilder.fooBuilder` and just write `fooBuilder()` in their code. -``` +```java @AutoBuilder(ofClass = Foo.class) public abstract class FooBuilder { public static FooBuilder fooBuilder() { @@ -388,7 +467,7 @@ If an `@AutoBuilder` is designed to call a static method that is not a factory method, the word "call" is better than "build" in the name of the type (`FooCaller`), the static method (`fooCaller()`), and the "build" method (`call()`). -``` +```java @AutoBuilder(callMethod = "log", ofClass = MyLogger.class) public abstract class LogCaller { public static LogCaller logCaller() { @@ -410,11 +489,6 @@ because they are the same as for `@AutoValue.Builder`. They include: * [Special treatment of collections](builders-howto.md#collection) * [Handling of nested builders](builders-howto.md#nested_builders) -There is currently no equivalent of AutoValue's -[`toBuilder()`](builders-howto.md#to_builder). Unlike AutoValue, there is not -generally a mapping back from the result of the constructor or method to its -parameters. - ## When parameter names are unavailable AutoBuilder depends on knowing the names of parameters. But parameter names are @@ -439,7 +513,7 @@ are available. Here's an example of fixing a problem this way. The code here typically will not compile, since parameter names of JDK methods are not available: -``` +```java import java.time.LocalTime; public class TimeUtils { @@ -470,11 +544,11 @@ have the real names. Introducing a static method fixes the problem: -``` +```java import java.time.LocalTime; public class TimeUtils { - static LocalTime localTimeOf(int hour, int second, int second) { + static LocalTime localTimeOf(int hour, int minute, int second) { return LocalTime.of(hour, minute, second); } diff --git a/value/userguide/builders-howto.md b/value/userguide/builders-howto.md index 7f8fdd4f70..f1f948092b 100644 --- a/value/userguide/builders-howto.md +++ b/value/userguide/builders-howto.md @@ -34,6 +34,8 @@ How do I... * ... [access nested builders while building?](#nested_builders) * ... [create a "step builder"?](#step) * ... [create a builder for something other than an `@AutoValue`?](#autobuilder) +* ... [use a different build method for a + property?](#build_method) ## ... use (or not use) `set` prefixes? @@ -154,7 +156,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 +203,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 +237,7 @@ public abstract class Animal { abstract Animal autoBuild(); // not public - public Animal build() { + public final Animal build() { setName(name().toLowerCase()); return autoBuild(); } @@ -251,13 +253,13 @@ non-[nullable](howto.md#nullable) property, `IllegalStateException` is thrown. Getters should generally only be used within the `Builder` as shown, so they are not public. -As an alternative to returning the same type as the property accessor method, -the builder getter can return an Optional wrapping of that type. This can be -used if you want to supply a default, but only if the property has not been set. -(The [usual way](#default) of supplying defaults means that the property always -appears to have been set.) For example, suppose you wanted the default name of -your Animal to be something like "4-legged creature", where 4 is the -`numberOfLegs()` property. You might write this: +

As an alternative to returning the same type as the +property accessor method, the builder getter can return an Optional wrapping of +that type. This can be used if you want to supply a default, but only if the +property has not been set. (The [usual way](#default) of supplying defaults +means that the property always appears to have been set.) For example, suppose +you wanted the default name of your Animal to be something like "4-legged +creature", where 4 is the `numberOfLegs()` property. You might write this: ```java @AutoValue @@ -279,8 +281,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(); @@ -311,7 +313,8 @@ property of type `Optional`, say, then it will default to an empty `Optional` without needing to [specify](#default) a default explicitly. And, instead of or as well as the normal `setFoo(Optional)` method, you can have `setFoo(String)`. Then `setFoo(s)` is equivalent to -`setFoo(Optional.of(s))`. +`setFoo(Optional.of(s))`. (If it is `setFoo(@Nullable String)`, then `setFoo(s)` +is equivalent to `setFoo(Optional.ofNullable(s))`.) Here, `Optional` means either [`java.util.Optional`] from Java (Java 8 or later), or [`com.google.common.base.Optional`] from Guava. Java 8 also @@ -490,7 +493,7 @@ public abstract class Animal { public abstract Builder setNumberOfLegs(int value); abstract ImmutableSet.Builder countriesBuilder(); - public Builder addCountry(String value) { + public final Builder addCountry(String value) { countriesBuilder().add(value); return this; } @@ -622,11 +625,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(); + } + } +``` ## ... create a builder for something other than an `@AutoValue`? @@ -635,4 +702,65 @@ build something other than an `@AutoValue` class, or even call a static method. In that case you can use `@AutoBuilder`. See [its documentation](autobuilder.md). +Sometimes you want to use a different build method for your property. This is +especially applicable for `ImmutableMap`, which has two different build methods. +`builder.buildOrThrow()` is used as the default build method for AutoValue. You +might prefer to use `builder.buildKeepingLast()` instead, so if the same key is +put more than once then the last value is retained rather than throwing an +exception. AutoValue doesn't currently have a way to request this, but here is a +workaround if you need it. Let's say you have a class like this: + +```java + @AutoValue + public abstract class Foo { + public abstract ImmutableMap map(); + ... + + @AutoValue.Builder + public abstract static class Builder { + public abstract ImmutableMap.Builder mapBuilder(); + public abstract Foo build(); + } + } +``` + +Instead, you could write this: + +```java + @AutoValue + public abstract class Foo { + public abstract ImmutableMap map(); + + // #start + // Needed only if your class has toBuilder() method + public Builder toBuilder() { + Builder builder = autoToBuilder(); + builder.mapBuilder().putAll(map()); + return builder; + } + + abstract Builder autoToBuilder(); // not public + // #end + + @AutoValue.Builder + public abstract static class Builder { + + private final ImmutableMap.Builder mapBuilder = ImmutableMap.builder(); + + public ImmutableMap.Builder mapBuilder() { + return mapBuilder; + } + + abstract Builder setMap(ImmutableMap map); // not public + + abstract Foo autoBuild(); // not public + + public Foo build() { + setMap(mapBuilder.buildKeepingLast()); + return autoBuild(); + } + } + } +``` + [protobuf]: https://developers.google.com/protocol-buffers/docs/reference/java-generated#builders diff --git a/value/userguide/builders.md b/value/userguide/builders.md index 1de6bfa40f..15c99449b4 100644 --- a/value/userguide/builders.md +++ b/value/userguide/builders.md @@ -104,3 +104,5 @@ exposing yourself to initialization-order problems. * ... [create a "step builder"?](builders-howto.md#step) * ... [create a builder for something other than an `@AutoValue`?](builders-howto.md#autobuilder) +* ... [use a different build method for a + property?](builders-howto.md#build_method) diff --git a/value/userguide/howto.md b/value/userguide/howto.md index de5649df18..6067181759 100644 --- a/value/userguide/howto.md +++ b/value/userguide/howto.md @@ -315,6 +315,58 @@ public class Names { } ``` +In Java the method will usually be static. In Kotlin, which doesn't have static +methods as such, a normal function can be used: + +```kotlin +public class Names { + @AutoAnnotation public fun named(value: String): Named { + return AutoAnnotation_Names_named(value); + } +} +``` + +Kotlin also allows you to instantiate annotations directly, so you may not need +AutoAnnotation: + +```kotlin +public class Names { + public fun named(value: String): Named { + return Named(value = value) + } +} +``` + +If your annotation has several elements, you may prefer to use `@AutoBuilder`: + +```java +public @interface Named { + String value(); + int priority() default 0; + int size() default 0; +} + +public class Names { + @AutoBuilder(ofClass = Named.class) + public interface NamedBuilder { + NamedBuilder value(String x); + NamedBuilder priority(int x); + NamedBuilder size(int x); + Named build(); + } + + public static NamedBuilder namedBuilder() { + return new AutoBuilder_Names_namedBuilder(); + } + + ... + Named named1 = namedBuilder().value("O'Cruiskeen").priority(17).size(23).build(); + Named named2 = namedBuilder().value("O'Cruiskeen").build(); + // priority and size get their default values + ... +} +``` + For more details, see the [`AutoAnnotation` javadoc](http://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/AutoAnnotation.java#L24). @@ -608,7 +660,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: @@ -708,10 +760,10 @@ Song { We're far from the shallow now., artists = [ Artist { - name = "Lady Gaga", + name = Lady Gaga, }, Artist { - name = "Bradley Cooper", + name = Bradley Cooper, } ], } diff --git a/value/userguide/index.md b/value/userguide/index.md index d6ef95c7ef..1a86aee05a 100644 --- a/value/userguide/index.md +++ b/value/userguide/index.md @@ -31,6 +31,16 @@ AutoValue provides an easier way to create immutable value classes, with a lot less code and less room for error, while **not restricting your freedom** to code almost any aspect of your class exactly the way you want it. +**Note**: If you are using Kotlin then its +[data classes](https://kotlinlang.org/docs/data-classes.html) are usually more +appropriate than AutoValue. Likewise, if you are using a version of Java that +has [records](https://docs.oracle.com/en/java/javase/17/language/records.html), +then those are usually more appropriate. For a detailed comparison of AutoValue +and records, including information on how to migrate from one to the other, see +[here](records.md).
+You can still use [AutoBuilder](autobuilder.md) to make builders for data +classes or records. + This page will walk you through how to use AutoValue. Looking for a little more persuasion? Please see [Why AutoValue?](why.md). @@ -72,7 +82,6 @@ Note that in real life, some classes and methods would presumably be public and have Javadoc. We're leaving these off in the User Guide only to keep the examples short and simple. - ### With Maven You will need `auto-value-annotations-${auto-value.version}.jar` in your @@ -149,14 +158,15 @@ Gradle users can declare the dependencies in their `build.gradle` script: ```groovy dependencies { - compileOnlyApi "com.google.auto.value:auto-value-annotations:${autoValueVersion}" + compileOnly "com.google.auto.value:auto-value-annotations:${autoValueVersion}" annotationProcessor "com.google.auto.value:auto-value:${autoValueVersion}" } ``` -Note: If you are using a version of Gradle prior to 6.7, use `compile` or (for -Android or java-library projects) `api` instead of `compileOnlyApi`. If you are -using a version prior to 4.6, you must apply an annotation processing plugin +Note: For java-library projects, use `compileOnlyApi` (or `api` for Gradle +versions prior to 6.7) instead of `compileOnly`. For Android projects, use `api` +instead of `compileOnly`. If you are using a version prior to 4.6, you must +apply an annotation processing plugin [as described in these instructions][tbroyer-apt]. [tbroyer-apt]: https://plugins.gradle.org/plugin/net.ltgt.apt @@ -188,8 +198,8 @@ public void testAnimal() { AutoValue runs inside `javac` as a standard annotation processor. It reads your abstract class and infers what the implementation class should look like. It -generates source code, in your package, of a concrete implementation class -which extends your abstract class, having: +generates source code, in your package, of a concrete implementation class which +extends your abstract class, having: * package visibility (non-public) * one field for each of your abstract accessor methods @@ -277,5 +287,4 @@ How do I... class/method/field?](howto.md#copy_annotations) * ... [create a **pretty string** representation?](howto.md#toprettystring) - diff --git a/value/userguide/practices.md b/value/userguide/practices.md index e78c6e5a70..5c9232d758 100644 --- a/value/userguide/practices.md +++ b/value/userguide/practices.md @@ -3,9 +3,11 @@ ## "Equals means interchangeable" -Don't use AutoValue to implement value semantics unless you really want value -semantics. In particular, you should never care about the difference between two -equal instances. +Use AutoValue when you want value semantics. Under value semantics, if `a` and +`b` are instances of the same AutoValue class, and `a.equals(b)`, then `a` and +`b` are considered interchangeable, and `a` can be used in place of `b` +everywhere and vice versa. If your AutoValue use case does not satisfy these +contracts, then AutoValue may not be a good fit. ## Avoid mutable property types diff --git a/value/userguide/records.md b/value/userguide/records.md new file mode 100644 index 0000000000..3635b707ee --- /dev/null +++ b/value/userguide/records.md @@ -0,0 +1,544 @@ +# AutoValue and Java Records + + +Starting with Java 16, +[records](https://docs.oracle.com/en/java/javase/19/language/records.html) are a +standard feature of the language. If records are available to you, is there any +reason to use AutoValue? + +## The short answer + +Generally, **use records** when you can. They have a very concise and readable +syntax, they produce less code, and they don't need any special configuration. +They are obviously a better choice when your class is just an aggregation of +values, for example to allow a method to return multiple values or to combine +values into a map key. + +(This was by design: the AutoValue authors were part of the +[Project Amber](https://openjdk.org/projects/amber/) working group, where our +goal was to make the records feature the best AutoValue replacement it could +be.) + +If you have existing code that has AutoValue classes, you might want to migrate +some or all of those classes to be records instead. In this document we will +explain how to do this, and in what cases you might prefer not to. + +## Reasons to stick with AutoValue + +While records are usually better, there are some AutoValue features that have no +simple equivalent with records. So you might prefer not to try migrating +AutoValue classes that use those features, and you might even sometimes make new +AutoValue classes even if records are available to you. + +### Extensions + +AutoValue has [extensions](extensions.md). Some are built in, like the +[`@Memoized`](https://javadoc.io/static/com.google.auto.value/auto-value-annotations/1.10/com/google/auto/value/extension/memoized/Memoized.html), +[`@ToPrettyString`](https://javadoc.io/static/com.google.auto.value/auto-value-annotations/1.10/com/google/auto/value/extension/toprettystring/ToPrettyString.html), +and +[`@SerializableAutoValue`](https://javadoc.io/static/com.google.auto.value/auto-value-annotations/1.10/com/google/auto/value/extension/serializable/SerializableAutoValue.html) +extensions. Most extensions will have no real equivalent with records. + +### Keeping the static factory method + +AutoValue has very few API-visible "quirks", but one is that it forces you to +use a static factory method as your class's creation API. A record can have this +too, but it can't prevent its constructor from *also* being visible, and +exposing two ways to do the same thing can be dangerous. + +We think most users will be happy to switch to constructors and drop the factory +methods, but you might want to keep it. Perhaps for compatibility reasons, or +because you are normalizing input data to different types, such as from `List` +to `ImmutableList`. + +In this event, you can still *discourage* callers by marking it deprecated. More +on this [below](#deprecating). + +Clever ways do exist to make calling the constructor impossible, but it's +probably simpler to keep using AutoValue. + +### Superclass + +The superclass of a record is always `java.lang.Record`. Occasionally the +superclass of an AutoValue class is something other than `Object`, for example +when two AutoValue classes share a subset of their properties. + +You might still be able to convert to records if you can convert these classes +into interfaces. + +### Derived properties + +Records can't have instance fields (other than their properties). So it is hard +to cache a derived property, for example. AutoValue makes this trivial with +[`@Memoized`](https://javadoc.io/static/com.google.auto.value/auto-value-annotations/1.10/com/google/auto/value/extension/memoized/Memoized.html). + +We suggest ways to achieve the same effect with records [below](#derived), but +it might be simpler to stick with AutoValue. + +### Primitive array properties + +AutoValue allows properties of primitive array types such as `byte[]` or `int[]` +and it will implement `equals` and `hashCode` using the methods of +`java.util.Arrays`. Records do not have any special treatment for primitive +arrays, so by default they will use the `equals` and `hashCode` methods of the +arrays. So two distinct arrays will never compare equal even if they have the +same contents. + +The best way to avoid this problem is not to have properties with primitive +array type, perhaps using alternatives such as +[`ImmutableIntArray`](http://guava.dev/ImmutableIntArray). Alternatively you can +define custom implementations of `equals` and `hashCode` as described in the +[section](#eqhc) on that topic. But again, you might prefer to keep using +AutoValue. + +(AutoValue doesn't allow properties of non-primitive array types.) + +## Translating an AutoValue class into a record + +Suppose you have existing AutoValue classes that you do want to translate into +records, and the [above reasons](#whynot) not to don't apply. What does the +translation look like? + +One important difference is that AutoValue does not allow properties to be +`null` unless they are marked `@Nullable`. Records require explicit null checks +to achieve the same effect, typically with +[`Objects.requireNonNull`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Objects.html#requireNonNull\(T\)). + +This might also be a good time to start using a nullness-analysis tool on your +code; see [NullAway](https://github.com/uber/NullAway) for example. + +The examples below show some before-and-after for various migration scenarios. +For brevity, we've mostly omitted the javadoc comments that good code should +have on its public classes and methods. + +### Basic example with only primitive properties + +Before: + +```java +@AutoValue +public abstract class Point { + public abstract int x(); + public abstract int y(); + + public static Point of(int x, int y) { + return new AutoValue_Point(x, y); + } +} +``` + +After: + +```java +public record Point(int x, int y) { + /** @deprecated Call the constructor directly. */ + @Deprecated + public static Point of(int x, int y) { + return new Point(x, y); + } +} +``` + +The static factory method `of` is retained so clients of the `Point` class don't +have to be updated. If possible, you should migrate clients to call `new +Point(...)` instead. Then the record can be as simple as this: + +```java +public record Point(int x, int y) {} +``` + +We've omitted the static factory methods from the other examples, but the +general approach applies: keep the method initially but deprecate it and change +its body so it just calls the constructor; migrate the callers so they call the +constructor directly; delete the method. You might be able to use the +[`InlineMe`](https://errorprone.info/docs/inlineme) mechanism from the Error +Prone project to encourage this migration: + +```java +package com.example.geometry; + +public record Point(int x, int y) { + /** @deprecated Call the constructor directly. */ + @Deprecated + @InlineMe(replacement = "new Point(x, y)", imports = "com.example.geometry.Point") + public static Point of(int x, int y) { + return new Point(x, y); + } +} +``` + +### Non-primitive properties that are not `@Nullable` + +Before: + +```java +@AutoValue +public abstract class Person { + public abstract String name(); + public abstract int id(); + + public static Person create(String name, int id) { + return new AutoValue_Person(name, id); + } +} +``` + +After: + +```java +public record Person(String name, int id) { + public Person { + Objects.requireNonNull(name, "name"); + } +} +``` + +### Non-primitive properties that are all `@Nullable` + +Before: + +```java +@AutoValue +public abstract class Person { + public abstract @Nullable String name(); + public abstract int id(); + + public static Person create(@Nullable String name, int id) { + return new AutoValue_Person(name, id); + } +} +``` + +After: + +```java +public record Person(@Nullable String name, int id) {} +``` + +### Validation + +Before: + +```java +@AutoValue +public abstract class Person { + public abstract String name(); + public abstract int id(); + + public static Person create(String name, int id) { + if (id <= 0) { + throw new IllegalArgumentException("Id must be positive: " + id); + } + return new AutoValue_Person(name, id); + } +} +``` + +After: + +```java +public record Person(String name, int id) { + public Person { + Objects.requireNonNull(name, "name"); + if (id <= 0) { + throw new IllegalArgumentException("Id must be positive: " + id); + } + } +} +``` + +### Normalization + +With records, you can rewrite the constructor parameters to apply normalization +or canonicalization rules. + +In this example we have two `int` values, but we don't care which order they are +supplied in. Therefore we have to put them in a standard order, or else `equals` +won't behave as expected. + +Before: + +```java +@AutoValue +public abstract class UnorderedPair { + public abstract int left(); + public abstract int right(); + + public static UnorderedPair of(int left, int right) { + int min = Math.min(left, right); + int max = Math.max(left, right); + return new AutoValue_UnorderedPair(min, max); + } +} +``` + +After: + +```java +public record UnorderedPair(int left, int right) { + public UnorderedPair { + int min = Math.min(left, right); + int max = Math.max(left, right); + left = min; + right = max; + } +} +``` + +If your normalization results in different types (or more or fewer separate +fields) than the parameters, you will need to keep the static factory method. +On a more subtle note, the user of this record might be surprised that what they +passed in as `left` doesn't always come out as `left()`; keeping the static +factory method would also allow the parameters to be named differently. See the +section on the [static factory](#staticfactory) method. + +### JavaBeans prefixes (`getFoo()`) + +AutoValue allows you to prefix every property getter with `get`, but records +don't have any special treatment here. Imagine you have a class like this: + +```java +@AutoValue +public abstract class Person { + public abstract String getName(); + public abstract int getId(); + + public static Person create(String name, int id) { + return new AutoValue_Person(name, id); + } +} +``` + +The names of the fields in `Person`, and the names in its `toString()`, don't +have the `get` prefix: + +``` +jshell> Person.create("Priz", 6) +$1 ==> Person{name=Priz, id=6} +jshell> $1.getName() +$2 ==> Priz +jshell> List showFields(Class c) { + ...> return Arrays.stream(c.getDeclaredFields()).map(Field::getName).toList(); + ...> } +jshell> showFields($1.getClass()) +$3 ==> [name, id] +``` + +You can translate this directly to a record if you don't mind a slightly strange +`toString()`, and strange field names from reflection and debuggers: + +```java +public record Person(String getName, int getId) { + public Person { + Objects.requireNonNull(getName); + } +} +``` + +``` +jshell> Person.create("Priz", 6) +$1 ==> Person[getName=Priz, getId=6] +jshell> $1.getName() +$2 ==> Priz +jshell> showFields($1.getClass()) +$3 ==> [getName, getId] +``` + +Alternatively, you can alias `Person.getName()` to be `Person.name()`, etc.: + +```java +public record Person(String name, int id) { + public Person { + Objects.requireNonNull(name); + } + + public String getName() { + return name(); + } + + public int getId() { + return id(); + } +} +``` + +So both `Person.getName()` and `Person.name()` are allowed. You might want to +deprecate the `get-` methods so you can eventually remove them. + +### Caching derived properties + +A record has an instance field for each of its properties, but cannot have other +instance fields. That means in particular that it is not easy to cache derived +properties, as you can with AutoValue and [`@Memoized`](howto.md#memoize). + +Records *can* have static fields, so one way to cache derived properties is to +map from record instances to their derived properties. + +Before: + +```java +@AutoValue +public abstract class Person { + public abstract String name(); + public abstract int id(); + + @Memoized + public UUID derivedProperty() { + return expensiveFunction(this); + } + + public static Person create(String name, int id) { + return new AutoValue_Person(name, id); + } +} +``` + +After: + +```java +public record Person(String name, int id) { + public Person { + Objects.requireNonNull(name); + } + + private static final Map derivedPropertyCache = new WeakHashMap<>(); + + public UUID derivedProperty() { + synchronized (derivedPropertyCache) { + return derivedPropertyCache.computeIfAbsent(this, person -> expensiveFunction(person))); + } + } +} +``` + +It's very important to use **`WeakHashMap`** (or similar) or you might suffer a +memory leak. As usual with `WeakHashMap`, you have to be sure that the values in +the map don't reference the keys. For more caching options, consider using +[Caffeine](https://github.com/ben-manes/caffeine). + +You might decide that AutoValue with `@Memoized` is simpler than records for +this case, though. + +### Builders + +Builders are still available when using records. Instead of +`@AutoValue.Builder`, you use [`@AutoBuilder`](autobuilder.md). + +Before: + +```java +@AutoValue +public abstract class Person { + public abstract String name(); + public abstract int id(); + + public static Builder builder() { + return new AutoValue_Person.Builder(); + } + + @AutoValue.Builder + public interface Builder { + Builder name(String name); + Builder id(int id); + Person build(); + } +} + +Person p = Person.builder().name("Priz").id(6).build(); +``` + +After: + +```java +public record Person(String name, int id) { + public static Builder builder() { + return new AutoBuilder_Person(); + } + + @AutoBuilder + public interface Builder { + Builder name(String name); + Builder id(int id); + Person build(); + } +} + +Person p = Person.builder().name("Priz").id(6).build(); +``` + +#### Deprecating the constructor + +As mentioned [above](#staticfactory), the primary constructor is always visible. +In the preceding example, the builder will enforce that the `name` property is +not null (since it is not marked @Nullable), but someone calling the constructor +will bypass that check. You could deprecate the constructor to discourage this: + +```java +public record Person(String name, int id) { + /** @deprecated Obtain instances using the {@link #builder()} instead. */ + @Deprecated + public Person {} + + public static Builder builder() { + return new AutoBuilder_Person(); + } + + @AutoBuilder + public interface Builder { + Builder name(String name); + Builder id(int id); + Person build(); + } +} +``` + +### Custom `toString()` + +A record can define its own `toString()` in exactly the same way as an AutoValue +class. + +### Custom `equals` and `hashCode` + +As with AutoValue, it's unusual to want to change the default implementations of +these methods, and if you do you run the risk of making subtle mistakes. Anyway, +the idea is the same with both AutoValue and records. + +Before: + +```java +@AutoValue +public abstract class Person { + ... + + @Override public boolean equals(Object o) { + return o instanceof Person that + && Ascii.equalsIgnoreCase(this.name(), that.name()) + && this.id() == that.id(); + } + + @Override public int hashCode() { + return Objects.hash(Ascii.toLowerCase(name()), id()); + } +} +``` + +After: + +```java +public record Person(String name, int id) { + ... + + @Override public boolean equals(Object o) { + return o instanceof Person that + && Ascii.equalsIgnoreCase(this.name, that.name) + && this.id == that.id; + } + + @Override public int hashCode() { + return Objects.hash(Ascii.toLowerCase(name), id); + } +} +``` + +With records, the methods can access fields directly or use the corresponding +methods (`this.name` or `this.name()`). diff --git a/value/userguide/why.md b/value/userguide/why.md index 40c994f65c..4ebb31f5bd 100644 --- a/value/userguide/why.md +++ b/value/userguide/why.md @@ -1,8 +1,10 @@ # Why use AutoValue? -AutoValue is the only solution to the value class problem in Java having all of -the following characteristics: +In versions of Java preceding +[records](https://docs.oracle.com/en/java/javase/16/language/records.html), +AutoValue is the only solution to the value class problem having all of the +following characteristics: * **API-invisible** (callers cannot become dependent on your choice to use it) * No runtime dependencies