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