8168789: ModuleReader.list and ModuleFinder.of update
authoralanb
Fri, 28 Oct 2016 10:18:07 +0100
changeset 41817 b90ad1de93ea
parent 41816 07e906f1a20b
child 41818 30bacee4827a
8168789: ModuleReader.list and ModuleFinder.of update Reviewed-by: mchung
jdk/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java
jdk/src/java.base/share/classes/java/lang/module/ModuleFinder.java
jdk/src/java.base/share/classes/java/lang/module/ModulePath.java
jdk/src/java.base/share/classes/java/lang/module/ModuleReader.java
jdk/src/java.base/share/classes/java/lang/module/ModuleReferences.java
jdk/src/java.base/share/classes/java/lang/module/SystemModuleFinder.java
jdk/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java
jdk/src/java.base/share/classes/jdk/internal/loader/BuiltinClassLoader.java
jdk/src/java.base/share/classes/jdk/internal/loader/Loader.java
jdk/src/java.base/share/classes/jdk/internal/misc/JavaLangModuleAccess.java
jdk/src/java.base/share/classes/jdk/internal/module/ConfigurableModuleFinder.java
jdk/src/java.base/share/classes/jdk/internal/module/ModuleInfoWriter.java
jdk/src/java.base/share/classes/jdk/internal/module/ModulePatcher.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java
jdk/test/java/lang/module/AutomaticModulesTest.java
jdk/test/java/lang/module/ModuleFinderTest.java
jdk/test/java/lang/module/ModuleReader/ModuleReaderTest.java
jdk/test/java/lang/module/ModuleReader/MultiReleaseJarTest.java
jdk/test/java/lang/module/MultiReleaseJarTest.java
jdk/test/java/util/ResourceBundle/modules/basic/src/asiabundles/jdk/test/resources/MyResources_ja_JP.properties
jdk/test/java/util/ResourceBundle/modules/basic/src/asiabundles/jdk/test/resources/asia/MyResources_ja_JP.properties
jdk/test/java/util/ResourceBundle/modules/basic/src/mainbundles/jdk/test/resources/MyResourcesProvider.java
jdk/test/tools/jmod/hashes/HashesTest.java
--- a/jdk/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java	Fri Oct 28 10:18:07 2016 +0100
@@ -31,6 +31,7 @@
 import java.io.UncheckedIOException;
 import java.net.URI;
 import java.nio.ByteBuffer;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -1997,6 +1998,13 @@
                 public Optional<ModuleHashes> hashes(ModuleDescriptor descriptor) {
                     return descriptor.hashes();
                 }
+
+                @Override
+                public ModuleFinder newModulePath(Runtime.Version version,
+                                                  boolean isLinkPhase,
+                                                  Path... entries) {
+                    return new ModulePath(version, isLinkPhase, entries);
+                }
             });
     }
 
--- a/jdk/src/java.base/share/classes/java/lang/module/ModuleFinder.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/src/java.base/share/classes/java/lang/module/ModuleFinder.java	Fri Oct 28 10:18:07 2016 +0100
@@ -228,7 +228,7 @@
      *
      *         <li><p> If the name matches the regular expression {@code
      *         "-(\\d+(\\.|$))"} then the module name will be derived from the
-     *         subsequence proceeding the hyphen of the first occurrence. The
+     *         subsequence preceding the hyphen of the first occurrence. The
      *         subsequence after the hyphen is parsed as a {@link
      *         ModuleDescriptor.Version} and ignored if it cannot be parsed as
      *         a {@code Version}. </p></li>
@@ -248,18 +248,23 @@
      *     <li><p> It {@link ModuleDescriptor#requires() requires} {@code
      *     java.base}. </p></li>
      *
-     *     <li><p> All entries in the JAR file with names ending with {@code
-     *     .class} are assumed to be class files where the name corresponds
-     *     to the fully qualified name of the class. The packages of all
-     *     classes are {@link ModuleDescriptor#exports() exported}. </p></li>
+     *     <li><p> The set of packages in the module is derived from the names
+     *     of non-directory entries in the JAR file. A candidate package name
+     *     is derived from an entry using the characters up to, but not
+     *     including, the last forward slash. All remaining forward slashes are
+     *     replaced with dot ({@code "."}). If the resulting string is a valid
+     *     Java identifier then it is assumed to be a package name. For example,
+     *     if the JAR file contains an entry "{@code p/q/Foo.class}" then the
+     *     package name derived is "{@code p.q}". All packages are {@link
+     *     ModuleDescriptor#exports() exported}. </p></li>
      *
-     *     <li><p> The contents of all entries starting with {@code
+     *     <li><p> The contents of entries starting with {@code
      *     META-INF/services/} are assumed to be service configuration files
-     *     (see {@link java.util.ServiceLoader}). The name of the file
-     *     (that follows {@code META-INF/services/}) is assumed to be the
-     *     fully-qualified binary name of a service type. The entries in the
-     *     file are assumed to be the fully-qualified binary names of
-     *     provider classes. </p></li>
+     *     (see {@link java.util.ServiceLoader}). If the name of a file
+     *     (that follows {@code META-INF/services/}) is a legal Java identifier
+     *     then it is assumed to be the fully-qualified binary name of a
+     *     service type. The entries in the file are assumed to be the
+     *     fully-qualified binary names of provider classes. </p></li>
      *
      *     <li><p> If the JAR file has a {@code Main-Class} attribute in its
      *     main manifest then its value is the {@link
@@ -271,8 +276,8 @@
      * {@link ModuleDescriptor.Builder ModuleDescriptor.Builder} API) for an
      * automatic module then {@code FindException} is thrown. This can arise,
      * for example, when a legal Java identifier name cannot be derived from
-     * the file name of the JAR file or where a package name derived from an
-     * entry ending with {@code .class} is not a legal Java identifier. </p>
+     * the file name of the JAR file or where the JAR file contains a {@code
+     * .class} in the top-level directory of the JAR file. </p>
      *
      * <p> In addition to JAR files, an implementation may also support modules
      * that are packaged in other implementation specific module formats. When
@@ -283,8 +288,10 @@
      *
      * <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>
+     * in the module. If a {@code .class} file (other than {@code
+     * module-info.class}) is found in the top-level directory then it is
+     * assumed to be a class in the unnamed package and so {@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.
@@ -341,7 +348,7 @@
      * @return A {@code ModuleFinder} that composes a sequence of module finders
      */
     static ModuleFinder compose(ModuleFinder... finders) {
-        // copy the list, also checking for nulls
+        // copy the list and check for nulls
         final List<ModuleFinder> finderList = List.of(finders);
 
         return new ModuleFinder() {
--- a/jdk/src/java.base/share/classes/java/lang/module/ModulePath.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/src/java.base/share/classes/java/lang/module/ModulePath.java	Fri Oct 28 10:18:07 2016 +0100
@@ -33,10 +33,12 @@
 import java.io.InputStreamReader;
 import java.io.UncheckedIOException;
 import java.lang.module.ModuleDescriptor.Requires;
+import java.net.URI;
 import java.nio.file.DirectoryStream;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.Collections;
 import java.util.HashMap;
@@ -52,49 +54,53 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 import jdk.internal.jmod.JmodFile;
 import jdk.internal.jmod.JmodFile.Section;
-import jdk.internal.module.ConfigurableModuleFinder;
+import jdk.internal.module.Checks;
 import jdk.internal.perf.PerfCounter;
+import jdk.internal.util.jar.VersionedStream;
 
 
 /**
  * A {@code ModuleFinder} that locates modules on the file system by searching
  * a sequence of directories or packaged modules.
  *
- * The {@code ModuleFinder} can be configured to work in either the run-time
+ * The {@code ModuleFinder} can be created to work in either the run-time
  * or link-time phases. In both cases it locates modular JAR and exploded
- * modules. When configured for link-time then it additionally locates
+ * modules. When created for link-time then it additionally locates
  * modules in JMOD files.
  */
 
-class ModulePath implements ConfigurableModuleFinder {
+class ModulePath implements ModuleFinder {
     private static final String MODULE_INFO = "module-info.class";
 
+    // the version to use for multi-release modular JARs
+    private final Runtime.Version releaseVersion;
+
+    // true for the link phase (supports modules packaged in JMOD format)
+    private final boolean isLinkPhase;
+
     // the entries on this module path
     private final Path[] entries;
     private int next;
 
-    // true if in the link phase
-    private boolean isLinkPhase;
-
     // map of module name to module reference map for modules already located
     private final Map<String, ModuleReference> cachedModules = new HashMap<>();
 
-    ModulePath(Path... entries) {
+    ModulePath(Runtime.Version version, boolean isLinkPhase, Path... entries) {
+        this.releaseVersion = version;
+        this.isLinkPhase = isLinkPhase;
         this.entries = entries.clone();
         for (Path entry : this.entries) {
             Objects.requireNonNull(entry);
         }
     }
 
-    @Override
-    public void configurePhase(Phase phase) {
-        isLinkPhase = (phase == Phase.LINK_TIME);
+    ModulePath(Path... entries) {
+        this(JarFile.runtimeVersion(), false, entries);
     }
 
     @Override
@@ -239,9 +245,13 @@
                 if (mref != null) {
                     // can have at most one version of a module in the directory
                     String name = mref.descriptor().name();
-                    if (nameToReference.put(name, mref) != null) {
+                    ModuleReference previous = nameToReference.put(name, mref);
+                    if (previous != null) {
+                        String fn1 = fileName(mref);
+                        String fn2 = fileName(previous);
                         throw new FindException("Two versions of module "
-                                                  + name + " found in " + dir);
+                                                 + name + " found in " + dir
+                                                 + " (" + fn1 + " and " + fn2 + ")");
                     }
                 }
             }
@@ -294,6 +304,25 @@
     }
 
 
+    /**
+     * Returns a string with the file name of the module if possible.
+     * If the module location is not a file URI then return the URI
+     * as a string.
+     */
+    private String fileName(ModuleReference mref) {
+        URI uri = mref.location().orElse(null);
+        if (uri != null) {
+            if (uri.getScheme().equalsIgnoreCase("file")) {
+                Path file = Paths.get(uri);
+                return file.getFileName().toString();
+            } else {
+                return uri.toString();
+            }
+        } else {
+            return "<unknown>";
+        }
+    }
+
     // -- jmod files --
 
     private Set<String> jmodPackages(JmodFile jf) {
@@ -301,7 +330,7 @@
             .filter(e -> e.section() == Section.CLASSES)
             .map(JmodFile.Entry::name)
             .map(this::toPackageName)
-            .filter(pkg -> pkg.length() > 0) // module-info
+            .flatMap(Optional::stream)
             .collect(Collectors.toSet());
     }
 
@@ -328,8 +357,8 @@
     private static final String SERVICES_PREFIX = "META-INF/services/";
 
     /**
-     * Returns a container with the service type corresponding to the name of
-     * a services configuration file.
+     * Returns the service type corresponding to the name of a services
+     * configuration file if it is a valid Java identifier.
      *
      * For example, if called with "META-INF/services/p.S" then this method
      * returns a container with the value "p.S".
@@ -341,7 +370,8 @@
             String prefix = cf.substring(0, index);
             if (prefix.equals(SERVICES_PREFIX)) {
                 String sn = cf.substring(index);
-                return Optional.of(sn);
+                if (Checks.isJavaIdentifier(sn))
+                    return Optional.of(sn);
             }
         }
         return Optional.empty();
@@ -416,28 +446,28 @@
         if (vs != null)
             builder.version(vs);
 
-        // scan the entries in the JAR file to locate the .class and service
-        // configuration file
-        Map<Boolean, Set<String>> map =
-            versionedStream(jf)
-              .map(JarEntry::getName)
-              .filter(s -> (s.endsWith(".class") ^ s.startsWith(SERVICES_PREFIX)))
-              .collect(Collectors.partitioningBy(s -> s.endsWith(".class"),
-                                                 Collectors.toSet()));
-        Set<String> classFiles = map.get(Boolean.TRUE);
-        Set<String> configFiles = map.get(Boolean.FALSE);
+        // scan the names of the entries in the JAR file
+        Map<Boolean, Set<String>> map = VersionedStream.stream(jf)
+                .filter(e -> !e.isDirectory())
+                .map(JarEntry::getName)
+                .collect(Collectors.partitioningBy(e -> e.startsWith(SERVICES_PREFIX),
+                                                   Collectors.toSet()));
+
+        Set<String> resources = map.get(Boolean.FALSE);
+        Set<String> configFiles = map.get(Boolean.TRUE);
 
         // all packages are exported
-        classFiles.stream()
-            .map(c -> toPackageName(c))
-            .distinct()
-            .forEach(builder::exports);
+        resources.stream()
+                .map(this::toPackageName)
+                .flatMap(Optional::stream)
+                .distinct()
+                .forEach(builder::exports);
 
         // map names of service configuration files to service names
         Set<String> serviceNames = configFiles.stream()
-            .map(this::toServiceName)
-            .flatMap(Optional::stream)
-            .collect(Collectors.toSet());
+                .map(this::toServiceName)
+                .flatMap(Optional::stream)
+                .collect(Collectors.toSet());
 
         // parse each service configuration file
         for (String sn : serviceNames) {
@@ -502,25 +532,13 @@
         return mn;
     }
 
-    private Stream<JarEntry> versionedStream(JarFile jf) {
-        if (jf.isMultiRelease()) {
-            // a stream of JarEntries whose names are base names and whose
-            // contents are from the corresponding versioned entries in
-            // a multi-release jar file
-            return jf.stream().map(JarEntry::getName)
-                    .filter(name -> !name.startsWith("META-INF/versions/"))
-                    .map(jf::getJarEntry);
-        } else {
-            return jf.stream();
-        }
-    }
-
     private Set<String> jarPackages(JarFile jf) {
-        return versionedStream(jf)
-            .filter(e -> e.getName().endsWith(".class"))
-            .map(e -> toPackageName(e.getName()))
-            .filter(pkg -> pkg.length() > 0)   // module-info
-            .collect(Collectors.toSet());
+        return VersionedStream.stream(jf)
+                .filter(e -> !e.isDirectory())
+                .map(JarEntry::getName)
+                .map(this::toPackageName)
+                .flatMap(Optional::stream)
+                .collect(Collectors.toSet());
     }
 
     /**
@@ -535,7 +553,7 @@
         try (JarFile jf = new JarFile(file.toFile(),
                                       true,               // verify
                                       ZipFile.OPEN_READ,
-                                      JarFile.runtimeVersion()))
+                                      releaseVersion))
         {
             ModuleDescriptor md;
             JarEntry entry = jf.getJarEntry(MODULE_INFO);
@@ -565,11 +583,11 @@
     private Set<String> explodedPackages(Path dir) {
         try {
             return Files.find(dir, Integer.MAX_VALUE,
-                              ((path, attrs) -> attrs.isRegularFile() &&
-                               path.toString().endsWith(".class")))
-                .map(path -> toPackageName(dir.relativize(path)))
-                .filter(pkg -> pkg.length() > 0)   // module-info
-                .collect(Collectors.toSet());
+                              ((path, attrs) -> attrs.isRegularFile()))
+                    .map(path -> dir.relativize(path))
+                    .map(this::toPackageName)
+                    .flatMap(Optional::stream)
+                    .collect(Collectors.toSet());
         } catch (IOException x) {
             throw new UncheckedIOException(x);
         }
@@ -595,29 +613,62 @@
         return ModuleReferences.newExplodedModule(md, dir);
     }
 
-
-    //
+    /**
+     * Maps the name of an entry in a JAR or ZIP file to a package name.
+     *
+     * @throws IllegalArgumentException if the name is a class file in
+     *         the top-level directory of the JAR/ZIP file (and it's
+     *         not module-info.class)
+     */
+    private Optional<String> toPackageName(String name) {
+        assert !name.endsWith("/");
 
-    // p/q/T.class => p.q
-    private String toPackageName(String cn) {
-        assert cn.endsWith(".class");
-        int start = 0;
-        int index = cn.lastIndexOf("/");
-        if (index > start) {
-            return cn.substring(start, index).replace('/', '.');
+        int index = name.lastIndexOf("/");
+        if (index == -1) {
+            if (name.endsWith(".class") && !name.equals(MODULE_INFO)) {
+                throw new IllegalArgumentException(name
+                        + " found in top-level directory:"
+                        + " (unnamed package not allowed in module)");
+            }
+            return Optional.empty();
+        }
+
+        String pn = name.substring(0, index).replace('/', '.');
+        if (Checks.isJavaIdentifier(pn)) {
+            return Optional.of(pn);
         } else {
-            return "";
+            // not a valid package name
+            return Optional.empty();
         }
     }
 
-    private String toPackageName(Path path) {
-        String name = path.toString();
-        assert name.endsWith(".class");
-        int index = name.lastIndexOf(File.separatorChar);
-        if (index != -1) {
-            return name.substring(0, index).replace(File.separatorChar, '.');
+    /**
+     * Maps the relative path of an entry in an exploded module to a package
+     * name.
+     *
+     * @throws IllegalArgumentException if the name is a class file in
+     *         the top-level directory (and it's not module-info.class)
+     */
+    private Optional<String> toPackageName(Path file) {
+        assert file.getRoot() == null;
+
+        Path parent = file.getParent();
+        if (parent == null) {
+            String name = file.toString();
+            if (name.endsWith(".class") && !name.equals(MODULE_INFO)) {
+                throw new IllegalArgumentException(name
+                        + " found in in top-level directory"
+                        + " (unnamed package not allowed in module)");
+            }
+            return Optional.empty();
+        }
+
+        String pn = parent.toString().replace(File.separatorChar, '.');
+        if (Checks.isJavaIdentifier(pn)) {
+            return Optional.of(pn);
         } else {
-            return "";
+            // not a valid package name
+            return Optional.empty();
         }
     }
 
--- a/jdk/src/java.base/share/classes/java/lang/module/ModuleReader.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/src/java.base/share/classes/java/lang/module/ModuleReader.java	Fri Oct 28 10:18:07 2016 +0100
@@ -32,6 +32,7 @@
 import java.nio.ByteBuffer;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.stream.Stream;
 
 
 /**
@@ -44,6 +45,11 @@
  * module. A module reader is also intended to be used by {@code ClassLoader}
  * implementations that load classes and resources from modules. </p>
  *
+ * <p> A resource in a module is identified by a name that is a
+ * '{@code /}'-separated path string. For example, module {@code java.base} may
+ * have a resource "{@code java/lang/Object.class}" that, by convention, is the
+ * class file for {@code java.lang.Object}. </p>
+ *
  * <p> A {@code ModuleReader} is {@linkplain ModuleReference#open open} upon
  * creation and is closed by invoking the {@link #close close} method.  Failure
  * to close a module reader may result in a resource leak.  The {@code
@@ -52,8 +58,8 @@
  *
  * <p> A {@code ModuleReader} implementation may require permissions to access
  * resources in the module. Consequently the {@link #find find}, {@link #open
- * open} and {@link #read read} methods may throw {@code SecurityException} if
- * access is denied by the security manager. </p>
+ * open}, {@link #read read}, and {@link #list list} methods may throw {@code
+ * SecurityException} if access is denied by the security manager. </p>
  *
  * @see ModuleReference
  * @since 9
@@ -84,6 +90,9 @@
      * Opens a resource, returning an input stream to read the resource in
      * the module.
      *
+     * <p> The behavior of the input stream when used after the module reader
+     * is closed is implementation specific and therefore not specified. </p>
+     *
      * @implSpec The default implementation invokes the {@link #find(String)
      * find} method to get a URI to the resource. If found, then it attempts
      * to construct a {@link java.net.URL URL} and open a connection to the
@@ -172,17 +181,37 @@
     }
 
     /**
+     * Lists the contents of the module, returning a stream of elements that
+     * are the names of all resources in the module.
+     *
+     * <p> In lazy implementations then an {@code IOException} may be thrown
+     * when using the stream to list the module contents. If this occurs then
+     * the {@code IOException} will be wrapped in an {@link
+     * java.io.UncheckedIOException} and thrown from the method that caused the
+     * access to be attempted. {@code SecurityException} may also be thrown
+     * when using the stream to list the module contents and access is denied
+     * by the security manager. </p>
+     *
+     * <p> The behavior of the stream when used after the module reader is
+     * closed is implementation specific and therefore not specified. </p>
+     *
+     * @return A stream of elements that are the names of all resources
+     *         in the module
+     *
+     * @throws IOException
+     *         If an I/O error occurs or the module reader is closed
+     * @throws SecurityException
+     *         If denied by the security manager
+     */
+    Stream<String> list() throws IOException;
+
+    /**
      * Closes the module reader. Once closed then subsequent calls to locate or
-     * read a resource will fail by returning {@code Optional.empty()} or
-     * throwing {@code IOException}.
+     * read a resource will fail by throwing {@code IOException}.
      *
      * <p> A module reader is not required to be asynchronously closeable. If a
      * thread is reading a resource and another thread invokes the close method,
-     * then the second thread may block until the read operation is complete.
-     *
-     * <p> The behavior of {@code InputStream}s obtained using the {@link
-     * #open(String) open} method and used after the module reader is closed
-     * is implementation specific and therefore not specified.
+     * then the second thread may block until the read operation is complete. </p>
      */
     @Override
     void close() throws IOException;
--- a/jdk/src/java.base/share/classes/java/lang/module/ModuleReferences.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/src/java.base/share/classes/java/lang/module/ModuleReferences.java	Fri Oct 28 10:18:07 2016 +0100
@@ -35,6 +35,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.locks.Lock;
@@ -43,14 +44,17 @@
 import java.util.function.Supplier;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
-import java.util.zip.ZipEntry;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import java.util.zip.ZipFile;
 
+import jdk.internal.jmod.JmodFile;
 import jdk.internal.misc.JavaLangAccess;
 import jdk.internal.misc.SharedSecrets;
 import jdk.internal.module.ModuleHashes;
 import jdk.internal.module.ModuleHashes.HashSupplier;
 import jdk.internal.module.ModulePatcher;
+import jdk.internal.util.jar.VersionedStream;
 import sun.net.www.ParseUtil;
 
 
@@ -140,6 +144,13 @@
         abstract Optional<InputStream> implOpen(String name) throws IOException;
 
         /**
+         * Returns a stream of the names of resources in the module. This
+         * method is invoked by the list method to do the actual work of
+         * creating the stream.
+         */
+        abstract Stream<String> implList() throws IOException;
+
+        /**
          * Closes the module reader. This method is invoked by close to do the
          * actual work of closing the module reader.
          */
@@ -175,7 +186,21 @@
         }
 
         @Override
-        public void close() throws IOException {
+        public final Stream<String> list() throws IOException {
+            readLock.lock();
+            try {
+                if (!closed) {
+                    return implList();
+                } else {
+                    throw new IOException("ModuleReader is closed");
+                }
+            } finally {
+                readLock.unlock();
+            }
+        }
+
+        @Override
+        public final void close() throws IOException {
             writeLock.lock();
             try {
                 if (!closed) {
@@ -241,6 +266,16 @@
         }
 
         @Override
+        Stream<String> implList() throws IOException {
+            // take snapshot to avoid async close
+            List<String> names = VersionedStream.stream(jf)
+                    .filter(e -> !e.isDirectory())
+                    .map(JarEntry::getName)
+                    .collect(Collectors.toList());
+            return names.stream();
+        }
+
+        @Override
         void implClose() throws IOException {
             jf.close();
         }
@@ -251,30 +286,31 @@
      * A ModuleReader for a JMOD file.
      */
     static class JModModuleReader extends SafeCloseModuleReader {
-        private final ZipFile zf;
+        private final JmodFile jf;
         private final URI uri;
 
-        static ZipFile newZipFile(Path path) {
+        static JmodFile newJmodFile(Path path) {
             try {
-                return new ZipFile(path.toFile());
+                return new JmodFile(path);
             } catch (IOException ioe) {
                 throw new UncheckedIOException(ioe);
             }
         }
 
         JModModuleReader(Path path, URI uri) {
-            this.zf = newZipFile(path);
+            this.jf = newJmodFile(path);
             this.uri = uri;
         }
 
-        private ZipEntry getEntry(String name) {
-            return zf.getEntry("classes/" + Objects.requireNonNull(name));
+        private JmodFile.Entry getEntry(String name) {
+            Objects.requireNonNull(name);
+            return jf.getEntry(JmodFile.Section.CLASSES, name);
         }
 
         @Override
         Optional<URI> implFind(String name) {
-            ZipEntry ze = getEntry(name);
-            if (ze != null) {
+            JmodFile.Entry je = getEntry(name);
+            if (je != null) {
                 String encodedPath = ParseUtil.encodePath(name, false);
                 String uris = "jmod:" + uri + "!/" + encodedPath;
                 return Optional.of(URI.create(uris));
@@ -285,17 +321,27 @@
 
         @Override
         Optional<InputStream> implOpen(String name) throws IOException {
-            ZipEntry ze = getEntry(name);
-            if (ze != null) {
-                return Optional.of(zf.getInputStream(ze));
+            JmodFile.Entry je = getEntry(name);
+            if (je != null) {
+                return Optional.of(jf.getInputStream(je));
             } else {
                 return Optional.empty();
             }
         }
 
         @Override
+        Stream<String> implList() throws IOException {
+            // take snapshot to avoid async close
+            List<String> names = jf.stream()
+                    .filter(e -> e.section() == JmodFile.Section.CLASSES)
+                    .map(JmodFile.Entry::name)
+                    .collect(Collectors.toList());
+            return names.stream();
+        }
+
+        @Override
         void implClose() throws IOException {
-            zf.close();
+            jf.close();
         }
     }
 
@@ -378,6 +424,17 @@
         }
 
         @Override
+        public Stream<String> list() throws IOException {
+            ensureOpen();
+            // sym links not followed
+            return Files.find(dir, Integer.MAX_VALUE,
+                              (path, attrs) -> attrs.isRegularFile())
+                    .map(f -> dir.relativize(f)
+                                 .toString()
+                                 .replace(File.separatorChar, '/'));
+        }
+
+        @Override
         public void close() {
             closed = true;
         }
--- a/jdk/src/java.base/share/classes/java/lang/module/SystemModuleFinder.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/src/java.base/share/classes/java/lang/module/SystemModuleFinder.java	Fri Oct 28 10:18:07 2016 +0100
@@ -32,14 +32,21 @@
 import java.net.URI;
 import java.net.URLConnection;
 import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
 import java.util.Collections;
+import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.Spliterator;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 
 import jdk.internal.jimage.ImageLocation;
 import jdk.internal.jimage.ImageReader;
@@ -62,6 +69,8 @@
 
 class SystemModuleFinder implements ModuleFinder {
 
+    private static final JavaNetUriAccess JNUA = SharedSecrets.getJavaNetUriAccess();
+
     private static final PerfCounter initTime
         = PerfCounter.newPerfCounter("jdk.module.finder.jimage.initTime");
     private static final PerfCounter moduleCount
@@ -73,8 +82,6 @@
     // ImageReader used to access all modules in the image
     private static final ImageReader imageReader;
 
-    private static final JavaNetUriAccess jnua = SharedSecrets.getJavaNetUriAccess();
-
     // the set of modules in the run-time image
     private static final Set<ModuleReference> modules;
 
@@ -170,8 +177,7 @@
                                                      HashSupplier hash)
     {
         String mn = md.name();
-
-        URI uri = jnua.create("jrt", "/".concat(mn));
+        URI uri = JNUA.create("jrt", "/".concat(mn));
 
         Supplier<ModuleReader> readerSupplier = new Supplier<>() {
             @Override
@@ -332,10 +338,101 @@
         }
 
         @Override
+        public Stream<String> list() throws IOException {
+            if (closed)
+                throw new IOException("ModuleReader is closed");
+
+            Spliterator<String> s = new ModuleContentSpliterator(module);
+            return StreamSupport.stream(s, false);
+        }
+
+        @Override
         public void close() {
             // nothing else to do
             closed = true;
         }
     }
 
+    /**
+     * A Spliterator for traversing the resources of a module linked into the
+     * run-time image.
+     */
+    static class ModuleContentSpliterator implements Spliterator<String> {
+        final String moduleRoot;
+        final Deque<ImageReader.Node> stack;
+        Iterator<ImageReader.Node> iterator;
+
+        ModuleContentSpliterator(String module) throws IOException {
+            moduleRoot = "/modules/" + module;
+            stack = new ArrayDeque<>();
+
+            // push the root node to the stack to get started
+            ImageReader.Node dir = imageReader.findNode(moduleRoot);
+            if (dir == null || !dir.isDirectory())
+                throw new IOException(moduleRoot + " not a directory");
+            stack.push(dir);
+            iterator = Collections.emptyIterator();
+        }
+
+        /**
+         * Returns the name of the next non-directory node or {@code null} if
+         * there are no remaining nodes to visit.
+         */
+        private String next() throws IOException {
+            for (;;) {
+                while (iterator.hasNext()) {
+                    ImageReader.Node node = iterator.next();
+                    String name = node.getName();
+                    if (node.isDirectory()) {
+                        // build node
+                        ImageReader.Node dir = imageReader.findNode(name);
+                        assert dir.isDirectory();
+                        stack.push(dir);
+                    } else {
+                        // strip /modules/$MODULE/ prefix
+                        return name.substring(moduleRoot.length() + 1);
+                    }
+                }
+
+                if (stack.isEmpty()) {
+                    return null;
+                } else {
+                    ImageReader.Node dir = stack.poll();
+                    assert dir.isDirectory();
+                    iterator = dir.getChildren().iterator();
+                }
+            }
+        }
+
+        @Override
+        public boolean tryAdvance(Consumer<? super String> action) {
+            String next;
+            try {
+                next = next();
+            } catch (IOException ioe) {
+                throw new UncheckedIOException(ioe);
+            }
+            if (next != null) {
+                action.accept(next);
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public Spliterator<String> trySplit() {
+            return null;
+        }
+
+        @Override
+        public int characteristics() {
+            return Spliterator.DISTINCT + Spliterator.NONNULL + Spliterator.IMMUTABLE;
+        }
+
+        @Override
+        public long estimateSize() {
+            return Long.MAX_VALUE;
+        }
+    }
 }
--- a/jdk/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java	Fri Oct 28 10:18:07 2016 +0100
@@ -176,6 +176,16 @@
     }
 
     /**
+     * Returns the {@code Entry} for a resource in a JMOD file section
+     * or {@code null} if not found.
+     */
+    public Entry getEntry(Section section, String name) {
+        String entry = section.jmodDir() + "/" + name;
+        ZipEntry ze = zipfile.getEntry(entry);
+        return (ze != null) ? new Entry(ze) : null;
+    }
+
+    /**
      * Opens an {@code InputStream} for reading the named entry of the given
      * section in this jmod file.
      *
@@ -185,7 +195,6 @@
     public InputStream getInputStream(Section section, String name)
         throws IOException
     {
-
         String entry = section.jmodDir() + "/" + name;
         ZipEntry e = zipfile.getEntry(entry);
         if (e == null) {
@@ -195,6 +204,15 @@
     }
 
     /**
+     * Opens an {@code InputStream} for reading an entry in the JMOD file.
+     *
+     * @throws IOException if an I/O error occurs
+     */
+    public InputStream getInputStream(Entry entry) throws IOException {
+        return zipfile.getInputStream(entry.zipEntry());
+    }
+
+    /**
      * Returns a stream of non-directory entries in this jmod file.
      */
     public Stream<Entry> stream() {
--- a/jdk/src/java.base/share/classes/jdk/internal/loader/BuiltinClassLoader.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/loader/BuiltinClassLoader.java	Fri Oct 28 10:18:07 2016 +0100
@@ -53,6 +53,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.jar.Attributes;
 import java.util.jar.Manifest;
+import java.util.stream.Stream;
 
 import jdk.internal.module.ModulePatcher.PatchedModuleReader;
 import jdk.internal.misc.VM;
@@ -749,6 +750,10 @@
             return Optional.empty();
         }
         @Override
+        public Stream<String> list() {
+            return Stream.empty();
+        }
+        @Override
         public void close() {
             throw new InternalError("Should not get here");
         }
--- a/jdk/src/java.base/share/classes/jdk/internal/loader/Loader.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/loader/Loader.java	Fri Oct 28 10:18:07 2016 +0100
@@ -53,6 +53,7 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Stream;
 
 
 /**
@@ -534,6 +535,10 @@
             return Optional.empty();
         }
         @Override
+        public Stream<String> list() {
+            return Stream.empty();
+        }
+        @Override
         public void close() {
             throw new InternalError("Should not get here");
         }
--- a/jdk/src/java.base/share/classes/jdk/internal/misc/JavaLangModuleAccess.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/misc/JavaLangModuleAccess.java	Fri Oct 28 10:18:07 2016 +0100
@@ -39,6 +39,7 @@
 import java.lang.module.ModuleReader;
 import java.lang.module.ModuleReference;
 import java.net.URI;
+import java.nio.file.Path;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -103,6 +104,11 @@
                                          ModuleHashes hashes);
 
     /**
+     * Returns the object with the hashes of other modules
+     */
+    Optional<ModuleHashes> hashes(ModuleDescriptor descriptor);
+
+    /**
      * Resolves a collection of root modules, with service binding
      * and the empty configuration as the parent. The post resolution
      * checks are optionally run.
@@ -120,8 +126,10 @@
                                      Supplier<ModuleReader> readerSupplier);
 
     /**
-     * Returns the object with the hashes of other modules
+     * Creates a ModuleFinder for a module path.
      */
-    Optional<ModuleHashes> hashes(ModuleDescriptor descriptor);
+    ModuleFinder newModulePath(Runtime.Version version,
+                               boolean isLinkPhase,
+                               Path... entries);
 
 }
--- a/jdk/src/java.base/share/classes/jdk/internal/module/ConfigurableModuleFinder.java	Thu Oct 27 23:49:38 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.internal.module;
-
-import java.lang.module.ModuleFinder;
-
-/**
- * A ModuleFinder that may be configured to work at either run-time
- * or link-time.
- */
-
-public interface ConfigurableModuleFinder extends ModuleFinder {
-
-    public static enum Phase {
-        RUN_TIME,
-        LINK_TIME
-    }
-
-    /**
-     * Configures this finder to work in the given phase.
-     */
-    void configurePhase(Phase phase);
-
-}
--- a/jdk/src/java.base/share/classes/jdk/internal/module/ModuleInfoWriter.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/module/ModuleInfoWriter.java	Fri Oct 28 10:18:07 2016 +0100
@@ -57,7 +57,15 @@
         cw.visit(Opcodes.V1_9, ACC_MODULE, name, null, null, null);
 
         cw.visitAttribute(new ModuleAttribute(md));
-        cw.visitAttribute(new ConcealedPackagesAttribute(md.conceals()));
+
+        // for tests: write the ConcealedPackages attribute when there are non-exported packages
+        long nExportedPackages = md.exports().stream()
+                .map(ModuleDescriptor.Exports::source)
+                .distinct()
+                .count();
+        if (md.packages().size() > nExportedPackages)
+            cw.visitAttribute(new ConcealedPackagesAttribute(md.packages()));
+
         md.version().ifPresent(v -> cw.visitAttribute(new VersionAttribute(v)));
         md.mainClass().ifPresent(mc -> cw.visitAttribute(new MainClassAttribute(mc)));
 
--- a/jdk/src/java.base/share/classes/jdk/internal/module/ModulePatcher.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/module/ModulePatcher.java	Fri Oct 28 10:18:07 2016 +0100
@@ -50,6 +50,7 @@
 import java.util.Set;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
+import java.util.stream.Stream;
 
 import jdk.internal.loader.Resource;
 import jdk.internal.misc.JavaLangModuleAccess;
@@ -159,21 +160,19 @@
                     // is not supported by the boot class loader
                     try (JarFile jf = new JarFile(file.toFile())) {
                         jf.stream()
-                          .filter(e -> e.getName().endsWith(".class"))
                           .map(e -> toPackageName(file, e))
-                          .filter(pn -> pn.length() > 0)
+                          .filter(Checks::isJavaIdentifier)
                           .forEach(packages::add);
                     }
 
                 } else if (Files.isDirectory(file)) {
 
-                    // exploded directory
+                    // exploded directory without following sym links
                     Path top = file;
                     Files.find(top, Integer.MAX_VALUE,
-                            ((path, attrs) -> attrs.isRegularFile() &&
-                                    path.toString().endsWith(".class")))
+                               ((path, attrs) -> attrs.isRegularFile()))
                             .map(path -> toPackageName(top, path))
-                            .filter(pn -> pn.length() > 0)
+                            .filter(Checks::isJavaIdentifier)
                             .forEach(packages::add);
 
                 }
@@ -381,6 +380,15 @@
         }
 
         @Override
+        public Stream<String> list() throws IOException {
+            Stream<String> s = delegate().list();
+            for (ResourceFinder finder : finders) {
+                s = Stream.concat(s, finder.list());
+            }
+            return s.distinct();
+        }
+
+        @Override
         public void close() throws IOException {
             closeAll(finders);
             delegate().close();
@@ -393,6 +401,7 @@
      */
     private static interface ResourceFinder extends Closeable {
         Resource find(String name) throws IOException;
+        Stream<String> list() throws IOException;
     }
 
 
@@ -453,6 +462,13 @@
                 }
             };
         }
+
+        @Override
+        public Stream<String> list() throws IOException {
+            return jf.stream()
+                    .filter(e -> !e.isDirectory())
+                    .map(JarEntry::getName);
+        }
     }
 
 
@@ -527,6 +543,15 @@
                 }
             };
         }
+
+        @Override
+        public Stream<String> list() throws IOException {
+            return Files.find(dir, Integer.MAX_VALUE,
+                              (path, attrs) -> attrs.isRegularFile())
+                    .map(f -> dir.relativize(f)
+                                 .toString()
+                                 .replace(File.separatorChar, '/'));
+        }
     }
 
 
@@ -537,7 +562,7 @@
         Path entry = top.relativize(file);
         Path parent = entry.getParent();
         if (parent == null) {
-            return warnUnnamedPackage(top, entry.toString());
+            return warnIfModuleInfo(top, entry.toString());
         } else {
             return parent.toString().replace(File.separatorChar, '.');
         }
@@ -557,14 +582,15 @@
         String name = entry.getName();
         int index = name.lastIndexOf("/");
         if (index == -1) {
-            return warnUnnamedPackage(file, name);
+            return warnIfModuleInfo(file, name);
         } else {
             return name.substring(0, index).replace('/', '.');
         }
     }
 
-    private static String warnUnnamedPackage(Path file, String e) {
-        System.err.println("WARNING: " + e + " not allowed in patch: " + file);
+    private static String warnIfModuleInfo(Path file, String e) {
+        if (e.equals("module-info.class"))
+            System.err.println("WARNING: " + e + " ignored in patch: " + file);
         return "";
     }
 
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java	Fri Oct 28 10:18:07 2016 +0100
@@ -42,8 +42,6 @@
 import java.util.*;
 import java.util.stream.Collectors;
 
-import jdk.internal.module.ConfigurableModuleFinder;
-import jdk.internal.module.ConfigurableModuleFinder.Phase;
 import jdk.tools.jlink.internal.TaskHelper.BadArgs;
 import static jdk.tools.jlink.internal.TaskHelper.JLINK_BUNDLE;
 import jdk.tools.jlink.internal.Jlink.JlinkConfiguration;
@@ -54,6 +52,7 @@
 import jdk.tools.jlink.plugin.PluginException;
 import jdk.tools.jlink.builder.DefaultImageBuilder;
 import jdk.tools.jlink.plugin.Plugin;
+import jdk.internal.misc.SharedSecrets;
 
 /**
  * Implementation for the jlink tool.
@@ -252,8 +251,9 @@
             throw new Exception("Empty module paths");
         }
 
-        ModuleFinder finder
-                = newModuleFinder(config.getModulepaths(), config.getLimitmods(), config.getModules());
+        ModuleFinder finder = newModuleFinder(config.getModulepaths(),
+                                              config.getLimitmods(),
+                                              config.getModules());
 
         // First create the image provider
         ImageProvider imageProvider
@@ -328,24 +328,41 @@
         return addMods;
     }
 
-    public static ModuleFinder newModuleFinder(List<Path> paths,
-                                               Set<String> limitMods,
-                                               Set<String> addMods)
-    {
-        ModuleFinder finder = ModuleFinder.of(paths.toArray(new Path[0]));
+    /**
+     * Returns a module finder to find the observable modules specified in
+     * the --module-path and --limit-modules options
+     */
+    private ModuleFinder modulePathFinder() {
+        Path[] entries = options.modulePath.toArray(new Path[0]);
+        ModuleFinder finder = SharedSecrets.getJavaLangModuleAccess()
+            .newModulePath(Runtime.version(), true, entries);
 
-        // jmods are located at link-time
-        if (finder instanceof ConfigurableModuleFinder) {
-            ((ConfigurableModuleFinder) finder).configurePhase(Phase.LINK_TIME);
-        }
-
-        // if limitmods is specified then limit the universe
-        if (!limitMods.isEmpty()) {
-            finder = limitFinder(finder, limitMods, addMods);
+        if (!options.limitMods.isEmpty()) {
+            finder = limitFinder(finder, options.limitMods, Collections.emptySet());
         }
         return finder;
     }
 
+    /**
+     * Returns a module finder of the given module path that limits
+     * the observable modules to those in the transitive closure of
+     * the modules specified in {@code limitMods} plus other modules
+     * specified in the {@code roots} set.
+     */
+    public static ModuleFinder newModuleFinder(List<Path> paths,
+                                               Set<String> limitMods,
+                                               Set<String> roots)
+    {
+        Path[] entries = paths.toArray(new Path[0]);
+        ModuleFinder finder = SharedSecrets.getJavaLangModuleAccess()
+            .newModulePath(Runtime.version(), true, entries);
+
+        // if limitmods is specified then limit the universe
+        if (!limitMods.isEmpty()) {
+            finder = limitFinder(finder, limitMods, roots);
+        }
+        return finder;
+    }
 
     private static Path toPathLocation(ResolvedModule m) {
         Optional<URI> ouri = m.reference().location();
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java	Fri Oct 28 10:18:07 2016 +0100
@@ -47,8 +47,6 @@
 import java.util.ResourceBundle;
 import java.util.Set;
 
-import jdk.internal.module.ConfigurableModuleFinder;
-import jdk.internal.module.ConfigurableModuleFinder.Phase;
 import jdk.tools.jlink.internal.plugins.ExcludeFilesPlugin;
 import jdk.tools.jlink.internal.plugins.ExcludeJmodSectionPlugin;
 import jdk.tools.jlink.plugin.Plugin;
@@ -60,6 +58,7 @@
 import jdk.tools.jlink.internal.plugins.PluginsResourceBundle;
 import jdk.tools.jlink.internal.plugins.DefaultCompressPlugin;
 import jdk.tools.jlink.internal.plugins.StripDebugPlugin;
+import jdk.internal.misc.SharedSecrets;
 
 /**
  *
@@ -726,14 +725,10 @@
     }
 
     static Layer createPluginsLayer(List<Path> paths) {
-        Path[] arr = new Path[paths.size()];
-        paths.toArray(arr);
-        ModuleFinder finder = ModuleFinder.of(arr);
 
-        // jmods are located at link-time
-        if (finder instanceof ConfigurableModuleFinder) {
-            ((ConfigurableModuleFinder) finder).configurePhase(Phase.LINK_TIME);
-        }
+        Path[] dirs = paths.toArray(new Path[0]);
+        ModuleFinder finder = SharedSecrets.getJavaLangModuleAccess()
+            .newModulePath(Runtime.version(), true, dirs);
 
         Configuration bootConfiguration = Layer.boot().configuration();
         try {
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java	Fri Oct 28 10:18:07 2016 +0100
@@ -98,8 +98,6 @@
 import jdk.internal.joptsimple.ValueConverter;
 import jdk.internal.misc.JavaLangModuleAccess;
 import jdk.internal.misc.SharedSecrets;
-import jdk.internal.module.ConfigurableModuleFinder;
-import jdk.internal.module.ConfigurableModuleFinder.Phase;
 import jdk.internal.module.ModuleHashes;
 import jdk.internal.module.ModuleInfoExtender;
 import jdk.tools.jlink.internal.Utils;
@@ -1298,9 +1296,7 @@
                 options.manPages = opts.valuesOf(manPages);
             if (opts.has(modulePath)) {
                 Path[] dirs = opts.valuesOf(modulePath).toArray(new Path[0]);
-                options.moduleFinder = ModuleFinder.of(dirs);
-                if (options.moduleFinder instanceof ConfigurableModuleFinder)
-                    ((ConfigurableModuleFinder)options.moduleFinder).configurePhase(Phase.LINK_TIME);
+                options.moduleFinder = JLMA.newModulePath(Runtime.version(), true, dirs);
             }
             if (opts.has(moduleVersion))
                 options.moduleVersion = opts.valueOf(moduleVersion);
--- a/jdk/test/java/lang/module/AutomaticModulesTest.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/test/java/lang/module/AutomaticModulesTest.java	Fri Oct 28 10:18:07 2016 +0100
@@ -158,40 +158,85 @@
      */
     public void testPackages() throws IOException {
         Path dir = Files.createTempDirectory(USER_DIR, "mods");
-        createDummyJarFile(dir.resolve("m1.jar"),
+        createDummyJarFile(dir.resolve("m.jar"),
                            "p/C1.class", "p/C2.class", "q/C1.class");
 
         ModuleFinder finder = ModuleFinder.of(dir);
+        Optional<ModuleReference> mref = finder.find("m");
+        assertTrue(mref.isPresent(), "m not found");
 
-        Configuration parent = Layer.boot().configuration();
-        Configuration cf = resolve(parent, finder, "m1");
+        ModuleDescriptor descriptor = mref.get().descriptor();
 
-        ModuleDescriptor m1 = findDescriptor(cf, "m1");
+        assertTrue(descriptor.packages().size() == 2);
+        assertTrue(descriptor.packages().contains("p"));
+        assertTrue(descriptor.packages().contains("q"));
 
-        Set<String> exports
-            = m1.exports().stream().map(Exports::source).collect(Collectors.toSet());
-
+        Set<String> exports = descriptor.exports().stream()
+                .map(Exports::source)
+                .collect(Collectors.toSet());
         assertTrue(exports.size() == 2);
         assertTrue(exports.contains("p"));
         assertTrue(exports.contains("q"));
-        assertTrue(m1.conceals().isEmpty());
+    }
+
+    /**
+     * Test class files in JAR file where the entry does not correspond to a
+     * legal package name.
+     */
+    public void testBadPackage() throws IOException {
+        Path dir = Files.createTempDirectory(USER_DIR, "mods");
+        createDummyJarFile(dir.resolve("m.jar"), "p/C1.class", "p-/C2.class");
+
+        ModuleFinder finder = ModuleFinder.of(dir);
+        Optional<ModuleReference> mref = finder.find("m");
+        assertTrue(mref.isPresent(), "m not found");
+
+        ModuleDescriptor descriptor = mref.get().descriptor();
+
+        assertTrue(descriptor.packages().size() == 1);
+        assertTrue(descriptor.packages().contains("p"));
+
+        Set<String> exports = descriptor.exports().stream()
+                .map(Exports::source)
+                .collect(Collectors.toSet());
+        assertTrue(exports.size() == 1);
+        assertTrue(exports.contains("p"));
     }
 
+    /**
+     * Test non-class resources in a JAR file.
+     */
+    public void testNonClassResources() throws IOException {
+        Path dir = Files.createTempDirectory(USER_DIR, "mods");
+        createDummyJarFile(dir.resolve("m.jar"),
+                "LICENSE",
+                "README",
+                "WEB-INF/tags",
+                "p/Type.class",
+                "p/resources/m.properties");
+
+        ModuleFinder finder = ModuleFinder.of(dir);
+        Optional<ModuleReference> mref = finder.find("m");
+        assertTrue(mref.isPresent(), "m not found");
+
+        ModuleDescriptor descriptor = mref.get().descriptor();
+
+        assertTrue(descriptor.packages().size() == 2);
+        assertTrue(descriptor.packages().contains("p"));
+        assertTrue(descriptor.packages().contains("p.resources"));
+    }
 
     /**
-     * Test class file in JAR file where the entry does not correspond to a
-     * legal package name.
+     * Test .class file in unnamed package (top-level directory)
      */
     @Test(expectedExceptions = FindException.class)
-    public void testBadPackage() throws IOException {
+    public void testClassInUnnamedPackage() throws IOException {
         Path dir = Files.createTempDirectory(USER_DIR, "mods");
-        createDummyJarFile(dir.resolve("m1.jar"), "p-/T.class");
-
-        // should throw FindException
-        ModuleFinder.of(dir).findAll();
+        createDummyJarFile(dir.resolve("m.jar"), "Mojo.class");
+        ModuleFinder finder = ModuleFinder.of(dir);
+        finder.findAll();
     }
 
-
     /**
      * Test JAR file with META-INF/services configuration file
      */
@@ -204,12 +249,12 @@
         Files.createDirectories(services);
         Files.write(services.resolve(service), Set.of(provider));
         Path dir = Files.createTempDirectory(USER_DIR, "mods");
-        JarUtils.createJarFile(dir.resolve("m1.jar"), tmpdir);
+        JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
 
         ModuleFinder finder = ModuleFinder.of(dir);
 
-        Optional<ModuleReference> mref = finder.find("m1");
-        assertTrue(mref.isPresent(), "m1 not found");
+        Optional<ModuleReference> mref = finder.find("m");
+        assertTrue(mref.isPresent(), "m not found");
 
         ModuleDescriptor descriptor = mref.get().descriptor();
         assertTrue(descriptor.provides().size() == 1);
@@ -220,17 +265,12 @@
     }
 
 
-    // META-INF/services configuration file/entries that are not legal
-    @DataProvider(name = "badproviders")
-    public Object[][] createProviders() {
+    // META-INF/services files that don't map to legal service names
+    @DataProvider(name = "badservices")
+    public Object[][] createBadServices() {
         return new Object[][] {
 
                 // service type         provider type
-
-                { "p.S",                "-" },
-                { "p.S",                ".S1" },
-                { "p.S",                "S1." },
-
                 { "-",                  "p.S1" },
                 { ".S",                 "p.S1" },
         };
@@ -240,8 +280,8 @@
      * Test JAR file with META-INF/services configuration file with bad
      * values or names.
      */
-    @Test(dataProvider = "badproviders", expectedExceptions = FindException.class)
-    public void testBadServicesConfiguration(String service, String provider)
+    @Test(dataProvider = "badservices")
+    public void testBadServicesNames(String service, String provider)
         throws IOException
     {
         Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
@@ -249,7 +289,41 @@
         Files.createDirectories(services);
         Files.write(services.resolve(service), Set.of(provider));
         Path dir = Files.createTempDirectory(USER_DIR, "mods");
-        JarUtils.createJarFile(dir.resolve("m1.jar"), tmpdir);
+        JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
+
+        Optional<ModuleReference> omref = ModuleFinder.of(dir).find("m");
+        assertTrue(omref.isPresent());
+        ModuleDescriptor descriptor = omref.get().descriptor();
+        assertTrue(descriptor.provides().isEmpty());
+    }
+
+
+    // META-INF/services configuration file entries that are not legal
+    @DataProvider(name = "badproviders")
+    public Object[][] createBadProviders() {
+        return new Object[][] {
+
+                // service type         provider type
+                { "p.S",                "-" },
+                { "p.S",                ".S1" },
+                { "p.S",                "S1." },
+        };
+    }
+
+    /**
+     * Test JAR file with META-INF/services configuration file with bad
+     * values or names.
+     */
+    @Test(dataProvider = "badproviders", expectedExceptions = FindException.class)
+    public void testBadProvideNames(String service, String provider)
+        throws IOException
+    {
+        Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
+        Path services = tmpdir.resolve("META-INF").resolve("services");
+        Files.createDirectories(services);
+        Files.write(services.resolve(service), Set.of(provider));
+        Path dir = Files.createTempDirectory(USER_DIR, "mods");
+        JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
 
         // should throw FindException
         ModuleFinder.of(dir).findAll();
--- a/jdk/test/java/lang/module/ModuleFinderTest.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/test/java/lang/module/ModuleFinderTest.java	Fri Oct 28 10:18:07 2016 +0100
@@ -29,6 +29,7 @@
  * @summary Basic tests for java.lang.module.ModuleFinder
  */
 
+import java.io.File;
 import java.io.OutputStream;
 import java.lang.module.FindException;
 import java.lang.module.InvalidModuleDescriptorException;
@@ -38,6 +39,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Optional;
 import java.util.Set;
 import java.util.jar.JarEntry;
 import java.util.jar.JarOutputStream;
@@ -317,6 +319,111 @@
 
 
     /**
+     * Test ModuleFinder with a JAR file containing a mix of class and
+     * non-class resources.
+     */
+    public void testOfOneJarFileWithResources() throws Exception {
+        Path dir = Files.createTempDirectory(USER_DIR, "mods");
+        Path jar = createModularJar(dir.resolve("m.jar"), "m",
+                "LICENSE",
+                "README",
+                "WEB-INF/tags",
+                "p/Type.class",
+                "p/resources/m.properties",
+                "q-/Type.class",                // not a legal package name
+                "q-/resources/m/properties");
+
+        ModuleFinder finder = ModuleFinder.of(jar);
+        Optional<ModuleReference> mref = finder.find("m");
+        assertTrue(mref.isPresent(), "m not found");
+
+        ModuleDescriptor descriptor = mref.get().descriptor();
+
+        assertTrue(descriptor.packages().size() == 2);
+        assertTrue(descriptor.packages().contains("p"));
+        assertTrue(descriptor.packages().contains("p.resources"));
+    }
+
+
+    /**
+     * Test ModuleFinder with an exploded module containing a mix of class
+     * and non-class resources
+     */
+    public void testOfOneExplodedModuleWithResources() throws Exception {
+        Path dir = Files.createTempDirectory(USER_DIR, "mods");
+        Path m_dir = createExplodedModule(dir.resolve("m"), "m",
+                "LICENSE",
+                "README",
+                "WEB-INF/tags",
+                "p/Type.class",
+                "p/resources/m.properties",
+                "q-/Type.class",                 // not a legal package name
+                "q-/resources/m/properties");
+
+        ModuleFinder finder = ModuleFinder.of(m_dir);
+        Optional<ModuleReference> mref = finder.find("m");
+        assertTrue(mref.isPresent(), "m not found");
+
+        ModuleDescriptor descriptor = mref.get().descriptor();
+
+        assertTrue(descriptor.packages().size() == 2);
+        assertTrue(descriptor.packages().contains("p"));
+        assertTrue(descriptor.packages().contains("p.resources"));
+    }
+
+
+    /**
+     * Test ModuleModule with a JAR file containing a .class file in the top
+     * level directory.
+     */
+    public void testOfOneJarFileWithTopLevelClass() throws Exception {
+        Path dir = Files.createTempDirectory(USER_DIR, "mods");
+        Path jar = createModularJar(dir.resolve("m.jar"), "m", "Mojo.class");
+
+        ModuleFinder finder = ModuleFinder.of(jar);
+        try {
+            finder.find("m");
+            assertTrue(false);
+        } catch (FindException e) {
+            assertTrue(e.getCause() instanceof InvalidModuleDescriptorException);
+        }
+
+        finder = ModuleFinder.of(jar);
+        try {
+            finder.findAll();
+            assertTrue(false);
+        } catch (FindException e) {
+            assertTrue(e.getCause() instanceof InvalidModuleDescriptorException);
+        }
+    }
+
+    /**
+     * Test ModuleModule with a JAR file containing a .class file in the top
+     * level directory.
+     */
+    public void testOfOneExplodedModuleWithTopLevelClass() throws Exception {
+        Path dir = Files.createTempDirectory(USER_DIR, "mods");
+        Path m_dir = createExplodedModule(dir.resolve("m"), "m", "Mojo.class");
+
+        ModuleFinder finder = ModuleFinder.of(m_dir);
+        try {
+            finder.find("m");
+            assertTrue(false);
+        } catch (FindException e) {
+            assertTrue(e.getCause() instanceof InvalidModuleDescriptorException);
+        }
+
+        finder = ModuleFinder.of(m_dir);
+        try {
+            finder.findAll();
+            assertTrue(false);
+        } catch (FindException e) {
+            assertTrue(e.getCause() instanceof InvalidModuleDescriptorException);
+        }
+    }
+
+
+    /**
      * Test ModuleFinder.of with a path to a file that does not exist.
      */
     public void testOfWithDoesNotExistEntry() throws Exception {
@@ -641,7 +748,7 @@
             vs = mid.substring(i+1);
         }
         ModuleDescriptor.Builder builder
-                = new ModuleDescriptor.Builder(mn).requires("java.base");
+            = new ModuleDescriptor.Builder(mn).requires("java.base");
         if (vs != null)
             builder.version(vs);
         return builder.build();
@@ -651,13 +758,22 @@
      * Creates an exploded module in the given directory and containing a
      * module descriptor with the given module name/version.
      */
-    static Path createExplodedModule(Path dir, String mid) throws Exception {
+    static Path createExplodedModule(Path dir, String mid, String... entries)
+        throws Exception
+    {
         ModuleDescriptor descriptor = newModuleDescriptor(mid);
         Files.createDirectories(dir);
         Path mi = dir.resolve("module-info.class");
         try (OutputStream out = Files.newOutputStream(mi)) {
             ModuleInfoWriter.write(descriptor, out);
         }
+
+        for (String entry : entries) {
+            Path file = dir.resolve(entry.replace('/', File.separatorChar));
+            Files.createDirectories(file.getParent());
+            Files.createFile(file);
+        }
+
         return dir;
     }
 
--- a/jdk/test/java/lang/module/ModuleReader/ModuleReaderTest.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/test/java/lang/module/ModuleReader/ModuleReaderTest.java	Fri Oct 28 10:18:07 2016 +0100
@@ -24,9 +24,8 @@
 /**
  * @test
  * @library /lib/testlibrary
- * @modules java.base/jdk.internal.module
+ * @modules java.base/jdk.internal.misc
  *          jdk.compiler
- *          jdk.jlink
  * @build ModuleReaderTest CompilerUtils JarUtils
  * @run testng ModuleReaderTest
  * @summary Basic tests for java.lang.module.ModuleReader
@@ -47,11 +46,14 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
 import java.util.spi.ToolProvider;
 
-import jdk.internal.module.ConfigurableModuleFinder;
-import jdk.internal.module.ConfigurableModuleFinder.Phase;
+import jdk.internal.misc.SharedSecrets;
 
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Test;
@@ -101,7 +103,7 @@
     /**
      * Test ModuleReader to module in runtime image
      */
-    public void testImage() throws Exception {
+    public void testImage() throws IOException {
 
         ModuleFinder finder = ModuleFinder.ofSystem();
         ModuleReference mref = finder.find(BASE_MODULE).get();
@@ -119,6 +121,7 @@
                 testFind(reader, name, expectedBytes);
                 testOpen(reader, name, expectedBytes);
                 testRead(reader, name, expectedBytes);
+                testList(reader, name);
 
             }
 
@@ -168,7 +171,7 @@
     /**
      * Test ModuleReader to exploded module
      */
-    public void testExplodedModule() throws Exception {
+    public void testExplodedModule() throws IOException {
         test(MODS_DIR);
     }
 
@@ -176,7 +179,7 @@
     /**
      * Test ModuleReader to modular JAR
      */
-    public void testModularJar() throws Exception {
+    public void testModularJar() throws IOException {
         Path dir = Files.createTempDirectory(USER_DIR, "mlib");
 
         // jar cf mlib/${TESTMODULE}.jar -C mods .
@@ -190,7 +193,7 @@
     /**
      * Test ModuleReader to JMOD
      */
-    public void testJMod() throws Exception {
+    public void testJMod() throws IOException {
         Path dir = Files.createTempDirectory(USER_DIR, "mlib");
 
         // jmod create --class-path mods/${TESTMODULE}  mlib/${TESTMODULE}.jmod
@@ -211,13 +214,10 @@
      * The test module is found on the given module path. Open a ModuleReader
      * to the test module and test the reader.
      */
-    void test(Path mp) throws Exception {
+    void test(Path mp) throws IOException {
 
-        ModuleFinder finder = ModuleFinder.of(mp);
-        if (finder instanceof ConfigurableModuleFinder) {
-            // need ModuleFinder to be in the phase to find JMOD files
-            ((ConfigurableModuleFinder)finder).configurePhase(Phase.LINK_TIME);
-        }
+        ModuleFinder finder = SharedSecrets.getJavaLangModuleAccess()
+            .newModulePath(Runtime.version(), true, mp);
 
         ModuleReference mref = finder.find(TEST_MODULE).get();
         ModuleReader reader = mref.open();
@@ -234,6 +234,7 @@
                 testFind(reader, name, expectedBytes);
                 testOpen(reader, name, expectedBytes);
                 testRead(reader, name, expectedBytes);
+                testList(reader, name);
             }
 
             // test "not found"
@@ -275,13 +276,18 @@
             reader.read(TEST_RESOURCES[0]);
             assertTrue(false);
         } catch (IOException expected) { }
+
+        try {
+            reader.list();
+            assertTrue(false);
+        } catch (IOException expected) { }
     }
 
     /**
      * Test ModuleReader#find
      */
     void testFind(ModuleReader reader, String name, byte[] expectedBytes)
-        throws Exception
+        throws IOException
     {
         Optional<URI> ouri = reader.find(name);
         assertTrue(ouri.isPresent());
@@ -301,7 +307,7 @@
      * Test ModuleReader#open
      */
     void testOpen(ModuleReader reader, String name, byte[] expectedBytes)
-        throws Exception
+        throws IOException
     {
         Optional<InputStream> oin = reader.open(name);
         assertTrue(oin.isPresent());
@@ -317,7 +323,7 @@
      * Test ModuleReader#read
      */
     void testRead(ModuleReader reader, String name, byte[] expectedBytes)
-        throws Exception
+        throws IOException
     {
         Optional<ByteBuffer> obb = reader.read(name);
         assertTrue(obb.isPresent());
@@ -334,4 +340,24 @@
         }
     }
 
+    /**
+     * Test ModuleReader#list
+     */
+    void testList(ModuleReader reader, String name) throws IOException {
+        List<String> list = reader.list().collect(Collectors.toList());
+        Set<String> names = new HashSet<>(list);
+        assertTrue(names.size() == list.size()); // no duplicates
+
+        assertTrue(names.contains("module-info.class"));
+        assertTrue(names.contains(name));
+
+        // all resources should be locatable via find
+        for (String e : names) {
+            assertTrue(reader.find(e).isPresent());
+        }
+
+        // should not contain directories
+        names.forEach(e -> assertFalse(e.endsWith("/")));
+    }
+
 }
--- a/jdk/test/java/lang/module/ModuleReader/MultiReleaseJarTest.java	Thu Oct 27 23:49:38 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,216 +0,0 @@
-/*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/**
- * @test
- * @library /lib/testlibrary
- * @modules java.base/jdk.internal.module
- * @build MultiReleaseJarTest JarUtils
- * @run testng MultiReleaseJarTest
- * @run testng/othervm -Djdk.util.jar.enableMultiRelease=false MultiReleaseJarTest
- * @summary Basic test of ModuleReader with a modular JAR that is also a
- *          multi-release JAR
- */
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.module.ModuleDescriptor;
-import java.lang.module.ModuleFinder;
-import java.lang.module.ModuleReader;
-import java.lang.module.ModuleReference;
-import java.net.URI;
-import java.net.URLConnection;
-import java.nio.ByteBuffer;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Optional;
-import java.util.Set;
-import java.util.jar.Attributes;
-import java.util.jar.Manifest;
-import java.util.stream.Collectors;
-
-import jdk.internal.module.ModuleInfoWriter;
-
-import org.testng.annotations.Test;
-import static org.testng.Assert.*;
-
-/**
- * Exercises ModuleReader with a modular JAR containing the following files:
- *
- * <pre>{@code
- *     module-info.class
- *     META-INF/versions/<version>/module.class
- * }</pre>
- *
- * The module-info.class in the top-level directory is the binary form of:
- * <pre>{@code
- *     module jdk.test {
- *         requires java.base;
- *     }
- * }</pre>
- *
- * The module-info.class in the versioned section is the binary form of:
- * <pre>{@code
- *     module jdk.test {
- *         requires java.base;
- *         requires jdk.unsupported;
- *     }
- * }</pre>
- */
-
-@Test
-public class MultiReleaseJarTest {
-
-    // Java SE/JDK major release
-    private static final int RELEASE = Runtime.version().major();
-
-    // the name of the test module
-    private static final String MODULE_NAME = "jdk.test";
-
-    private static final String MODULE_INFO_CLASS = "module-info.class";
-
-    /**
-     * Uses the ModuleFinder API to locate the module packaged as a modular
-     * and mutli-release JAR and then creates a ModuleReader to access the
-     * contents of the module.
-     */
-    public void testMultiReleaseJar() throws IOException {
-
-        // are multi-release JARs enabled?
-        String s = System.getProperty("jdk.util.jar.enableMultiRelease");
-        boolean multiRelease = (s == null || Boolean.parseBoolean(s));
-
-        // create the multi-release modular JAR
-        Path jarfile = createJarFile();
-
-        // find the module
-        ModuleFinder finder = ModuleFinder.of(jarfile);
-        Optional<ModuleReference> omref = finder.find(MODULE_NAME);
-        assertTrue((omref.isPresent()));
-        ModuleReference mref = omref.get();
-
-        // test that correct module-info.class was read
-        checkDescriptor(mref.descriptor(), multiRelease);
-
-        // test ModuleReader
-        try (ModuleReader reader = mref.open()) {
-
-            // open resource
-            Optional<InputStream> oin = reader.open(MODULE_INFO_CLASS);
-            assertTrue(oin.isPresent());
-            try (InputStream in = oin.get()) {
-                checkDescriptor(ModuleDescriptor.read(in), multiRelease);
-            }
-
-            // read resource
-            Optional<ByteBuffer> obb = reader.read(MODULE_INFO_CLASS);
-            assertTrue(obb.isPresent());
-            ByteBuffer bb = obb.get();
-            try {
-                checkDescriptor(ModuleDescriptor.read(bb), multiRelease);
-            } finally {
-                reader.release(bb);
-            }
-
-            // find resource
-            Optional<URI> ouri = reader.find(MODULE_INFO_CLASS);
-            assertTrue(ouri.isPresent());
-            URI uri = ouri.get();
-
-            String expectedTail = "!/";
-            if (multiRelease)
-                expectedTail += "META-INF/versions/" + RELEASE + "/";
-            expectedTail += MODULE_INFO_CLASS;
-            assertTrue(uri.toString().endsWith(expectedTail));
-
-            URLConnection uc = uri.toURL().openConnection();
-            uc.setUseCaches(false);
-            try (InputStream in = uc.getInputStream()) {
-                checkDescriptor(ModuleDescriptor.read(in), multiRelease);
-            }
-
-        }
-
-    }
-
-    /**
-     * Checks that the module descriptor is the expected module descriptor.
-     * When the multi release JAR feature is enabled then the module
-     * descriptor is expected to have been read from the versioned section
-     * of the JAR file.
-     */
-    private void checkDescriptor(ModuleDescriptor descriptor, boolean multiRelease) {
-        Set<String> requires = descriptor.requires().stream()
-                .map(ModuleDescriptor.Requires::name)
-                .collect(Collectors.toSet());
-        assertTrue(requires.contains("java.base"));
-        assertTrue(requires.contains("jdk.unsupported") == multiRelease);
-    }
-
-    /**
-     * Creates the modular JAR for the test, returning the Path to the JAR file.
-     */
-    private Path createJarFile() throws IOException {
-
-        // module descriptor for top-level directory
-        ModuleDescriptor descriptor1
-            = new ModuleDescriptor.Builder(MODULE_NAME)
-                .requires("java.base")
-                .build();
-
-        // module descriptor for versioned section
-        ModuleDescriptor descriptor2
-            = new ModuleDescriptor.Builder(MODULE_NAME)
-                .requires("java.base")
-                .requires("jdk.unsupported")
-                .build();
-
-        Path top = Paths.get(MODULE_NAME);
-        Files.createDirectories(top);
-
-        Path mi1 = Paths.get(MODULE_INFO_CLASS);
-        try (OutputStream out = Files.newOutputStream(top.resolve(mi1))) {
-            ModuleInfoWriter.write(descriptor1, out);
-        }
-
-        Path vdir = Paths.get("META-INF", "versions", Integer.toString(RELEASE));
-        Files.createDirectories(top.resolve(vdir));
-
-        Path mi2 = vdir.resolve(MODULE_INFO_CLASS);
-        try (OutputStream out = Files.newOutputStream(top.resolve(mi2))) {
-            ModuleInfoWriter.write(descriptor2, out);
-        }
-
-        Manifest man = new Manifest();
-        Attributes attrs = man.getMainAttributes();
-        attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
-        attrs.put(Attributes.Name.MULTI_RELEASE, "true");
-
-        Path jarfile = Paths.get(MODULE_NAME + ".jar");
-        JarUtils.createJarFile(jarfile, man, top, mi1, mi2);
-
-        return jarfile;
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/module/MultiReleaseJarTest.java	Fri Oct 28 10:18:07 2016 +0100
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @library /lib/testlibrary
+ * @modules java.base/jdk.internal.module
+ * @build MultiReleaseJarTest JarUtils
+ * @run testng MultiReleaseJarTest
+ * @run testng/othervm -Djdk.util.jar.enableMultiRelease=false MultiReleaseJarTest
+ * @summary Basic test of modular JARs as multi-release JARs
+ */
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleFinder;
+import java.lang.module.ModuleReader;
+import java.lang.module.ModuleReference;
+import java.net.URI;
+import java.net.URLConnection;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import jdk.internal.module.ModuleInfoWriter;
+
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+
+
+@Test
+public class MultiReleaseJarTest {
+
+    private static final String MODULE_INFO = "module-info.class";
+
+    private static final int RELEASE = Runtime.version().major();
+
+    // are multi-release JARs enabled?
+    private static final boolean MULTI_RELEASE;
+    static {
+        String s = System.getProperty("jdk.util.jar.enableMultiRelease");
+        MULTI_RELEASE = (s == null || Boolean.parseBoolean(s));
+    }
+
+    /**
+     * Basic test of a multi-release JAR.
+     */
+    public void testBasic() throws Exception {
+        String name = "m1";
+
+        ModuleDescriptor descriptor = new ModuleDescriptor.Builder(name)
+                .requires("java.base")
+                .build();
+
+        Path jar = new JarBuilder(name)
+                .moduleInfo("module-info.class", descriptor)
+                .resource("p/Main.class")
+                .resource("p/Helper.class")
+                .resource("META-INF/versions/9/p/Helper.class")
+                .resource("META-INF/versions/9/p/internal/Helper9.class")
+                .build();
+
+        // find the module
+        ModuleFinder finder = ModuleFinder.of(jar);
+        Optional<ModuleReference> omref = finder.find(name);
+        assertTrue((omref.isPresent()));
+        ModuleReference mref = omref.get();
+
+        // check module packages
+        descriptor = mref.descriptor();
+        Set<String> packages = descriptor.packages();
+        assertTrue(packages.contains("p"));
+        if (MULTI_RELEASE) {
+            assertTrue(packages.size() == 2);
+            assertTrue(packages.contains("p.internal"));
+        } else {
+            assertTrue(packages.size() == 1);
+        }
+    }
+
+    /**
+     * Test a multi-release JAR with a module-info.class in the versioned
+     * section of the JAR.
+     */
+    public void testModuleInfoInVersionedSection() throws Exception {
+        String name = "m1";
+
+        ModuleDescriptor descriptor1 = new ModuleDescriptor.Builder(name)
+                .requires("java.base")
+                .build();
+
+        // module descriptor for versioned section
+        ModuleDescriptor descriptor2 = new ModuleDescriptor.Builder(name)
+                .requires("java.base")
+                .requires("jdk.unsupported")
+                .build();
+
+        Path jar = new JarBuilder(name)
+                .moduleInfo(MODULE_INFO, descriptor1)
+                .resource("p/Main.class")
+                .resource("p/Helper.class")
+                .moduleInfo("META-INF/versions/9/" + MODULE_INFO, descriptor2)
+                .resource("META-INF/versions/9/p/Helper.class")
+                .resource("META-INF/versions/9/p/internal/Helper9.class")
+                .build();
+
+        // find the module
+        ModuleFinder finder = ModuleFinder.of(jar);
+        Optional<ModuleReference> omref = finder.find(name);
+        assertTrue((omref.isPresent()));
+        ModuleReference mref = omref.get();
+
+        // ensure that the right module-info.class is loaded
+        ModuleDescriptor descriptor = mref.descriptor();
+        assertEquals(descriptor.name(), name);
+        if (MULTI_RELEASE) {
+            assertEquals(descriptor.requires(), descriptor2.requires());
+        } else {
+            assertEquals(descriptor.requires(), descriptor1.requires());
+        }
+    }
+
+    /**
+     * Test multi-release JAR as an automatic module.
+     */
+    public void testAutomaticModule() throws Exception {
+        String name = "m";
+
+        Path jar = new JarBuilder(name)
+                .resource("p/Main.class")
+                .resource("p/Helper.class")
+                .resource("META-INF/versions/9/p/Helper.class")
+                .resource("META-INF/versions/9/p/internal/Helper9.class")
+                .build();
+
+        // find the module
+        ModuleFinder finder = ModuleFinder.of(jar);
+        Optional<ModuleReference> omref = finder.find(name);
+        assertTrue((omref.isPresent()));
+        ModuleReference mref = omref.get();
+
+        // check module packages
+        ModuleDescriptor descriptor = mref.descriptor();
+        Set<String> packages = descriptor.packages();
+        if (MULTI_RELEASE) {
+            assertTrue(packages.size() == 2);
+            assertTrue(packages.contains("p.internal"));
+        } else {
+            assertTrue(packages.size() == 1);
+        }
+    }
+
+    /**
+     * Exercise ModuleReader on a multi-release JAR
+     */
+    public void testModuleReader() throws Exception {
+        String name = "m1";
+
+        ModuleDescriptor descriptor1 = new ModuleDescriptor.Builder(name)
+                .requires("java.base")
+                .build();
+
+        // module descriptor for versioned section
+        ModuleDescriptor descriptor2 = new ModuleDescriptor.Builder(name)
+                .requires("java.base")
+                .requires("jdk.unsupported")
+                .build();
+
+        Path jar = new JarBuilder(name)
+                .moduleInfo(MODULE_INFO, descriptor1)
+                .moduleInfo("META-INF/versions/9/" + MODULE_INFO, descriptor2)
+                .build();
+
+        // find the module
+        ModuleFinder finder = ModuleFinder.of(jar);
+        Optional<ModuleReference> omref = finder.find(name);
+        assertTrue((omref.isPresent()));
+        ModuleReference mref = omref.get();
+
+        ModuleDescriptor expected;
+        if (MULTI_RELEASE) {
+            expected = descriptor2;
+        } else {
+            expected = descriptor1;
+        }
+
+        // test ModuleReader by reading module-info.class resource
+        try (ModuleReader reader = mref.open()) {
+
+            // open resource
+            Optional<InputStream> oin = reader.open(MODULE_INFO);
+            assertTrue(oin.isPresent());
+            try (InputStream in = oin.get()) {
+                checkRequires(ModuleDescriptor.read(in), expected);
+            }
+
+            // read resource
+            Optional<ByteBuffer> obb = reader.read(MODULE_INFO);
+            assertTrue(obb.isPresent());
+            ByteBuffer bb = obb.get();
+            try {
+                checkRequires(ModuleDescriptor.read(bb), expected);
+            } finally {
+                reader.release(bb);
+            }
+
+            // find resource
+            Optional<URI> ouri = reader.find(MODULE_INFO);
+            assertTrue(ouri.isPresent());
+            URI uri = ouri.get();
+
+            String expectedTail = "!/";
+            if (MULTI_RELEASE)
+                expectedTail += "META-INF/versions/" + RELEASE + "/";
+            expectedTail += MODULE_INFO;
+            assertTrue(uri.toString().endsWith(expectedTail));
+
+            URLConnection uc = uri.toURL().openConnection();
+            uc.setUseCaches(false);
+            try (InputStream in = uc.getInputStream()) {
+                checkRequires(ModuleDescriptor.read(in), expected);
+            }
+
+        }
+    }
+
+    /**
+     * Check that two ModuleDescriptor have the same requires
+     */
+    static void checkRequires(ModuleDescriptor md1, ModuleDescriptor md2) {
+        assertEquals(md1.requires(), md2.requires());
+    }
+
+    /**
+     * A builder of multi-release JAR files.
+     */
+    static class JarBuilder {
+        private String name;
+        private Set<String> resources = new HashSet<>();
+        private Map<String, ModuleDescriptor> descriptors = new HashMap<>();
+
+        JarBuilder(String name) {
+            this.name = name;
+        }
+
+        /**
+         * Adds a module-info.class to the JAR file.
+         */
+        JarBuilder moduleInfo(String name, ModuleDescriptor descriptor) {
+            descriptors.put(name, descriptor);
+            return this;
+        }
+
+        /**
+         * Adds a dummy resource to the JAR file.
+         */
+        JarBuilder resource(String name) {
+            resources.add(name);
+            return this;
+        }
+
+        /**
+         * Create the multi-release JAR, returning its file path.
+         */
+        Path build() throws Exception {
+            Path dir = Files.createTempDirectory(Paths.get(""), "jar");
+            List<Path> files = new ArrayList<>();
+
+            // write the module-info.class
+            for (Map.Entry<String, ModuleDescriptor> e : descriptors.entrySet()) {
+                String name = e.getKey();
+                ModuleDescriptor descriptor = e.getValue();
+                Path mi = Paths.get(name.replace('/', File.separatorChar));
+                Path parent = dir.resolve(mi).getParent();
+                if (parent != null)
+                    Files.createDirectories(parent);
+                try (OutputStream out = Files.newOutputStream(dir.resolve(mi))) {
+                    ModuleInfoWriter.write(descriptor, out);
+                }
+                files.add(mi);
+            }
+
+            // write the dummy resources
+            for (String name : resources) {
+                Path file = Paths.get(name.replace('/', File.separatorChar));
+                // create dummy resource
+                Path parent = dir.resolve(file).getParent();
+                if (parent != null)
+                    Files.createDirectories(parent);
+                Files.createFile(dir.resolve(file));
+                files.add(file);
+            }
+
+            Manifest man = new Manifest();
+            Attributes attrs = man.getMainAttributes();
+            attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
+            attrs.put(Attributes.Name.MULTI_RELEASE, "true");
+
+            Path jarfile = Paths.get(name + ".jar");
+            JarUtils.createJarFile(jarfile, man, dir, files.toArray(new Path[0]));
+            return jarfile;
+        }
+    }
+}
--- a/jdk/test/java/util/ResourceBundle/modules/basic/src/asiabundles/jdk/test/resources/MyResources_ja_JP.properties	Thu Oct 27 23:49:38 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-#
-# Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
-# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
-#
-# This code is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 2 only, as
-# published by the Free Software Foundation.
-#
-# This code is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-# version 2 for more details (a copy is included in the LICENSE file that
-# accompanied this code).
-#
-# You should have received a copy of the GNU General Public License version
-# 2 along with this work; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
-# or visit www.oracle.com if you need additional information or have any
-# questions.
-#
-
-# This resource bundle is located at jdk/test/resources to demonstrate
-# the unique package requirement is not applicable to .properties bundles.
-
-key=ja-JP: message
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/ResourceBundle/modules/basic/src/asiabundles/jdk/test/resources/asia/MyResources_ja_JP.properties	Fri Oct 28 10:18:07 2016 +0100
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+# This resource bundle is located at jdk/test/resources to demonstrate
+# the unique package requirement is not applicable to .properties bundles.
+
+key=ja-JP: message
--- a/jdk/test/java/util/ResourceBundle/modules/basic/src/mainbundles/jdk/test/resources/MyResourcesProvider.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/test/java/util/ResourceBundle/modules/basic/src/mainbundles/jdk/test/resources/MyResourcesProvider.java	Fri Oct 28 10:18:07 2016 +0100
@@ -62,9 +62,7 @@
 
     @Override
     protected String toBundleName(String baseName, Locale locale) {
-        // The resource bundle for Locale.JAPAN is loccated at jdk.test.resources
-        // in module "asiabundles".
-        String name = locale.equals(Locale.JAPAN) ? baseName : addRegion(baseName);
+        String name = addRegion(baseName);
         return Control.getControl(Control.FORMAT_DEFAULT).toBundleName(name, locale);
     }
 
--- a/jdk/test/tools/jmod/hashes/HashesTest.java	Thu Oct 27 23:49:38 2016 +0000
+++ b/jdk/test/tools/jmod/hashes/HashesTest.java	Fri Oct 28 10:18:07 2016 +0100
@@ -26,7 +26,8 @@
  * @summary Test the recording and checking of module hashes
  * @author Andrei Eremeev
  * @library /lib/testlibrary
- * @modules java.base/jdk.internal.module
+ * @modules java.base/jdk.internal.misc
+ *          java.base/jdk.internal.module
  *          jdk.jlink
  *          jdk.compiler
  * @build CompilerUtils
@@ -39,7 +40,6 @@
 import java.lang.module.ModuleFinder;
 import java.lang.module.ModuleReader;
 import java.lang.module.ModuleReference;
-import java.lang.reflect.Method;
 import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -55,8 +55,10 @@
 import java.util.spi.ToolProvider;
 import java.util.stream.Collectors;
 
-import jdk.internal.module.ConfigurableModuleFinder;
+import jdk.internal.misc.SharedSecrets;
+import jdk.internal.misc.JavaLangModuleAccess;
 import jdk.internal.module.ModuleHashes;
+
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Test;
 
@@ -74,7 +76,6 @@
     private final Path jmods = Paths.get("jmods");
     private final String[] modules = new String[] { "m1", "m2", "m3"};
 
-    private static Method hashesMethod;
     @BeforeTest
     private void setup() throws Exception {
         if (Files.exists(jmods)) {
@@ -97,13 +98,6 @@
         // compile org.bar and org.foo
         compileModule("org.bar", modSrc);
         compileModule("org.foo", modSrc);
-
-        try {
-            hashesMethod = ModuleDescriptor.class.getDeclaredMethod("hashes");
-            hashesMethod.setAccessible(true);
-        } catch (ReflectiveOperationException x) {
-            throw new InternalError(x);
-        }
     }
 
     @Test
@@ -143,17 +137,14 @@
     }
 
     private Optional<ModuleHashes> hashes(String name) throws Exception {
-        ModuleFinder finder = ModuleFinder.of(jmods.resolve(name + ".jmod"));
-        if (finder instanceof ConfigurableModuleFinder) {
-            ((ConfigurableModuleFinder) finder)
-                .configurePhase(ConfigurableModuleFinder.Phase.LINK_TIME);
-        }
+        ModuleFinder finder = SharedSecrets.getJavaLangModuleAccess()
+            .newModulePath(Runtime.version(), true, jmods.resolve(name + ".jmod"));
         ModuleReference mref = finder.find(name).orElseThrow(RuntimeException::new);
         ModuleReader reader = mref.open();
         try (InputStream in = reader.open("module-info.class").get()) {
             ModuleDescriptor md = ModuleDescriptor.read(in);
-            Optional<ModuleHashes> hashes =
-                (Optional<ModuleHashes>) hashesMethod.invoke(md);
+            JavaLangModuleAccess jmla = SharedSecrets.getJavaLangModuleAccess();
+            Optional<ModuleHashes> hashes = jmla.hashes(md);
             System.out.format("hashes in module %s %s%n", name,
                               hashes.isPresent() ? "present" : "absent");
             if (hashes.isPresent()) {