changeset 16518:70fb51b05cce

8152650: ModuleFinder.compose should accept varargs Reviewed-by: mchung
author alanb
date Sat, 21 May 2016 08:01:03 +0100
parents 4cd0d71cf20e
children 997dcff5075f
files make/src/classes/build/tools/jigsaw/GenGraphs.java make/src/classes/build/tools/jigsaw/ModuleSummary.java src/java.base/share/classes/java/lang/module/Configuration.java src/java.base/share/classes/java/lang/module/ModuleFinder.java src/java.base/share/classes/java/lang/reflect/Layer.java src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java src/jdk.jartool/share/classes/sun/tools/jar/Main.java src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java test/java/lang/Class/forName/modules/TestLayer.java test/java/lang/Class/forName/modules/src/m3/p3/NoAccess.java test/java/lang/module/AutomaticModulesTest.java test/java/lang/module/ConfigurationTest.java test/java/lang/module/ModuleFinderTest.java test/java/lang/reflect/Layer/BasicLayerTest.java test/java/lang/reflect/Layer/LayerAndLoadersTest.java test/java/lang/reflect/Proxy/ProxyClassAccessTest.java test/java/lang/reflect/Proxy/ProxyLayerTest.java test/java/util/ServiceLoader/modules/ServicesTest.java test/jdk/modules/scenarios/container/src/container/container/Main.java
diffstat 20 files changed, 243 insertions(+), 148 deletions(-) [+]
line wrap: on
line diff
--- a/make/src/classes/build/tools/jigsaw/GenGraphs.java	Fri May 20 16:34:14 2016 -0700
+++ b/make/src/classes/build/tools/jigsaw/GenGraphs.java	Sat May 21 08:01:03 2016 +0100
@@ -82,14 +82,14 @@
             mods.add(name);
             Configuration cf = Configuration.empty()
                     .resolveRequires(finder,
-                                     ModuleFinder.empty(),
+                                     ModuleFinder.of(),
                                      Set.of(name));
             genGraphs.genDotFile(dir, name, cf);
         }
 
         Configuration cf = Configuration.empty()
                 .resolveRequires(finder,
-                                 ModuleFinder.empty(),
+                                 ModuleFinder.of(),
                                  mods);
         genGraphs.genDotFile(dir, "jdk", cf);
 
--- a/make/src/classes/build/tools/jigsaw/ModuleSummary.java	Fri May 20 16:34:14 2016 -0700
+++ b/make/src/classes/build/tools/jigsaw/ModuleSummary.java	Sat May 21 08:01:03 2016 +0100
@@ -292,7 +292,7 @@
     static Configuration resolve(Set<String> roots) {
         return Configuration.empty()
             .resolveRequires(ModuleFinder.ofSystem(),
-                             ModuleFinder.empty(),
+                             ModuleFinder.of(),
                              roots);
     }
 
--- a/src/java.base/share/classes/java/lang/module/Configuration.java	Fri May 20 16:34:14 2016 -0700
+++ b/src/java.base/share/classes/java/lang/module/Configuration.java	Sat May 21 08:01:03 2016 +0100
@@ -152,7 +152,7 @@
  *    Configuration parent = Layer.boot().configuration();
  *
  *    Configuration cf = parent.resolveRequires(finder,
- *                                              ModuleFinder.empty(),
+ *                                              ModuleFinder.of(),
  *                                              Set.of("myapp"));
  *    cf.modules().forEach(m -> {
  *        System.out.format("%s -> %s%n",
@@ -366,7 +366,7 @@
         Configuration parent = empty();
 
         Resolver resolver
-            = new Resolver(finder, parent, ModuleFinder.empty(), traceOutput);
+            = new Resolver(finder, parent, ModuleFinder.of(), traceOutput);
         resolver.resolveRequires(roots).resolveUses();
 
         return new Configuration(parent, resolver, check);
--- a/src/java.base/share/classes/java/lang/module/ModuleFinder.java	Fri May 20 16:34:14 2016 -0700
+++ b/src/java.base/share/classes/java/lang/module/ModuleFinder.java	Sat May 21 08:01:03 2016 +0100
@@ -33,12 +33,15 @@
 import java.security.AccessController;
 import java.security.Permission;
 import java.security.PrivilegedAction;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 import sun.security.action.GetPropertyAction;
 
 /**
@@ -60,15 +63,15 @@
  *     ModuleFinder finder = ModuleFinder.of(dir1, dir2, dir3);
  *
  *     Optional<ModuleReference> omref = finder.find("jdk.foo");
- *     if (omref.isPresent()) { ... }
+ *     omref.ifPresent(mref -> ... );
  *
  * }</pre>
  *
  * <p> The {@link #find(String) find} and {@link #findAll() findAll} methods
- * defined here can fail for several reasons. These include include I/O errors,
- * errors detected parsing a module descriptor ({@code module-info.class}), or
- * in the case of {@code ModuleFinder} returned by {@link #of ModuleFinder.of},
- * that two or more modules with the same name are found in a directory.
+ * defined here can fail for several reasons. These include I/O errors, errors
+ * detected parsing a module descriptor ({@code module-info.class}), or in the
+ * case of {@code ModuleFinder} returned by {@link #of ModuleFinder.of}, that
+ * two or more modules with the same name are found in a directory.
  * When an error is detected then these methods throw {@link FindException
  * FindException} with an appropriate {@link Throwable#getCause cause}.
  * The behavior of a {@code ModuleFinder} after a {@code FindException} is
@@ -205,11 +208,13 @@
      *
      * <p> The module finder returned by this method supports modules that are
      * packaged as JAR files. A JAR file with a {@code module-info.class} in
-     * the top-level directory of the JAR file is a modular JAR and is an
-     * <em>explicit module</em>. A JAR file that does not have a {@code
-     * module-info.class} in the top-level directory is an {@link
-     * ModuleDescriptor#isAutomatic automatic} module. The {@link
-     * ModuleDescriptor} for an automatic module is created as follows:
+     * the top-level directory of the JAR file (or overridden by a versioned
+     * entry in a {@link java.util.jar.JarFile#isMultiRelease() multi-release}
+     * JAR file) is a modular JAR and is an <em>explicit module</em>.
+     * A JAR file that does not have a {@code module-info.class} in the
+     * top-level directory is an {@link ModuleDescriptor#isAutomatic automatic}
+     * module. The {@link ModuleDescriptor} for an automatic module is created as
+     * follows:
      *
      * <ul>
      *
@@ -263,18 +268,22 @@
      * </ul>
      *
      * <p> In addition to JAR files, an implementation may also support modules
-     * that are packaged in other implementation specific module formats. As
-     * with automatic modules, the contents of a packaged or exploded module
-     * may need to be <em>scanned</em> in order to determine the packages in
-     * the module. If a {@code .class} file that corresponds to a class in an
+     * that are packaged in other implementation specific module formats. When
+     * a file is encountered that is not recognized as a packaged module then
+     * {@code FindException} is thrown. An implementation may choose to ignore
+     * some files, {@link java.nio.file.Files#isHidden hidden} files for
+     * example. Paths to files that do not exist are always ignored. </p>
+     *
+     * <p> As with automatic modules, the contents of a packaged or exploded
+     * module may need to be <em>scanned</em> in order to determine the packages
+     * in the module. If a {@code .class} file that corresponds to a class in an
      * unnamed package is encountered then {@code FindException} is thrown. </p>
      *
      * <p> Finders created by this method are lazy and do not eagerly check
      * that the given file paths are directories or packaged modules.
      * Consequently, the {@code find} or {@code findAll} methods will only
      * fail if invoking these methods results in searching a directory or
-     * packaged module and an error is encountered. Paths to files that do not
-     * exist are ignored. </p>
+     * packaged module and an error is encountered. </p>
      *
      * @param entries
      *        A possibly-empty array of paths to directories of modules
@@ -283,81 +292,87 @@
      * @return A {@code ModuleFinder} that locates modules on the file system
      */
     static ModuleFinder of(Path... entries) {
+        // special case zero entries
+        if (entries.length == 0) {
+            return new ModuleFinder() {
+                @Override
+                public Optional<ModuleReference> find(String name) {
+                    Objects.requireNonNull(name);
+                    return Optional.empty();
+                }
+
+                @Override
+                public Set<ModuleReference> findAll() {
+                    return Collections.emptySet();
+                }
+            };
+        }
+
         return new ModulePath(entries);
     }
 
     /**
-     * Returns a module finder that is the equivalent to composing two
-     * module finders. The resulting finder will locate modules references
-     * using {@code first}; if not found then it will attempt to locate module
-     * references using {@code second}.
+     * Returns a module finder that is composed from a sequence of zero or more
+     * module finders. The {@link #find(String) find} method of the resulting
+     * module finder will locate a module by invoking the {@code find} method
+     * of each module finder, in array index order, until either the module is
+     * found or all module finders have been searched. The {@link #findAll()
+     * findAll} method of the resulting module finder will return a set of
+     * modules that includes all modules located by the first module finder.
+     * The set of modules will include all modules located by the second or
+     * subsequent module finder that are not located by previous module finders
+     * in the sequence.
      *
-     * <p> The {@link #findAll() findAll} method of the resulting module finder
-     * will locate all modules located by the first module finder. It will
-     * also locate all modules located by the second module finder that are not
-     * located by the first module finder. </p>
+     * <p> When locating modules then any exceptions or errors thrown by the
+     * {@code find} or {@code findAll} methods of the underlying module finders
+     * will be propogated to the caller of the resulting module finder's
+     * {@code find} or {@code findAll} methods. </p>
      *
-     * @apiNote This method will eventually be changed to take a sequence of
-     *          module finders.
+     * @param finders
+     *        The array of module finders
      *
-     * @param first
-     *        The first module finder
-     * @param second
-     *        The second module finder
-     *
-     * @return A {@code ModuleFinder} that composes two module finders
+     * @return A {@code ModuleFinder} that composes a sequence of module finders
      */
-    static ModuleFinder compose(ModuleFinder first, ModuleFinder second) {
-        Objects.requireNonNull(first);
-        Objects.requireNonNull(second);
+    static ModuleFinder compose(ModuleFinder... finders) {
+        final List<ModuleFinder> finderList = Arrays.asList(finders);
+        finderList.forEach(Objects::requireNonNull);
 
         return new ModuleFinder() {
-            Set<ModuleReference> allModules;
+            private final Map<String, ModuleReference> nameToModule = new HashMap<>();
+            private Set<ModuleReference> allModules;
 
             @Override
             public Optional<ModuleReference> find(String name) {
-                Optional<ModuleReference> om = first.find(name);
-                if (!om.isPresent())
-                    om = second.find(name);
-                return om;
+                // cached?
+                ModuleReference mref = nameToModule.get(name);
+                if (mref != null)
+                    return Optional.of(mref);
+                Optional<ModuleReference> omref = finderList.stream()
+                        .map(f -> f.find(name))
+                        .flatMap(Optional::stream)
+                        .findFirst();
+                omref.ifPresent(m -> nameToModule.put(name, m));
+                return omref;
             }
+
             @Override
             public Set<ModuleReference> findAll() {
-                if (allModules == null) {
-                    allModules = Stream.concat(first.findAll().stream(),
-                                               second.findAll().stream())
-                                       .map(a -> a.descriptor().name())
-                                       .distinct()
-                                       .map(this::find)
-                                       .map(Optional::get)
-                                       .collect(Collectors.toSet());
-                }
+                if (allModules != null)
+                    return allModules;
+                // seed with modules already found
+                Set<ModuleReference> result = new HashSet<>(nameToModule.values());
+                finderList.stream()
+                          .flatMap(f -> f.findAll().stream())
+                          .forEach(mref -> {
+                              String name = mref.descriptor().name();
+                              if (nameToModule.putIfAbsent(name, mref) == null) {
+                                  result.add(mref);
+                              }
+                          });
+                allModules = Collections.unmodifiableSet(result);
                 return allModules;
             }
         };
     }
 
-    /**
-     * Returns an empty module finder.  The empty finder does not find any
-     * modules.
-     *
-     * @apiNote This is useful when using methods such as {@link
-     * Configuration#resolveRequires resolveRequires} where two finders are
-     * specified. An alternative is {@code ModuleFinder.of()}.
-     *
-     * @return A {@code ModuleFinder} that does not find any modules
-     */
-    static ModuleFinder empty() {
-        // an alternative implementation of ModuleFinder.of()
-        return new ModuleFinder() {
-            @Override public Optional<ModuleReference> find(String name) {
-                Objects.requireNonNull(name);
-                return Optional.empty();
-            }
-            @Override public Set<ModuleReference> findAll() {
-                return Collections.emptySet();
-            }
-        };
-    }
-
 }
--- a/src/java.base/share/classes/java/lang/reflect/Layer.java	Fri May 20 16:34:14 2016 -0700
+++ b/src/java.base/share/classes/java/lang/reflect/Layer.java	Sat May 21 08:01:03 2016 +0100
@@ -77,7 +77,7 @@
  * #boot() boot} layer, that is created when the Java virtual machine is
  * started. The <em>system modules</em>, including {@code java.base}, are in
  * the boot layer. The modules in the boot layer are mapped to the bootstrap
- * class loader and other class loaders that are built-in into the ava virtual
+ * class loader and other class loaders that are built-in into the Java virtual
  * machine. The boot layer will often be the {@link #parent() parent} when
  * creating additional layers. </p>
  *
@@ -105,7 +105,7 @@
  *     Layer parent = Layer.boot();
  *
  *     Configuration cf = parent.configuration()
- *         .resolveRequires(finder, ModuleFinder.empty(), Set.of("myapp"));
+ *         .resolveRequires(finder, ModuleFinder.of(), Set.of("myapp"));
  *
  *     ClassLoader scl = ClassLoader.getSystemClassLoader();
  *
--- a/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java	Fri May 20 16:34:14 2016 -0700
+++ b/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java	Sat May 21 08:01:03 2016 +0100
@@ -341,7 +341,7 @@
         // resolve all root modules
         Configuration cf = Configuration.empty()
                 .resolveRequires(finder,
-                                 ModuleFinder.empty(),
+                                 ModuleFinder.of(),
                                  roots);
 
         // module name -> reference
--- a/src/jdk.jartool/share/classes/sun/tools/jar/Main.java	Fri May 20 16:34:14 2016 -0700
+++ b/src/jdk.jartool/share/classes/sun/tools/jar/Main.java	Sat May 21 08:01:03 2016 +0100
@@ -125,7 +125,7 @@
     boolean printModuleDescriptor;
     Version moduleVersion;
     Pattern modulesToHash;
-    ModuleFinder moduleFinder = ModuleFinder.empty();
+    ModuleFinder moduleFinder = ModuleFinder.of();
 
     private static final String MODULE_INFO = "module-info.class";
 
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java	Fri May 20 16:34:14 2016 -0700
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java	Sat May 21 08:01:03 2016 +0100
@@ -371,7 +371,7 @@
 
         Configuration cf = Configuration.empty()
                 .resolveRequires(finder,
-                                 ModuleFinder.empty(),
+                                 ModuleFinder.of(),
                                  addMods);
 
         Map<String, Path> mods = cf.modules().stream()
@@ -390,7 +390,7 @@
         // resolve all root modules
         Configuration cf = Configuration.empty()
                 .resolveRequires(finder,
-                                 ModuleFinder.empty(),
+                                 ModuleFinder.of(),
                                  roots);
 
         // module name -> reference
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java	Fri May 20 16:34:14 2016 -0700
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java	Sat May 21 08:01:03 2016 +0100
@@ -705,7 +705,7 @@
         Configuration bootConfiguration = Layer.boot().configuration();
         try {
             Configuration cf = bootConfiguration
-                .resolveRequiresAndUses(ModuleFinder.empty(),
+                .resolveRequiresAndUses(ModuleFinder.of(),
                                         finder,
                                         Collections.emptySet());
             ClassLoader scl = ClassLoader.getSystemClassLoader();
--- a/test/java/lang/Class/forName/modules/TestLayer.java	Fri May 20 16:34:14 2016 -0700
+++ b/test/java/lang/Class/forName/modules/TestLayer.java	Sat May 21 08:01:03 2016 +0100
@@ -46,7 +46,7 @@
         ModuleFinder finder = ModuleFinder.of(MODS_DIR);
 
         Configuration parent = Layer.boot().configuration();
-        Configuration cf = parent.resolveRequiresAndUses(ModuleFinder.empty(),
+        Configuration cf = parent.resolveRequiresAndUses(ModuleFinder.of(),
                                                          finder,
                                                          modules);
 
--- a/test/java/lang/Class/forName/modules/src/m3/p3/NoAccess.java	Fri May 20 16:34:14 2016 -0700
+++ b/test/java/lang/Class/forName/modules/src/m3/p3/NoAccess.java	Sat May 21 08:01:03 2016 +0100
@@ -51,7 +51,7 @@
         Configuration parent = bootLayer.configuration();
 
         Configuration cf = parent.resolveRequiresAndUses(finder,
-                                                         ModuleFinder.empty(),
+                                                         ModuleFinder.of(),
                                                          Set.of("m1", "m2"));
 
         ClassLoader scl = ClassLoader.getSystemClassLoader();
--- a/test/java/lang/module/AutomaticModulesTest.java	Fri May 20 16:34:14 2016 -0700
+++ b/test/java/lang/module/AutomaticModulesTest.java	Sat May 21 08:01:03 2016 +0100
@@ -469,7 +469,7 @@
     static Configuration resolve(Configuration parent,
                                  ModuleFinder finder,
                                  String... roots) {
-        return parent.resolveRequires(finder, ModuleFinder.empty(), Set.of(roots));
+        return parent.resolveRequires(finder, ModuleFinder.of(), Set.of(roots));
     }
 
     /**
--- a/test/java/lang/module/ConfigurationTest.java	Fri May 20 16:34:14 2016 -0700
+++ b/test/java/lang/module/ConfigurationTest.java	Sat May 21 08:01:03 2016 +0100
@@ -721,7 +721,7 @@
         // finder2 is the after ModuleFinder and so p@2.0 should not be located
         // as module p is in parent configuration.
 
-        cf2 = resolveRequiresAndUses(cf1, ModuleFinder.empty(), finder2, "m1");
+        cf2 = resolveRequiresAndUses(cf1, ModuleFinder.of(), finder2, "m1");
 
         assertTrue(cf2.parent().get() == cf1);
         assertTrue(cf2.modules().size() == 1);
@@ -865,7 +865,7 @@
 
         ModuleFinder finder2 = ModuleUtils.finderOf(descriptor2);
 
-        Configuration cf2 = resolveRequires(cf1, ModuleFinder.empty(), finder2, "m2");
+        Configuration cf2 = resolveRequires(cf1, ModuleFinder.of(), finder2, "m2");
 
         assertTrue(cf2.modules().size() == 1);
         assertTrue(cf2.findModule("m2").isPresent());
@@ -967,7 +967,7 @@
      */
     @Test(expectedExceptions = { ResolutionException.class })
     public void testRootNotFound() {
-        resolveRequires(ModuleFinder.empty(), "m1");
+        resolveRequires(ModuleFinder.of(), "m1");
     }
 
 
@@ -1440,22 +1440,22 @@
 
     @Test(expectedExceptions = { NullPointerException.class })
     public void testResolveRequiresWithNull1() {
-        resolveRequires((ModuleFinder)null, ModuleFinder.empty());
+        resolveRequires((ModuleFinder)null, ModuleFinder.of());
     }
 
     @Test(expectedExceptions = { NullPointerException.class })
     public void testResolveRequiresWithNull2() {
-        resolveRequires(ModuleFinder.empty(), (ModuleFinder)null);
+        resolveRequires(ModuleFinder.of(), (ModuleFinder)null);
     }
 
     @Test(expectedExceptions = { NullPointerException.class })
     public void testResolveRequiresAndUsesWithNull1() {
-        resolveRequiresAndUses((ModuleFinder) null, ModuleFinder.empty());
+        resolveRequiresAndUses((ModuleFinder) null, ModuleFinder.of());
     }
 
     @Test(expectedExceptions = { NullPointerException.class })
     public void testResolveRequiresAndUsesWithNull2() {
-        resolveRequiresAndUses(ModuleFinder.empty(), (ModuleFinder) null);
+        resolveRequiresAndUses(ModuleFinder.of(), (ModuleFinder) null);
     }
 
     @Test(expectedExceptions = { NullPointerException.class })
@@ -1493,7 +1493,7 @@
     private Configuration resolveRequires(Configuration parent,
                                           ModuleFinder before,
                                           String... roots) {
-        return resolveRequires(parent, before, ModuleFinder.empty(), roots);
+        return resolveRequires(parent, before, ModuleFinder.of(), roots);
     }
 
     private Configuration resolveRequires(ModuleFinder before,
@@ -1521,7 +1521,7 @@
     private Configuration resolveRequiresAndUses(Configuration parent,
                                                  ModuleFinder before,
                                                  String... roots) {
-        return resolveRequiresAndUses(parent, before, ModuleFinder.empty(), roots);
+        return resolveRequiresAndUses(parent, before, ModuleFinder.of(), roots);
     }
 
     private Configuration resolveRequiresAndUses(ModuleFinder before,
--- a/test/java/lang/module/ModuleFinderTest.java	Fri May 20 16:34:14 2016 -0700
+++ b/test/java/lang/module/ModuleFinderTest.java	Sat May 21 08:01:03 2016 +0100
@@ -76,9 +76,9 @@
 
 
     /**
-     * Test ModuleFinder.of with zero entries
+     * Test ModuleFinder.of with no entries
      */
-    public void testOfZeroEntries() {
+    public void testOfNoEntries() {
         ModuleFinder finder = ModuleFinder.of();
         assertTrue(finder.findAll().isEmpty());
         assertFalse(finder.find("java.rhubarb").isPresent());
@@ -454,18 +454,46 @@
 
 
     /**
-     * Test ModuleFinder.compose
+     * Test ModuleFinder.compose with no module finders
      */
-    public void testCompose() throws Exception {
+    public void testComposeOfNone() throws Exception {
+        ModuleFinder finder = ModuleFinder.of();
+        assertTrue(finder.findAll().isEmpty());
+        assertFalse(finder.find("java.rhubarb").isPresent());
+    }
+
+
+    /**
+     * Test ModuleFinder.compose with one module finder
+     */
+    public void testComposeOfOne() throws Exception {
+        Path dir = Files.createTempDirectory(USER_DIR, "mods");
+        createModularJar(dir.resolve("m1.jar"), "m1");
+        createModularJar(dir.resolve("m2.jar"), "m2");
+
+        ModuleFinder finder1 = ModuleFinder.of(dir);
+
+        ModuleFinder finder = ModuleFinder.compose(finder1);
+        assertTrue(finder.findAll().size() == 2);
+        assertTrue(finder.find("m1").isPresent());
+        assertTrue(finder.find("m2").isPresent());
+        assertFalse(finder.find("java.rhubarb").isPresent());
+    }
+
+
+    /**
+     * Test ModuleFinder.compose with two module finders
+     */
+    public void testComposeOfTwo() throws Exception {
         Path dir1 = Files.createTempDirectory(USER_DIR, "mods1");
-        createExplodedModule(dir1.resolve("m1"), "m1@1.0");
-        createExplodedModule(dir1.resolve("m2"), "m2@1.0");
+        createModularJar(dir1.resolve("m1.jar"), "m1@1.0");
+        createModularJar(dir1.resolve("m2.jar"), "m2@1.0");
 
         Path dir2 = Files.createTempDirectory(USER_DIR, "mods2");
-        createExplodedModule(dir2.resolve("m1"), "m1@2.0");
-        createExplodedModule(dir2.resolve("m2"), "m2@2.0");
-        createExplodedModule(dir2.resolve("m3"), "m3");
-        createExplodedModule(dir2.resolve("m4"), "m4");
+        createModularJar(dir2.resolve("m1.jar"), "m1@2.0");
+        createModularJar(dir2.resolve("m2.jar"), "m2@2.0");
+        createModularJar(dir2.resolve("m3.jar"), "m3");
+        createModularJar(dir2.resolve("m4.jar"), "m4");
 
         ModuleFinder finder1 = ModuleFinder.of(dir1);
         ModuleFinder finder2 = ModuleFinder.of(dir2);
@@ -478,23 +506,65 @@
         assertTrue(finder.find("m4").isPresent());
         assertFalse(finder.find("java.rhubarb").isPresent());
 
-        // check that m1@1.0 (and not m1@2.0) is found
+        // check that m1@1.0 is found
         ModuleDescriptor m1 = finder.find("m1").get().descriptor();
         assertEquals(m1.version().get().toString(), "1.0");
 
-        // check that m2@1.0 (and not m2@2.0) is found
+        // check that m2@1.0 is found
         ModuleDescriptor m2 = finder.find("m2").get().descriptor();
         assertEquals(m2.version().get().toString(), "1.0");
     }
 
 
     /**
-     * Test ModuleFinder.empty
+     * Test ModuleFinder.compose with three module finders
      */
-    public void testEmpty() {
-        ModuleFinder finder = ModuleFinder.empty();
-        assertTrue(finder.findAll().isEmpty());
+    public void testComposeOfThree() throws Exception {
+        Path dir1 = Files.createTempDirectory(USER_DIR, "mods1");
+        createModularJar(dir1.resolve("m1.jar"), "m1@1.0");
+        createModularJar(dir1.resolve("m2.jar"), "m2@1.0");
+
+        Path dir2 = Files.createTempDirectory(USER_DIR, "mods2");
+        createModularJar(dir2.resolve("m1.jar"), "m1@2.0");
+        createModularJar(dir2.resolve("m2.jar"), "m2@2.0");
+        createModularJar(dir2.resolve("m3.jar"), "m3@2.0");
+        createModularJar(dir2.resolve("m4.jar"), "m4@2.0");
+
+        Path dir3 = Files.createTempDirectory(USER_DIR, "mods3");
+        createModularJar(dir3.resolve("m3.jar"), "m3@3.0");
+        createModularJar(dir3.resolve("m4.jar"), "m4@3.0");
+        createModularJar(dir3.resolve("m5.jar"), "m5");
+        createModularJar(dir3.resolve("m6.jar"), "m6");
+
+        ModuleFinder finder1 = ModuleFinder.of(dir1);
+        ModuleFinder finder2 = ModuleFinder.of(dir2);
+        ModuleFinder finder3 = ModuleFinder.of(dir3);
+
+        ModuleFinder finder = ModuleFinder.compose(finder1, finder2, finder3);
+        assertTrue(finder.findAll().size() == 6);
+        assertTrue(finder.find("m1").isPresent());
+        assertTrue(finder.find("m2").isPresent());
+        assertTrue(finder.find("m3").isPresent());
+        assertTrue(finder.find("m4").isPresent());
+        assertTrue(finder.find("m5").isPresent());
+        assertTrue(finder.find("m6").isPresent());
         assertFalse(finder.find("java.rhubarb").isPresent());
+
+        // check that m1@1.0 is found
+        ModuleDescriptor m1 = finder.find("m1").get().descriptor();
+        assertEquals(m1.version().get().toString(), "1.0");
+
+        // check that m2@1.0 is found
+        ModuleDescriptor m2 = finder.find("m2").get().descriptor();
+        assertEquals(m2.version().get().toString(), "1.0");
+
+        // check that m3@2.0 is found
+        ModuleDescriptor m3 = finder.find("m3").get().descriptor();
+        assertEquals(m3.version().get().toString(), "2.0");
+
+        // check that m4@2.0 is found
+        ModuleDescriptor m4 = finder.find("m4").get().descriptor();
+        assertEquals(m4.version().get().toString(), "2.0");
     }
 
 
@@ -503,34 +573,46 @@
      */
     public void testNulls() {
 
+        // ofSystem
         try {
             ModuleFinder.ofSystem().find(null);
             assertTrue(false);
         } catch (NullPointerException expected) { }
 
+        // of
+        Path dir = Paths.get("d");
         try {
             ModuleFinder.of().find(null);
             assertTrue(false);
         } catch (NullPointerException expected) { }
-
         try {
-            ModuleFinder.empty().find(null);
+            ModuleFinder.of((Path)null);
             assertTrue(false);
         } catch (NullPointerException expected) { }
-
         try {
             ModuleFinder.of((Path[])null);
             assertTrue(false);
         } catch (NullPointerException expected) { }
-
         try {
-            ModuleFinder.of((Path)null);
+            ModuleFinder.of(dir, null);
+            assertTrue(false);
+        } catch (NullPointerException expected) { }
+        try {
+            ModuleFinder.of(null, dir);
             assertTrue(false);
         } catch (NullPointerException expected) { }
 
         // compose
         ModuleFinder finder = ModuleFinder.of();
         try {
+            ModuleFinder.compose((ModuleFinder)null);
+            assertTrue(false);
+        } catch (NullPointerException expected) { }
+        try {
+            ModuleFinder.compose((ModuleFinder[])null);
+            assertTrue(false);
+        } catch (NullPointerException expected) { }
+        try {
             ModuleFinder.compose(finder, null);
             assertTrue(false);
         } catch (NullPointerException expected) { }
--- a/test/java/lang/reflect/Layer/BasicLayerTest.java	Fri May 20 16:34:14 2016 -0700
+++ b/test/java/lang/reflect/Layer/BasicLayerTest.java	Sat May 21 08:01:03 2016 +0100
@@ -33,7 +33,6 @@
 import java.lang.module.Configuration;
 import java.lang.module.ModuleDescriptor;
 import java.lang.module.ModuleFinder;
-import static java.lang.module.ModuleFinder.empty;
 import java.lang.reflect.Layer;
 import java.lang.reflect.LayerInstantiationException;
 import java.lang.reflect.Module;
@@ -358,7 +357,7 @@
         ModuleFinder finder = ModuleUtils.finderOf(descriptor);
 
         Configuration parent = Layer.boot().configuration();
-        Configuration cf = parent.resolveRequires(finder, empty(), Set.of("m1"));
+        Configuration cf = parent.resolveRequires(finder, ModuleFinder.of(), Set.of("m1"));
         assertTrue(cf.modules().size() == 1);
 
         ClassLoader loader = new ClassLoader() { };
@@ -684,7 +683,7 @@
 
         Configuration parent = Layer.boot().configuration();
 
-        Configuration cf = parent.resolveRequires(finder, empty(), Set.of("m"));
+        Configuration cf = parent.resolveRequires(finder, ModuleFinder.of(), Set.of("m"));
 
         ClassLoader loader = new ClassLoader() { };
 
@@ -724,13 +723,13 @@
 
         Configuration parent = Layer.boot().configuration();
 
-        Configuration cf1 = parent.resolveRequires(finder, empty(), Set.of("m1"));
+        Configuration cf1 = parent.resolveRequires(finder, ModuleFinder.of(), Set.of("m1"));
 
         Layer layer1 = Layer.boot().defineModules(cf1, mn -> loader);
 
         // attempt to define m2 containing package p to class loader
 
-        Configuration cf2 = parent.resolveRequires(finder, empty(), Set.of("m2"));
+        Configuration cf2 = parent.resolveRequires(finder, ModuleFinder.of(), Set.of("m2"));
 
         // should throw exception because p already in m1
         Layer layer2 = Layer.boot().defineModules(cf2, mn -> loader);
@@ -757,7 +756,7 @@
         ModuleFinder finder = ModuleUtils.finderOf(md);
 
         Configuration parent = Layer.boot().configuration();
-        Configuration cf = parent.resolveRequires(finder, empty(), Set.of("m"));
+        Configuration cf = parent.resolveRequires(finder, ModuleFinder.of(), Set.of("m"));
 
         Layer.boot().defineModules(cf, mn -> c.getClassLoader());
     }
@@ -777,7 +776,7 @@
         ModuleFinder finder = ModuleUtils.finderOf(descriptor1);
 
         Configuration parent = Layer.boot().configuration();
-        Configuration cf = parent.resolveRequires(finder, empty(), Set.of("m1"));
+        Configuration cf = parent.resolveRequires(finder, ModuleFinder.of(), Set.of("m1"));
 
         ClassLoader loader = new ClassLoader() { };
         Layer.empty().defineModules(cf, mn -> loader);
@@ -814,7 +813,7 @@
     @Test(expectedExceptions = { NullPointerException.class })
     public void testCreateWithNull2() {
         ClassLoader loader = new ClassLoader() { };
-        Configuration cf = resolveRequires(Layer.boot().configuration(), empty());
+        Configuration cf = resolveRequires(Layer.boot().configuration(), ModuleFinder.of());
         Layer.boot().defineModules(cf, null);
     }
 
@@ -857,7 +856,7 @@
     private static Configuration resolveRequires(Configuration cf,
                                                  ModuleFinder finder,
                                                  String... roots) {
-        return cf.resolveRequires(finder, empty(), Set.of(roots));
+        return cf.resolveRequires(finder, ModuleFinder.of(), Set.of(roots));
     }
 
     private static Configuration resolveRequires(ModuleFinder finder,
--- a/test/java/lang/reflect/Layer/LayerAndLoadersTest.java	Fri May 20 16:34:14 2016 -0700
+++ b/test/java/lang/reflect/Layer/LayerAndLoadersTest.java	Sat May 21 08:01:03 2016 +0100
@@ -33,7 +33,6 @@
 import java.lang.module.Configuration;
 import java.lang.module.ModuleDescriptor;
 import java.lang.module.ModuleFinder;
-import static java.lang.module.ModuleFinder.empty;
 import java.lang.module.ModuleReference;
 import java.lang.reflect.Layer;
 import java.lang.reflect.LayerInstantiationException;
@@ -71,7 +70,7 @@
 
 
     /**
-     * Basic test of Layer.createWithOneLoader
+     * Basic test of Layer.defineModulesWithOneLoader
      *
      * Test scenario:
      *   m1 requires m2 and m3
@@ -100,7 +99,7 @@
 
 
     /**
-     * Basic test of Layer.createWithManyLoaders
+     * Basic test of Layer.defineModulesWithManyLoaders
      *
      * Test scenario:
      *   m1 requires m2 and m3
@@ -132,7 +131,7 @@
 
 
     /**
-     * Basic test of Layer.createWithOneLoader where one of the modules
+     * Basic test of Layer.defineModulesWithOneLoader where one of the modules
      * is a service provider module.
      *
      * Test scenario:
@@ -173,7 +172,7 @@
 
 
     /**
-     * Basic test of Layer.createWithManyLoaders where one of the modules
+     * Basic test of Layer.defineModulesWithManyLoaders where one of the modules
      * is a service provider module.
      *
      * Test scenario:
@@ -273,7 +272,7 @@
 
         Configuration cf = Layer.boot()
             .configuration()
-            .resolveRequires(finder, empty(), Set.of("m1", "m2"));
+            .resolveRequires(finder, ModuleFinder.of(), Set.of("m1", "m2"));
 
         // cannot define both module m1 and m2 to the same class loader
         try {
@@ -307,7 +306,7 @@
 
         Configuration cf1 = Layer.boot()
             .configuration()
-            .resolveRequires(finder1, empty(), Set.of("m1", "m2"));
+            .resolveRequires(finder1, ModuleFinder.of(), Set.of("m1", "m2"));
 
         Layer layer1 = Layer.boot().defineModulesWithManyLoaders(cf1, null);
         checkLayer(layer1, "m1", "m2");
@@ -320,7 +319,7 @@
 
         ModuleFinder finder2 = ModuleUtils.finderOf(descriptor3, descriptor4);
 
-        Configuration cf2 = cf1.resolveRequires(finder2, empty(), Set.of("m3", "m4"));
+        Configuration cf2 = cf1.resolveRequires(finder2, ModuleFinder.of(), Set.of("m3", "m4"));
 
         // package p cannot be supplied by two class loaders
         try {
@@ -351,7 +350,7 @@
         checkLayer(layer1, "m1", "m2", "m3");
 
         ModuleFinder finder = ModuleFinder.of(MODS_DIR);
-        Configuration cf2 = cf1.resolveRequires(finder, empty(), Set.of("m1"));
+        Configuration cf2 = cf1.resolveRequires(finder, ModuleFinder.of(), Set.of("m1"));
 
         Layer layer2 = layer1.defineModulesWithOneLoader(cf2, null);
         checkLayer(layer2, "m1", "m2", "m3");
@@ -399,7 +398,7 @@
         checkLayer(layer1, "m1", "m2", "m3");
 
         ModuleFinder finder = ModuleFinder.of(MODS_DIR);
-        Configuration cf2 = cf1.resolveRequires(finder, empty(), Set.of("m1"));
+        Configuration cf2 = cf1.resolveRequires(finder, ModuleFinder.of(), Set.of("m1"));
 
         Layer layer2 = layer1.defineModulesWithManyLoaders(cf2, null);
         checkLayer(layer2, "m1", "m2", "m3");
@@ -493,7 +492,7 @@
 
         ModuleFinder finder = finderFor("m1", "m3");
 
-        Configuration cf2 = cf1.resolveRequires(finder, empty(), Set.of("m1"));
+        Configuration cf2 = cf1.resolveRequires(finder, ModuleFinder.of(), Set.of("m1"));
 
         Layer layer2 = layer1.defineModulesWithOneLoader(cf2, null);
         checkLayer(layer2, "m1", "m3");
@@ -529,7 +528,7 @@
 
         ModuleFinder finder = finderFor("m1", "m3");
 
-        Configuration cf2 = cf1.resolveRequires(finder, empty(), Set.of("m1"));
+        Configuration cf2 = cf1.resolveRequires(finder, ModuleFinder.of(), Set.of("m1"));
 
         Layer layer2 = layer1.defineModulesWithManyLoaders(cf2, null);
         checkLayer(layer2, "m1", "m3");
@@ -575,7 +574,7 @@
         ModuleFinder finder = ModuleFinder.of(MODS_DIR);
         return Layer.boot()
             .configuration()
-            .resolveRequires(finder, empty(), Set.of(roots));
+            .resolveRequires(finder, ModuleFinder.of(), Set.of(roots));
     }
 
     /**
@@ -586,7 +585,7 @@
         ModuleFinder finder = ModuleFinder.of(MODS_DIR);
         return Layer.boot()
             .configuration()
-            .resolveRequiresAndUses(finder, empty(), Set.of(roots));
+            .resolveRequiresAndUses(finder, ModuleFinder.of(), Set.of(roots));
     }
 
 
--- a/test/java/lang/reflect/Proxy/ProxyClassAccessTest.java	Fri May 20 16:34:14 2016 -0700
+++ b/test/java/lang/reflect/Proxy/ProxyClassAccessTest.java	Sat May 21 08:01:03 2016 +0100
@@ -91,7 +91,7 @@
         Layer bootLayer = Layer.boot();
         Configuration cf = bootLayer
                 .configuration()
-                .resolveRequiresAndUses(ModuleFinder.empty(), finder, modules);
+                .resolveRequiresAndUses(ModuleFinder.of(), finder, modules);
         ClassLoader parentLoader = this.getClass().getClassLoader();
         Layer layer = bootLayer.defineModulesWithOneLoader(cf, parentLoader);
 
--- a/test/java/lang/reflect/Proxy/ProxyLayerTest.java	Fri May 20 16:34:14 2016 -0700
+++ b/test/java/lang/reflect/Proxy/ProxyLayerTest.java	Sat May 21 08:01:03 2016 +0100
@@ -79,7 +79,7 @@
         Layer bootLayer = Layer.boot();
         Configuration cf = bootLayer
                 .configuration()
-                .resolveRequiresAndUses(ModuleFinder.empty(), finder, Arrays.asList(modules));
+                .resolveRequiresAndUses(ModuleFinder.of(), finder, Arrays.asList(modules));
         ClassLoader scl = ClassLoader.getSystemClassLoader();
         Layer layer = bootLayer.defineModulesWithOneLoader(cf, scl);
 
@@ -113,7 +113,7 @@
         Layer bootLayer = Layer.boot();
         Configuration cf = bootLayer
                 .configuration()
-                .resolveRequiresAndUses(ModuleFinder.empty(), finder, Arrays.asList(modules));
+                .resolveRequiresAndUses(ModuleFinder.of(), finder, Arrays.asList(modules));
         ClassLoader scl = ClassLoader.getSystemClassLoader();
         Layer layer = bootLayer.defineModulesWithOneLoader(cf, scl);
 
@@ -143,7 +143,7 @@
         Layer bootLayer = Layer.boot();
         Configuration cf = bootLayer
                 .configuration()
-                .resolveRequiresAndUses(ModuleFinder.empty(), finder, Arrays.asList(modules));
+                .resolveRequiresAndUses(ModuleFinder.of(), finder, Arrays.asList(modules));
         ClassLoader scl = ClassLoader.getSystemClassLoader();
         Layer layer = bootLayer.defineModulesWithOneLoader(cf, scl);
 
--- a/test/java/util/ServiceLoader/modules/ServicesTest.java	Fri May 20 16:34:14 2016 -0700
+++ b/test/java/util/ServiceLoader/modules/ServicesTest.java	Sat May 21 08:01:03 2016 +0100
@@ -157,7 +157,7 @@
         Layer bootLayer = Layer.boot();
         Configuration parent = bootLayer.configuration();
         Configuration cf
-            = parent.resolveRequiresAndUses(finder, ModuleFinder.empty(), Set.of());
+            = parent.resolveRequiresAndUses(finder, ModuleFinder.of(), Set.of());
         ClassLoader scl = ClassLoader.getSystemClassLoader();
         Layer layer = bootLayer.defineModulesWithOneLoader(cf, scl);
 
--- a/test/jdk/modules/scenarios/container/src/container/container/Main.java	Fri May 20 16:34:14 2016 -0700
+++ b/test/jdk/modules/scenarios/container/src/container/container/Main.java	Sat May 21 08:01:03 2016 +0100
@@ -72,7 +72,7 @@
 
         Configuration cf = Layer.boot().configuration()
             .resolveRequiresAndUses(finder,
-                                    ModuleFinder.empty(),
+                                    ModuleFinder.of(),
                                     Set.of(appModuleName));
 
         System.out.println("Resolved");