1 /*
2  * Copyright (c) 2015, 2017, 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  * @library /test/lib
27  * @build AutomaticModulesTest
28  *        jdk.test.lib.util.JarUtils
29  *        jdk.test.lib.util.ModuleUtils
30  * @run testng AutomaticModulesTest
31  * @summary Basic tests for automatic modules
32  */
33 
34 import java.io.IOException;
35 import java.lang.module.Configuration;
36 import java.lang.module.FindException;
37 import java.lang.module.ModuleDescriptor;
38 import java.lang.module.ModuleDescriptor.Requires.Modifier;
39 import java.lang.module.ModuleFinder;
40 import java.lang.module.ModuleReference;
41 import java.lang.module.ResolutionException;
42 import java.lang.module.ResolvedModule;
43 import java.nio.file.Files;
44 import java.nio.file.Path;
45 import java.nio.file.Paths;
46 import java.util.Optional;
47 import java.util.Set;
48 import java.util.jar.Attributes;
49 import java.util.jar.Manifest;
50 import java.util.stream.Collectors;
51 import java.util.stream.Stream;
52 
53 import jdk.test.lib.util.JarUtils;
54 import jdk.test.lib.util.ModuleUtils;
55 
56 import org.testng.annotations.DataProvider;
57 import org.testng.annotations.Test;
58 import static org.testng.Assert.*;
59 
60 @Test
61 public class AutomaticModulesTest {
62 
63     private static final Path USER_DIR
64          = Paths.get(System.getProperty("user.dir"));
65 
66     @DataProvider(name = "jarnames")
createJarNames()67     public Object[][] createJarNames() {
68         return new Object[][] {
69 
70             // JAR file name                module-name[/version]
71 
72             { "foo.jar",                    "foo" },
73             { "foo4j.jar",                  "foo4j", },
74 
75             { "foo1.jar",                   "foo1" },
76             { "foo10.jar",                  "foo10" },
77 
78             { "foo-1.jar",                  "foo/1" },
79             { "foo-1.2.jar",                "foo/1.2" },
80             { "foo-1.2.3.jar",              "foo/1.2.3" },
81             { "foo-1.2.3.4.jar",            "foo/1.2.3.4" },
82 
83             { "foo-10.jar",                 "foo/10" },
84             { "foo-10.20.jar",              "foo/10.20" },
85             { "foo-10.20.30.jar",           "foo/10.20.30" },
86             { "foo-10.20.30.40.jar",        "foo/10.20.30.40" },
87 
88             { "foo-bar.jar",                "foo.bar" },
89             { "foo-bar-1.jar",              "foo.bar/1" },
90             { "foo-bar-1.2.jar",            "foo.bar/1.2"},
91             { "foo-bar-10.jar",             "foo.bar/10" },
92             { "foo-bar-10.20.jar",          "foo.bar/10.20" },
93 
94             { "foo.bar1.jar",               "foo.bar1" },
95             { "foo.bar10.jar",              "foo.bar10" },
96 
97             { "foo-1.2-SNAPSHOT.jar",       "foo/1.2-SNAPSHOT" },
98             { "foo-bar-1.2-SNAPSHOT.jar",   "foo.bar/1.2-SNAPSHOT" },
99 
100             { "foo--bar-1.0.jar",           "foo.bar/1.0" },
101             { "-foo-bar-1.0.jar",           "foo.bar/1.0" },
102             { "foo-bar--1.0.jar",           "foo.bar/1.0" },
103 
104         };
105     }
106 
107     // JAR file names that do not map to a legal module name
108     @DataProvider(name = "badjarnames")
createBadNames()109     public Object[][] createBadNames() {
110         return new Object[][]{
111 
112             { ".jar",          null },
113             { "_.jar",         null },
114 
115             { "foo.1.jar",     null },
116             { "1foo.jar",      null },
117             { "foo.1bar.jar",  null },
118 
119         };
120     }
121 
122     /**
123      * Test mapping of JAR file names to module names
124      */
125     @Test(dataProvider = "jarnames")
testNames(String fn, String mid)126     public void testNames(String fn, String mid) throws IOException {
127         String[] s = mid.split("/");
128         String mn = s[0];
129         String vs = (s.length == 2) ? s[1] : null;
130 
131         Path dir = Files.createTempDirectory(USER_DIR, "mods");
132         Path jf = dir.resolve(fn);
133 
134         // create empty JAR file
135         createDummyJarFile(jf);
136 
137         // create a ModuleFinder to find modules in the directory
138         ModuleFinder finder = ModuleFinder.of(dir);
139 
140         // a module with the expected name should be found
141         Optional<ModuleReference> mref = finder.find(mn);
142         assertTrue(mref.isPresent(), mn + " not found");
143 
144         ModuleDescriptor descriptor = mref.get().descriptor();
145         assertTrue(descriptor.isAutomatic());
146         assertEquals(descriptor.name(), mn);
147         if (vs == null) {
148             assertFalse(descriptor.version().isPresent());
149         } else {
150             assertEquals(descriptor.version().get().toString(), vs);
151         }
152     }
153 
154     /**
155      * Test impossible mapping of JAR files to modules names
156      */
157     @Test(dataProvider = "badjarnames", expectedExceptions = FindException.class)
testBadNames(String fn, String ignore)158     public void testBadNames(String fn, String ignore) throws IOException {
159         Path dir = Files.createTempDirectory(USER_DIR, "mods");
160         Path jf = dir.resolve(fn);
161 
162         // create empty JAR file
163         createDummyJarFile(jf);
164 
165         // should throw FindException
166         ModuleFinder.of(dir).findAll();
167     }
168 
169 
170     @DataProvider(name = "modulenames")
createModuleNames()171     public Object[][] createModuleNames() {
172         return new Object[][] {
173             { "foo",        null },
174             { "foo",        "1.0" },
175             { "foo.bar",    null },
176             { "foo.bar",    "1.0" },
177             { "class_",     null },
178             { "class_",     "1.0" },
179         };
180     }
181 
182     @DataProvider(name = "badmodulenames")
createBadModuleNames()183     public Object[][] createBadModuleNames() {
184         return new Object[][] {
185             { "",            null },
186             { "",            "1.0" },
187             { "666",         null },
188             { "666",         "1.0" },
189             { "foo.class",   null },
190             { "foo.class",   "1.0" },
191         };
192     }
193 
194     /**
195      * Test JAR files with the Automatic-Module-Name attribute
196      */
197     @Test(dataProvider = "modulenames")
testAutomaticModuleNameAttribute(String name, String vs)198     public void testAutomaticModuleNameAttribute(String name, String vs)
199         throws IOException
200     {
201         Manifest man = new Manifest();
202         Attributes attrs = man.getMainAttributes();
203         attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0.0");
204         attrs.put(new Attributes.Name("Automatic-Module-Name"), name);
205 
206         Path dir = Files.createTempDirectory(USER_DIR, "mods");
207         String jar;
208         if (vs == null) {
209             jar = "m.jar";
210         } else {
211             jar = "m-" + vs + ".jar";
212         }
213         createDummyJarFile(dir.resolve(jar), man);
214 
215         ModuleFinder finder = ModuleFinder.of(dir);
216 
217         assertTrue(finder.findAll().size() == 1);
218         assertTrue(finder.find(name).isPresent());
219 
220         ModuleReference mref = finder.find(name).get();
221         ModuleDescriptor descriptor = mref.descriptor();
222         assertEquals(descriptor.name(), name);
223         assertEquals(descriptor.version()
224                 .map(ModuleDescriptor.Version::toString)
225                 .orElse(null), vs);
226     }
227 
228     /**
229      * Test JAR files with the Automatic-Module-Name attribute with a value
230      * that is not a legal module name.
231      */
232     @Test(dataProvider = "badmodulenames", expectedExceptions = FindException.class)
testBadAutomaticModuleNameAttribute(String name, String ignore)233     public void testBadAutomaticModuleNameAttribute(String name, String ignore)
234         throws IOException
235     {
236         // should throw FindException
237         testAutomaticModuleNameAttribute(name, null);
238     }
239 
240     /**
241      * Test all packages are exported
242      */
testPackages()243     public void testPackages() throws IOException {
244         Path dir = Files.createTempDirectory(USER_DIR, "mods");
245         createDummyJarFile(dir.resolve("m.jar"),
246                            "p/C1.class", "p/C2.class", "q/C1.class");
247 
248         ModuleFinder finder = ModuleFinder.of(dir);
249         Optional<ModuleReference> mref = finder.find("m");
250         assertTrue(mref.isPresent(), "m not found");
251 
252         ModuleDescriptor descriptor = mref.get().descriptor();
253         assertTrue(descriptor.isAutomatic());
254 
255         assertTrue(descriptor.packages().size() == 2);
256         assertTrue(descriptor.packages().contains("p"));
257         assertTrue(descriptor.packages().contains("q"));
258 
259         assertTrue(descriptor.exports().isEmpty());
260         assertTrue(descriptor.opens().isEmpty());
261     }
262 
263     /**
264      * Test class files in JAR file where the entry does not correspond to a
265      * legal package name.
266      */
testBadPackage()267     public void testBadPackage() throws IOException {
268         Path dir = Files.createTempDirectory(USER_DIR, "mods");
269         createDummyJarFile(dir.resolve("m.jar"), "p/C1.class", "p-/C2.class");
270 
271         ModuleFinder finder = ModuleFinder.of(dir);
272         Optional<ModuleReference> mref = finder.find("m");
273         assertTrue(mref.isPresent(), "m not found");
274 
275         ModuleDescriptor descriptor = mref.get().descriptor();
276         assertTrue(descriptor.isAutomatic());
277 
278         assertTrue(descriptor.packages().size() == 1);
279         assertTrue(descriptor.packages().contains("p"));
280 
281         assertTrue(descriptor.exports().isEmpty());
282         assertTrue(descriptor.opens().isEmpty());
283     }
284 
285     /**
286      * Test non-class resources in a JAR file.
287      */
testNonClassResources()288     public void testNonClassResources() throws IOException {
289         Path dir = Files.createTempDirectory(USER_DIR, "mods");
290         createDummyJarFile(dir.resolve("m.jar"),
291                 "LICENSE",
292                 "README",
293                 "WEB-INF/tags",
294                 "p/Type.class",
295                 "p/resources/m.properties");
296 
297         ModuleFinder finder = ModuleFinder.of(dir);
298         Optional<ModuleReference> mref = finder.find("m");
299         assertTrue(mref.isPresent(), "m not found");
300 
301         ModuleDescriptor descriptor = mref.get().descriptor();
302         assertTrue(descriptor.isAutomatic());
303 
304         assertTrue(descriptor.packages().size() == 1);
305         assertTrue(descriptor.packages().contains("p"));
306     }
307 
308     /**
309      * Test .class file in unnamed package (top-level directory)
310      */
311     @Test(expectedExceptions = FindException.class)
testClassInUnnamedPackage()312     public void testClassInUnnamedPackage() throws IOException {
313         Path dir = Files.createTempDirectory(USER_DIR, "mods");
314         createDummyJarFile(dir.resolve("m.jar"), "Mojo.class");
315         ModuleFinder finder = ModuleFinder.of(dir);
316         finder.findAll();
317     }
318 
319     /**
320      * Test JAR file with META-INF/services configuration file
321      */
testServicesConfiguration()322     public void testServicesConfiguration() throws IOException {
323         String service = "p.S";
324         String provider = "p.S1";
325 
326         Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
327 
328         // provider class
329         Path providerClass = tmpdir.resolve(provider.replace('.', '/') + ".class");
330         Files.createDirectories(providerClass.getParent());
331         Files.createFile(providerClass);
332 
333         // services configuration file
334         Path services = tmpdir.resolve("META-INF").resolve("services");
335         Files.createDirectories(services);
336         Files.write(services.resolve(service), Set.of(provider));
337 
338         Path dir = Files.createTempDirectory(USER_DIR, "mods");
339         JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
340 
341         ModuleFinder finder = ModuleFinder.of(dir);
342 
343         Optional<ModuleReference> mref = finder.find("m");
344         assertTrue(mref.isPresent(), "m not found");
345 
346         ModuleDescriptor descriptor = mref.get().descriptor();
347         assertTrue(descriptor.provides().size() == 1);
348         ModuleDescriptor.Provides provides = descriptor.provides().iterator().next();
349         assertEquals(provides.service(), service);
350         assertTrue(provides.providers().size() == 1);
351         assertTrue(provides.providers().contains((provider)));
352     }
353 
354     // META-INF/services files that don't map to legal service names
355     @DataProvider(name = "badservices")
createBadServices()356     public Object[][] createBadServices() {
357         return new Object[][] {
358 
359                 // service type         provider type
360                 { "-",                  "p.S1" },
361                 { ".S",                 "p.S1" },
362         };
363     }
364 
365     /**
366      * Test JAR file with META-INF/services configuration file with bad
367      * values or names.
368      */
369     @Test(dataProvider = "badservices")
testBadServicesNames(String service, String provider)370     public void testBadServicesNames(String service, String provider)
371         throws IOException
372     {
373         Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
374         Path services = tmpdir.resolve("META-INF").resolve("services");
375         Files.createDirectories(services);
376         Files.write(services.resolve(service), Set.of(provider));
377         Path dir = Files.createTempDirectory(USER_DIR, "mods");
378         JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
379 
380         Optional<ModuleReference> omref = ModuleFinder.of(dir).find("m");
381         assertTrue(omref.isPresent());
382         ModuleDescriptor descriptor = omref.get().descriptor();
383         assertTrue(descriptor.provides().isEmpty());
384     }
385 
386     // META-INF/services configuration file entries that are not legal
387     @DataProvider(name = "badproviders")
createBadProviders()388     public Object[][] createBadProviders() {
389         return new Object[][] {
390 
391                 // service type         provider type
392                 { "p.S",                "-" },
393                 { "p.S",                "p..S1" },
394                 { "p.S",                "S1." },
395         };
396     }
397 
398     /**
399      * Test JAR file with META-INF/services configuration file with bad
400      * values or names.
401      */
402     @Test(dataProvider = "badproviders", expectedExceptions = FindException.class)
testBadProviderNames(String service, String provider)403     public void testBadProviderNames(String service, String provider)
404         throws IOException
405     {
406         Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
407 
408         // provider class
409         Path providerClass = tmpdir.resolve(provider.replace('.', '/') + ".class");
410         Files.createDirectories(providerClass.getParent());
411         Files.createFile(providerClass);
412 
413         // services configuration file
414         Path services = tmpdir.resolve("META-INF").resolve("services");
415         Files.createDirectories(services);
416         Files.write(services.resolve(service), Set.of(provider));
417 
418         Path dir = Files.createTempDirectory(USER_DIR, "mods");
419         JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
420 
421         // should throw FindException
422         ModuleFinder.of(dir).findAll();
423     }
424 
425     /**
426      * Test JAR file with META-INF/services configuration file listing a
427      * provider that is not in the module.
428      */
429     @Test(expectedExceptions = FindException.class)
testMissingProviderPackage()430     public void testMissingProviderPackage() throws IOException {
431         Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
432 
433         // services configuration file
434         Path services = tmpdir.resolve("META-INF").resolve("services");
435         Files.createDirectories(services);
436         Files.write(services.resolve("p.S"), Set.of("q.P"));
437 
438         Path dir = Files.createTempDirectory(USER_DIR, "mods");
439         JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
440 
441         // should throw FindException
442         ModuleFinder.of(dir).findAll();
443     }
444 
445     /**
446      * Test that a JAR file with a Main-Class attribute results
447      * in a module with a main class.
448      */
testMainClass()449     public void testMainClass() throws IOException {
450         String mainClass = "p.Main";
451 
452         Manifest man = new Manifest();
453         Attributes attrs = man.getMainAttributes();
454         attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0.0");
455         attrs.put(Attributes.Name.MAIN_CLASS, mainClass);
456 
457         Path dir = Files.createTempDirectory(USER_DIR, "mods");
458         String entry = mainClass.replace('.', '/') + ".class";
459         createDummyJarFile(dir.resolve("m.jar"), man, entry);
460 
461         ModuleFinder finder = ModuleFinder.of(dir);
462 
463         Configuration parent = ModuleLayer.boot().configuration();
464         Configuration cf = resolve(parent, finder, "m");
465 
466         ModuleDescriptor descriptor = findDescriptor(cf, "m");
467 
468         assertTrue(descriptor.mainClass().isPresent());
469         assertEquals(descriptor.mainClass().get(), mainClass);
470     }
471 
472     // Main-Class files that do not map to a legal qualified type name
473     @DataProvider(name = "badmainclass")
createBadMainClass()474     public Object[][] createBadMainClass() {
475         return new Object[][] {
476             { "p..Main",     null },
477             { "p-.Main",     null },
478 
479         };
480     }
481 
482     /**
483      * Test that a JAR file with a Main-Class attribute that is not a qualified
484      * type name.
485      */
486     @Test(dataProvider = "badmainclass")
testBadMainClass(String mainClass, String ignore)487     public void testBadMainClass(String mainClass, String ignore) throws IOException {
488         Manifest man = new Manifest();
489         Attributes attrs = man.getMainAttributes();
490         attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0.0");
491         attrs.put(Attributes.Name.MAIN_CLASS, mainClass);
492 
493         Path dir = Files.createTempDirectory(USER_DIR, "mods");
494         String entry = mainClass.replace('.', '/') + ".class";
495         createDummyJarFile(dir.resolve("m.jar"), man, entry);
496 
497         // bad Main-Class value should be ignored
498         Optional<ModuleReference> omref = ModuleFinder.of(dir).find("m");
499         assertTrue(omref.isPresent());
500         ModuleDescriptor descriptor = omref.get().descriptor();
501         assertFalse(descriptor.mainClass().isPresent());
502     }
503 
504     /**
505      * Test that a JAR file with a Main-Class attribute that is not in the module
506      */
testMissingMainClassPackage()507     public void testMissingMainClassPackage() throws IOException {
508         Manifest man = new Manifest();
509         Attributes attrs = man.getMainAttributes();
510         attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0.0");
511         attrs.put(Attributes.Name.MAIN_CLASS, "p.Main");
512 
513         Path dir = Files.createTempDirectory(USER_DIR, "mods");
514         createDummyJarFile(dir.resolve("m.jar"), man);
515 
516         // Main-Class should be ignored because package p is not in module
517         Optional<ModuleReference> omref = ModuleFinder.of(dir).find("m");
518         assertTrue(omref.isPresent());
519         ModuleDescriptor descriptor = omref.get().descriptor();
520         assertFalse(descriptor.mainClass().isPresent());
521     }
522 
523     /**
524      * Basic test of a configuration created with automatic modules.
525      *   a requires b*
526      *   a requires c*
527      *   b*
528      *   c*
529      */
testConfiguration1()530     public void testConfiguration1() throws Exception {
531         ModuleDescriptor descriptor1
532             = ModuleDescriptor.newModule("a")
533                 .requires("b")
534                 .requires("c")
535                 .requires("java.base")
536                 .build();
537 
538         // b and c are automatic modules
539         Path dir = Files.createTempDirectory(USER_DIR, "mods");
540         createDummyJarFile(dir.resolve("b.jar"), "p/T.class");
541         createDummyJarFile(dir.resolve("c.jar"), "q/T.class");
542 
543         // module finder locates a and the modules in the directory
544         ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1);
545         ModuleFinder finder2 = ModuleFinder.of(dir);
546         ModuleFinder finder = ModuleFinder.compose(finder1, finder2);
547 
548         Configuration parent = ModuleLayer.boot().configuration();
549         Configuration cf = resolve(parent, finder, "a");
550 
551         assertTrue(cf.modules().size() == 3);
552         assertTrue(cf.findModule("a").isPresent());
553         assertTrue(cf.findModule("b").isPresent());
554         assertTrue(cf.findModule("c").isPresent());
555 
556         ResolvedModule base = cf.findModule("java.base").get();
557         assertTrue(base.configuration() == ModuleLayer.boot().configuration());
558         ResolvedModule a = cf.findModule("a").get();
559         ResolvedModule b = cf.findModule("b").get();
560         ResolvedModule c = cf.findModule("c").get();
561 
562         // b && c only require java.base
563         assertTrue(b.reference().descriptor().requires().size() == 1);
564         assertTrue(c.reference().descriptor().requires().size() == 1);
565 
566         // readability
567 
568         assertTrue(a.reads().size() == 3);
569         assertTrue(a.reads().contains(base));
570         assertTrue(a.reads().contains(b));
571         assertTrue(a.reads().contains(c));
572 
573         assertTrue(b.reads().contains(a));
574         assertTrue(b.reads().contains(c));
575         testReadAllBootModules(cf, "b");  // b reads all modules in boot layer
576 
577         assertTrue(c.reads().contains(a));
578         assertTrue(c.reads().contains(b));
579         testReadAllBootModules(cf, "c");  // c reads all modules in boot layer
580 
581     }
582 
583     /**
584      * Basic test of a configuration created with automatic modules
585      *   a requires b
586      *   b requires c*
587      *   c*
588      *   d*
589      */
testInConfiguration2()590     public void testInConfiguration2() throws IOException {
591         ModuleDescriptor descriptor1
592             = ModuleDescriptor.newModule("a")
593                 .requires("b")
594                 .requires("java.base")
595                 .build();
596 
597         ModuleDescriptor descriptor2
598             = ModuleDescriptor.newModule("b")
599                 .requires("c")
600                 .requires("java.base")
601                 .build();
602 
603         // c and d are automatic modules
604         Path dir = Files.createTempDirectory(USER_DIR, "mods");
605         createDummyJarFile(dir.resolve("c.jar"), "p/T.class");
606         createDummyJarFile(dir.resolve("d.jar"), "q/T.class");
607 
608         // module finder locates a and the modules in the directory
609         ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2);
610         ModuleFinder finder2 = ModuleFinder.of(dir);
611         ModuleFinder finder = ModuleFinder.compose(finder1, finder2);
612 
613         Configuration parent = ModuleLayer.boot().configuration();
614         Configuration cf = resolve(parent, finder, "a", "d");
615 
616         assertTrue(cf.modules().size() == 4);
617         assertTrue(cf.findModule("a").isPresent());
618         assertTrue(cf.findModule("b").isPresent());
619         assertTrue(cf.findModule("c").isPresent());
620         assertTrue(cf.findModule("d").isPresent());
621 
622         // c && d should only require java.base
623         assertTrue(findDescriptor(cf, "c").requires().size() == 1);
624         assertTrue(findDescriptor(cf, "d").requires().size() == 1);
625 
626         // readability
627 
628         ResolvedModule base = cf.findModule("java.base").get();
629         assertTrue(base.configuration() == ModuleLayer.boot().configuration());
630         ResolvedModule a = cf.findModule("a").get();
631         ResolvedModule b = cf.findModule("b").get();
632         ResolvedModule c = cf.findModule("c").get();
633         ResolvedModule d = cf.findModule("d").get();
634 
635         assertTrue(a.reads().size() == 2);
636         assertTrue(a.reads().contains(b));
637         assertTrue(a.reads().contains(base));
638 
639         assertTrue(b.reads().size() == 3);
640         assertTrue(b.reads().contains(c));
641         assertTrue(b.reads().contains(d));
642         assertTrue(b.reads().contains(base));
643 
644         assertTrue(c.reads().contains(a));
645         assertTrue(c.reads().contains(b));
646         assertTrue(c.reads().contains(d));
647         testReadAllBootModules(cf, "c");   // c reads all modules in boot layer
648 
649         assertTrue(d.reads().contains(a));
650         assertTrue(d.reads().contains(b));
651         assertTrue(d.reads().contains(c));
652         testReadAllBootModules(cf, "d");    // d reads all modules in boot layer
653     }
654 
655     /**
656      * Basic test of a configuration created with automatic modules
657      *   a requires b
658      *   b requires transitive c*
659      *   c*
660      *   d*
661      */
testInConfiguration3()662     public void testInConfiguration3() throws IOException {
663         ModuleDescriptor descriptor1
664             = ModuleDescriptor.newModule("a")
665                 .requires("b")
666                 .requires("java.base")
667                 .build();
668 
669         ModuleDescriptor descriptor2
670             = ModuleDescriptor.newModule("b")
671                 .requires(Set.of(Modifier.TRANSITIVE), "c")
672                 .requires("java.base")
673                 .build();
674 
675         // c and d are automatic modules
676         Path dir = Files.createTempDirectory(USER_DIR, "mods");
677         createDummyJarFile(dir.resolve("c.jar"), "p/T.class");
678         createDummyJarFile(dir.resolve("d.jar"), "q/T.class");
679 
680         // module finder locates a and the modules in the directory
681         ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2);
682         ModuleFinder finder2 = ModuleFinder.of(dir);
683         ModuleFinder finder = ModuleFinder.compose(finder1, finder2);
684 
685         Configuration parent = ModuleLayer.boot().configuration();
686         Configuration cf = resolve(parent, finder, "a", "d");
687 
688         assertTrue(cf.modules().size() == 4);
689         assertTrue(cf.findModule("a").isPresent());
690         assertTrue(cf.findModule("b").isPresent());
691         assertTrue(cf.findModule("c").isPresent());
692         assertTrue(cf.findModule("d").isPresent());
693 
694         ResolvedModule base = cf.findModule("java.base").get();
695         assertTrue(base.configuration() == ModuleLayer.boot().configuration());
696         ResolvedModule a = cf.findModule("a").get();
697         ResolvedModule b = cf.findModule("b").get();
698         ResolvedModule c = cf.findModule("c").get();
699         ResolvedModule d = cf.findModule("d").get();
700 
701         // c && d should only require java.base
702         assertTrue(findDescriptor(cf, "c").requires().size() == 1);
703         assertTrue(findDescriptor(cf, "d").requires().size() == 1);
704 
705         // readability
706 
707         assertTrue(a.reads().size() == 4);
708         assertTrue(a.reads().contains(b));
709         assertTrue(a.reads().contains(c));
710         assertTrue(a.reads().contains(d));
711         assertTrue(a.reads().contains(base));
712 
713         assertTrue(b.reads().size() == 3);
714         assertTrue(b.reads().contains(c));
715         assertTrue(b.reads().contains(d));
716         assertTrue(b.reads().contains(base));
717 
718         assertTrue(reads(cf, "b", "c"));
719         assertTrue(reads(cf, "b", "d"));
720         assertTrue(reads(cf, "b", "java.base"));
721 
722         assertTrue(c.reads().contains(a));
723         assertTrue(c.reads().contains(b));
724         assertTrue(c.reads().contains(d));
725         testReadAllBootModules(cf, "c");   // c reads all modules in boot layer
726 
727         assertTrue(d.reads().contains(a));
728         assertTrue(d.reads().contains(b));
729         assertTrue(d.reads().contains(c));
730         testReadAllBootModules(cf, "d");    // d reads all modules in boot layer
731     }
732 
733     /**
734      * Basic test to ensure that no automatic modules are resolved when
735      * an automatic module is not a root or required by other modules.
736      */
testInConfiguration4()737     public void testInConfiguration4() throws IOException {
738         ModuleDescriptor descriptor1
739             = ModuleDescriptor.newModule("m1")
740                 .requires("java.base")
741                 .build();
742 
743         // automatic modules
744         Path dir = Files.createTempDirectory(USER_DIR, "mods");
745         createDummyJarFile(dir.resolve("auto1.jar"), "p1/C.class");
746         createDummyJarFile(dir.resolve("auto2.jar"), "p2/C.class");
747         createDummyJarFile(dir.resolve("auto3.jar"), "p3/C.class");
748 
749         // module finder locates m1 and the modules in the directory
750         ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1);
751         ModuleFinder finder2 =  ModuleFinder.of(dir);
752         ModuleFinder finder = ModuleFinder.compose(finder1, finder2);
753 
754         Configuration parent = ModuleLayer.boot().configuration();
755         Configuration cf = resolve(parent, finder, "m1");
756 
757         // ensure that no automatic module is resolved
758         assertTrue(cf.modules().size() == 1);
759         assertTrue(cf.findModule("m1").isPresent());
760     }
761 
762     /**
763      * Basic test to ensure that if an automatic module is resolved then
764      * all observable automatic modules are resolved.
765      */
testInConfiguration5()766     public void testInConfiguration5() throws IOException {
767         // m1 requires m2
768         ModuleDescriptor descriptor1
769             = ModuleDescriptor.newModule("m1")
770                 .requires("m2").build();
771 
772         // m2 requires automatic module
773         ModuleDescriptor descriptor2
774             = ModuleDescriptor.newModule("m2")
775                 .requires("auto1")
776                 .build();
777 
778         // automatic modules
779         Path dir = Files.createTempDirectory(USER_DIR, "mods");
780         createDummyJarFile(dir.resolve("auto1.jar"), "p1/C.class");
781         createDummyJarFile(dir.resolve("auto2.jar"), "p2/C.class");
782         createDummyJarFile(dir.resolve("auto3.jar"), "p3/C.class");
783 
784         // module finder locates m1, m2, and the modules in the directory
785         ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2);
786         ModuleFinder finder2 =  ModuleFinder.of(dir);
787         ModuleFinder finder = ModuleFinder.compose(finder1, finder2);
788 
789         Configuration parent = ModuleLayer.boot().configuration();
790         Configuration cf = resolve(parent, finder, "m1");
791 
792         // all automatic modules should be resolved
793         assertTrue(cf.modules().size() == 5);
794         assertTrue(cf.findModule("m1").isPresent());
795         assertTrue(cf.findModule("m2").isPresent());
796         assertTrue(cf.findModule("auto1").isPresent());
797         assertTrue(cf.findModule("auto2").isPresent());
798         assertTrue(cf.findModule("auto3").isPresent());
799 
800         ResolvedModule base = parent.findModule("java.base")
801                                     .orElseThrow(() -> new RuntimeException());
802         ResolvedModule m1 = cf.findModule("m1").get();
803         ResolvedModule m2 = cf.findModule("m2").get();
804         ResolvedModule auto1 = cf.findModule("auto1").get();
805         ResolvedModule auto2 = cf.findModule("auto2").get();
806         ResolvedModule auto3 = cf.findModule("auto3").get();
807 
808         // m1 does not read the automatic modules
809         assertTrue(m1.reads().size() == 2);
810         assertTrue(m1.reads().contains(m2));
811         assertTrue(m1.reads().contains(base));
812 
813         // m2 should read all the automatic modules
814         assertTrue(m2.reads().size() == 4);
815         assertTrue(m2.reads().contains(auto1));
816         assertTrue(m2.reads().contains(auto2));
817         assertTrue(m2.reads().contains(auto3));
818         assertTrue(m2.reads().contains(base));
819 
820         assertTrue(auto1.reads().contains(m1));
821         assertTrue(auto1.reads().contains(m2));
822         assertTrue(auto1.reads().contains(auto2));
823         assertTrue(auto1.reads().contains(auto3));
824         assertTrue(auto1.reads().contains(base));
825 
826         assertTrue(auto2.reads().contains(m1));
827         assertTrue(auto2.reads().contains(m2));
828         assertTrue(auto2.reads().contains(auto1));
829         assertTrue(auto2.reads().contains(auto3));
830         assertTrue(auto2.reads().contains(base));
831 
832         assertTrue(auto3.reads().contains(m1));
833         assertTrue(auto3.reads().contains(m2));
834         assertTrue(auto3.reads().contains(auto1));
835         assertTrue(auto3.reads().contains(auto2));
836         assertTrue(auto3.reads().contains(base));
837     }
838 
839     /**
840      * Basic test of automatic modules in a child configuration. All automatic
841      * modules that are found with the before finder should be resolved. The
842      * automatic modules that are found by the after finder and not shadowed
843      * by the before finder, or parent configurations, should also be resolved.
844      */
testInConfiguration6()845     public void testInConfiguration6() throws IOException {
846         // m1 requires auto1
847         ModuleDescriptor descriptor1
848             = ModuleDescriptor.newModule("m1")
849                 .requires("auto1")
850                 .build();
851 
852         Path dir = Files.createTempDirectory(USER_DIR, "mods");
853         createDummyJarFile(dir.resolve("auto1.jar"), "p1/C.class");
854 
855         // module finder locates m1 and auto1
856         ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1);
857         ModuleFinder finder2 =  ModuleFinder.of(dir);
858         ModuleFinder finder = ModuleFinder.compose(finder1, finder2);
859 
860         Configuration parent = ModuleLayer.boot().configuration();
861         Configuration cf1 = resolve(parent, finder, "m1");
862 
863         assertTrue(cf1.modules().size() == 2);
864         assertTrue(cf1.findModule("m1").isPresent());
865         assertTrue(cf1.findModule("auto1").isPresent());
866 
867         ResolvedModule base = parent.findModule("java.base")
868                                     .orElseThrow(() -> new RuntimeException());
869         ResolvedModule m1 = cf1.findModule("m1").get();
870         ResolvedModule auto1 = cf1.findModule("auto1").get();
871 
872         assertTrue(m1.reads().size() == 2);
873         assertTrue(m1.reads().contains(auto1));
874         assertTrue(m1.reads().contains(base));
875 
876         assertTrue(auto1.reads().contains(m1));
877         assertTrue(auto1.reads().contains(base));
878 
879 
880         // create child configuration - the after finder locates auto1
881 
882         dir = Files.createTempDirectory(USER_DIR, "mods");
883         createDummyJarFile(dir.resolve("auto2.jar"), "p2/C.class");
884         ModuleFinder beforeFinder =  ModuleFinder.of(dir);
885 
886         dir = Files.createTempDirectory(USER_DIR, "mods");
887         createDummyJarFile(dir.resolve("auto1.jar"), "p1/C.class");
888         createDummyJarFile(dir.resolve("auto2.jar"), "p2/C.class");
889         createDummyJarFile(dir.resolve("auto3.jar"), "p3/C.class");
890         ModuleFinder afterFinder =  ModuleFinder.of(dir);
891 
892         Configuration cf2 = cf1.resolve(beforeFinder, afterFinder, Set.of("auto2"));
893 
894         // auto1 should be found in parent and should not be in cf2
895         assertTrue(cf2.modules().size() == 2);
896         assertTrue(cf2.findModule("auto2").isPresent());
897         assertTrue(cf2.findModule("auto3").isPresent());
898 
899         ResolvedModule auto2 = cf2.findModule("auto2").get();
900         ResolvedModule auto3 = cf2.findModule("auto3").get();
901 
902         assertTrue(auto2.reads().contains(m1));
903         assertTrue(auto2.reads().contains(auto1));
904         assertTrue(auto2.reads().contains(auto3));
905         assertTrue(auto2.reads().contains(base));
906 
907         assertTrue(auto3.reads().contains(m1));
908         assertTrue(auto3.reads().contains(auto1));
909         assertTrue(auto3.reads().contains(auto2));
910         assertTrue(auto3.reads().contains(base));
911     }
912 
913     /**
914      * Basic test of a configuration created with automatic modules
915      *   a requires b* and c*
916      *   b* contains p
917      *   c* contains p
918      */
919     @Test(expectedExceptions = { ResolutionException.class })
testDuplicateSuppliers1()920     public void testDuplicateSuppliers1() throws IOException {
921         ModuleDescriptor descriptor
922             = ModuleDescriptor.newModule("a")
923                 .requires("b")
924                 .requires("c")
925                 .build();
926 
927         // c and d are automatic modules with the same package
928         Path dir = Files.createTempDirectory(USER_DIR, "mods");
929         createDummyJarFile(dir.resolve("b.jar"), "p/T.class");
930         createDummyJarFile(dir.resolve("c.jar"), "p/T.class");
931 
932         // module finder locates 'a' and the modules in the directory
933         ModuleFinder finder
934             = ModuleFinder.compose(ModuleUtils.finderOf(descriptor),
935                                    ModuleFinder.of(dir));
936 
937         Configuration parent = ModuleLayer.boot().configuration();
938         resolve(parent, finder, "a");
939     }
940 
941     /**
942      * Basic test of a configuration created with automatic modules
943      *   a contains p, requires b*
944      *   b* contains p
945      */
946     @Test(expectedExceptions = { ResolutionException.class })
testDuplicateSuppliers2()947     public void testDuplicateSuppliers2() throws IOException {
948         ModuleDescriptor descriptor
949             = ModuleDescriptor.newModule("a")
950                 .packages(Set.of("p"))
951                 .requires("b")
952                 .build();
953 
954         // c and d are automatic modules with the same package
955         Path dir = Files.createTempDirectory(USER_DIR, "mods");
956         createDummyJarFile(dir.resolve("b.jar"), "p/T.class");
957 
958         // module finder locates 'a' and the modules in the directory
959         ModuleFinder finder
960             = ModuleFinder.compose(ModuleUtils.finderOf(descriptor),
961                                    ModuleFinder.of(dir));
962 
963         Configuration parent = ModuleLayer.boot().configuration();
964         resolve(parent, finder, "a");
965     }
966 
967     /**
968      * Basic test of layer containing automatic modules
969      */
testInLayer()970     public void testInLayer() throws IOException {
971         ModuleDescriptor descriptor
972             = ModuleDescriptor.newModule("a")
973                 .requires("b")
974                 .requires("c")
975                 .build();
976 
977         // b and c are simple JAR files
978         Path dir = Files.createTempDirectory(USER_DIR, "mods");
979         createDummyJarFile(dir.resolve("b.jar"), "p/T.class");
980         createDummyJarFile(dir.resolve("c.jar"), "q/T2.class");
981 
982         // module finder locates a and the modules in the directory
983         ModuleFinder finder
984             = ModuleFinder.compose(ModuleUtils.finderOf(descriptor),
985                 ModuleFinder.of(dir));
986 
987         Configuration parent = ModuleLayer.boot().configuration();
988         Configuration cf = resolve(parent, finder, "a");
989         assertTrue(cf.modules().size() == 3);
990 
991         // each module gets its own loader
992         ModuleLayer layer = ModuleLayer.boot().defineModules(cf, mn -> new ClassLoader() { });
993 
994         // an unnamed module
995         Module unnamed = (new ClassLoader() { }).getUnnamedModule();
996 
997         Module b = layer.findModule("b").get();
998         assertTrue(b.isNamed());
999         assertTrue(b.canRead(unnamed));
1000         testsReadsAll(b, layer);
1001 
1002         Module c = layer.findModule("c").get();
1003         assertTrue(c.isNamed());
1004         assertTrue(b.canRead(unnamed));
1005         testsReadsAll(c, layer);
1006     }
1007 
1008     /**
1009      * Test miscellaneous methods.
1010      */
testMisc()1011     public void testMisc() throws IOException {
1012         Path dir = Files.createTempDirectory(USER_DIR, "mods");
1013         Path m_jar = createDummyJarFile(dir.resolve("m.jar"), "p/T.class");
1014 
1015         ModuleFinder finder = ModuleFinder.of(m_jar);
1016 
1017         assertTrue(finder.find("m").isPresent());
1018         ModuleDescriptor m = finder.find("m").get().descriptor();
1019 
1020         // test miscellaneous methods
1021         assertTrue(m.isAutomatic());
1022         assertFalse(m.modifiers().contains(ModuleDescriptor.Modifier.SYNTHETIC));
1023     }
1024 
1025     /**
1026      * Invokes parent.resolve to resolve the given root modules.
1027      */
resolve(Configuration parent, ModuleFinder finder, String... roots)1028     static Configuration resolve(Configuration parent,
1029                                  ModuleFinder finder,
1030                                  String... roots) {
1031         return parent.resolve(finder, ModuleFinder.of(), Set.of(roots));
1032     }
1033 
1034     /**
1035      * Finds a module in the given configuration or its parents, returning
1036      * the module descriptor (or null if not found)
1037      */
findDescriptor(Configuration cf, String name)1038     static ModuleDescriptor findDescriptor(Configuration cf, String name) {
1039         Optional<ResolvedModule> om = cf.findModule(name);
1040         if (om.isPresent()) {
1041             return om.get().reference().descriptor();
1042         } else {
1043             return null;
1044         }
1045     }
1046 
1047     /**
1048      * Test that a module in a configuration reads all modules in the boot
1049      * configuration.
1050      */
testReadAllBootModules(Configuration cf, String mn)1051     static void testReadAllBootModules(Configuration cf, String mn) {
1052 
1053         Set<String> bootModules = ModuleLayer.boot().modules().stream()
1054                 .map(Module::getName)
1055                 .collect(Collectors.toSet());
1056 
1057         bootModules.forEach(other -> assertTrue(reads(cf, mn, other)));
1058 
1059     }
1060 
1061     /**
1062      * Test that the given Module reads all module in the given layer
1063      * and its parent layers.
1064      */
testsReadsAll(Module m, ModuleLayer layer)1065     static void testsReadsAll(Module m, ModuleLayer layer) {
1066         // check that m reads all modules in the layer
1067         layer.configuration().modules().stream()
1068             .map(ResolvedModule::name)
1069             .map(layer::findModule)
1070             .map(Optional::get)
1071             .forEach(other -> assertTrue(m.canRead(other)));
1072 
1073         // also check parent layers
1074         layer.parents().forEach(l -> testsReadsAll(m, l));
1075     }
1076 
1077     /**
1078      * Returns {@code true} if the configuration contains module mn1
1079      * that reads module mn2.
1080      */
reads(Configuration cf, String mn1, String mn2)1081     static boolean reads(Configuration cf, String mn1, String mn2) {
1082         Optional<ResolvedModule> om = cf.findModule(mn1);
1083         if (!om.isPresent())
1084             return false;
1085 
1086         return om.get().reads().stream()
1087                 .map(ResolvedModule::name)
1088                 .anyMatch(mn2::equals);
1089     }
1090 
1091     /**
1092      * Creates a JAR file, optionally with a manifest, and with the given
1093      * entries. The entries will be empty in the resulting JAR file.
1094      */
createDummyJarFile(Path jarfile, Manifest man, String... entries)1095     static Path createDummyJarFile(Path jarfile, Manifest man, String... entries)
1096         throws IOException
1097     {
1098         Path dir = Files.createTempDirectory(USER_DIR, "tmp");
1099 
1100         for (String entry : entries) {
1101             Path file = dir.resolve(entry);
1102             Path parent = file.getParent();
1103             if (parent != null)
1104                 Files.createDirectories(parent);
1105             Files.createFile(file);
1106         }
1107 
1108         Path[] paths = Stream.of(entries).map(Paths::get).toArray(Path[]::new);
1109         JarUtils.createJarFile(jarfile, man, dir, paths);
1110         return jarfile;
1111     }
1112 
1113     /**
1114      * Creates a JAR file and with the given entries. The entries will be empty
1115      * in the resulting JAR file.
1116      */
createDummyJarFile(Path jarfile, String... entries)1117     static Path createDummyJarFile(Path jarfile, String... entries)
1118         throws IOException
1119     {
1120         return createDummyJarFile(jarfile, null, entries);
1121     }
1122 
1123 }
1124