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 }