1 /*
   2  * Copyright (c) 2018, 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 8058202
  27  * @summary Test java.lang.Object methods on AnnotatedType objects.
  28  */
  29 
  30 import java.lang.annotation.*;
  31 import java.lang.reflect.*;
  32 import java.util.*;
  33 
  34 /**
  35  * Test toString, equals, and hashCode on various AnnotatedType objects.
  36  */
  37 public class TestObjectMethods {
  38     private static int errors = 0;
  39 
  40     /*
  41      * There are various subtypes of AnnotatedType implementations:
  42      *
  43      * AnnotatedType
  44      * AnnotatedArrayType
  45      * AnnotatedParameterizedType
  46      * AnnotatedTypeVariable
  47      * AnnotatedWildcardType
  48      *
  49      * The implementations of each these implementations are
  50      * examined. Wildcards don't appear as top-level types and need to
  51      * be extracted from bounds.
  52      *
  53      * AnnotatedTypes with and without annotations are examined as
  54      * well.
  55      */
  56     public static void main(String... args) {
  57         Class<?>[] testClasses = {TypeHost.class, AnnotatedTypeHost.class};
  58 
  59         for (Class<?> clazz : testClasses) {
  60             testEqualsReflexivity(clazz);
  61             testEquals(clazz);
  62         }
  63 
  64         testToString(TypeHost.class, false);
  65         testToString(AnnotatedTypeHost.class, true);
  66 
  67         testAnnotationsMatterForEquals(TypeHost.class, AnnotatedTypeHost.class);
  68 
  69         testGetAnnotations(TypeHost.class, false);
  70         testGetAnnotations(AnnotatedTypeHost.class, true);
  71 
  72         testWildcards();
  73 
  74         if (errors > 0) {
  75             throw new RuntimeException(errors + " errors");
  76         }
  77     }
  78 
  79     /*
  80      * For non-array types, verify toString version of the annotated
  81      * type ends with the same string as the generic type.
  82      */
  83     static void testToString(Class<?> clazz, boolean leadingAnnotations) {
  84         System.err.println("Testing toString on methods of class " + clazz.getName());
  85         Method[] methods = clazz.getDeclaredMethods();
  86         for (Method m : methods) {
  87             AnnotatedType annotType = m.getAnnotatedReturnType();
  88             String annotTypeString = annotType.toString();
  89 
  90             Type type = m.getGenericReturnType();
  91             String typeString = type.toString();
  92 
  93             boolean isArray = annotType instanceof AnnotatedArrayType;
  94             boolean isVoid = "void".equals(typeString);
  95 
  96             boolean valid;
  97             if (!isArray) {
  98                 if (leadingAnnotations && !isVoid) {
  99                     valid =
 100                         annotTypeString.endsWith(typeString) &&
 101                         !annotTypeString.startsWith(typeString);
 102                 } else {
 103                     valid = annotTypeString.equals(typeString);
 104                 }
 105             } else {
 106                 // Find final non-array component type and gets its name.
 107                 typeString = null;
 108 
 109                 AnnotatedType componentType = annotType;
 110                 while (componentType instanceof AnnotatedArrayType) {
 111                     AnnotatedArrayType annotatedArrayType = (AnnotatedArrayType) componentType;
 112                     componentType = annotatedArrayType.getAnnotatedGenericComponentType();
 113                 }
 114 
 115                 String componentName = componentType.getType().getTypeName();
 116                 valid = annotTypeString.contains(componentName);
 117             }
 118 
 119             if (!valid) {
 120                 errors++;
 121                 System.err.println(typeString + "\n" + annotTypeString +
 122                                    "\n " + valid  +
 123                                    "\n\n");
 124             }
 125         }
 126     }
 127 
 128     static void testGetAnnotations(Class<?> clazz, boolean annotationsExpectedOnMethods) {
 129         System.err.println("Testing getAnnotations on methods of class " + clazz.getName());
 130         Method[] methods = clazz.getDeclaredMethods();
 131         for (Method m : methods) {
 132             Type type = m.getGenericReturnType();
 133             AnnotatedType annotType = m.getAnnotatedReturnType();
 134             Annotation[] annotations = annotType.getAnnotations();
 135 
 136             boolean isVoid = "void".equals(type.toString());
 137 
 138             if (annotationsExpectedOnMethods && !isVoid) {
 139                 if (annotations.length == 0 ) {
 140                     errors++;
 141                     System.err.println("Expected annotations missing on " + annotType);
 142                 }
 143             } else {
 144                 if (annotations.length > 0 ) {
 145                     errors++;
 146                     System.err.println("Unexpected annotations present on " + annotType);
 147                 }
 148             }
 149         }
 150     }
 151 
 152     static void testEqualsReflexivity(Class<?> clazz) {
 153         System.err.println("Testing reflexivity of equals on methods of class " + clazz.getName());
 154         Method[] methods = clazz.getDeclaredMethods();
 155         for (Method m : methods) {
 156             checkTypesForEquality(m.getAnnotatedReturnType(),
 157                                   m.getAnnotatedReturnType(),
 158                                   true);
 159         }
 160     }
 161 
 162     private static void checkTypesForEquality(AnnotatedType annotType1,
 163                                               AnnotatedType annotType2,
 164                                               boolean expected) {
 165         boolean comparison = annotType1.equals(annotType2);
 166 
 167         if (comparison) {
 168             int hash1 = annotType1.hashCode();
 169             int hash2 = annotType2.hashCode();
 170             if (hash1 != hash2) {
 171                 errors++;
 172                 System.err.format("Equal AnnotatedTypes with unequal hash codes: %n%s%n%s%n",
 173                                   annotType1.toString(), annotType2.toString());
 174             }
 175         }
 176 
 177         if (comparison != expected) {
 178             errors++;
 179             System.err.println(annotType1);
 180             System.err.println(expected ? " is not equal to " : " is equal to ");
 181             System.err.println(annotType2);
 182             System.err.println();
 183         }
 184     }
 185 
 186     /*
 187      * For each of the type host classes, the return type of a method
 188      * should only equal the return type of that method.
 189      */
 190     static void testEquals(Class<?> clazz) {
 191         Method[] methods = clazz.getDeclaredMethods();
 192 
 193         for (int i = 0; i < methods.length; i++) {
 194             for (int j = 0; j < methods.length; j++) {
 195                 if (i == j)
 196                     continue;
 197                 else {
 198                     checkTypesForEquality(methods[i].getAnnotatedReturnType(),
 199                                           methods[j].getAnnotatedReturnType(),
 200                                           false);
 201                 }
 202             }
 203         }
 204     }
 205 
 206     /**
 207      * Roughly, compare the return types of corresponding methods on
 208      * TypeHost and AnnotatedtypeHost and verify the AnnotatedType
 209      * objects are *not* equal even if their underlying generic types
 210      * are.
 211      */
 212     static void testAnnotationsMatterForEquals(Class<?> clazz1, Class<?> clazz2) {
 213         System.err.println("Testing that presence/absence of annotations matters for equals comparison.");
 214 
 215         String methodName = null;
 216         for (Method method :  clazz1.getDeclaredMethods()) {
 217             if ("void".equals(method.getReturnType().toString())) {
 218                 continue;
 219             }
 220 
 221             methodName = method.getName();
 222             try {
 223                 checkTypesForEquality(method.getAnnotatedReturnType(),
 224                                       clazz2.getDeclaredMethod(methodName).getAnnotatedReturnType(),
 225                                       false);
 226             } catch (Exception e) {
 227                 errors++;
 228                 System.err.println("Method " + methodName + " not found.");
 229             }
 230         }
 231     }
 232 
 233 
 234     static void testWildcards() {
 235         System.err.println("Testing wildcards");
 236         // public @AnnotType(10) Set<? extends Number> fooNumberSet() {return null;}
 237         // public @AnnotType(11) Set<@AnnotType(13) ? extends Number> fooNumberSet2() {return null;}
 238         AnnotatedWildcardType awt1 = extractWildcard("fooNumberSet");
 239         AnnotatedWildcardType awt2 = extractWildcard("fooNumberSet2");
 240 
 241         if (!awt1.equals(extractWildcard("fooNumberSet")) ||
 242             !awt2.equals(extractWildcard("fooNumberSet2"))) {
 243             errors++;
 244             System.err.println("Bad equality comparison on wildcards.");
 245         }
 246 
 247         checkTypesForEquality(awt1, awt2, false);
 248 
 249         if (awt2.getAnnotations().length == 0) {
 250             errors++;
 251             System.err.println("Expected annotations not found.");
 252         }
 253     }
 254 
 255     private static AnnotatedWildcardType extractWildcard(String methodName) {
 256         try {
 257             return (AnnotatedWildcardType)
 258                 (((AnnotatedParameterizedType)(AnnotatedTypeHost.class.getMethod(methodName).
 259                                                getAnnotatedReturnType())).
 260                  getAnnotatedActualTypeArguments()[0] );
 261         } catch (Exception e) {
 262             throw new RuntimeException(e);
 263         }
 264     }
 265 
 266     // The TypeHost and AnnotatedTypeHost classes declare methods with
 267     // the same name and signatures but with the AnnotatedTypeHost
 268     // methods having annotations on their return type, where
 269     // possible.
 270 
 271     static class TypeHost<E, F extends Number> {
 272         public void fooVoid() {return;}
 273 
 274         public int foo() {return 0;}
 275         public String fooString() {return null;}
 276 
 277         public int[] fooIntArray() {return null;}
 278         public String[] fooStringArray() {return null;}
 279         public String [][] fooStringArrayArray() {return null;}
 280 
 281         public Set<String> fooSetString() {return null;}
 282         public E fooE() {return null;}
 283         public F fooF() {return null;}
 284         public <G> G fooG() {return null;}
 285 
 286         public  Set<? extends Number> fooNumberSet() {return null;}
 287         public  Set<? extends Integer> fooNumberSet2() {return null;}
 288     }
 289 
 290     @Retention(RetentionPolicy.RUNTIME)
 291     @Target(ElementType.TYPE_USE)
 292     static @interface AnnotType {
 293         int value() default 0;
 294     }
 295 
 296     static class AnnotatedTypeHost<E, F extends Number> {
 297         public /*@AnnotType(0)*/ void fooVoid() {return;} // Illegal to annotate void
 298 
 299         public @AnnotType(1) int foo() {return 0;}
 300         public @AnnotType(2) String fooString() {return null;}
 301 
 302         public  int @AnnotType(3) [] fooIntArray() {return null;}
 303         public  String @AnnotType(4) [] fooStringArray() {return null;}
 304         public  @AnnotType(5) String  @AnnotType(0) [] @AnnotType(1) [] fooStringArrayArray() {return null;}
 305 
 306         public @AnnotType(6) Set<String> fooSetString() {return null;}
 307         public @AnnotType(7) E fooE() {return null;}
 308         public @AnnotType(8) F fooF() {return null;}
 309         public @AnnotType(9) <G> G fooG() {return null;}
 310 
 311         public @AnnotType(10) Set<? extends Number> fooNumberSet() {return null;}
 312         public @AnnotType(11) Set<@AnnotType(13) ? extends Number> fooNumberSet2() {return null;}
 313     }
 314 }