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