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