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