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