1 /*
2  * Copyright (c) 2013, 2017, 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 consistent parsing of ex-RUNTIME annotations that
27  *          were changed and separately compiled to have CLASS retention
28  * @run main AnnotationTypeRuntimeAssumptionTest
29  */
30 
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.lang.annotation.Retention;
34 import java.lang.annotation.RetentionPolicy;
35 
36 import static java.lang.annotation.RetentionPolicy.CLASS;
37 import static java.lang.annotation.RetentionPolicy.RUNTIME;
38 
39 /**
40  * This test simulates a situation where there are two mutually recursive
41  * {@link RetentionPolicy#RUNTIME RUNTIME} annotations {@link AnnA_v1 AnnA_v1}
42  * and {@link AnnB AnnB} and then the first is changed to have
43  * {@link RetentionPolicy#CLASS CLASS} retention and separately compiled.
44  * When {@link AnnA_v1 AnnA_v1} annotation is looked-up on {@link AnnB AnnB}
45  * it still appears to have {@link RetentionPolicy#RUNTIME RUNTIME} retention.
46  */
47 public class AnnotationTypeRuntimeAssumptionTest {
48 
49     @Retention(RUNTIME)
50     @AnnB
51     public @interface AnnA_v1 {
52     }
53 
54     // An alternative version of AnnA_v1 with CLASS retention instead.
55     // Used to simulate separate compilation (see AltClassLoader below).
56     @Retention(CLASS)
57     @AnnB
58     public @interface AnnA_v2 {
59     }
60 
61     @Retention(RUNTIME)
62     @AnnA_v1
63     public @interface AnnB {
64     }
65 
66     @AnnA_v1
67     public static class TestTask implements Runnable {
68         @Override
run()69         public void run() {
70             AnnA_v1 ann1 = TestTask.class.getDeclaredAnnotation(AnnA_v1.class);
71             if (ann1 != null) {
72                 throw new IllegalStateException(
73                     "@" + ann1.annotationType().getSimpleName() +
74                     " found on: " + TestTask.class.getName() +
75                     " should not be visible at runtime");
76             }
77             AnnA_v1 ann2 = AnnB.class.getDeclaredAnnotation(AnnA_v1.class);
78             if (ann2 != null) {
79                 throw new IllegalStateException(
80                     "@" + ann2.annotationType().getSimpleName() +
81                     " found on: " + AnnB.class.getName() +
82                     " should not be visible at runtime");
83             }
84         }
85     }
86 
main(String[] args)87     public static void main(String[] args) throws Exception {
88         ClassLoader altLoader = new AltClassLoader(
89             AnnotationTypeRuntimeAssumptionTest.class.getClassLoader());
90 
91         Runnable altTask = (Runnable) Class.forName(
92             TestTask.class.getName(),
93             true,
94             altLoader).newInstance();
95 
96         altTask.run();
97     }
98 
99     /**
100      * A ClassLoader implementation that loads alternative implementations of
101      * classes. If class name ends with "_v1" it locates instead a class with
102      * name ending with "_v2" and loads that class instead.
103      */
104     static class AltClassLoader extends ClassLoader {
AltClassLoader(ClassLoader parent)105         AltClassLoader(ClassLoader parent) {
106             super(parent);
107         }
108 
109         @Override
loadClass(String name, boolean resolve)110         protected Class<?> loadClass(String name, boolean resolve)
111                 throws ClassNotFoundException {
112             if (name.indexOf('.') < 0) { // root package is our class
113                 synchronized (getClassLoadingLock(name)) {
114                     // First, check if the class has already been loaded
115                     Class<?> c = findLoadedClass(name);
116                     if (c == null) {
117                         c = findClass(name);
118                     }
119                     if (resolve) {
120                         resolveClass(c);
121                     }
122                     return c;
123                 }
124             }
125             else { // not our class
126                 return super.loadClass(name, resolve);
127             }
128         }
129 
130         @Override
findClass(String name)131         protected Class<?> findClass(String name)
132                 throws ClassNotFoundException {
133             // special class name -> replace it with alternative name
134             if (name.endsWith("_v1")) {
135                 String altName = name.substring(0, name.length() - 3) + "_v2";
136                 String altPath = altName.replace('.', '/').concat(".class");
137                 try (InputStream is = getResourceAsStream(altPath)) {
138                     if (is != null) {
139                         byte[] bytes = is.readAllBytes();
140                         // patch class bytes to contain original name
141                         for (int i = 0; i < bytes.length - 2; i++) {
142                             if (bytes[i] == '_' &&
143                                 bytes[i + 1] == 'v' &&
144                                 bytes[i + 2] == '2') {
145                                 bytes[i + 2] = '1';
146                             }
147                         }
148                         return defineClass(name, bytes, 0, bytes.length);
149                     }
150                     else {
151                         throw new ClassNotFoundException(name);
152                     }
153                 }
154                 catch (IOException e) {
155                     throw new ClassNotFoundException(name, e);
156                 }
157             }
158             else { // not special class name -> just load the class
159                 String path = name.replace('.', '/').concat(".class");
160                 try (InputStream is = getResourceAsStream(path)) {
161                     if (is != null) {
162                         byte[] bytes = is.readAllBytes();
163                         return defineClass(name, bytes, 0, bytes.length);
164                     }
165                     else {
166                         throw new ClassNotFoundException(name);
167                     }
168                 }
169                 catch (IOException e) {
170                     throw new ClassNotFoundException(name, e);
171                 }
172             }
173         }
174     }
175 }
176