1 /*
   2  * Copyright (c) 2013, 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 8011940
  27  * @summary Test inheritance, order and class redefinition behaviour of RUNTIME
  28  *          class annotations
  29  * @author plevart
  30  */
  31 
  32 import sun.reflect.annotation.AnnotationParser;
  33 
  34 import java.lang.annotation.Annotation;
  35 import java.lang.annotation.Inherited;
  36 import java.lang.annotation.Retention;
  37 import java.lang.annotation.RetentionPolicy;
  38 import java.lang.reflect.Field;
  39 import java.lang.reflect.InvocationTargetException;
  40 import java.util.ArrayList;
  41 import java.util.Arrays;
  42 import java.util.Collections;
  43 import java.util.List;
  44 import java.util.StringJoiner;
  45 
  46 public class AnnotationsInheritanceOrderRedefinitionTest {
  47 
  48     @Retention(RetentionPolicy.RUNTIME)
  49     @Inherited
  50     @interface Ann1 {
  51         String value();
  52     }
  53 
  54     @Retention(RetentionPolicy.RUNTIME)
  55     @Inherited
  56     @interface Ann2 {
  57         String value();
  58     }
  59 
  60     @Retention(RetentionPolicy.RUNTIME)
  61     @Inherited
  62     @interface Ann3 {
  63         String value();
  64     }
  65 
  66     @Ann1("A")
  67     @Ann2("A")
  68     static class A {}
  69 
  70     @Ann3("B")
  71     static class B extends A {}
  72 
  73     @Ann1("C")
  74     @Ann3("C")
  75     static class C extends B {}
  76 
  77     public static void main(String[] args) {
  78 
  79         StringBuilder msgs = new StringBuilder();
  80         boolean ok = true;
  81 
  82         ok &= annotationsEqual(msgs, A.class, true,
  83             ann(Ann1.class, "A"), ann(Ann2.class, "A"));
  84         ok &= annotationsEqual(msgs, A.class, false,
  85             ann(Ann1.class, "A"), ann(Ann2.class, "A"));
  86         ok &= annotationsEqual(msgs, B.class, true,
  87             ann(Ann3.class, "B"));
  88         ok &= annotationsEqual(msgs, B.class, false,
  89             ann(Ann1.class, "A"), ann(Ann2.class, "A"), ann(Ann3.class, "B"));
  90         ok &= annotationsEqual(msgs, C.class, true,
  91             ann(Ann1.class, "C"), ann(Ann3.class, "C"));
  92         ok &= annotationsEqual(msgs, C.class, false,
  93             ann(Ann1.class, "C"), ann(Ann2.class, "A"), ann(Ann3.class, "C"));
  94 
  95         Annotation[] declaredAnnotatiosA = A.class.getDeclaredAnnotations();
  96         Annotation[] annotationsA = A.class.getAnnotations();
  97         Annotation[] declaredAnnotatiosB = B.class.getDeclaredAnnotations();
  98         Annotation[] annotationsB = B.class.getAnnotations();
  99         Annotation[] declaredAnnotatiosC = C.class.getDeclaredAnnotations();
 100         Annotation[] annotationsC = C.class.getAnnotations();
 101 
 102         incrementClassRedefinedCount(A.class);
 103         incrementClassRedefinedCount(B.class);
 104         incrementClassRedefinedCount(C.class);
 105 
 106         ok &= annotationsEqualButNotSame(msgs, A.class, true, declaredAnnotatiosA);
 107         ok &= annotationsEqualButNotSame(msgs, A.class, false, annotationsA);
 108         ok &= annotationsEqualButNotSame(msgs, B.class, true, declaredAnnotatiosB);
 109         ok &= annotationsEqualButNotSame(msgs, B.class, false, annotationsB);
 110         ok &= annotationsEqualButNotSame(msgs, C.class, true, declaredAnnotatiosC);
 111         ok &= annotationsEqualButNotSame(msgs, C.class, false, annotationsC);
 112 
 113         if (!ok) {
 114             throw new RuntimeException("test failure\n" + msgs);
 115         }
 116     }
 117 
 118     // utility methods
 119 
 120     private static boolean annotationsEqualButNotSame(StringBuilder msgs,
 121             Class<?> declaringClass, boolean declaredOnly, Annotation[] oldAnns) {
 122         if (!annotationsEqual(msgs, declaringClass, declaredOnly, oldAnns)) {
 123             return false;
 124         }
 125         Annotation[] anns = declaredOnly
 126                             ? declaringClass.getDeclaredAnnotations()
 127                             : declaringClass.getAnnotations();
 128         List<Annotation> sameAnns = new ArrayList<>();
 129         for (int i = 0; i < anns.length; i++) {
 130             if (anns[i] == oldAnns[i]) {
 131                 sameAnns.add(anns[i]);
 132             }
 133         }
 134         if (!sameAnns.isEmpty()) {
 135             msgs.append(declaredOnly ? "declared " : "").append("annotations for ")
 136                 .append(declaringClass.getSimpleName())
 137                 .append(" not re-parsed after class redefinition: ")
 138                 .append(toSimpleString(sameAnns)).append("\n");
 139             return false;
 140         } else {
 141             return true;
 142         }
 143     }
 144 
 145     private static boolean annotationsEqual(StringBuilder msgs,
 146             Class<?> declaringClass, boolean declaredOnly, Annotation... expectedAnns) {
 147         Annotation[] anns = declaredOnly
 148                             ? declaringClass.getDeclaredAnnotations()
 149                             : declaringClass.getAnnotations();
 150         if (!Arrays.equals(anns, expectedAnns)) {
 151             msgs.append(declaredOnly ? "declared " : "").append("annotations for ")
 152                 .append(declaringClass.getSimpleName()).append(" are: ")
 153                 .append(toSimpleString(anns)).append(", expected: ")
 154                 .append(toSimpleString(expectedAnns)).append("\n");
 155             return false;
 156         } else {
 157             return true;
 158         }
 159     }
 160 
 161     private static Annotation ann(Class<? extends Annotation> annotationType,
 162                                   Object value) {
 163         return AnnotationParser.annotationForMap(annotationType,
 164             Collections.singletonMap("value", value));
 165     }
 166 
 167     private static String toSimpleString(List<Annotation> anns) {
 168         return toSimpleString(anns.toArray(new Annotation[anns.size()]));
 169     }
 170 
 171     private static String toSimpleString(Annotation[] anns) {
 172         StringJoiner joiner = new StringJoiner(", ");
 173         for (Annotation ann : anns) {
 174             joiner.add(toSimpleString(ann));
 175         }
 176         return joiner.toString();
 177     }
 178 
 179     private static String toSimpleString(Annotation ann) {
 180         Class<? extends Annotation> annotationType = ann.annotationType();
 181         Object value;
 182         try {
 183             value = annotationType.getDeclaredMethod("value").invoke(ann);
 184         } catch (IllegalAccessException | InvocationTargetException
 185             | NoSuchMethodException e) {
 186             throw new RuntimeException(e);
 187         }
 188         return "@" + annotationType.getSimpleName() + "(" + value + ")";
 189     }
 190 
 191     private static final Field classRedefinedCountField;
 192 
 193     static {
 194         try {
 195             classRedefinedCountField = Class.class.getDeclaredField("classRedefinedCount");
 196             classRedefinedCountField.setAccessible(true);
 197         } catch (NoSuchFieldException e) {
 198             throw new Error(e);
 199         }
 200     }
 201 
 202     private static void incrementClassRedefinedCount(Class<?> clazz) {
 203         try {
 204             classRedefinedCountField.set(clazz,
 205                 ((Integer) classRedefinedCountField.get(clazz)) + 1);
 206         } catch (IllegalAccessException e) {
 207             throw new RuntimeException(e);
 208         }
 209     }
 210 }