1 /* 2 * Copyright (c) 2016, 2024, 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 import java.io.IOException; 25 import java.io.OutputStream; 26 import java.lang.annotation.Annotation; 27 import java.lang.classfile.AnnotationElement; 28 import java.lang.classfile.AnnotationValue; 29 import java.lang.classfile.ClassBuilder; 30 import java.lang.classfile.ClassElement; 31 import java.lang.classfile.ClassFile; 32 import java.lang.classfile.ClassTransform; 33 import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; 34 import java.lang.module.Configuration; 35 import java.lang.module.ModuleDescriptor; 36 import java.lang.module.ModuleFinder; 37 import java.net.URL; 38 import java.net.URLClassLoader; 39 import java.nio.file.Files; 40 import java.nio.file.Path; 41 import java.util.ArrayList; 42 import java.util.List; 43 import java.util.Set; 44 45 import jdk.test.lib.util.ModuleInfoWriter; 46 47 import org.testng.annotations.Test; 48 import static org.testng.Assert.*; 49 50 /** 51 * @test 52 * @modules java.base/jdk.internal.module 53 * @library /test/lib 54 * @run testng AnnotationsTest 55 * @summary Basic test of annotations on modules 56 */ 57 58 public class AnnotationsTest { 59 60 /** 61 * Test that there are no annotations on an unnamed module. 62 */ 63 @Test 64 public void testUnnamedModule() { 65 Module module = this.getClass().getModule(); 66 assertTrue(module.getAnnotations().length == 0); 67 assertTrue(module.getDeclaredAnnotations().length == 0); 68 } 69 70 /** 71 * Test reflectively reading the annotations on a named module. 72 */ 73 @Test 74 public void testNamedModule() throws IOException { 75 Path mods = Files.createTempDirectory(Path.of(""), "mods"); 76 77 // @Deprecated(since="9", forRemoval=true) module foo { } 78 ModuleDescriptor descriptor = ModuleDescriptor.newModule("foo").build(); 79 byte[] classBytes = ModuleInfoWriter.toBytes(descriptor); 80 classBytes = addDeprecated(classBytes, true, "9"); 81 Files.write(mods.resolve("module-info.class"), classBytes); 82 83 // create module layer with module foo 84 Module module = loadModule(mods, "foo"); 85 86 // check the annotation is present 87 assertTrue(module.isAnnotationPresent(Deprecated.class)); 88 Deprecated d = module.getAnnotation(Deprecated.class); 89 assertNotNull(d, "@Deprecated not found"); 90 assertTrue(d.forRemoval()); 91 assertEquals(d.since(), "9"); 92 Annotation[] a = module.getAnnotations(); 93 assertTrue(a.length == 1); 94 assertTrue(a[0] instanceof Deprecated); 95 assertEquals(module.getDeclaredAnnotations(), a); 96 } 97 98 /** 99 * Test reflectively reading annotations on a named module where the module 100 * is mapped to a class loader that can locate a module-info.class. 101 */ 102 @Test 103 public void testWithModuleInfoResourceXXXX() throws IOException { 104 Path mods = Files.createTempDirectory(Path.of(""), "mods"); 105 106 // classes directory with module-info.class 107 Path classes = Files.createTempDirectory(Path.of("."), "classes"); 108 Path mi = classes.resolve("module-info.class"); 109 try (OutputStream out = Files.newOutputStream(mi)) { 110 ModuleDescriptor descriptor = ModuleDescriptor.newModule("lurker").build(); 111 ModuleInfoWriter.write(descriptor, out); 112 } 113 114 // URLClassLoader that can locate a module-info.class resource 115 URL url = classes.toUri().toURL(); 116 URLClassLoader loader = new URLClassLoader(new URL[] { url }); 117 assertTrue(loader.findResource("module-info.class") != null); 118 119 // module foo { } 120 ModuleDescriptor descriptor = ModuleDescriptor.newModule("foo").build(); 121 byte[] classBytes = ModuleInfoWriter.toBytes(descriptor); 122 Files.write(mods.resolve("module-info.class"), classBytes); 123 124 // create module layer with module foo 125 Module foo = loadModule(mods, "foo", loader); 126 127 // check the annotation is not present 128 assertFalse(foo.isAnnotationPresent(Deprecated.class)); 129 130 // @Deprecated(since="11", forRemoval=true) module bar { } 131 descriptor = ModuleDescriptor.newModule("bar").build(); 132 classBytes = ModuleInfoWriter.toBytes(descriptor); 133 classBytes = addDeprecated(classBytes, true, "11"); 134 Files.write(mods.resolve("module-info.class"), classBytes); 135 136 // create module layer with module bar 137 Module bar = loadModule(mods, "bar", loader); 138 139 // check the annotation is present 140 assertTrue(bar.isAnnotationPresent(Deprecated.class)); 141 } 142 143 /** 144 * Adds the Deprecated annotation to the given module-info class file. 145 */ 146 static byte[] addDeprecated(byte[] bytes, boolean forRemoval, String since) { 147 var cf = ClassFile.of(); 148 var oldModel = cf.parse(bytes); 149 return cf.transformClass(oldModel, new ClassTransform() { 150 boolean rvaaFound = false; 151 152 @Override 153 public void accept(ClassBuilder builder, ClassElement element) { 154 if (!rvaaFound && element instanceof RuntimeVisibleAnnotationsAttribute rvaa) { 155 rvaaFound = true; 156 var res = new ArrayList<java.lang.classfile.Annotation>(rvaa.annotations().size() + 1); 157 res.addAll(rvaa.annotations()); 158 res.add(createDeprecated()); 159 builder.accept(RuntimeVisibleAnnotationsAttribute.of(res)); 160 return; 161 } 162 builder.accept(element); 163 } 164 165 @Override 166 public void atEnd(ClassBuilder builder) { 167 if (!rvaaFound) { 168 builder.accept(RuntimeVisibleAnnotationsAttribute.of(List.of(createDeprecated()))); 169 } 170 } 171 172 private java.lang.classfile.Annotation createDeprecated() { 173 return java.lang.classfile.Annotation.of( 174 Deprecated.class.describeConstable().orElseThrow(), 175 AnnotationElement.of("forRemoval", AnnotationValue.ofBoolean(forRemoval)), 176 AnnotationElement.of("since", AnnotationValue.ofString(since)) 177 ); 178 } 179 }); 180 } 181 182 /** 183 * Load the module of the given name in the given directory into a 184 * child layer with the given class loader as the parent class loader. 185 */ 186 static Module loadModule(Path dir, String name, ClassLoader parent) 187 throws IOException 188 { 189 ModuleFinder finder = ModuleFinder.of(dir); 190 191 ModuleLayer bootLayer = ModuleLayer.boot(); 192 193 Configuration cf = bootLayer.configuration() 194 .resolve(finder, ModuleFinder.of(), Set.of(name)); 195 196 ModuleLayer layer = bootLayer.defineModulesWithOneLoader(cf, parent); 197 198 Module module = layer.findModule(name).orElse(null); 199 assertNotNull(module, name + " not loaded"); 200 return module; 201 } 202 203 static Module loadModule(Path dir, String name) throws IOException { 204 return loadModule(dir, name, ClassLoader.getSystemClassLoader()); 205 } 206 }