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 testng 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.file.Files;
 49 import java.nio.file.Path;
 50 import java.nio.file.Paths;
 51 import java.util.Arrays;
 52 import java.util.HashSet;
 53 import java.util.List;
 54 import java.util.Optional;
 55 import java.util.Set;
 56 import java.util.stream.Collectors;
 57 import java.util.spi.ToolProvider;
 58 import java.util.stream.Stream;
 59 
 60 import jdk.internal.module.ModulePath;
 61 import jdk.test.lib.compiler.CompilerUtils;
 62 import jdk.test.lib.util.JarUtils;
 63 
 64 import org.testng.annotations.BeforeTest;
 65 import org.testng.annotations.Test;
 66 import static org.testng.Assert.*;
 67 
 68 public class ModuleReaderTest {
 69     private static final String TEST_SRC = System.getProperty("test.src");
 70 
 71     private static final Path USER_DIR   = Paths.get(System.getProperty("user.dir"));
 72     private static final Path SRC_DIR    = Paths.get(TEST_SRC, "src");
 73     private static final Path MODS_DIR   = Paths.get("mods");
 74 
 75     // the module name of the base module
 76     private static final String BASE_MODULE = "java.base";
 77 
 78     // the module name of the test module
 79     private static final String TEST_MODULE = "m";
 80 
 81     // resources in the base module
 82     private static final String[] BASE_RESOURCES = {
 83         "java/lang/Object.class"
 84     };
 85 
 86     // (directory) resources that may be in the base module
 87     private static final String[] MAYBE_BASE_RESOURCES = {
 88         "java",
 89         "java/",
 90         "java/lang",
 91         "java/lang/",
 92     };
 93 
 94     // resource names that should not be found in the base module
 95     private static final String[] NOT_BASE_RESOURCES = {
 96         "NotFound",
 97         "/java",
 98         "//java",
 99         "/java/lang",
100         "//java/lang",
101         "java//lang",
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         "java/../lang/Object.class",
111         "java/lang/../Object.class",
112 
113         // junk resource names
114         "java\u0000",
115         "C:java",
116         "C:\\java",
117         "java\\lang\\Object.class"
118     };
119 
120     // resources in test module (can't use module-info.class as a test
121     // resource as it will be modified by the jmod tool)
122     private static final String[] TEST_RESOURCES = {
123         "p/Main.class"
124     };
125 
126     // (directory) resources that may be in the test module
127     private static final String[] MAYBE_TEST_RESOURCES = {
128         "p",
129         "p/"
130     };
131 
132     // resource names that should not be found in the test module
133     private static final String[] NOT_TEST_RESOURCES = {
134         "NotFound",
135         "/p",
136         "//p",
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/Main.class",
144         "p/../p/Main.class",
145 
146         // junk resource names
147         "p\u0000",
148         "C:p",
149         "C:\\p",
150         "p\\Main.class"
151     };
152 
153     @BeforeTest
154     public void compileTestModule() throws Exception {
155         // javac -d mods/$TESTMODULE src/$TESTMODULE/**
156         boolean compiled = CompilerUtils.compile(SRC_DIR.resolve(TEST_MODULE),
157                                                  MODS_DIR.resolve(TEST_MODULE));
158         assertTrue(compiled, "test module did not compile");
159     }
160 
161     /**
162      * Test ModuleReader with module in runtime image.
163      */
164     @Test
165     public void testImage() throws IOException {
166         ModuleFinder finder = ModuleFinder.ofSystem();
167         ModuleReference mref = finder.find(BASE_MODULE).get();
168         ModuleReader reader = mref.open();
169 
170         try (reader) {
171 
172             for (String name : BASE_RESOURCES) {
173                 byte[] expectedBytes;
174                 Module baseModule = Object.class.getModule();
175                 try (InputStream in = baseModule.getResourceAsStream(name)) {
176                     expectedBytes = in.readAllBytes();
177                 }
178 
179                 testFind(reader, name, expectedBytes);
180                 testOpen(reader, name, expectedBytes);
181                 testRead(reader, name, expectedBytes);
182                 testList(reader, name);
183             }
184 
185             // test resources that may be in the base module
186             for (String name : MAYBE_BASE_RESOURCES) {
187                 Optional<URI> ouri = reader.find(name);
188                 ouri.ifPresent(uri -> {
189                     if (name.endsWith("/"))
190                         assertTrue(uri.toString().endsWith("/"));
191                 });
192             }
193 
194             // test "not found" in java.base module
195             for (String name : NOT_BASE_RESOURCES) {
196                 assertFalse(reader.find(name).isPresent());
197                 assertFalse(reader.open(name).isPresent());
198                 assertFalse(reader.read(name).isPresent());
199             }
200 
201             // test nulls
202             try {
203                 reader.find(null);
204                 assertTrue(false);
205             } catch (NullPointerException expected) { }
206 
207             try {
208                 reader.open(null);
209                 assertTrue(false);
210             } catch (NullPointerException expected) { }
211 
212             try {
213                 reader.read(null);
214                 assertTrue(false);
215             } catch (NullPointerException expected) { }
216 
217             try {
218                 reader.release(null);
219                 assertTrue(false);
220             } catch (NullPointerException expected) { }
221 
222         }
223 
224         // test closed ModuleReader
225         try {
226             reader.open(BASE_RESOURCES[0]);
227             assertTrue(false);
228         } catch (IOException expected) { }
229 
230 
231         try {
232             reader.read(BASE_RESOURCES[0]);
233             assertTrue(false);
234         } catch (IOException expected) { }
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 ModuleReader with module in modular JAR.
247      */
248     @Test
249     public void testModularJar() throws IOException {
250         Path dir = Files.createTempDirectory(USER_DIR, "mlib");
251 
252         // jar cf mlib/${TESTMODULE}.jar -C mods .
253         JarUtils.createJarFile(dir.resolve("m.jar"),
254                                MODS_DIR.resolve(TEST_MODULE));
255 
256         test(dir);
257     }
258 
259     /**
260      * Test ModuleReader with module in a JMOD file.
261      */
262     @Test
263     public void testJMod() throws IOException {
264         Path dir = Files.createTempDirectory(USER_DIR, "mlib");
265 
266         // jmod create --class-path mods/${TESTMODULE}  mlib/${TESTMODULE}.jmod
267         String cp = MODS_DIR.resolve(TEST_MODULE).toString();
268         String jmod = dir.resolve("m.jmod").toString();
269         String[] args = { "create", "--class-path", cp, jmod };
270         ToolProvider jmodTool = ToolProvider.findFirst("jmod")
271             .orElseThrow(() ->
272                 new RuntimeException("jmod tool not found")
273             );
274         assertEquals(jmodTool.run(System.out, System.out, args), 0);
275 
276         test(dir);
277     }
278 
279     /**
280      * The test module is found on the given module path. Open a ModuleReader
281      * to the test module and test the reader.
282      */
283     void test(Path mp) throws IOException {
284         ModuleFinder finder = ModulePath.of(Runtime.version(), true, mp);
285         ModuleReference mref = finder.find(TEST_MODULE).get();
286         ModuleReader reader = mref.open();
287 
288         try (reader) {
289 
290             // test resources in test module
291             for (String name : TEST_RESOURCES) {
292                 System.out.println("resource: " + name);
293                 byte[] expectedBytes
294                     = Files.readAllBytes(MODS_DIR
295                         .resolve(TEST_MODULE)
296                         .resolve(name.replace('/', File.separatorChar)));
297 
298                 testFind(reader, name, expectedBytes);
299                 testOpen(reader, name, expectedBytes);
300                 testRead(reader, name, expectedBytes);
301                 testList(reader, name);
302             }
303 
304             // test resources that may be in the test module
305             for (String name : MAYBE_TEST_RESOURCES) {
306                 System.out.println("resource: " + name);
307                 Optional<URI> ouri = reader.find(name);
308                 ouri.ifPresent(uri -> {
309                     if (name.endsWith("/"))
310                         assertTrue(uri.toString().endsWith("/"));
311                 });
312             }
313 
314             // test "not found" in test module
315             for (String name : NOT_TEST_RESOURCES) {
316                 System.out.println("resource: " + name);
317                 assertFalse(reader.find(name).isPresent());
318                 assertFalse(reader.open(name).isPresent());
319                 assertFalse(reader.read(name).isPresent());
320             }
321 
322             // test nulls
323             try {
324                 reader.find(null);
325                 assertTrue(false);
326             } catch (NullPointerException expected) { }
327 
328             try {
329                 reader.open(null);
330                 assertTrue(false);
331             } catch (NullPointerException expected) { }
332 
333             try {
334                 reader.read(null);
335                 assertTrue(false);
336             } catch (NullPointerException expected) { }
337 
338             try {
339                 reader.release(null);
340                 throw new RuntimeException();
341             } catch (NullPointerException expected) { }
342 
343         }
344 
345         // test closed ModuleReader
346         try {
347             reader.open(TEST_RESOURCES[0]);
348             assertTrue(false);
349         } catch (IOException expected) { }
350 
351 
352         try {
353             reader.read(TEST_RESOURCES[0]);
354             assertTrue(false);
355         } catch (IOException expected) { }
356 
357         try {
358             reader.list();
359             assertTrue(false);
360         } catch (IOException expected) { }
361     }
362 
363     /**
364      * Test ModuleReader#find
365      */
366     void testFind(ModuleReader reader, String name, byte[] expectedBytes)
367         throws IOException
368     {
369         Optional<URI> ouri = reader.find(name);
370         assertTrue(ouri.isPresent());
371 
372         URL url = ouri.get().toURL();
373         if (!url.getProtocol().equalsIgnoreCase("jmod")) {
374             URLConnection uc = url.openConnection();
375             uc.setUseCaches(false);
376             try (InputStream in = uc.getInputStream()) {
377                 byte[] bytes = in.readAllBytes();
378                 assertTrue(Arrays.equals(bytes, expectedBytes));
379             }
380         }
381     }
382 
383     /**
384      * Test ModuleReader#open
385      */
386     void testOpen(ModuleReader reader, String name, byte[] expectedBytes)
387         throws IOException
388     {
389         Optional<InputStream> oin = reader.open(name);
390         assertTrue(oin.isPresent());
391 
392         InputStream in = oin.get();
393         try (in) {
394             byte[] bytes = in.readAllBytes();
395             assertTrue(Arrays.equals(bytes, expectedBytes));
396         }
397     }
398 
399     /**
400      * Test ModuleReader#read
401      */
402     void testRead(ModuleReader reader, String name, byte[] expectedBytes)
403         throws IOException
404     {
405         Optional<ByteBuffer> obb = reader.read(name);
406         assertTrue(obb.isPresent());
407 
408         ByteBuffer bb = obb.get();
409         try {
410             int rem = bb.remaining();
411             assertTrue(rem == expectedBytes.length);
412             byte[] bytes = new byte[rem];
413             bb.get(bytes);
414             assertTrue(Arrays.equals(bytes, expectedBytes));
415         } finally {
416             reader.release(bb);
417         }
418     }
419 
420     /**
421      * Test ModuleReader#list
422      */
423     void testList(ModuleReader reader, String name) throws IOException {
424         final List<String> list;
425         try (Stream<String> stream = reader.list()) {
426             list = stream.toList();
427         }
428         Set<String> names = new HashSet<>(list);
429         assertTrue(names.size() == list.size()); // no duplicates
430 
431         assertTrue(names.contains("module-info.class"));
432         assertTrue(names.contains(name));
433 
434         // all resources should be locatable via find
435         for (String e : names) {
436             assertTrue(reader.find(e).isPresent());
437         }
438     }
439 
440 }