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 30 import sun.misc.IOUtils; 31 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.lang.annotation.Retention; 35 import java.lang.annotation.RetentionPolicy; 36 37 import static java.lang.annotation.RetentionPolicy.CLASS; 38 import 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 {@link RetentionPolicy#CLASS CLASS} 44 * retention and separately compiled. When {@link AnnA_v1 AnnA_v1} annotation si looked-up on 45 * {@link AnnB AnnB} it still appears to have {@link RetentionPolicy#RUNTIME RUNTIME} 46 * retention. 47 */ 48 public 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("@" + 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("@" + ann2.annotationType().getSimpleName() + 80 " found on: " + AnnB.class.getName() + 81 " should not be visible at runtime"); 82 } 83 } 84 } 85 86 public static void main(String[] args) throws Exception { 87 ClassLoader altLoader = new AltClassLoader(AnnotationTypeRuntimeAssumptionTest.class.getClassLoader()); 88 Runnable altTask = (Runnable) Class.forName(TestTask.class.getName(), true, altLoader).newInstance(); 89 altTask.run(); 90 } 91 92 /** 93 * A ClassLoader implementation that loads alternative implementations of classes instead of original. 94 * If class name ends with "_v1" it locates instead class with name ending with "_v2" and then renames 95 * the class bytes to the original name before defining the class. 96 */ 97 static class AltClassLoader extends ClassLoader { 98 AltClassLoader(ClassLoader parent) { 99 super(parent); 100 } 101 102 @Override 103 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { 104 if (name.indexOf('.') < 0) { // root package is our class 105 synchronized (getClassLoadingLock(name)) { 106 // First, check if the class has already been loaded 107 Class<?> c = findLoadedClass(name); 108 if (c == null) { 109 c = findClass(name); 110 } 111 if (resolve) { 112 resolveClass(c); 113 } 114 return c; 115 } 116 } 117 else { // not our class 118 return super.loadClass(name, resolve); 119 } 120 } 121 122 @Override 123 protected Class<?> findClass(String name) throws ClassNotFoundException { 124 if (name.endsWith("_v1")) { // special class name -> replace it with alternative name 125 String altName = name.substring(0, name.length() - 3) + "_v2"; 126 String altPath = altName.replace('.', '/').concat(".class"); 127 try (InputStream is = getResourceAsStream(altPath)) { 128 if (is != null) { 129 byte[] bytes = IOUtils.readFully(is, -1, true); 130 // patch class bytes to contain original (not alternative) name 131 for (int i = 0; i < bytes.length - 2; i++) { 132 if (bytes[i] == '_' && bytes[i + 1] == 'v' && bytes[i + 2] == '2') { 133 bytes[i + 2] = '1'; 134 } 135 } 136 return defineClass(name, bytes, 0, bytes.length); 137 } 138 else { 139 throw new ClassNotFoundException(name); 140 } 141 } 142 catch (IOException e) { 143 throw new ClassNotFoundException(name, e); 144 } 145 } 146 else { // no special class name -> just load the class by original name 147 String path = name.replace('.', '/').concat(".class"); 148 try (InputStream is = getResourceAsStream(path)) { 149 if (is != null) { 150 byte[] bytes = IOUtils.readFully(is, -1, true); 151 return defineClass(name, bytes, 0, bytes.length); 152 } 153 else { 154 throw new ClassNotFoundException(name); 155 } 156 } 157 catch (IOException e) { 158 throw new ClassNotFoundException(name, e); 159 } 160 } 161 } 162 } 163 }