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 ---