1 /*
   2  * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 8164819
  27  * @summary Test of toString on normal annotations
  28  * @library /tools/javac/lib
  29  * @build   JavacTestingAbstractProcessor AnnotationToStringTest
  30  * @compile -processor AnnotationToStringTest -proc:only AnnotationToStringTest.java
  31  */
  32 
  33 // See also the sibling core reflection test
  34 // test/jdk/java/lang/annotation/AnnotationToStringTest.java
  35 
  36 import java.lang.annotation.*;
  37 import java.lang.reflect.*;
  38 import java.util.*;
  39 import javax.annotation.processing.*;
  40 import javax.lang.model.AnnotatedConstruct;
  41 import javax.lang.model.element.*;
  42 import javax.lang.model.util.*;
  43 
  44 /**
  45  * The expected string values are stored in @ExpectedString
  46  * annotations. The essence of the test is comparing the toString()
  47  * result of annotations to the corresponding ExpectedString.value().
  48  *
  49  * Two flavors of comparison are made:
  50  *
  51  * 1) Against the AnnotationMirror value from getAnnotationMirrors()
  52  *
  53  * 2) Against the *Annotation* from getAnnotation(Class<A>)
  54  *
  55  * These have separate but related implementations.
  56  */
  57 public class AnnotationToStringTest extends JavacTestingAbstractProcessor {
  58     public boolean process(Set<? extends TypeElement> annotations,
  59                            RoundEnvironment roundEnv) {
  60         if (!roundEnv.processingOver()) {
  61 
  62             int failures = 0;
  63 
  64             TypeElement primHostElt =
  65                 Objects.requireNonNull(elements.getTypeElement("AnnotationToStringTest.PrimHost"));
  66 
  67             List<? extends AnnotationMirror> annotMirrors = primHostElt.getAnnotationMirrors();
  68 
  69             String expectedString = primHostElt.getAnnotation(MostlyPrimitive.class).toString();
  70 
  71             failures += check(expectedString,
  72                               primHostElt.getAnnotation(ExpectedString.class).value());
  73 
  74             failures += check(expectedString,
  75                               retrieveAnnotationMirrorAsString(primHostElt,
  76                                                                "MostlyPrimitive"));
  77             failures += classyTest();
  78             failures += arrayAnnotationTest();
  79 
  80             if (failures > 0)
  81                 throw new RuntimeException(failures + " failures");
  82         }
  83         return true;
  84     }
  85 
  86     /**
  87      * Examine annotation mirrors, find the one that matches
  88      * annotationName, and return its toString value.
  89      */
  90     private String retrieveAnnotationMirrorAsString(AnnotatedConstruct annotated,
  91                                                     String annotationName) {
  92         return retrieveAnnotationMirror(annotated, annotationName).toString();
  93     }
  94 
  95     private String retrieveAnnotationMirrorValue(AnnotatedConstruct annotated,
  96                                                  String annotationName) {
  97         AnnotationMirror annotationMirror =
  98             retrieveAnnotationMirror(annotated, annotationName);
  99         for (var entry : annotationMirror.getElementValues().entrySet()) {
 100             if (entry.getKey().getSimpleName().contentEquals("value")) {
 101                 return entry.getValue().toString();
 102             }
 103         }
 104         throw new RuntimeException("Annotation value() method not found: " +
 105                                    annotationMirror.toString());
 106     }
 107 
 108     private AnnotationMirror retrieveAnnotationMirror(AnnotatedConstruct annotated,
 109                                                       String annotationName) {
 110         for (AnnotationMirror annotationMirror : annotated.getAnnotationMirrors()) {
 111             System.out.println(annotationMirror.getAnnotationType());
 112             if (annotationMirror
 113                 .getAnnotationType()
 114                 .toString()
 115                 .equals(annotationName) ) {
 116                 return annotationMirror;
 117             }
 118         }
 119         throw new RuntimeException("Annotation " + annotationName + " not found.");
 120     }
 121 
 122     private static int check(String expected, String actual) {
 123         if (!expected.equals(actual)) {
 124             System.err.printf("ERROR: Expected ''%s'';%ngot             ''%s''.\n",
 125                               expected, actual);
 126             return 1;
 127         } else
 128             return 0;
 129     }
 130 
 131     @ExpectedString(
 132         "@MostlyPrimitive(c0='a', "+
 133         "c1='\\'', " +
 134         "i0=1, " +
 135         "i1=2, " +
 136         "f0=1.0f, " +
 137         "f1=0.0f/0.0f, " +
 138         "d0=0.0, " +
 139         "d1=1.0/0.0, " +
 140         "l0=5L, " +
 141         "l1=9223372036854775807L, " +
 142         "l2=-9223372036854775808L, " +
 143         "l3=-2147483648L, " +
 144         "s0=\"Hello world.\", " +
 145         "s1=\"a\\\"b\", " +
 146         "class0=Obj[].class, " + 
 147         "classArray={Obj[].class})")
 148     @MostlyPrimitive(
 149         c0='a',
 150         c1='\'',
 151         i0=1,
 152         i1=2,
 153         f0=1.0f,
 154         f1=Float.NaN,
 155         d0=0.0,
 156         d1=2.0/0.0,
 157         l0=5,
 158         l1=Long.MAX_VALUE,
 159         l2=Long.MIN_VALUE,
 160         l3=Integer.MIN_VALUE,
 161         s0="Hello world.",
 162         s1="a\"b",
 163         class0=Obj[].class,
 164         classArray={Obj[].class}
 165     )
 166     static class PrimHost{}
 167 
 168     private int classyTest() {
 169         int failures = 0;
 170 
 171         TypeElement annotationHostElt =
 172             Objects.requireNonNull(elements.getTypeElement("AnnotationToStringTest.AnnotationHost"));
 173 
 174         for (VariableElement f : ElementFilter.fieldsIn(annotationHostElt.getEnclosedElements())) {
 175             String expected = f.getAnnotation(ExpectedString.class).value();
 176             Annotation a = f.getAnnotation(Classy.class);
 177 
 178             System.out.println(a);
 179             failures += check(expected, a.toString());
 180 
 181             // The toString of an AnnotationMirror does a better job
 182             // of eliding "value=" when not needed then the toString
 183             // from an Annotation. Strip "value=" from the expected
 184             // string to allow the comparison to proceed successfully.
 185             failures += check(expected.replace("value=", ""),
 186                               retrieveAnnotationMirrorAsString(f, "Classy") );
 187         }
 188         return failures;
 189     }
 190 
 191     static class AnnotationHost {
 192         @ExpectedString(
 193        "@Classy(value=Obj.class)")
 194         @Classy(value=Obj.class)
 195         public int f0;
 196 
 197         @ExpectedString(
 198        "@Classy(value=Obj[].class)")
 199         @Classy(value=Obj[].class)
 200         public int f1;
 201 
 202         @ExpectedString(
 203        "@Classy(value=Obj[][].class)")
 204         @Classy(value=Obj[][].class)
 205         public int f2;
 206 
 207         @ExpectedString(
 208        "@Classy(value=Obj[][][].class)")
 209         @Classy(value=Obj[][][].class)
 210         public int f3;
 211 
 212         @ExpectedString(
 213        "@Classy(value=int.class)")
 214         @Classy(value=int.class)
 215         public int f4;
 216 
 217         @ExpectedString(
 218        "@Classy(value=int[][][].class)")
 219         @Classy(value=int[][][].class)
 220         public int f5;
 221     }
 222 
 223     /**
 224      * Each field should have two annotations, the first being
 225      * @ExpectedString and the second the annotation under test.
 226      */
 227     private int arrayAnnotationTest() {
 228         int failures = 0;
 229 
 230         TypeElement arrayAnnotationHostElt =
 231             Objects.requireNonNull(elements
 232                                    .getTypeElement("AnnotationToStringTest.ArrayAnnotationHost"));
 233 
 234         for (VariableElement f :
 235                  ElementFilter.fieldsIn(arrayAnnotationHostElt.getEnclosedElements())) {
 236             var annotations = f.getAnnotationMirrors();
 237             // String expected = retrieveAnnotationMirrorValue(f, "ExpectedString");
 238             String expected = f.getAnnotation(ExpectedString.class).value();
 239 
 240             // Problem with 
 241             // Need a de-quote method...
 242             // expected = expected.substring(1, expected.length() - 1);
 243 
 244             // The toString of an AnnotationMirror does a better job
 245             // of eliding "value=" when not needed then the toString
 246             // from an Annotation. Strip "value=" from the expected
 247             // string to allow the comparison to proceed successfully.
 248               failures +=
 249                   check(expected.replace("value=", ""),
 250                         annotations.get(1).toString());
 251 
 252             // Get the array-valued annotation as an annotation
 253               failures +=
 254                   check(expected.replace("value=", ""),
 255                         retrieveAnnotationMirrorAsString(f,
 256                                                          annotations.get(1)
 257                                                          .getAnnotationType().toString()));
 258         }
 259         return failures;
 260     }
 261 
 262     static class ArrayAnnotationHost {
 263         @ExpectedString(
 264        "@BooleanArray(value={true, false, true})")
 265         @BooleanArray(value={true, false, true})
 266         public boolean[]   f0;
 267 
 268         @ExpectedString(
 269        "@FloatArray(value={3.0f, 4.0f, 0.0f/0.0f, -1.0f/0.0f, 1.0f/0.0f})")
 270         @FloatArray(value={3.0f, 4.0f, Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY})
 271         public float[]     f1;
 272 
 273         @ExpectedString(
 274        "@DoubleArray(value={1.0, 2.0, 0.0/0.0, 1.0/0.0, -1.0/0.0})")
 275         @DoubleArray(value={1.0, 2.0, Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY,})
 276         public double[]    f2;
 277 
 278 
 279         @ExpectedString(
 280        "@ByteArray(value={(byte)0x0a, (byte)0x0b, (byte)0x0c})")
 281         @ByteArray(value={10, 11, 12})
 282         public byte[]      f3;
 283 
 284         @ExpectedString(
 285        "@ShortArray(value={0, 4, 5})")
 286         @ShortArray(value={0, 4, 5})
 287         public short[]     f4;
 288 
 289         @ExpectedString(
 290        "@CharArray(value={'a', 'b', 'c', '\\''})")
 291         @CharArray(value={'a', 'b', 'c', '\''})
 292         public char[]      f5;
 293 
 294         @ExpectedString(
 295        "@IntArray(value={1})")
 296         @IntArray(value={1})
 297         public int[]       f6;
 298 
 299         @ExpectedString(
 300        "@LongArray(value={-9223372036854775808L, -2147483649L, -2147483648L," +
 301                 " -2147483647L, 2147483648L, 9223372036854775807L})")
 302         @LongArray(value={Long.MIN_VALUE, Integer.MIN_VALUE-1L, Integer.MIN_VALUE,
 303                 -Integer.MAX_VALUE, Integer.MAX_VALUE+1L, Long.MAX_VALUE})
 304         public long[]      f7;
 305 
 306         @ExpectedString(
 307        "@StringArray(value={\"A\", \"B\", \"C\", \"\\\"Quote\\\"\"})")
 308         @StringArray(value={"A", "B", "C", "\"Quote\""})
 309         public String[]    f8;
 310 
 311         @ExpectedString(
 312        "@ClassArray(value={int.class, Obj[].class})")
 313         @ClassArray(value={int.class, Obj[].class})
 314         public Class<?>[]  f9;
 315 
 316         @ExpectedString(
 317        "@EnumArray(value={java.lang.annotation.RetentionPolicy.SOURCE})")
 318         @EnumArray(value={RetentionPolicy.SOURCE})
 319         public RetentionPolicy[]  f10;
 320     }
 321 }
 322 
 323 // ------------ Supporting types ------------
 324 
 325 class Obj {}
 326 
 327 @Retention(RetentionPolicy.RUNTIME)
 328 @interface ExpectedString {
 329     String value();
 330 }
 331 
 332 @Retention(RetentionPolicy.RUNTIME)
 333 @interface Classy {
 334     Class<?> value();
 335 }
 336 
 337 @Retention(RetentionPolicy.RUNTIME)
 338 @interface BooleanArray {
 339     boolean[] value();
 340 }
 341 
 342 @Retention(RetentionPolicy.RUNTIME)
 343 @interface FloatArray {
 344     float[] value();
 345 }
 346 
 347 @Retention(RetentionPolicy.RUNTIME)
 348 @interface DoubleArray {
 349     double[] value();
 350 }
 351 
 352 @Retention(RetentionPolicy.RUNTIME)
 353 @interface ByteArray {
 354     byte[] value();
 355 }
 356 
 357 @Retention(RetentionPolicy.RUNTIME)
 358 @interface ShortArray {
 359     short[] value();
 360 }
 361 
 362 @Retention(RetentionPolicy.RUNTIME)
 363 @interface CharArray {
 364     char[] value();
 365 }
 366 
 367 @Retention(RetentionPolicy.RUNTIME)
 368 @interface IntArray {
 369     int[] value();
 370 }
 371 
 372 @Retention(RetentionPolicy.RUNTIME)
 373 @interface LongArray {
 374     long[] value();
 375 }
 376 
 377 @Retention(RetentionPolicy.RUNTIME)
 378 @interface ClassArray {
 379     Class<?>[] value() default {int.class, Obj[].class};
 380 }
 381 
 382 @Retention(RetentionPolicy.RUNTIME)
 383 @interface StringArray {
 384     String[] value();
 385 }
 386 
 387 @Retention(RetentionPolicy.RUNTIME)
 388 @interface EnumArray {
 389     RetentionPolicy[] value();
 390 }
 391 
 392 @Retention(RetentionPolicy.RUNTIME)
 393 @interface MostlyPrimitive {
 394     char   c0();
 395     char   c1();
 396     int    i0();
 397     int    i1();
 398     float  f0();
 399     float  f1();
 400     double d0();
 401     double d1();
 402     long   l0();
 403     long   l1();
 404     long   l2();
 405     long   l3();
 406     String s0();
 407     String s1();
 408     Class<?> class0();
 409     Class<?>[] classArray();
 410 }