1 /*
  2  * Copyright (c) 2015, 2023, 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 /*
 25  * @test
 26  * @bug 8142968 8300228
 27  * @library /test/lib
 28  * @modules java.base/jdk.internal.module
 29  *          jdk.compiler
 30  *          jdk.jlink
 31  * @build ModuleReaderTest
 32  *        jdk.test.lib.compiler.CompilerUtils
 33  *        jdk.test.lib.util.JarUtils
 34  * @run junit ModuleReaderTest
 35  * @summary Basic tests for java.lang.module.ModuleReader
 36  */
 37 
 38 import java.io.File;
 39 import java.io.IOException;
 40 import java.io.InputStream;
 41 import java.lang.module.ModuleFinder;
 42 import java.lang.module.ModuleReader;
 43 import java.lang.module.ModuleReference;
 44 import java.net.URI;
 45 import java.net.URL;
 46 import java.net.URLConnection;
 47 import java.nio.ByteBuffer;
 48 import java.nio.charset.StandardCharsets;
 49 import java.nio.file.Files;
 50 import java.nio.file.Path;

 51 import java.util.HashSet;
 52 import java.util.List;
 53 import java.util.Optional;
 54 import java.util.Set;
 55 import java.util.spi.ToolProvider;
 56 import java.util.stream.Stream;
 57 
 58 import jdk.internal.module.ModulePath;
 59 import jdk.test.lib.compiler.CompilerUtils;
 60 import jdk.test.lib.util.JarUtils;
 61 import org.junit.jupiter.api.BeforeAll;
 62 import org.junit.jupiter.api.Test;
 63 
 64 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 65 import static org.junit.jupiter.api.Assertions.assertEquals;
 66 import static org.junit.jupiter.api.Assertions.assertFalse;
 67 import static org.junit.jupiter.api.Assertions.assertThrows;
 68 import static org.junit.jupiter.api.Assertions.assertTrue;
 69 
 70 public class ModuleReaderTest {
 71     private static final Path MODS_DIR = Path.of("mods");




 72 
 73     // the module name of the base module
 74     private static final String BASE_MODULE = "java.base";
 75 
 76     // the module name of the test module
 77     private static final String TEST_MODULE = "m";
 78 
 79     // resources in the base module
 80     private static final String[] BASE_RESOURCES = {
 81         "java/lang/Object.class"
 82     };
 83 
 84     // (directory) resources that may be in the base module
 85     private static final String[] MAYBE_BASE_RESOURCES = {
 86         "java",
 87         "java/",
 88         "java/lang",
 89         "java/lang/",
 90     };
 91 
 92     // resource names that should not be found in the base module
 93     private static final String[] NOT_BASE_RESOURCES = {
 94         "NotFound",
 95         "/java",
 96         "//java",
 97         "/java/lang",
 98         "//java/lang",
 99         "java//lang",
100         "/java/lang/Object.class",
101         "//java/lang/Object.class",
102         "java/lang/Object.class/",
103         "java//lang//Object.class",
104         "./java/lang/Object.class",
105         "java/./lang/Object.class",
106         "java/lang/./Object.class",
107         "../java/lang/Object.class",
108         "java/../lang/Object.class",
109         "java/lang/../Object.class",
110 
111         // junk resource names
112         "java\u0000",
113         "C:java",
114         "C:\\java",
115         "java\\lang\\Object.class"
116     };
117 
118     // resources in test module (can't use module-info.class as a test
119     // resource as it will be modified by the jmod tool)
120     private static final String[] TEST_RESOURCES = {
121         "p/Main.class",
122         "p/test.txt"
123     };
124 
125     // (directory) resources that may be in the test module
126     private static final String[] MAYBE_TEST_RESOURCES = {
127         "p",
128         "p/"
129     };
130 
131     // resource names that should not be found in the test module
132     private static final String[] NOT_TEST_RESOURCES = {
133         "NotFound",
134         "/p",
135         "//p",
136         "/p/Main.class",
137         "//p/Main.class",
138         "p/Main.class/",
139         "p//Main.class",
140         "./p/Main.class",
141         "p/./Main.class",
142         "../p/Main.class",
143         "p/../p/Main.class",
144 
145         // junk resource names
146         "p\u0000",
147         "C:p",
148         "C:\\p",
149         "p\\Main.class"
150     };
151 
152     @BeforeAll
153     public static void compileTestModules() throws Exception {
154         // create a simple module-info.java
155         Path srcDir = Path.of("src", TEST_MODULE);
156         Files.createDirectories(srcDir);
157         Files.writeString(srcDir.resolve("module-info.java"), "module " + TEST_MODULE + " {}");
158 
159         // write and compile test class "p.Main"
160         Path pkgPath = Path.of("p");
161         Path javaSrc = srcDir.resolve(pkgPath).resolve("Main.java");
162         Files.createDirectories(javaSrc.getParent());
163         Files.writeString(javaSrc,
164                 """
165                 package p;
166                 public class Main {
167                     public static void main(String[] args) { }
168                 }
169                 """);
170 
171         // javac -d <outDir> <srcDir>/**
172         Path outDir = MODS_DIR.resolve(TEST_MODULE);
173         boolean compiled = CompilerUtils.compile(srcDir, outDir);
174         assertTrue(compiled, "test module did not compile");
175 
176         // add two versions of a resource to allow for preview mode testing
177         Files.writeString(outDir.resolve(pkgPath).resolve("test.txt"), "Original");
178         Path previewDir = outDir.resolve("META-INF", "preview").resolve(pkgPath);
179         Files.createDirectories(previewDir);
180         Files.writeString(previewDir.resolve("test.txt"), "Preview Version");
181     }
182 
183     /**
184      * Test ModuleReader with module in runtime image.
185      */
186     @Test
187     public void testImage() throws IOException {
188         ModuleFinder finder = ModuleFinder.ofSystem();
189         ModuleReference mref = finder.find(BASE_MODULE).get();
190         ModuleReader reader = mref.open();
191 
192         try (reader) {
193 
194             for (String name : BASE_RESOURCES) {
195                 byte[] expectedBytes;
196                 Module baseModule = Object.class.getModule();
197                 try (InputStream in = baseModule.getResourceAsStream(name)) {
198                     expectedBytes = in.readAllBytes();
199                 }
200 
201                 testFind(reader, name, expectedBytes);
202                 testOpen(reader, name, expectedBytes);
203                 testRead(reader, name, expectedBytes);
204                 testList(reader, name);
205             }
206 
207             // test resources that may be in the base module
208             for (String name : MAYBE_BASE_RESOURCES) {
209                 Optional<URI> ouri = reader.find(name);
210                 ouri.ifPresent(uri -> {
211                     if (name.endsWith("/"))
212                         assertTrue(uri.toString().endsWith("/"),
213                                 "mismatched directory URI for '" + name + "': " + uri);
214                 });
215             }
216 
217             // test "not found" in java.base module
218             for (String name : NOT_BASE_RESOURCES) {
219                 assertFalse(reader.find(name).isPresent(), "Unexpected resource found: " + name);
220                 assertFalse(reader.open(name).isPresent(), "Unexpected resource opened: " + name);
221                 assertFalse(reader.read(name).isPresent(), "Unexpected resource read: " + name);
222             }
223 
224             // test nulls
225             assertThrows(NullPointerException.class, () -> reader.find(null));
226             assertThrows(NullPointerException.class, () -> reader.open(null));
227             assertThrows(NullPointerException.class, () -> reader.read(null));
228             assertThrows(NullPointerException.class, () -> reader.release(null));
229         }
230 
231         // test closed ModuleReader
232         assertThrows(IOException.class, () -> reader.open(BASE_RESOURCES[0]));
233         assertThrows(IOException.class, () -> reader.read(BASE_RESOURCES[0]));
234         assertThrows(IOException.class, reader::list);
235     }
236 
237     /**
238      * Test ModuleReader with exploded module.
239      */
240     @Test
241     public void testExplodedModule() throws IOException {
242         test(MODS_DIR);
243     }
244 
245     /**
246      * Test the ModuleReader used for the JDK exploded image. A module finder for
247      * a JDK exploded image can be created to locate resources in META-INF/preview
248      * when running with preview feature enabled. This test exercises the ModuleReader
249      * obtained when the module finder is created with preview features enabled and
250      * disabled.
251      */
252     @Test
253     public void testExplodedImage() throws IOException {
254         // preview features disabled
255         ModuleFinder finder = ModulePath.ofExplodedImage(MODS_DIR, null, false);
256         try (ModuleReader reader = finder.find(TEST_MODULE).get().open()) {
257             testReader(reader, "p/test.txt", "Original");
258             testReader(reader, "META-INF/preview/p/test.txt", "Preview Version");
259         }
260 
261         // preview features enabled
262         ModuleFinder previewFinder = ModulePath.ofExplodedImage(MODS_DIR, null, true);
263         try (ModuleReader reader = previewFinder.find(TEST_MODULE).get().open()) {
264             testReader(reader, "p/test.txt", "Preview Version");
265             assertFalse(reader.find("META-INF/preview/p/test.txt").isPresent(), "unexpected preview resource");
266         }
267     }
268 
269     /**
270      * Test that a ModuleReader locates, opens and reads a resource.
271      */
272     private void testReader(ModuleReader reader, String name, String expectedContent) throws IOException {
273         // check resource is found and that the URI locates the resource
274         Optional<URI> ouri = reader.find(name);
275         assertTrue(ouri.isPresent(), "resource not found: " + name);
276         URI uri = ouri.get();
277         assertTrue(uri.getPath().endsWith(name), "unexpected URI path component: " + uri);
278 
279         // read resource bytes with input stream
280         Optional<InputStream> oin = reader.open(name);
281         assertTrue(oin.isPresent(), "resource cannot be opened: " + name);
282         byte[] bytes;
283         try (InputStream in = oin.get()) {
284             bytes = in.readAllBytes();
285         }
286 
287         // read resource bytes into byte buffer
288         Optional<ByteBuffer> obb = reader.read(name);
289         assertTrue(obb.isPresent(), "resource cannot be read: " + name);
290         ByteBuffer bb = obb.get();
291         try {
292             assertEquals(ByteBuffer.wrap(bytes), bb, "resource bytes differ: " + name);
293         } finally {
294             reader.release(bb);
295         }
296 
297         // test that resource has the expected contents
298         byte[] expectedBytes = expectedContent.getBytes(StandardCharsets.UTF_8);
299         assertArrayEquals(expectedBytes, bytes, "unexpected content");
300     }
301 
302     /**
303      * Test ModuleReader with module in modular JAR.
304      */
305     @Test
306     public void testModularJar() throws IOException {
307         Path dir = Files.createTempDirectory(Path.of("."), "mlib");
308 
309         // jar cf mlib/${TESTMODULE}.jar -C mods .
310         JarUtils.createJarFile(dir.resolve(TEST_MODULE + ".jar"),
311                                MODS_DIR.resolve(TEST_MODULE));
312 
313         test(dir);
314     }
315 
316     /**
317      * Test ModuleReader with module in a JMOD file.
318      */
319     @Test
320     public void testJMod() throws IOException {
321         Path dir = Files.createTempDirectory(Path.of("."), "mlib");
322 
323         // jmod create --class-path mods/${TESTMODULE}  mlib/${TESTMODULE}.jmod
324         String cp = MODS_DIR.resolve(TEST_MODULE).toString();
325         String jmod = dir.resolve(TEST_MODULE + ".jmod").toString();
326         String[] args = { "create", "--class-path", cp, jmod };
327         ToolProvider jmodTool = ToolProvider.findFirst("jmod")
328                 .orElseThrow(() ->
329                         new RuntimeException("jmod tool not found")
330                 );
331         assertEquals(0, jmodTool.run(System.out, System.out, args), "jmod tool failed");
332 
333         test(dir);
334     }
335 
336     /**
337      * The test module is found on the given module path. Open a ModuleReader
338      * to the test module and test the reader.
339      */
340     void test(Path mp) throws IOException {
341         ModuleFinder finder = ModulePath.of(Runtime.version(), true, mp);
342         ModuleReference mref = finder.find(TEST_MODULE).get();
343         ModuleReader reader = mref.open();
344 
345         try (reader) {
346 
347             // test resources in test module
348             for (String name : TEST_RESOURCES) {
349                 System.out.println("resource: " + name);
350                 byte[] expectedBytes
351                     = Files.readAllBytes(MODS_DIR
352                         .resolve(TEST_MODULE)
353                         .resolve(name.replace('/', File.separatorChar)));
354 
355                 testFind(reader, name, expectedBytes);
356                 testOpen(reader, name, expectedBytes);
357                 testRead(reader, name, expectedBytes);
358                 testList(reader, name);
359             }
360 
361             // test resources that may be in the test module
362             for (String name : MAYBE_TEST_RESOURCES) {
363                 System.out.println("resource: " + name);
364                 Optional<URI> ouri = reader.find(name);
365                 ouri.ifPresent(uri -> {
366                     if (name.endsWith("/"))
367                         assertTrue(uri.toString().endsWith("/"),
368                                 "mismatched directory URI for '" + name + "': " + uri);
369                 });
370             }
371 
372             // test "not found" in test module
373             for (String name : NOT_TEST_RESOURCES) {
374                 System.out.println("resource: " + name);
375                 assertFalse(reader.find(name).isPresent(), "Unexpected resource found: " + name);
376                 assertFalse(reader.open(name).isPresent(), "Unexpected resource open: " + name);
377                 assertFalse(reader.read(name).isPresent(), "Unexpected resource read: " + name);
378             }
379 
380             // test nulls
381             assertThrows(NullPointerException.class, () -> reader.find(null));
382             assertThrows(NullPointerException.class, () -> reader.open(null));
383             assertThrows(NullPointerException.class, () -> reader.read(null));
384             assertThrows(NullPointerException.class, () -> reader.release(null));
385         }
386 
387         // test closed ModuleReader
388         assertThrows(IOException.class, () -> reader.open(BASE_RESOURCES[0]));
389         assertThrows(IOException.class, () -> reader.read(BASE_RESOURCES[0]));
390         assertThrows(IOException.class, reader::list);
391     }
392 
393     /**
394      * Test ModuleReader#find
395      */
396     void testFind(ModuleReader reader, String name, byte[] expectedBytes)
397         throws IOException
398     {
399         Optional<URI> ouri = reader.find(name);
400         assertTrue(ouri.isPresent(), "missing URI for: " + name);
401 
402         URL url = ouri.get().toURL();
403         if (!url.getProtocol().equalsIgnoreCase("jmod")) {
404             URLConnection uc = url.openConnection();
405             uc.setUseCaches(false);
406             try (InputStream in = uc.getInputStream()) {
407                 byte[] bytes = in.readAllBytes();
408                 assertArrayEquals(expectedBytes, bytes, "resource bytes differ for: " + name);
409             }
410         }
411     }
412 
413     /**
414      * Test ModuleReader#open
415      */
416     void testOpen(ModuleReader reader, String name, byte[] expectedBytes)
417         throws IOException
418     {
419         Optional<InputStream> oin = reader.open(name);
420         assertTrue(oin.isPresent(), "missing input stream for: " + name);
421         try (InputStream in = oin.get()) {
422             byte[] bytes = in.readAllBytes();
423             assertArrayEquals(expectedBytes, bytes, "resource bytes differ for: " + name);
424         }
425     }
426 
427     /**
428      * Test ModuleReader#read
429      */
430     void testRead(ModuleReader reader, String name, byte[] expectedBytes)
431         throws IOException
432     {
433         Optional<ByteBuffer> obb = reader.read(name);
434         assertTrue(obb.isPresent());
435 
436         ByteBuffer bb = obb.get();
437         try {
438             int rem = bb.remaining();
439             assertEquals(expectedBytes.length, rem, "resource lengths differ: " + name);
440             byte[] bytes = new byte[rem];
441             bb.get(bytes);
442             assertArrayEquals(expectedBytes, bytes, "resource bytes differ: " + name);
443         } finally {
444             reader.release(bb);
445         }
446     }
447 
448     /**
449      * Test ModuleReader#list
450      */
451     void testList(ModuleReader reader, String name) throws IOException {
452         final List<String> list;
453         try (Stream<String> stream = reader.list()) {
454             list = stream.toList();
455         }
456         Set<String> names = new HashSet<>(list);
457         assertEquals(names.size(), list.size(), "resource list contains duplicates: " + list);
458 
459         assertTrue(names.contains("module-info.class"), "resource list did not contain 'module-info.class': " + list);
460         assertTrue(names.contains(name), "resource list did not contain '" + name + "'" + list);
461 
462         // all resources should be locatable via find
463         for (String e : names) {
464             assertTrue(reader.find(e).isPresent(), "resource not found: " + name);
465         }
466     }
467 
468 }
--- EOF ---