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