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