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 }