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 }