--- /dev/null 2019-05-31 16:58:41.056000000 -0700 +++ new/test/langtools/tools/javac/processing/model/element/AnnotationToStringTest.java 2019-06-02 13:31:02.742012000 -0700 @@ -0,0 +1,410 @@ +/* + * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8164819 + * @summary Test of toString on normal annotations + * @library /tools/javac/lib + * @build JavacTestingAbstractProcessor AnnotationToStringTest + * @compile -processor AnnotationToStringTest -proc:only AnnotationToStringTest.java + */ + +// See also the sibling core reflection test +// test/jdk/java/lang/annotation/AnnotationToStringTest.java + +import java.lang.annotation.*; +import java.lang.reflect.*; +import java.util.*; +import javax.annotation.processing.*; +import javax.lang.model.AnnotatedConstruct; +import javax.lang.model.element.*; +import javax.lang.model.util.*; + +/** + * The expected string values are stored in @ExpectedString + * annotations. The essence of the test is comparing the toString() + * result of annotations to the corresponding ExpectedString.value(). + * + * Two flavors of comparison are made: + * + * 1) Against the AnnotationMirror value from getAnnotationMirrors() + * + * 2) Against the *Annotation* from getAnnotation(Class) + * + * These have separate but related implementations. + */ +public class AnnotationToStringTest extends JavacTestingAbstractProcessor { + public boolean process(Set annotations, + RoundEnvironment roundEnv) { + if (!roundEnv.processingOver()) { + + int failures = 0; + + TypeElement primHostElt = + Objects.requireNonNull(elements.getTypeElement("AnnotationToStringTest.PrimHost")); + + List annotMirrors = primHostElt.getAnnotationMirrors(); + + String expectedString = primHostElt.getAnnotation(MostlyPrimitive.class).toString(); + + failures += check(expectedString, + primHostElt.getAnnotation(ExpectedString.class).value()); + + failures += check(expectedString, + retrieveAnnotationMirrorAsString(primHostElt, + "MostlyPrimitive")); + failures += classyTest(); + failures += arrayAnnotationTest(); + + if (failures > 0) + throw new RuntimeException(failures + " failures"); + } + return true; + } + + /** + * Examine annotation mirrors, find the one that matches + * annotationName, and return its toString value. + */ + private String retrieveAnnotationMirrorAsString(AnnotatedConstruct annotated, + String annotationName) { + return retrieveAnnotationMirror(annotated, annotationName).toString(); + } + + private String retrieveAnnotationMirrorValue(AnnotatedConstruct annotated, + String annotationName) { + AnnotationMirror annotationMirror = + retrieveAnnotationMirror(annotated, annotationName); + for (var entry : annotationMirror.getElementValues().entrySet()) { + if (entry.getKey().getSimpleName().contentEquals("value")) { + return entry.getValue().toString(); + } + } + throw new RuntimeException("Annotation value() method not found: " + + annotationMirror.toString()); + } + + private AnnotationMirror retrieveAnnotationMirror(AnnotatedConstruct annotated, + String annotationName) { + for (AnnotationMirror annotationMirror : annotated.getAnnotationMirrors()) { + System.out.println(annotationMirror.getAnnotationType()); + if (annotationMirror + .getAnnotationType() + .toString() + .equals(annotationName) ) { + return annotationMirror; + } + } + throw new RuntimeException("Annotation " + annotationName + " not found."); + } + + private static int check(String expected, String actual) { + if (!expected.equals(actual)) { + System.err.printf("ERROR: Expected ''%s'';%ngot ''%s''.\n", + expected, actual); + return 1; + } else + return 0; + } + + @ExpectedString( + "@MostlyPrimitive(c0='a', "+ + "c1='\\'', " + + "i0=1, " + + "i1=2, " + + "f0=1.0f, " + + "f1=0.0f/0.0f, " + + "d0=0.0, " + + "d1=1.0/0.0, " + + "l0=5L, " + + "l1=9223372036854775807L, " + + "l2=-9223372036854775808L, " + + "l3=-2147483648L, " + + "s0=\"Hello world.\", " + + "s1=\"a\\\"b\", " + + "class0=Obj[].class, " + + "classArray={Obj[].class})") + @MostlyPrimitive( + c0='a', + c1='\'', + i0=1, + i1=2, + f0=1.0f, + f1=Float.NaN, + d0=0.0, + d1=2.0/0.0, + l0=5, + l1=Long.MAX_VALUE, + l2=Long.MIN_VALUE, + l3=Integer.MIN_VALUE, + s0="Hello world.", + s1="a\"b", + class0=Obj[].class, + classArray={Obj[].class} + ) + static class PrimHost{} + + private int classyTest() { + int failures = 0; + + TypeElement annotationHostElt = + Objects.requireNonNull(elements.getTypeElement("AnnotationToStringTest.AnnotationHost")); + + for (VariableElement f : ElementFilter.fieldsIn(annotationHostElt.getEnclosedElements())) { + String expected = f.getAnnotation(ExpectedString.class).value(); + Annotation a = f.getAnnotation(Classy.class); + + System.out.println(a); + failures += check(expected, a.toString()); + + // The toString of an AnnotationMirror does a better job + // of eliding "value=" when not needed then the toString + // from an Annotation. Strip "value=" from the expected + // string to allow the comparison to proceed successfully. + failures += check(expected.replace("value=", ""), + retrieveAnnotationMirrorAsString(f, "Classy") ); + } + return failures; + } + + static class AnnotationHost { + @ExpectedString( + "@Classy(value=Obj.class)") + @Classy(value=Obj.class) + public int f0; + + @ExpectedString( + "@Classy(value=Obj[].class)") + @Classy(value=Obj[].class) + public int f1; + + @ExpectedString( + "@Classy(value=Obj[][].class)") + @Classy(value=Obj[][].class) + public int f2; + + @ExpectedString( + "@Classy(value=Obj[][][].class)") + @Classy(value=Obj[][][].class) + public int f3; + + @ExpectedString( + "@Classy(value=int.class)") + @Classy(value=int.class) + public int f4; + + @ExpectedString( + "@Classy(value=int[][][].class)") + @Classy(value=int[][][].class) + public int f5; + } + + /** + * Each field should have two annotations, the first being + * @ExpectedString and the second the annotation under test. + */ + private int arrayAnnotationTest() { + int failures = 0; + + TypeElement arrayAnnotationHostElt = + Objects.requireNonNull(elements + .getTypeElement("AnnotationToStringTest.ArrayAnnotationHost")); + + for (VariableElement f : + ElementFilter.fieldsIn(arrayAnnotationHostElt.getEnclosedElements())) { + var annotations = f.getAnnotationMirrors(); + // String expected = retrieveAnnotationMirrorValue(f, "ExpectedString"); + String expected = f.getAnnotation(ExpectedString.class).value(); + + // Problem with + // Need a de-quote method... + // expected = expected.substring(1, expected.length() - 1); + + // The toString of an AnnotationMirror does a better job + // of eliding "value=" when not needed then the toString + // from an Annotation. Strip "value=" from the expected + // string to allow the comparison to proceed successfully. + failures += + check(expected.replace("value=", ""), + annotations.get(1).toString()); + + // Get the array-valued annotation as an annotation + failures += + check(expected.replace("value=", ""), + retrieveAnnotationMirrorAsString(f, + annotations.get(1) + .getAnnotationType().toString())); + } + return failures; + } + + static class ArrayAnnotationHost { + @ExpectedString( + "@BooleanArray(value={true, false, true})") + @BooleanArray(value={true, false, true}) + public boolean[] f0; + + @ExpectedString( + "@FloatArray(value={3.0f, 4.0f, 0.0f/0.0f, -1.0f/0.0f, 1.0f/0.0f})") + @FloatArray(value={3.0f, 4.0f, Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY}) + public float[] f1; + + @ExpectedString( + "@DoubleArray(value={1.0, 2.0, 0.0/0.0, 1.0/0.0, -1.0/0.0})") + @DoubleArray(value={1.0, 2.0, Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY,}) + public double[] f2; + + + @ExpectedString( + "@ByteArray(value={(byte)0x0a, (byte)0x0b, (byte)0x0c})") + @ByteArray(value={10, 11, 12}) + public byte[] f3; + + @ExpectedString( + "@ShortArray(value={0, 4, 5})") + @ShortArray(value={0, 4, 5}) + public short[] f4; + + @ExpectedString( + "@CharArray(value={'a', 'b', 'c', '\\''})") + @CharArray(value={'a', 'b', 'c', '\''}) + public char[] f5; + + @ExpectedString( + "@IntArray(value={1})") + @IntArray(value={1}) + public int[] f6; + + @ExpectedString( + "@LongArray(value={-9223372036854775808L, -2147483649L, -2147483648L," + + " -2147483647L, 2147483648L, 9223372036854775807L})") + @LongArray(value={Long.MIN_VALUE, Integer.MIN_VALUE-1L, Integer.MIN_VALUE, + -Integer.MAX_VALUE, Integer.MAX_VALUE+1L, Long.MAX_VALUE}) + public long[] f7; + + @ExpectedString( + "@StringArray(value={\"A\", \"B\", \"C\", \"\\\"Quote\\\"\"})") + @StringArray(value={"A", "B", "C", "\"Quote\""}) + public String[] f8; + + @ExpectedString( + "@ClassArray(value={int.class, Obj[].class})") + @ClassArray(value={int.class, Obj[].class}) + public Class[] f9; + + @ExpectedString( + "@EnumArray(value={java.lang.annotation.RetentionPolicy.SOURCE})") + @EnumArray(value={RetentionPolicy.SOURCE}) + public RetentionPolicy[] f10; + } +} + +// ------------ Supporting types ------------ + +class Obj {} + +@Retention(RetentionPolicy.RUNTIME) +@interface ExpectedString { + String value(); +} + +@Retention(RetentionPolicy.RUNTIME) +@interface Classy { + Class value(); +} + +@Retention(RetentionPolicy.RUNTIME) +@interface BooleanArray { + boolean[] value(); +} + +@Retention(RetentionPolicy.RUNTIME) +@interface FloatArray { + float[] value(); +} + +@Retention(RetentionPolicy.RUNTIME) +@interface DoubleArray { + double[] value(); +} + +@Retention(RetentionPolicy.RUNTIME) +@interface ByteArray { + byte[] value(); +} + +@Retention(RetentionPolicy.RUNTIME) +@interface ShortArray { + short[] value(); +} + +@Retention(RetentionPolicy.RUNTIME) +@interface CharArray { + char[] value(); +} + +@Retention(RetentionPolicy.RUNTIME) +@interface IntArray { + int[] value(); +} + +@Retention(RetentionPolicy.RUNTIME) +@interface LongArray { + long[] value(); +} + +@Retention(RetentionPolicy.RUNTIME) +@interface ClassArray { + Class[] value() default {int.class, Obj[].class}; +} + +@Retention(RetentionPolicy.RUNTIME) +@interface StringArray { + String[] value(); +} + +@Retention(RetentionPolicy.RUNTIME) +@interface EnumArray { + RetentionPolicy[] value(); +} + +@Retention(RetentionPolicy.RUNTIME) +@interface MostlyPrimitive { + char c0(); + char c1(); + int i0(); + int i1(); + float f0(); + float f1(); + double d0(); + double d1(); + long l0(); + long l1(); + long l2(); + long l3(); + String s0(); + String s1(); + Class class0(); + Class[] classArray(); +}