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  * @enablePreview
 27  * @modules java.base/jdk.internal.module
 28  * @library /test/lib
 29  * @build ModuleFinderTest jdk.test.lib.util.ModuleInfoWriter
 30  * @run testng ModuleFinderTest
 31  * @summary Basic tests for java.lang.module.ModuleFinder
 32  */
 33 
 34 import java.io.File;
 35 import java.io.OutputStream;
 36 import java.lang.module.FindException;
 37 import java.lang.module.InvalidModuleDescriptorException;
 38 import java.lang.module.ModuleDescriptor;
 39 import java.lang.module.ModuleFinder;
 40 import java.lang.module.ModuleReference;
 41 import java.nio.file.Files;
 42 import java.nio.file.Path;
 43 import java.nio.file.Paths;
 44 import java.util.Optional;
 45 import java.util.Set;
 46 import java.util.jar.JarEntry;
 47 import java.util.jar.JarOutputStream;
 48 import java.util.stream.Collectors;
 49 
 50 import jdk.test.lib.util.ModuleInfoWriter;
 51 
 52 import org.testng.annotations.Test;
 53 import static org.testng.Assert.*;
 54 
 55 @Test
 56 public class ModuleFinderTest {
 57 
 58     private static final Path USER_DIR
 59         = Paths.get(System.getProperty("user.dir"));
 60 
 61 
 62     /**
 63      * Test ModuleFinder.ofSystem
 64      */
 65     public void testOfSystem() {
 66         ModuleFinder finder = ModuleFinder.ofSystem();
 67 
 68         assertTrue(finder.find("java.se").isPresent());
 69         assertTrue(finder.find("java.base").isPresent());
 70         assertFalse(finder.find("java.rhubarb").isPresent());
 71 
 72         Set<String> names = finder.findAll().stream()
 73             .map(ModuleReference::descriptor)
 74             .map(ModuleDescriptor::name)
 75             .collect(Collectors.toSet());
 76         assertTrue(names.contains("java.se"));
 77         assertTrue(names.contains("java.base"));
 78         assertFalse(names.contains("java.rhubarb"));
 79     }
 80 
 81 
 82     /**
 83      * Test ModuleFinder.of with no entries
 84      */
 85     public void testOfNoEntries() {
 86         ModuleFinder finder = ModuleFinder.of();
 87         assertTrue(finder.findAll().isEmpty());
 88         assertFalse(finder.find("java.rhubarb").isPresent());
 89     }
 90 
 91 
 92     /**
 93      * Test ModuleFinder.of with one directory of modules
 94      */
 95     public void testOfOneDirectory() throws Exception {
 96         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 97         createExplodedModule(dir.resolve("m1"), "m1");
 98         createModularJar(dir.resolve("m2.jar"), "m2");
 99 
100         ModuleFinder finder = ModuleFinder.of(dir);
101         assertTrue(finder.findAll().size() == 2);
102         assertTrue(finder.find("m1").isPresent());
103         assertTrue(finder.find("m2").isPresent());
104         assertFalse(finder.find("java.rhubarb").isPresent());
105     }
106 
107 
108     /**
109      * Test ModuleFinder.of with two directories
110      */
111     public void testOfTwoDirectories() throws Exception {
112         Path dir1 = Files.createTempDirectory(USER_DIR, "mods1");
113         createExplodedModule(dir1.resolve("m1"), "m1@1.0");
114         createModularJar(dir1.resolve("m2.jar"), "m2@1.0");
115 
116         Path dir2 = Files.createTempDirectory(USER_DIR, "mods2");
117         createExplodedModule(dir2.resolve("m1"), "m1@2.0");
118         createModularJar(dir2.resolve("m2.jar"), "m2@2.0");
119         createExplodedModule(dir2.resolve("m3"), "m3");
120         createModularJar(dir2.resolve("m4.jar"), "m4");
121 
122         ModuleFinder finder = ModuleFinder.of(dir1, dir2);
123         assertTrue(finder.findAll().size() == 4);
124         assertTrue(finder.find("m1").isPresent());
125         assertTrue(finder.find("m2").isPresent());
126         assertTrue(finder.find("m3").isPresent());
127         assertTrue(finder.find("m4").isPresent());
128         assertFalse(finder.find("java.rhubarb").isPresent());
129 
130         // check that m1@1.0 (and not m1@2.0) is found
131         ModuleDescriptor m1 = finder.find("m1").get().descriptor();
132         assertEquals(m1.version().get().toString(), "1.0");
133 
134         // check that m2@1.0 (and not m2@2.0) is found
135         ModuleDescriptor m2 = finder.find("m2").get().descriptor();
136         assertEquals(m2.version().get().toString(), "1.0");
137     }
138 
139 
140     /**
141      * Test ModuleFinder.of with one JAR file
142      */
143     public void testOfOneJarFile() throws Exception {
144         Path dir = Files.createTempDirectory(USER_DIR, "mods");
145         Path jar1 = createModularJar(dir.resolve("m1.jar"), "m1");
146 
147         ModuleFinder finder = ModuleFinder.of(jar1);
148         assertTrue(finder.findAll().size() == 1);
149         assertTrue(finder.find("m1").isPresent());
150         assertFalse(finder.find("java.rhubarb").isPresent());
151     }
152 
153 
154     /**
155      * Test ModuleFinder.of with two JAR files
156      */
157     public void testOfTwoJarFiles() throws Exception {
158         Path dir = Files.createTempDirectory(USER_DIR, "mods");
159 
160         Path jar1 = createModularJar(dir.resolve("m1.jar"), "m1");
161         Path jar2 = createModularJar(dir.resolve("m2.jar"), "m2");
162 
163         ModuleFinder finder = ModuleFinder.of(jar1, jar2);
164         assertTrue(finder.findAll().size() == 2);
165         assertTrue(finder.find("m1").isPresent());
166         assertTrue(finder.find("m2").isPresent());
167         assertFalse(finder.find("java.rhubarb").isPresent());
168     }
169 
170 
171     /**
172      * Test ModuleFinder.of with many JAR files
173      */
174     public void testOfManyJarFiles() throws Exception {
175         Path dir = Files.createTempDirectory(USER_DIR, "mods");
176 
177         Path jar1 = createModularJar(dir.resolve("m1@1.0.jar"), "m1@1.0");
178         Path jar2 = createModularJar(dir.resolve("m2@1.0.jar"), "m2");
179         Path jar3 = createModularJar(dir.resolve("m1@2.0.jar"), "m1@2.0"); // shadowed
180         Path jar4 = createModularJar(dir.resolve("m3@1.0.jar"), "m3");
181 
182         ModuleFinder finder = ModuleFinder.of(jar1, jar2, jar3, jar4);
183         assertTrue(finder.findAll().size() == 3);
184         assertTrue(finder.find("m1").isPresent());
185         assertTrue(finder.find("m2").isPresent());
186         assertTrue(finder.find("m3").isPresent());
187         assertFalse(finder.find("java.rhubarb").isPresent());
188 
189         // check that m1@1.0 (and not m1@2.0) is found
190         ModuleDescriptor m1 = finder.find("m1").get().descriptor();
191         assertEquals(m1.version().get().toString(), "1.0");
192     }
193 
194 
195     /**
196      * Test ModuleFinder.of with one exploded module.
197      */
198     public void testOfOneExplodedModule() throws Exception {
199         Path dir = Files.createTempDirectory(USER_DIR, "mods");
200         Path m1_dir = createExplodedModule(dir.resolve("m1"), "m1");
201 
202         ModuleFinder finder = ModuleFinder.of(m1_dir);
203         assertTrue(finder.findAll().size() == 1);
204         assertTrue(finder.find("m1").isPresent());
205         assertFalse(finder.find("java.rhubarb").isPresent());
206     }
207 
208 
209     /**
210      * Test ModuleFinder.of with two exploded modules.
211      */
212     public void testOfTwoExplodedModules() throws Exception {
213         Path dir = Files.createTempDirectory(USER_DIR, "mods");
214         Path m1_dir = createExplodedModule(dir.resolve("m1"), "m1");
215         Path m2_dir = createExplodedModule(dir.resolve("m2"), "m2");
216 
217         ModuleFinder finder = ModuleFinder.of(m1_dir, m2_dir);
218         assertTrue(finder.findAll().size() == 2);
219         assertTrue(finder.find("m1").isPresent());
220         assertTrue(finder.find("m2").isPresent());
221         assertFalse(finder.find("java.rhubarb").isPresent());
222     }
223 
224 
225     /**
226      * Test ModuleFinder.of with a mix of module directories and JAR files.
227      */
228     public void testOfMixDirectoriesAndJars() throws Exception {
229 
230         // directory with m1@1.0 and m2@1.0
231         Path dir1 = Files.createTempDirectory(USER_DIR, "mods1");
232         createExplodedModule(dir1.resolve("m1"), "m1@1.0");
233         createModularJar(dir1.resolve("m2.jar"), "m2@1.0");
234 
235         // JAR files: m1@2.0, m2@2.0, m3@2.0, m4@2.0
236         Path dir2 = Files.createTempDirectory(USER_DIR, "mods2");
237         Path jar1 = createModularJar(dir2.resolve("m1.jar"), "m1@2.0");
238         Path jar2 = createModularJar(dir2.resolve("m2.jar"), "m2@2.0");
239         Path jar3 = createModularJar(dir2.resolve("m3.jar"), "m3@2.0");
240         Path jar4 = createModularJar(dir2.resolve("m4.jar"), "m4@2.0");
241 
242         // directory with m3@3.0 and m4@3.0
243         Path dir3 = Files.createTempDirectory(USER_DIR, "mods3");
244         createExplodedModule(dir3.resolve("m3"), "m3@3.0");
245         createModularJar(dir3.resolve("m4.jar"), "m4@3.0");
246 
247         // JAR files: m5 and m6
248         Path dir4 = Files.createTempDirectory(USER_DIR, "mods4");
249         Path jar5 = createModularJar(dir4.resolve("m5.jar"), "m5@4.0");
250         Path jar6 = createModularJar(dir4.resolve("m6.jar"), "m6@4.0");
251 
252 
253         ModuleFinder finder
254             = ModuleFinder.of(dir1, jar1, jar2, jar3, jar4, dir3, jar5, jar6);
255         assertTrue(finder.findAll().size() == 6);
256         assertTrue(finder.find("m1").isPresent());
257         assertTrue(finder.find("m2").isPresent());
258         assertTrue(finder.find("m3").isPresent());
259         assertTrue(finder.find("m4").isPresent());
260         assertTrue(finder.find("m5").isPresent());
261         assertTrue(finder.find("m6").isPresent());
262         assertFalse(finder.find("java.rhubarb").isPresent());
263 
264         // m1 and m2 should be located in dir1
265         ModuleDescriptor m1 = finder.find("m1").get().descriptor();
266         assertEquals(m1.version().get().toString(), "1.0");
267         ModuleDescriptor m2 = finder.find("m2").get().descriptor();
268         assertEquals(m2.version().get().toString(), "1.0");
269 
270         // m3 and m4 should be located in JAR files
271         ModuleDescriptor m3 = finder.find("m3").get().descriptor();
272         assertEquals(m3.version().get().toString(), "2.0");
273         ModuleDescriptor m4 = finder.find("m4").get().descriptor();
274         assertEquals(m4.version().get().toString(), "2.0");
275 
276         // m5 and m6 should be located in JAR files
277         ModuleDescriptor m5 = finder.find("m5").get().descriptor();
278         assertEquals(m5.version().get().toString(), "4.0");
279         ModuleDescriptor m6 = finder.find("m6").get().descriptor();
280         assertEquals(m6.version().get().toString(), "4.0");
281     }
282 
283 
284     /**
285      * Test ModuleFinder.of with a mix of module directories and exploded
286      * modules.
287      */
288     public void testOfMixDirectoriesAndExplodedModules() throws Exception {
289         // directory with m1@1.0 and m2@1.0
290         Path dir1 = Files.createTempDirectory(USER_DIR, "mods1");
291         createExplodedModule(dir1.resolve("m1"), "m1@1.0");
292         createModularJar(dir1.resolve("m2.jar"), "m2@1.0");
293 
294         // exploded modules: m1@2.0, m2@2.0, m3@2.0, m4@2.0
295         Path dir2 = Files.createTempDirectory(USER_DIR, "mods2");
296         Path m1_dir = createExplodedModule(dir2.resolve("m1"), "m1@2.0");
297         Path m2_dir = createExplodedModule(dir2.resolve("m2"), "m2@2.0");
298         Path m3_dir = createExplodedModule(dir2.resolve("m3"), "m3@2.0");
299         Path m4_dir = createExplodedModule(dir2.resolve("m4"), "m4@2.0");
300 
301         ModuleFinder finder = ModuleFinder.of(dir1, m1_dir, m2_dir, m3_dir, m4_dir);
302         assertTrue(finder.findAll().size() == 4);
303         assertTrue(finder.find("m1").isPresent());
304         assertTrue(finder.find("m2").isPresent());
305         assertTrue(finder.find("m3").isPresent());
306         assertTrue(finder.find("m4").isPresent());
307         assertFalse(finder.find("java.rhubarb").isPresent());
308 
309         // m1 and m2 should be located in dir1
310         ModuleDescriptor m1 = finder.find("m1").get().descriptor();
311         assertEquals(m1.version().get().toString(), "1.0");
312         ModuleDescriptor m2 = finder.find("m2").get().descriptor();
313         assertEquals(m2.version().get().toString(), "1.0");
314 
315         // m3 and m4 should be located in dir2
316         ModuleDescriptor m3 = finder.find("m3").get().descriptor();
317         assertEquals(m3.version().get().toString(), "2.0");
318         ModuleDescriptor m4 = finder.find("m4").get().descriptor();
319         assertEquals(m4.version().get().toString(), "2.0");
320     }
321 
322 
323     /**
324      * Test ModuleFinder with a JAR file containing a mix of class and
325      * non-class resources.
326      */
327     public void testOfOneJarFileWithResources() throws Exception {
328         Path dir = Files.createTempDirectory(USER_DIR, "mods");
329         Path jar = createModularJar(dir.resolve("m.jar"), "m",
330                 "LICENSE",
331                 "README",
332                 "WEB-INF/tags",
333                 "p/Type.class",
334                 "p/resources/m.properties",
335                 "q-/Type.class",                // not a legal package name
336                 "q-/resources/m/properties");
337 
338         ModuleFinder finder = ModuleFinder.of(jar);
339         Optional<ModuleReference> mref = finder.find("m");
340         assertTrue(mref.isPresent(), "m1 not found");
341 
342         ModuleDescriptor descriptor = mref.get().descriptor();
343 
344         assertTrue(descriptor.packages().size() == 2);
345         assertTrue(descriptor.packages().contains("p"));
346         assertTrue(descriptor.packages().contains("p.resources"));
347     }
348 
349 
350     /**
351      * Test ModuleFinder with an exploded module containing a mix of class
352      * and non-class resources
353      */
354     public void testOfOneExplodedModuleWithResources() throws Exception {
355         Path dir = Files.createTempDirectory(USER_DIR, "mods");
356         Path m_dir = createExplodedModule(dir.resolve("m"), "m",
357                 "LICENSE",
358                 "README",
359                 "WEB-INF/tags",
360                 "p/Type.class",
361                 "p/resources/m.properties",
362                 "q-/Type.class",                 // not a legal package name
363                 "q-/resources/m/properties");
364 
365         ModuleFinder finder = ModuleFinder.of(m_dir);
366         Optional<ModuleReference> mref = finder.find("m");
367         assertTrue(mref.isPresent(), "m not found");
368 
369         ModuleDescriptor descriptor = mref.get().descriptor();
370 
371         assertTrue(descriptor.packages().size() == 2);
372         assertTrue(descriptor.packages().contains("p"));
373         assertTrue(descriptor.packages().contains("p.resources"));
374     }
375 
376 
377     /**
378      * Test ModuleFinder with a JAR file containing a .class file in the top
379      * level directory.
380      */
381     public void testOfOneJarFileWithTopLevelClass() throws Exception {
382         Path dir = Files.createTempDirectory(USER_DIR, "mods");
383         Path jar = createModularJar(dir.resolve("m.jar"), "m", "Mojo.class");
384 
385         ModuleFinder finder = ModuleFinder.of(jar);
386         try {
387             finder.find("m");
388             assertTrue(false);
389         } catch (FindException e) {
390             assertTrue(e.getCause() instanceof InvalidModuleDescriptorException);
391             assertTrue(e.getCause().getMessage().contains("Mojo.class"));
392         }
393 
394         finder = ModuleFinder.of(jar);
395         try {
396             finder.findAll();
397             assertTrue(false);
398         } catch (FindException e) {
399             assertTrue(e.getCause() instanceof InvalidModuleDescriptorException);
400             assertTrue(e.getCause().getMessage().contains("Mojo.class"));
401         }
402     }
403 
404     /**
405      * Test ModuleFinder with a JAR file containing a .class file in the top
406      * level directory.
407      */
408     public void testOfOneExplodedModuleWithTopLevelClass() throws Exception {
409         Path dir = Files.createTempDirectory(USER_DIR, "mods");
410         Path m_dir = createExplodedModule(dir.resolve("m"), "m", "Mojo.class");
411 
412         ModuleFinder finder = ModuleFinder.of(m_dir);
413         try {
414             finder.find("m");
415             assertTrue(false);
416         } catch (FindException e) {
417             assertTrue(e.getCause() instanceof InvalidModuleDescriptorException);
418             assertTrue(e.getCause().getMessage().contains("Mojo.class"));
419         }
420 
421         finder = ModuleFinder.of(m_dir);
422         try {
423             finder.findAll();
424             assertTrue(false);
425         } catch (FindException e) {
426             assertTrue(e.getCause() instanceof InvalidModuleDescriptorException);
427             assertTrue(e.getCause().getMessage().contains("Mojo.class"));
428         }
429     }
430 
431 
432     /**
433      * Test ModuleFinder.of with a path to a file that does not exist.
434      */
435     public void testOfWithDoesNotExistEntry() throws Exception {
436         Path dir1 = Files.createTempDirectory(USER_DIR, "mods1");
437 
438         Path dir2 = Files.createTempDirectory(USER_DIR, "mods2");
439         createModularJar(dir2.resolve("m2.jar"), "m2@1.0");
440 
441         Files.delete(dir1);
442 
443         ModuleFinder finder = ModuleFinder.of(dir1, dir2);
444 
445         assertTrue(finder.find("m2").isPresent());
446         assertTrue(finder.findAll().size() == 1);
447         assertFalse(finder.find("java.rhubarb").isPresent());
448     }
449 
450 
451     /**
452      * Test ModuleFinder.of with a file path to an unrecognized file type.
453      */
454     public void testOfWithUnrecognizedEntry() throws Exception {
455         Path dir = Files.createTempDirectory(USER_DIR, "mods");
456         Path mod = Files.createTempFile(dir, "m", ".junk");
457 
458         ModuleFinder finder = ModuleFinder.of(mod);
459         try {
460             finder.find("java.rhubarb");
461             assertTrue(false);
462         } catch (FindException e) {
463             // expected
464         }
465 
466         finder = ModuleFinder.of(mod);
467         try {
468             finder.findAll();
469             assertTrue(false);
470         } catch (FindException e) {
471             // expected
472         }
473     }
474 
475 
476     /**
477      * Test ModuleFinder.of with a file path to a directory containing a file
478      * that will not be recognized as a module.
479      */
480     public void testOfWithUnrecognizedEntryInDirectory1() throws Exception {
481         Path dir = Files.createTempDirectory(USER_DIR, "mods");
482         Files.createTempFile(dir, "m", ".junk");
483 
484         ModuleFinder finder = ModuleFinder.of(dir);
485         assertFalse(finder.find("java.rhubarb").isPresent());
486 
487         finder = ModuleFinder.of(dir);
488         assertTrue(finder.findAll().isEmpty());
489     }
490 
491 
492     /**
493      * Test ModuleFinder.of with a file path to a directory containing a file
494      * that will not be recognized as a module.
495      */
496     public void testOfWithUnrecognizedEntryInDirectory2() throws Exception {
497         Path dir = Files.createTempDirectory(USER_DIR, "mods");
498         createModularJar(dir.resolve("m1.jar"), "m1");
499         Files.createTempFile(dir, "m2", ".junk");
500 
501         ModuleFinder finder = ModuleFinder.of(dir);
502         assertTrue(finder.find("m1").isPresent());
503         assertFalse(finder.find("m2").isPresent());
504 
505         finder = ModuleFinder.of(dir);
506         assertTrue(finder.findAll().size() == 1);
507     }
508 
509 
510     /**
511      * Test ModuleFinder.of with a directory that contains two
512      * versions of the same module
513      */
514     public void testOfDuplicateModulesInDirectory() throws Exception {
515         Path dir = Files.createTempDirectory(USER_DIR, "mods");
516         createModularJar(dir.resolve("m1@1.0.jar"), "m1");
517         createModularJar(dir.resolve("m1@2.0.jar"), "m1");
518 
519         ModuleFinder finder = ModuleFinder.of(dir);
520         try {
521             finder.find("m1");
522             assertTrue(false);
523         } catch (FindException expected) { }
524 
525         finder = ModuleFinder.of(dir);
526         try {
527             finder.findAll();
528             assertTrue(false);
529         } catch (FindException expected) { }
530     }
531 
532 
533     /**
534      * Test ModuleFinder.of with a directory containing hidden files
535      */
536     public void testOfWithHiddenFiles() throws Exception {
537         Path dir = Files.createTempDirectory(USER_DIR, "mods");
538         createExplodedModule(dir.resolve("m"), "m",
539                 "com/.ignore",
540                 "com/foo/.ignore",
541                 "com/foo/foo.properties");
542 
543         ModuleFinder finder = ModuleFinder.of(dir);
544         ModuleReference mref = finder.find("m").orElse(null);
545         assertNotNull(mref);
546 
547         Set<String> expectedPackages;
548         if (System.getProperty("os.name").startsWith("Windows")) {
549             expectedPackages = Set.of("com", "com.foo");
550         } else {
551             expectedPackages = Set.of("com.foo");
552         }
553         assertEquals(mref.descriptor().packages(), expectedPackages);
554     }
555 
556 
557     /**
558      * Test ModuleFinder.of with a truncated module-info.class
559      */
560     public void testOfWithTruncatedModuleInfo() throws Exception {
561         Path dir = Files.createTempDirectory(USER_DIR, "mods");
562 
563         // create an empty <dir>/rhubarb/module-info.class
564         Path subdir = Files.createDirectory(dir.resolve("rhubarb"));
565         Files.createFile(subdir.resolve("module-info.class"));
566 
567         ModuleFinder finder = ModuleFinder.of(dir);
568         try {
569             finder.find("rhubarb");
570             assertTrue(false);
571         } catch (FindException e) {
572             assertTrue(e.getCause() instanceof InvalidModuleDescriptorException);
573         }
574 
575         finder = ModuleFinder.of(dir);
576         try {
577             finder.findAll();
578             assertTrue(false);
579         } catch (FindException e) {
580             assertTrue(e.getCause() instanceof InvalidModuleDescriptorException);
581         }
582     }
583 
584 
585     /**
586      * Test ModuleFinder.compose with no module finders
587      */
588     public void testComposeOfNone() throws Exception {
589         ModuleFinder finder = ModuleFinder.of();
590         assertTrue(finder.findAll().isEmpty());
591         assertFalse(finder.find("java.rhubarb").isPresent());
592     }
593 
594 
595     /**
596      * Test ModuleFinder.compose with one module finder
597      */
598     public void testComposeOfOne() throws Exception {
599         Path dir = Files.createTempDirectory(USER_DIR, "mods");
600         createModularJar(dir.resolve("m1.jar"), "m1");
601         createModularJar(dir.resolve("m2.jar"), "m2");
602 
603         ModuleFinder finder1 = ModuleFinder.of(dir);
604 
605         ModuleFinder finder = ModuleFinder.compose(finder1);
606         assertTrue(finder.findAll().size() == 2);
607         assertTrue(finder.find("m1").isPresent());
608         assertTrue(finder.find("m2").isPresent());
609         assertFalse(finder.find("java.rhubarb").isPresent());
610     }
611 
612 
613     /**
614      * Test ModuleFinder.compose with two module finders
615      */
616     public void testComposeOfTwo() throws Exception {
617         Path dir1 = Files.createTempDirectory(USER_DIR, "mods1");
618         createModularJar(dir1.resolve("m1.jar"), "m1@1.0");
619         createModularJar(dir1.resolve("m2.jar"), "m2@1.0");
620 
621         Path dir2 = Files.createTempDirectory(USER_DIR, "mods2");
622         createModularJar(dir2.resolve("m1.jar"), "m1@2.0");
623         createModularJar(dir2.resolve("m2.jar"), "m2@2.0");
624         createModularJar(dir2.resolve("m3.jar"), "m3");
625         createModularJar(dir2.resolve("m4.jar"), "m4");
626 
627         ModuleFinder finder1 = ModuleFinder.of(dir1);
628         ModuleFinder finder2 = ModuleFinder.of(dir2);
629 
630         ModuleFinder finder = ModuleFinder.compose(finder1, finder2);
631         assertTrue(finder.findAll().size() == 4);
632         assertTrue(finder.find("m1").isPresent());
633         assertTrue(finder.find("m2").isPresent());
634         assertTrue(finder.find("m3").isPresent());
635         assertTrue(finder.find("m4").isPresent());
636         assertFalse(finder.find("java.rhubarb").isPresent());
637 
638         // check that m1@1.0 is found
639         ModuleDescriptor m1 = finder.find("m1").get().descriptor();
640         assertEquals(m1.version().get().toString(), "1.0");
641 
642         // check that m2@1.0 is found
643         ModuleDescriptor m2 = finder.find("m2").get().descriptor();
644         assertEquals(m2.version().get().toString(), "1.0");
645     }
646 
647 
648     /**
649      * Test ModuleFinder.compose with three module finders
650      */
651     public void testComposeOfThree() throws Exception {
652         Path dir1 = Files.createTempDirectory(USER_DIR, "mods1");
653         createModularJar(dir1.resolve("m1.jar"), "m1@1.0");
654         createModularJar(dir1.resolve("m2.jar"), "m2@1.0");
655 
656         Path dir2 = Files.createTempDirectory(USER_DIR, "mods2");
657         createModularJar(dir2.resolve("m1.jar"), "m1@2.0");
658         createModularJar(dir2.resolve("m2.jar"), "m2@2.0");
659         createModularJar(dir2.resolve("m3.jar"), "m3@2.0");
660         createModularJar(dir2.resolve("m4.jar"), "m4@2.0");
661 
662         Path dir3 = Files.createTempDirectory(USER_DIR, "mods3");
663         createModularJar(dir3.resolve("m3.jar"), "m3@3.0");
664         createModularJar(dir3.resolve("m4.jar"), "m4@3.0");
665         createModularJar(dir3.resolve("m5.jar"), "m5");
666         createModularJar(dir3.resolve("m6.jar"), "m6");
667 
668         ModuleFinder finder1 = ModuleFinder.of(dir1);
669         ModuleFinder finder2 = ModuleFinder.of(dir2);
670         ModuleFinder finder3 = ModuleFinder.of(dir3);
671 
672         ModuleFinder finder = ModuleFinder.compose(finder1, finder2, finder3);
673         assertTrue(finder.findAll().size() == 6);
674         assertTrue(finder.find("m1").isPresent());
675         assertTrue(finder.find("m2").isPresent());
676         assertTrue(finder.find("m3").isPresent());
677         assertTrue(finder.find("m4").isPresent());
678         assertTrue(finder.find("m5").isPresent());
679         assertTrue(finder.find("m6").isPresent());
680         assertFalse(finder.find("java.rhubarb").isPresent());
681 
682         // check that m1@1.0 is found
683         ModuleDescriptor m1 = finder.find("m1").get().descriptor();
684         assertEquals(m1.version().get().toString(), "1.0");
685 
686         // check that m2@1.0 is found
687         ModuleDescriptor m2 = finder.find("m2").get().descriptor();
688         assertEquals(m2.version().get().toString(), "1.0");
689 
690         // check that m3@2.0 is found
691         ModuleDescriptor m3 = finder.find("m3").get().descriptor();
692         assertEquals(m3.version().get().toString(), "2.0");
693 
694         // check that m4@2.0 is found
695         ModuleDescriptor m4 = finder.find("m4").get().descriptor();
696         assertEquals(m4.version().get().toString(), "2.0");
697     }
698 
699 
700     /**
701      * Test null handling
702      */
703     public void testNulls() {
704 
705         // ofSystem
706         try {
707             ModuleFinder.ofSystem().find(null);
708             assertTrue(false);
709         } catch (NullPointerException expected) { }
710 
711         // of
712         Path dir = Paths.get("d");
713         try {
714             ModuleFinder.of().find(null);
715             assertTrue(false);
716         } catch (NullPointerException expected) { }
717         try {
718             ModuleFinder.of((Path)null);
719             assertTrue(false);
720         } catch (NullPointerException expected) { }
721         try {
722             ModuleFinder.of((Path[])null);
723             assertTrue(false);
724         } catch (NullPointerException expected) { }
725         try {
726             ModuleFinder.of(dir, null);
727             assertTrue(false);
728         } catch (NullPointerException expected) { }
729         try {
730             ModuleFinder.of(null, dir);
731             assertTrue(false);
732         } catch (NullPointerException expected) { }
733 
734         // compose
735         ModuleFinder finder = ModuleFinder.of();
736         try {
737             ModuleFinder.compose((ModuleFinder)null);
738             assertTrue(false);
739         } catch (NullPointerException expected) { }
740         try {
741             ModuleFinder.compose((ModuleFinder[])null);
742             assertTrue(false);
743         } catch (NullPointerException expected) { }
744         try {
745             ModuleFinder.compose(finder, null);
746             assertTrue(false);
747         } catch (NullPointerException expected) { }
748         try {
749             ModuleFinder.compose(null, finder);
750             assertTrue(false);
751         } catch (NullPointerException expected) { }
752 
753     }
754 
755 
756     /**
757      * Parses a string of the form {@code name[@version]} and returns a
758      * ModuleDescriptor with that name and version. The ModuleDescriptor
759      * will have a requires on java.base.
760      */
761     static ModuleDescriptor newModuleDescriptor(String mid) {
762         String mn;
763         String vs;
764         int i = mid.indexOf("@");
765         if (i == -1) {
766             mn = mid;
767             vs = null;
768         } else {
769             mn = mid.substring(0, i);
770             vs = mid.substring(i+1);
771         }
772         ModuleDescriptor.Builder builder
773             = ModuleDescriptor.newModule(mn).requires("java.base");
774         if (vs != null)
775             builder.version(vs);
776         return builder.build();
777     }
778 
779     /**
780      * Creates an exploded module in the given directory and containing a
781      * module descriptor with the given module name/version.
782      */
783     static Path createExplodedModule(Path dir, String mid, String... entries)
784         throws Exception
785     {
786         ModuleDescriptor descriptor = newModuleDescriptor(mid);
787         Files.createDirectories(dir);
788         Path mi = dir.resolve("module-info.class");
789         try (OutputStream out = Files.newOutputStream(mi)) {
790             ModuleInfoWriter.write(descriptor, out);
791         }
792 
793         for (String entry : entries) {
794             Path file = dir.resolve(entry.replace('/', File.separatorChar));
795             Files.createDirectories(file.getParent());
796             Files.createFile(file);
797         }
798 
799         return dir;
800     }
801 
802     /**
803      * Creates a JAR file with the given file path and containing a module
804      * descriptor with the given module name/version.
805      */
806     static Path createModularJar(Path file, String mid, String ... entries)
807         throws Exception
808     {
809         ModuleDescriptor descriptor = newModuleDescriptor(mid);
810         try (OutputStream out = Files.newOutputStream(file)) {
811             try (JarOutputStream jos = new JarOutputStream(out)) {
812 
813                 JarEntry je = new JarEntry("module-info.class");
814                 jos.putNextEntry(je);
815                 ModuleInfoWriter.write(descriptor, jos);
816                 jos.closeEntry();
817 
818                 for (String entry : entries) {
819                     je = new JarEntry(entry);
820                     jos.putNextEntry(je);
821                     jos.closeEntry();
822                 }
823             }
824 
825         }
826         return file;
827     }
828 
829 }
830