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