1 /*
   2  * Copyright (c) 2015, 2016, 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 /lib/testlibrary
  27  * @build AutomaticModulesTest ModuleUtils JarUtils
  28  * @run testng AutomaticModulesTest
  29  * @summary Basic tests for automatic modules
  30  */
  31 
  32 import java.io.IOException;
  33 import java.lang.module.Configuration;
  34 import java.lang.module.FindException;
  35 import java.lang.module.ModuleDescriptor;
  36 import java.lang.module.ModuleDescriptor.Exports;
  37 import java.lang.module.ModuleDescriptor.Requires.Modifier;
  38 import java.lang.module.ModuleFinder;
  39 import java.lang.module.ModuleReference;
  40 import java.lang.module.ResolvedModule;
  41 import java.lang.reflect.Layer;
  42 import java.lang.reflect.Module;
  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 org.testng.annotations.DataProvider;
  54 import org.testng.annotations.Test;
  55 import static org.testng.Assert.*;
  56 
  57 @Test
  58 public class AutomaticModulesTest {
  59 
  60     private static final Path USER_DIR
  61          = Paths.get(System.getProperty("user.dir"));
  62 
  63     @DataProvider(name = "names")
  64     public Object[][] createNames() {
  65         return new Object[][] {
  66 
  67             // JAR file name                module-name[/version]
  68 
  69             { "foo.jar",                    "foo" },
  70             { "foo4j.jar",                  "foo4j", },
  71 
  72             { "foo1.jar",                   "foo" },
  73             { "foo1.2.jar",                 "foo" },
  74             { "foo1.2.3.jar",               "foo" },
  75 
  76             { "foo10.jar",                  "foo" },
  77             { "foo10.20.jar",               "foo" },
  78             { "foo10.20.30.jar",            "foo" },
  79 
  80             { "foo-1.jar",                  "foo/1" },
  81             { "foo-1.2.jar",                "foo/1.2" },
  82             { "foo-1.2.3.jar",              "foo/1.2.3" },
  83             { "foo-1.2.3.4.jar",            "foo/1.2.3.4" },
  84 
  85             { "foo-10.jar",                 "foo/10" },
  86             { "foo-10.20.jar",              "foo/10.20" },
  87             { "foo-10.20.30.jar",           "foo/10.20.30" },
  88             { "foo-10.20.30.40.jar",        "foo/10.20.30.40" },
  89 
  90             { "foo-bar.jar",                "foo.bar" },
  91             { "foo-bar-1.jar",              "foo.bar/1" },
  92             { "foo-bar-1.2.jar",            "foo.bar/1.2"},
  93             { "foo-bar-10.jar",             "foo.bar/10" },
  94             { "foo-bar-10.20.jar",          "foo.bar/10.20" },
  95 
  96             { "foo-1.2-SNAPSHOT.jar",       "foo/1.2-SNAPSHOT" },
  97             { "foo-bar-1.2-SNAPSHOT.jar",   "foo.bar/1.2-SNAPSHOT" },
  98 
  99             { "foo--bar-1.0.jar",           "foo.bar/1.0" },
 100             { "-foo-bar-1.0.jar",           "foo.bar/1.0" },
 101             { "foo-bar--1.0.jar",           "foo.bar/1.0" },
 102 
 103         };
 104     }
 105 
 106     // JAR file names that do not map to a legal module name
 107     @DataProvider(name = "badnames")
 108     public Object[][] createBadNames() {
 109         return new Object[][]{
 110 
 111             { ".jar",     null },
 112             { "_.jar",    null }
 113 
 114         };
 115     }
 116 
 117     /**
 118      * Test mapping of JAR file names to module names
 119      */
 120     @Test(dataProvider = "names")
 121     public void testNames(String fn, String mid) throws IOException {
 122         String[] s = mid.split("/");
 123         String mn = s[0];
 124         String vs = (s.length == 2) ? s[1] : null;
 125 
 126         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 127         Path jf = dir.resolve(fn);
 128 
 129         // create empty JAR file
 130         createDummyJarFile(jf);
 131 
 132         // create a ModuleFinder to find modules in the directory
 133         ModuleFinder finder = ModuleFinder.of(dir);
 134 
 135         // a module with the expected name should be found
 136         Optional<ModuleReference> mref = finder.find(mn);
 137         assertTrue(mref.isPresent(), mn + " not found");
 138 
 139         ModuleDescriptor descriptor = mref.get().descriptor();
 140         assertEquals(descriptor.name(), mn);
 141         if (vs == null) {
 142             assertFalse(descriptor.version().isPresent());
 143         } else {
 144             assertEquals(descriptor.version().get().toString(), vs);
 145         }
 146     }
 147 
 148 
 149     /**
 150      * Test impossible mapping of JAR files to modules names
 151      */
 152     @Test(dataProvider = "badnames", expectedExceptions = FindException.class)
 153     public void testBadNames(String fn, String ignore) throws IOException {
 154         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 155         Path jf = dir.resolve(fn);
 156 
 157         // create empty JAR file
 158         createDummyJarFile(jf);
 159 
 160         // should throw FindException
 161         ModuleFinder.of(dir).findAll();
 162     }
 163 
 164 
 165     /**
 166      * Test all packages are exported
 167      */
 168     public void testPackages() throws IOException {
 169         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 170         createDummyJarFile(dir.resolve("m.jar"),
 171                            "p/C1.class", "p/C2.class", "q/C1.class");
 172 
 173         ModuleFinder finder = ModuleFinder.of(dir);
 174         Optional<ModuleReference> mref = finder.find("m");
 175         assertTrue(mref.isPresent(), "m not found");
 176 
 177         ModuleDescriptor descriptor = mref.get().descriptor();
 178 
 179         assertTrue(descriptor.packages().size() == 2);
 180         assertTrue(descriptor.packages().contains("p"));
 181         assertTrue(descriptor.packages().contains("q"));
 182 
 183         Set<String> exports = descriptor.exports().stream()
 184                 .map(Exports::source)
 185                 .collect(Collectors.toSet());
 186         assertTrue(exports.size() == 2);
 187         assertTrue(exports.contains("p"));
 188         assertTrue(exports.contains("q"));
 189     }
 190 
 191     /**
 192      * Test class files in JAR file where the entry does not correspond to a
 193      * legal package name.
 194      */
 195     public void testBadPackage() throws IOException {
 196         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 197         createDummyJarFile(dir.resolve("m.jar"), "p/C1.class", "p-/C2.class");
 198 
 199         ModuleFinder finder = ModuleFinder.of(dir);
 200         Optional<ModuleReference> mref = finder.find("m");
 201         assertTrue(mref.isPresent(), "m not found");
 202 
 203         ModuleDescriptor descriptor = mref.get().descriptor();
 204 
 205         assertTrue(descriptor.packages().size() == 1);
 206         assertTrue(descriptor.packages().contains("p"));
 207 
 208         Set<String> exports = descriptor.exports().stream()
 209                 .map(Exports::source)
 210                 .collect(Collectors.toSet());
 211         assertTrue(exports.size() == 1);
 212         assertTrue(exports.contains("p"));
 213     }
 214 
 215     /**
 216      * Test non-class resources in a JAR file.
 217      */
 218     public void testNonClassResources() throws IOException {
 219         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 220         createDummyJarFile(dir.resolve("m.jar"),
 221                 "LICENSE",
 222                 "README",
 223                 "WEB-INF/tags",
 224                 "p/Type.class",
 225                 "p/resources/m.properties");
 226 
 227         ModuleFinder finder = ModuleFinder.of(dir);
 228         Optional<ModuleReference> mref = finder.find("m");
 229         assertTrue(mref.isPresent(), "m not found");
 230 
 231         ModuleDescriptor descriptor = mref.get().descriptor();
 232 
 233         assertTrue(descriptor.packages().size() == 2);
 234         assertTrue(descriptor.packages().contains("p"));
 235         assertTrue(descriptor.packages().contains("p.resources"));
 236     }
 237 
 238     /**
 239      * Test .class file in unnamed package (top-level directory)
 240      */
 241     @Test(expectedExceptions = FindException.class)
 242     public void testClassInUnnamedPackage() throws IOException {
 243         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 244         createDummyJarFile(dir.resolve("m.jar"), "Mojo.class");
 245         ModuleFinder finder = ModuleFinder.of(dir);
 246         finder.findAll();
 247     }
 248 
 249     /**
 250      * Test JAR file with META-INF/services configuration file
 251      */
 252     public void testServicesConfiguration() throws IOException {
 253         String service = "p.S";
 254         String provider = "p.S1";
 255 
 256         Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
 257         Path services = tmpdir.resolve("META-INF").resolve("services");
 258         Files.createDirectories(services);
 259         Files.write(services.resolve(service), Set.of(provider));
 260         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 261         JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
 262 
 263         ModuleFinder finder = ModuleFinder.of(dir);
 264 
 265         Optional<ModuleReference> mref = finder.find("m");
 266         assertTrue(mref.isPresent(), "m not found");
 267 
 268         ModuleDescriptor descriptor = mref.get().descriptor();
 269         assertTrue(descriptor.provides().size() == 1);
 270         ModuleDescriptor.Provides provides = descriptor.provides().iterator().next();
 271         assertEquals(provides.service(), service);
 272         assertTrue(provides.providers().size() == 1);
 273         assertTrue(provides.providers().contains((provider)));
 274     }
 275 
 276 
 277     // META-INF/services files that don't map to legal service names
 278     @DataProvider(name = "badservices")
 279     public Object[][] createBadServices() {
 280         return new Object[][] {
 281 
 282                 // service type         provider type
 283                 { "-",                  "p.S1" },
 284                 { ".S",                 "p.S1" },
 285         };
 286     }
 287 
 288     /**
 289      * Test JAR file with META-INF/services configuration file with bad
 290      * values or names.
 291      */
 292     @Test(dataProvider = "badservices")
 293     public void testBadServicesNames(String service, String provider)
 294         throws IOException
 295     {
 296         Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
 297         Path services = tmpdir.resolve("META-INF").resolve("services");
 298         Files.createDirectories(services);
 299         Files.write(services.resolve(service), Set.of(provider));
 300         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 301         JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
 302 
 303         Optional<ModuleReference> omref = ModuleFinder.of(dir).find("m");
 304         assertTrue(omref.isPresent());
 305         ModuleDescriptor descriptor = omref.get().descriptor();
 306         assertTrue(descriptor.provides().isEmpty());
 307     }
 308 
 309 
 310     // META-INF/services configuration file entries that are not legal
 311     @DataProvider(name = "badproviders")
 312     public Object[][] createBadProviders() {
 313         return new Object[][] {
 314 
 315                 // service type         provider type
 316                 { "p.S",                "-" },
 317                 { "p.S",                ".S1" },
 318                 { "p.S",                "S1." },
 319         };
 320     }
 321 
 322     /**
 323      * Test JAR file with META-INF/services configuration file with bad
 324      * values or names.
 325      */
 326     @Test(dataProvider = "badproviders", expectedExceptions = FindException.class)
 327     public void testBadProvideNames(String service, String provider)
 328         throws IOException
 329     {
 330         Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
 331         Path services = tmpdir.resolve("META-INF").resolve("services");
 332         Files.createDirectories(services);
 333         Files.write(services.resolve(service), Set.of(provider));
 334         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 335         JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
 336 
 337         // should throw FindException
 338         ModuleFinder.of(dir).findAll();
 339     }
 340 
 341 
 342     /**
 343      * Test that a JAR file with a Main-Class attribute results
 344      * in a module with a main class.
 345      */
 346     public void testMainClass() throws IOException {
 347         String mainClass = "p.Main";
 348 
 349         Manifest man = new Manifest();
 350         Attributes attrs = man.getMainAttributes();
 351         attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0.0");
 352         attrs.put(Attributes.Name.MAIN_CLASS, mainClass);
 353 
 354         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 355         createDummyJarFile(dir.resolve("m.jar"), man);
 356 
 357         ModuleFinder finder = ModuleFinder.of(dir);
 358 
 359         Configuration parent = Layer.boot().configuration();
 360         Configuration cf = resolve(parent, finder, "m");
 361 
 362         ModuleDescriptor descriptor = findDescriptor(cf, "m");
 363 
 364         assertTrue(descriptor.mainClass().isPresent());
 365         assertEquals(descriptor.mainClass().get(), mainClass);
 366     }
 367 
 368 
 369     // Main-Class files that do not map to a legal Java identifier
 370     @DataProvider(name = "badmainclass")
 371     public Object[][] createBadMainClass() {
 372         return new Object[][]{
 373 
 374             { "p-.Main",     null },
 375             { ".Main",       null }
 376 
 377         };
 378     }
 379 
 380     /**
 381      * Test that a JAR file with a Main-Class attribute that is not a valid
 382      * Java identifier
 383      */
 384     @Test(dataProvider = "badmainclass", expectedExceptions = FindException.class)
 385     public void testBadMainClass(String mainClass, String ignore) throws IOException {
 386         Manifest man = new Manifest();
 387         Attributes attrs = man.getMainAttributes();
 388         attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0.0");
 389         attrs.put(Attributes.Name.MAIN_CLASS, mainClass);
 390 
 391         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 392         createDummyJarFile(dir.resolve("m.jar"), man);
 393 
 394         // should throw FindException
 395         ModuleFinder.of(dir).findAll();
 396     }
 397 
 398 
 399     /**
 400      * Basic test of a configuration created with automatic modules.
 401      *   a requires b*
 402      *   a requires c*
 403      *   b*
 404      *   c*
 405      */
 406     public void testConfiguration1() throws Exception {
 407         ModuleDescriptor descriptor1
 408             = ModuleDescriptor.module("a")
 409                 .requires("b")
 410                 .requires("c")
 411                 .requires("java.base")
 412                 .build();
 413 
 414         // b and c are automatic modules
 415         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 416         createDummyJarFile(dir.resolve("b.jar"), "p/T.class");
 417         createDummyJarFile(dir.resolve("c.jar"), "q/T.class");
 418 
 419         // module finder locates a and the modules in the directory
 420         ModuleFinder finder
 421             = ModuleFinder.compose(ModuleUtils.finderOf(descriptor1),
 422                 ModuleFinder.of(dir));
 423 
 424         Configuration parent = Layer.boot().configuration();
 425         Configuration cf = resolve(parent, finder, "a");
 426 
 427         assertTrue(cf.modules().size() == 3);
 428         assertTrue(cf.findModule("a").isPresent());
 429         assertTrue(cf.findModule("b").isPresent());
 430         assertTrue(cf.findModule("c").isPresent());
 431 
 432         ResolvedModule base = cf.findModule("java.base").get();
 433         assertTrue(base.configuration() == Layer.boot().configuration());
 434         ResolvedModule a = cf.findModule("a").get();
 435         ResolvedModule b = cf.findModule("b").get();
 436         ResolvedModule c = cf.findModule("c").get();
 437 
 438         // b && c only require java.base
 439         assertTrue(b.reference().descriptor().requires().size() == 1);
 440         assertTrue(c.reference().descriptor().requires().size() == 1);
 441 
 442         // readability
 443 
 444         assertTrue(a.reads().size() == 3);
 445         assertTrue(a.reads().contains(base));
 446         assertTrue(a.reads().contains(b));
 447         assertTrue(a.reads().contains(c));
 448 
 449         assertTrue(b.reads().contains(a));
 450         assertTrue(b.reads().contains(c));
 451         testReadAllBootModules(cf, "b");  // b reads all modules in boot layer
 452 
 453         assertTrue(c.reads().contains(a));
 454         assertTrue(c.reads().contains(b));
 455         testReadAllBootModules(cf, "c");  // c reads all modules in boot layer
 456 
 457     }
 458 
 459     /**
 460      * Basic test of a configuration created with automatic modules
 461      *   a requires b
 462      *   b requires c*
 463      *   c*
 464      *   d*
 465      */
 466     public void testInConfiguration2() throws IOException {
 467         ModuleDescriptor descriptor1
 468             = ModuleDescriptor.module("a")
 469                 .requires("b")
 470                 .requires("java.base")
 471                 .build();
 472 
 473         ModuleDescriptor descriptor2
 474             = ModuleDescriptor.module("b")
 475                 .requires("c")
 476                 .requires("java.base")
 477                 .build();
 478 
 479         // c and d are automatic modules
 480         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 481         createDummyJarFile(dir.resolve("c.jar"), "p/T.class");
 482         createDummyJarFile(dir.resolve("d.jar"), "q/T.class");
 483 
 484         // module finder locates a and the modules in the directory
 485         ModuleFinder finder
 486             = ModuleFinder.compose(ModuleUtils.finderOf(descriptor1, descriptor2),
 487                                    ModuleFinder.of(dir));
 488 
 489         Configuration parent = Layer.boot().configuration();
 490         Configuration cf = resolve(parent, finder, "a", "d");
 491 
 492         assertTrue(cf.modules().size() == 4);
 493         assertTrue(cf.findModule("a").isPresent());
 494         assertTrue(cf.findModule("b").isPresent());
 495         assertTrue(cf.findModule("c").isPresent());
 496         assertTrue(cf.findModule("d").isPresent());
 497 
 498         // c && d should only require java.base
 499         assertTrue(findDescriptor(cf, "c").requires().size() == 1);
 500         assertTrue(findDescriptor(cf, "d").requires().size() == 1);
 501 
 502         // readability
 503 
 504         ResolvedModule base = cf.findModule("java.base").get();
 505         assertTrue(base.configuration() == Layer.boot().configuration());
 506         ResolvedModule a = cf.findModule("a").get();
 507         ResolvedModule b = cf.findModule("b").get();
 508         ResolvedModule c = cf.findModule("c").get();
 509         ResolvedModule d = cf.findModule("d").get();
 510 
 511         assertTrue(a.reads().size() == 2);
 512         assertTrue(a.reads().contains(b));
 513         assertTrue(a.reads().contains(base));
 514 
 515         assertTrue(b.reads().size() == 3);
 516         assertTrue(b.reads().contains(c));
 517         assertTrue(b.reads().contains(d));
 518         assertTrue(b.reads().contains(base));
 519 
 520         assertTrue(c.reads().contains(a));
 521         assertTrue(c.reads().contains(b));
 522         assertTrue(c.reads().contains(d));
 523         testReadAllBootModules(cf, "c");   // c reads all modules in boot layer
 524 
 525         assertTrue(d.reads().contains(a));
 526         assertTrue(d.reads().contains(b));
 527         assertTrue(d.reads().contains(c));
 528         testReadAllBootModules(cf, "d");    // d reads all modules in boot layer
 529     }
 530 
 531 
 532     /**
 533      * Basic test of a configuration created with automatic modules
 534      *   a requires b
 535      *   b requires transitive c*
 536      *   c*
 537      *   d*
 538      */
 539     public void testInConfiguration3() throws IOException {
 540         ModuleDescriptor descriptor1
 541             = ModuleDescriptor.module("a")
 542                 .requires("b")
 543                 .requires("java.base")
 544                 .build();
 545 
 546         ModuleDescriptor descriptor2
 547             = ModuleDescriptor.module("b")
 548                 .requires(Set.of(Modifier.TRANSITIVE), "c")
 549                 .requires("java.base")
 550                 .build();
 551 
 552         // c and d are automatic modules
 553         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 554         createDummyJarFile(dir.resolve("c.jar"), "p/T.class");
 555         createDummyJarFile(dir.resolve("d.jar"), "q/T.class");
 556 
 557         // module finder locates a and the modules in the directory
 558         ModuleFinder finder
 559             = ModuleFinder.compose(ModuleUtils.finderOf(descriptor1, descriptor2),
 560                 ModuleFinder.of(dir));
 561 
 562         Configuration parent = Layer.boot().configuration();
 563         Configuration cf = resolve(parent, finder, "a", "d");
 564 
 565         assertTrue(cf.modules().size() == 4);
 566         assertTrue(cf.findModule("a").isPresent());
 567         assertTrue(cf.findModule("b").isPresent());
 568         assertTrue(cf.findModule("c").isPresent());
 569         assertTrue(cf.findModule("d").isPresent());
 570 
 571         ResolvedModule base = cf.findModule("java.base").get();
 572         assertTrue(base.configuration() == Layer.boot().configuration());
 573         ResolvedModule a = cf.findModule("a").get();
 574         ResolvedModule b = cf.findModule("b").get();
 575         ResolvedModule c = cf.findModule("c").get();
 576         ResolvedModule d = cf.findModule("d").get();
 577 
 578         // c && d should only require java.base
 579         assertTrue(findDescriptor(cf, "c").requires().size() == 1);
 580         assertTrue(findDescriptor(cf, "d").requires().size() == 1);
 581 
 582         // readability
 583 
 584         assertTrue(a.reads().size() == 4);
 585         assertTrue(a.reads().contains(b));
 586         assertTrue(a.reads().contains(c));
 587         assertTrue(a.reads().contains(d));
 588         assertTrue(a.reads().contains(base));
 589 
 590         assertTrue(b.reads().size() == 3);
 591         assertTrue(b.reads().contains(c));
 592         assertTrue(b.reads().contains(d));
 593         assertTrue(b.reads().contains(base));
 594 
 595         assertTrue(reads(cf, "b", "c"));
 596         assertTrue(reads(cf, "b", "d"));
 597         assertTrue(reads(cf, "b", "java.base"));
 598 
 599         assertTrue(c.reads().contains(a));
 600         assertTrue(c.reads().contains(b));
 601         assertTrue(c.reads().contains(d));
 602         testReadAllBootModules(cf, "c");   // c reads all modules in boot layer
 603 
 604         assertTrue(d.reads().contains(a));
 605         assertTrue(d.reads().contains(b));
 606         assertTrue(d.reads().contains(c));
 607         testReadAllBootModules(cf, "d");    // d reads all modules in boot layer
 608     }
 609 
 610 
 611     /**
 612      * Basic test of Layer containing automatic modules
 613      */
 614     public void testInLayer() throws IOException {
 615         ModuleDescriptor descriptor
 616             = ModuleDescriptor.module("a")
 617                 .requires("b")
 618                 .requires("c")
 619                 .build();
 620 
 621         // b and c are simple JAR files
 622         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 623         createDummyJarFile(dir.resolve("b.jar"), "p/T.class");
 624         createDummyJarFile(dir.resolve("c.jar"), "q/T2.class");
 625 
 626         // module finder locates a and the modules in the directory
 627         ModuleFinder finder
 628             = ModuleFinder.compose(ModuleUtils.finderOf(descriptor),
 629                 ModuleFinder.of(dir));
 630 
 631         Configuration parent = Layer.boot().configuration();
 632         Configuration cf = resolve(parent, finder, "a");
 633         assertTrue(cf.modules().size() == 3);
 634 
 635         // each module gets its own loader
 636         Layer layer = Layer.boot().defineModules(cf, mn -> new ClassLoader() { });
 637 
 638         // an unnamed module
 639         Module unnamed = (new ClassLoader() { }).getUnnamedModule();
 640 
 641         Module b = layer.findModule("b").get();
 642         assertTrue(b.isNamed());
 643         assertTrue(b.canRead(unnamed));
 644         testsReadsAll(b, layer);
 645 
 646         Module c = layer.findModule("c").get();
 647         assertTrue(c.isNamed());
 648         assertTrue(b.canRead(unnamed));
 649         testsReadsAll(c, layer);
 650     }
 651 
 652 
 653     /**
 654      * Test miscellaneous methods.
 655      */
 656     public void testMisc() throws IOException {
 657         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 658         Path m_jar = createDummyJarFile(dir.resolve("m.jar"), "p/T.class");
 659 
 660         ModuleFinder finder = ModuleFinder.of(m_jar);
 661 
 662         assertTrue(finder.find("m").isPresent());
 663         ModuleDescriptor m = finder.find("m").get().descriptor();
 664 
 665         // test miscellaneous methods
 666         assertTrue(m.isAutomatic());
 667         assertFalse(m.isSynthetic());
 668         assertFalse(m.osName().isPresent());
 669         assertFalse(m.osArch().isPresent());
 670         assertFalse(m.osVersion().isPresent());
 671     }
 672 
 673 
 674     /**
 675      * Invokes parent.resolveRequires to resolve the given root modules.
 676      */
 677     static Configuration resolve(Configuration parent,
 678                                  ModuleFinder finder,
 679                                  String... roots) {
 680         return parent.resolveRequires(finder, ModuleFinder.of(), Set.of(roots));
 681     }
 682 
 683     /**
 684      * Finds a module in the given configuration or its parents, returning
 685      * the module descriptor (or null if not found)
 686      */
 687     static ModuleDescriptor findDescriptor(Configuration cf, String name) {
 688         Optional<ResolvedModule> om = cf.findModule(name);
 689         if (om.isPresent()) {
 690             return om.get().reference().descriptor();
 691         } else {
 692             return null;
 693         }
 694     }
 695 
 696     /**
 697      * Test that a module in a configuration reads all modules in the boot
 698      * configuration.
 699      */
 700     static void testReadAllBootModules(Configuration cf, String mn) {
 701 
 702         Set<String> bootModules = Layer.boot().modules().stream()
 703                 .map(Module::getName)
 704                 .collect(Collectors.toSet());
 705 
 706         bootModules.forEach(other -> assertTrue(reads(cf, mn, other)));
 707 
 708     }
 709 
 710     /**
 711      * Test that the given Module reads all module in the given Layer
 712      * and its parent Layers.
 713      */
 714     static void testsReadsAll(Module m, Layer layer) {
 715         // check that m reads all modules in the layer
 716         layer.configuration().modules().stream()
 717             .map(ResolvedModule::name)
 718             .map(layer::findModule)
 719             .map(Optional::get)
 720             .forEach(other -> assertTrue(m.canRead(other)));
 721 
 722         // also check parent layers
 723         layer.parents().forEach(l -> testsReadsAll(m, l));
 724     }
 725 
 726     /**
 727      * Returns {@code true} if the configuration contains module mn1
 728      * that reads module mn2.
 729      */
 730     static boolean reads(Configuration cf, String mn1, String mn2) {
 731         Optional<ResolvedModule> om = cf.findModule(mn1);
 732         if (!om.isPresent())
 733             return false;
 734 
 735         return om.get().reads().stream()
 736                 .map(ResolvedModule::name)
 737                 .anyMatch(mn2::equals);
 738     }
 739 
 740     /**
 741      * Creates a JAR file, optionally with a manifest, and with the given
 742      * entries. The entries will be empty in the resulting JAR file.
 743      */
 744     static Path createDummyJarFile(Path jarfile, Manifest man, String... entries)
 745         throws IOException
 746     {
 747         Path dir = Files.createTempDirectory(USER_DIR, "tmp");
 748 
 749         for (String entry : entries) {
 750             Path file = dir.resolve(entry);
 751             Path parent = file.getParent();
 752             if (parent != null)
 753                 Files.createDirectories(parent);
 754             Files.createFile(file);
 755         }
 756 
 757         Path[] paths = Stream.of(entries).map(Paths::get).toArray(Path[]::new);
 758         JarUtils.createJarFile(jarfile, man, dir, paths);
 759         return jarfile;
 760     }
 761 
 762     /**
 763      * Creates a JAR file and with the given entries. The entries will be empty
 764      * in the resulting JAR file.
 765      */
 766     static Path createDummyJarFile(Path jarfile, String... entries)
 767             throws IOException
 768     {
 769         return createDummyJarFile(jarfile, null, entries);
 770     }
 771 
 772 }