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