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