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 }