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 }