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