changeset 16183:7b997de570ba

Merge
author lana
date Thu, 01 Dec 2016 21:39:49 +0000
parents 46b340c4e9bf fdc2a054d947
children 685512caa8bf
files src/java.base/share/classes/java/lang/module/Dependence.java src/java.base/share/classes/sun/util/locale/provider/ResourceBundleProviderSupport.java test/java/awt/Focus/DisposedWindow/DisposeDialogNotActivateOwnerTest/DisposeDialogNotActivateOwnerTest.html test/java/lang/Class/getResource/src/m3/module-info.java test/java/lang/Class/getResource/src/m3/p3/Main.java test/java/lang/ClassLoader/getResource/modules/src/m3/module-info.java test/java/lang/ClassLoader/getResource/modules/src/m3/p3/Main.java test/java/lang/reflect/Module/access/src/target/p/Exported.java test/java/lang/reflect/Module/access/src/target/p/Helper.java test/java/lang/reflect/Module/access/src/target/q/Internal.java test/java/net/Authenticator/B4933582.sh test/java/rmi/activation/rmidViaInheritedChannel/RmidViaInheritedChannel.java test/java/util/ServiceLoader/Basic.java test/java/util/ServiceLoader/FooProvider1.java test/java/util/ServiceLoader/FooProvider2.java test/java/util/ServiceLoader/FooProvider3.java test/java/util/ServiceLoader/FooService.java test/java/util/ServiceLoader/Load.java test/java/util/ServiceLoader/basic.sh test/java/util/ServiceLoader/modules/MiscTests.java test/java/util/ServiceLoader/modules/ServicesTest.java test/java/util/ServiceLoader/modules/src/bananascript/module-info.java test/java/util/ServiceLoader/modules/src/bananascript/org/banana/BananaScript.java test/java/util/ServiceLoader/modules/src/bananascript/org/banana/BananaScriptEngineFactory.java test/java/util/ServiceLoader/modules/src/test/module-info.java test/java/util/ServiceLoader/modules/src/test/test/Main.java
diffstat 637 files changed, 22660 insertions(+), 8641 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu Dec 01 21:01:53 2016 +0000
+++ b/.hgignore	Thu Dec 01 21:39:49 2016 +0000
@@ -5,7 +5,6 @@
 /nbproject/private/
 ^make/netbeans/.*/build/
 ^make/netbeans/.*/dist/
-^.hgtip
 .DS_Store
 .*/JTreport/.*
 .*/JTwork/.*
--- a/make/data/jdwp/jdwp.spec	Thu Dec 01 21:01:53 2016 +0000
+++ b/make/data/jdwp/jdwp.spec	Thu Dec 01 21:39:49 2016 +0000
@@ -2709,22 +2709,6 @@
             (Error VM_DEAD)
         )
     )
-    (Command CanRead=3
-        "Returns true if this module can read the source module; false otherwise."
-        "<p>Since JDWP version 9."
-        (Out
-            (moduleID module "This module.")
-            (moduleID sourceModule "The source module.")
-        )
-        (Reply
-            (boolean canRead  "true if this module can read the source module; false otherwise.")
-        )
-        (ErrorSet
-            (Error INVALID_MODULE  "This module or sourceModule is not the ID of a module.")
-            (Error NOT_IMPLEMENTED)
-            (Error VM_DEAD)
-        )
-    )
 )
 (CommandSet Event=64
     (Command Composite=100
--- a/make/launcher/Launcher-jdk.jconsole.gmk	Thu Dec 01 21:01:53 2016 +0000
+++ b/make/launcher/Launcher-jdk.jconsole.gmk	Thu Dec 01 21:39:49 2016 +0000
@@ -27,7 +27,8 @@
 
 $(eval $(call SetupBuildLauncher, jconsole, \
     MAIN_CLASS := sun.tools.jconsole.JConsole, \
-    JAVA_ARGS := -Djconsole.showOutputViewer, \
+    JAVA_ARGS := --add-opens java.base/java.io=jdk.jconsole \
+		 -Djconsole.showOutputViewer, \
     CFLAGS_windows := -DJAVAW, \
     LIBS_windows := user32.lib, \
 ))
--- a/make/lib/Awt2dLibraries.gmk	Thu Dec 01 21:01:53 2016 +0000
+++ b/make/lib/Awt2dLibraries.gmk	Thu Dec 01 21:39:49 2016 +0000
@@ -287,9 +287,8 @@
         $(JDK_TOPDIR)/src/java.desktop/share/native/common/awt/debug \
         $(JDK_TOPDIR)/src/java.desktop/share/native/common/awt/utility \
         $(JDK_TOPDIR)/src/java.desktop/share/native/common/font \
-        $(JDK_TOPDIR)/src/java.desktop/share/native/common/java2d/opengl \
-        $(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/java2d/opengl \
-        $(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/java2d/x11 \
+        $(JDK_TOPDIR)/src/java.desktop/share/native/common/java2d \
+        $(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/java2d \
         $(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/awt \
         #
 
@@ -516,24 +515,25 @@
 
   LIBAWT_HEADLESS_DIRS := $(JDK_TOPDIR)/src/java.desktop/unix/native/libawt_headless/awt \
       $(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/awt \
-      $(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/java2d/opengl \
-      $(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/java2d/x11 \
-      $(JDK_TOPDIR)/src/java.desktop/share/native/common/java2d/opengl \
+      $(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/java2d \
+      $(JDK_TOPDIR)/src/java.desktop/share/native/common/java2d \
       $(JDK_TOPDIR)/src/java.desktop/share/native/common/font \
       #
 
   LIBAWT_HEADLESS_EXCLUDES := medialib
   LIBAWT_HEADLESS_CFLAGS := -I$(SUPPORT_OUTPUTDIR)/headers/java.desktop \
       $(addprefix -I, $(LIBAWT_HEADLESS_DIRS)) \
+      -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/awt/image \
+      -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/awt/image/cvutils \
       -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/java2d \
+      -I$(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/libawt/java2d \
       -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/java2d/loops \
-      -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/awt/image/cvutils \
       -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/java2d/pipe \
-      -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/awt/image \
-      -I$(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/libawt/java2d \
+      -I$(JDK_TOPDIR)/src/java.desktop/share/native/common/awt/debug \
       -I$(JDK_TOPDIR)/src/java.desktop/share/native/common/font \
-      -I$(JDK_TOPDIR)/src/java.desktop/share/native/common/awt/debug \
       -I$(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/font \
+      -I$(JDK_TOPDIR)/src/java.desktop/share/native/common/java2d/opengl \
+      -I$(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/common/java2d/opengl \
       -I$(JDK_TOPDIR)/src/java.desktop/$(OPENJDK_TARGET_OS_TYPE)/native/libsunwjdga/ \
       $(LIBJAVA_HEADER_FLAGS) \
       #
@@ -862,7 +862,7 @@
 
   ifeq ($(OPENJDK_TARGET_OS), windows)
     LIBSPLASHSCREEN_DIRS += $(JDK_TOPDIR)/src/java.desktop/windows/native/common/awt/systemscale
-  endif	
+  endif
   LIBSPLASHSCREEN_CFLAGS += -DSPLASHSCREEN -DPNG_NO_MMX_CODE -DPNG_ARM_NEON_OPT=0 \
       $(addprefix -I, $(LIBSPLASHSCREEN_DIRS)) \
       $(LIBJAVA_HEADER_FLAGS) \
@@ -952,26 +952,27 @@
       $(JDK_TOPDIR)/src/java.desktop/macosx/native/libawt_lwawt \
       $(JDK_TOPDIR)/src/java.desktop/unix/native/common/awt \
       $(JDK_TOPDIR)/src/java.desktop/share/native/common/font \
-      $(JDK_TOPDIR)/src/java.desktop/share/native/common/java2d/opengl \
+      $(JDK_TOPDIR)/src/java.desktop/share/native/common/java2d \
       #
 
   LIBAWT_LWAWT_CFLAGS := \
       $(addprefix -I, $(LIBAWT_LWAWT_DIRS)) \
       -I$(SUPPORT_OUTPUTDIR)/headers/java.desktop \
-      -I$(JDK_TOPDIR)/src/java.desktop/macosx/native/include \
-      -I$(JDK_TOPDIR)/src/java.desktop/share/native/include \
-      -I$(JDK_TOPDIR)/src/java.desktop/macosx/native/libawt_lwawt/java2d/opengl \
       -I$(JDK_TOPDIR)/src/java.desktop/macosx/native/libawt_lwawt/awt \
       -I$(JDK_TOPDIR)/src/java.desktop/unix/native/libawt_xawt/awt \
       -I$(JDK_TOPDIR)/src/java.desktop/macosx/native/libawt_lwawt/font \
+      -I$(JDK_TOPDIR)/src/java.desktop/macosx/native/libawt_lwawt/java2d/opengl \
+      -I$(JDK_TOPDIR)/src/java.desktop/share/native/common/awt/debug \
+      -I$(JDK_TOPDIR)/src/java.desktop/share/native/common/java2d/opengl \
+      -I$(JDK_TOPDIR)/src/java.desktop/macosx/native/include \
+      -I$(JDK_TOPDIR)/src/java.desktop/share/native/include \
       -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/awt/image \
+      -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/awt/image/cvutils \
       -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/java2d \
       -I$(JDK_TOPDIR)/src/java.desktop/unix/native/libawt/java2d \
-      -I$(JDK_TOPDIR)/src/java.desktop/share/native/libmlib_image/ \
-      -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/awt/image/cvutils \
       -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/java2d/loops \
       -I$(JDK_TOPDIR)/src/java.desktop/share/native/libawt/java2d/pipe \
-      -I$(JDK_TOPDIR)/src/java.desktop/share/native/common/awt/debug \
+      -I$(JDK_TOPDIR)/src/java.desktop/share/native/libmlib_image/ \
       -I$(JDK_TOPDIR)/src/java.desktop/macosx/native/libosxapp \
       $(LIBJAVA_HEADER_FLAGS) \
       #
--- a/make/src/classes/build/tools/jigsaw/AddPackagesAttribute.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/make/src/classes/build/tools/jigsaw/AddPackagesAttribute.java	Thu Dec 01 21:39:49 2016 +0000
@@ -65,7 +65,7 @@
                     String mn = entry.getFileName().toString();
                     Optional<ModuleReference> omref = finder.find(mn);
                     if (omref.isPresent()) {
-                        Set<String> packages = omref.get().descriptor().conceals();
+                        Set<String> packages = omref.get().descriptor().packages();
                         addPackagesAttribute(mi, packages);
                     }
                 }
@@ -77,7 +77,7 @@
         byte[] bytes;
         try (InputStream in = Files.newInputStream(mi)) {
             ModuleInfoExtender extender = ModuleInfoExtender.newExtender(in);
-            extender.conceals(packages);
+            extender.packages(packages);
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             extender.write(baos);
             bytes = baos.toByteArray();
--- a/make/src/classes/build/tools/jigsaw/GenGraphs.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/make/src/classes/build/tools/jigsaw/GenGraphs.java	Thu Dec 01 21:39:49 2016 +0000
@@ -43,7 +43,7 @@
 import java.util.TreeSet;
 import java.util.function.Function;
 import java.util.stream.Collectors;
-import static java.lang.module.ModuleDescriptor.Requires.Modifier.PUBLIC;
+import static java.lang.module.ModuleDescriptor.Requires.Modifier.TRANSITIVE;
 
 /**
  * Generate the DOT file for a module graph for each module in the JDK
@@ -172,14 +172,14 @@
             Graph<String> graph = gengraph(cf);
             descriptors.forEach(md -> {
                 String mn = md.name();
-                Set<String> requiresPublic = md.requires().stream()
-                        .filter(d -> d.modifiers().contains(PUBLIC))
+                Set<String> requiresTransitive = md.requires().stream()
+                        .filter(d -> d.modifiers().contains(TRANSITIVE))
                         .map(d -> d.name())
                         .collect(Collectors.toSet());
 
                 graph.adjacentNodes(mn).forEach(dn -> {
                     String attr = dn.equals("java.base") ? REQUIRES_BASE
-                            : (requiresPublic.contains(dn) ? REEXPORTS : REQUIRES);
+                            : (requiresTransitive.contains(dn) ? REEXPORTS : REQUIRES);
                     int w = weightOf(mn, dn);
                     if (w > 1)
                         attr += "weight=" + w;
@@ -194,8 +194,8 @@
     /**
      * Returns a Graph of the given Configuration after transitive reduction.
      *
-     * Transitive reduction of requires public edge and requires edge have
-     * to be applied separately to prevent the requires public edges
+     * Transitive reduction of requires transitive edge and requires edge have
+     * to be applied separately to prevent the requires transitive edges
      * (e.g. U -> V) from being reduced by a path (U -> X -> Y -> V)
      * in which  V would not be re-exported from U.
      */
@@ -208,21 +208,21 @@
                     .map(ResolvedModule::name)
                     .forEach(target -> builder.addEdge(mn, target));
         }
-        Graph<String> rpg = requiresPublicGraph(cf);
+        Graph<String> rpg = requiresTransitiveGraph(cf);
         return builder.build().reduce(rpg);
     }
 
     /**
-     * Returns a Graph containing only requires public edges
+     * Returns a Graph containing only requires transitive edges
      * with transitive reduction.
      */
-    private Graph<String> requiresPublicGraph(Configuration cf) {
+    private Graph<String> requiresTransitiveGraph(Configuration cf) {
         Graph.Builder<String> builder = new Graph.Builder<>();
         for (ResolvedModule resolvedModule : cf.modules()) {
             ModuleDescriptor descriptor = resolvedModule.reference().descriptor();
             String mn = descriptor.name();
             descriptor.requires().stream()
-                    .filter(d -> d.modifiers().contains(PUBLIC))
+                    .filter(d -> d.modifiers().contains(TRANSITIVE))
                     .map(d -> d.name())
                     .forEach(d -> builder.addEdge(mn, d));
         }
--- a/make/src/classes/build/tools/jigsaw/ModuleSummary.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/make/src/classes/build/tools/jigsaw/ModuleSummary.java	Thu Dec 01 21:39:49 2016 +0000
@@ -299,7 +299,7 @@
     static class HtmlDocument {
         final String title;
         final Map<String, ModuleSummary> modules;
-        boolean requiresPublicNote = false;
+        boolean requiresTransitiveNote = false;
         boolean aggregatorNote = false;
         boolean totalBytesNote = false;
         HtmlDocument(String title, Map<String, ModuleSummary> modules) {
@@ -510,16 +510,16 @@
             public String requiresColumn() {
                 StringBuilder sb = new StringBuilder();
                 sb.append(String.format("<td>"));
-                boolean footnote = requiresPublicNote;
+                boolean footnote = requiresTransitiveNote;
                 ms.descriptor().requires().stream()
                         .sorted(Comparator.comparing(Requires::name))
                         .forEach(r -> {
-                            boolean requiresPublic = r.modifiers().contains(Requires.Modifier.PUBLIC);
-                            Selector sel = requiresPublic ? REQUIRES_PUBLIC : REQUIRES;
+                            boolean requiresTransitive = r.modifiers().contains(Requires.Modifier.TRANSITIVE);
+                            Selector sel = requiresTransitive ? REQUIRES_PUBLIC : REQUIRES;
                             String req = String.format("<a class=\"%s\" href=\"#%s\">%s</a>",
                                                        sel, r.name(), r.name());
-                            if (!requiresPublicNote && requiresPublic) {
-                                requiresPublicNote = true;
+                            if (!requiresTransitiveNote && requiresTransitive) {
+                                requiresTransitiveNote = true;
                                 req += "<sup>*</sup>";
                             }
                             sb.append(req).append("\n").append("<br>");
@@ -534,8 +534,8 @@
                     sb.append("<br>");
                     sb.append("<i>+").append(indirectDeps).append(" transitive dependencies</i>");
                 }
-                if (footnote != requiresPublicNote) {
-                    sb.append("<br><br>").append("<i>* bold denotes requires public</i>");
+                if (footnote != requiresTransitiveNote) {
+                    sb.append("<br><br>").append("<i>* bold denotes requires transitive</i>");
                 }
                 sb.append("</td>");
                 return sb.toString();
@@ -558,11 +558,10 @@
                 ms.descriptor().uses().stream()
                         .sorted()
                         .forEach(s -> sb.append("uses ").append(s).append("<br>").append("\n"));
-                ms.descriptor().provides().entrySet().stream()
-                        .sorted(Map.Entry.comparingByKey())
-                        .flatMap(e -> e.getValue().providers().stream()
-                                .map(p -> String.format("provides %s<br>&nbsp;&nbsp;&nbsp;&nbsp;with %s",
-                                                        e.getKey(), p)))
+                ms.descriptor().provides().stream()
+                        .sorted(Comparator.comparing(Provides::service))
+                        .map(p -> String.format("provides %s<br>&nbsp;&nbsp;&nbsp;&nbsp;with %s",
+                                                p.service(), p.providers()))
                         .forEach(p -> sb.append(p).append("<br>").append("\n"));
                 sb.append("</td>");
                 return sb.toString();
--- a/make/src/classes/build/tools/module/GenModuleInfoSource.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/make/src/classes/build/tools/module/GenModuleInfoSource.java	Thu Dec 01 21:39:49 2016 +0000
@@ -30,219 +30,593 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
-import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import static java.util.stream.Collectors.*;
 
 /**
  * A build tool to extend the module-info.java in the source tree for
- * platform-specific exports, uses, and provides and write to the specified
- * output file. Injecting platform-specific requires is not supported.
+ * platform-specific exports, opens, uses, and provides and write to
+ * the specified output file.
  *
- * The extra exports, uses, provides can be specified in module-info.java.extra
- * files and GenModuleInfoSource will be invoked for each module that has
+ * GenModuleInfoSource will be invoked for each module that has
  * module-info.java.extra in the source directory.
+ *
+ * The extra exports, opens, uses, provides can be specified
+ * in module-info.java.extra.
+ * Injecting platform-specific requires is not supported.
+ *
+ * @see build.tools.module.ModuleInfoExtraTest for basic testing
  */
 public class GenModuleInfoSource {
     private final static String USAGE =
-        "Usage: GenModuleInfoSource [option] -o <output file> <module-info-java>\n" +
-        "Options are:\n" +
-        "  -exports  <package-name>\n" +
-        "  -exports  <package-name>[/<module-name>]\n" +
-        "  -uses     <service>\n" +
-        "  -provides <service>/<provider-impl-classname>\n";
+        "Usage: GenModuleInfoSource -o <output file> \n" +
+        "  --source-file <module-info-java>\n" +
+        "  --modules <module-name>[,<module-name>...]\n" +
+        "  <module-info.java.extra> ...\n";
 
+    static boolean verbose = false;
     public static void main(String... args) throws Exception {
         Path outfile = null;
         Path moduleInfoJava = null;
-        GenModuleInfoSource genModuleInfo = new GenModuleInfoSource();
-
+        Set<String> modules = Collections.emptySet();
+        List<Path> extras = new ArrayList<>();
         // validate input arguments
         for (int i = 0; i < args.length; i++){
             String option = args[i];
-            if (option.startsWith("-")) {
-                String arg = args[++i];
-                if (option.equals("-exports")) {
-                    int index = arg.indexOf('/');
-                        if (index > 0) {
-                            String pn = arg.substring(0, index);
-                            String mn = arg.substring(index + 1, arg.length());
-                            genModuleInfo.exportTo(pn, mn);
-                        } else {
-                            genModuleInfo.export(arg);
-                        }
-                } else if (option.equals("-uses")) {
-                    genModuleInfo.use(arg);
-                } else if (option.equals("-provides")) {
-                        int index = arg.indexOf('/');
-                        if (index <= 0) {
-                            throw new IllegalArgumentException("invalid -provide argument: " + arg);
-                        }
-                        String service = arg.substring(0, index);
-                        String impl = arg.substring(index + 1, arg.length());
-                        genModuleInfo.provide(service, impl);
-                } else if (option.equals("-o")) {
+            String arg = i+1 < args.length ? args[i+1] : null;
+            switch (option) {
+                case "-o":
                     outfile = Paths.get(arg);
-                } else {
-                    throw new IllegalArgumentException("invalid option: " + option);
-                }
-            } else if (moduleInfoJava != null) {
-                throw new IllegalArgumentException("more than one module-info.java");
-            } else {
-                moduleInfoJava = Paths.get(option);
-                if (Files.notExists(moduleInfoJava)) {
-                    throw new IllegalArgumentException(option + " not exist");
-                }
+                    i++;
+                    break;
+                case "--source-file":
+                    moduleInfoJava = Paths.get(arg);
+                    if (Files.notExists(moduleInfoJava)) {
+                        throw new IllegalArgumentException(moduleInfoJava + " not exist");
+                    }
+                    i++;
+                    break;
+                case "--modules":
+                    modules = Arrays.stream(arg.split(","))
+                                    .collect(toSet());
+                    i++;
+                    break;
+                case "-v":
+                    verbose = true;
+                    break;
+                default:
+                    Path file = Paths.get(option);
+                    if (Files.notExists(file)) {
+                        throw new IllegalArgumentException(file + " not exist");
+                    }
+                    extras.add(file);
             }
         }
 
-        if (moduleInfoJava == null || outfile == null) {
+        if (moduleInfoJava == null || outfile == null ||
+                modules.isEmpty() || extras.isEmpty()) {
             System.err.println(USAGE);
             System.exit(-1);
         }
 
+        GenModuleInfoSource genModuleInfo =
+            new GenModuleInfoSource(moduleInfoJava, extras, modules);
+
         // generate new module-info.java
-        genModuleInfo.generate(moduleInfoJava, outfile);
+        genModuleInfo.generate(outfile);
     }
 
-    private final Set<String> exports = new HashSet<>();
-    private final Map<String, Set<String>> exportsTo = new HashMap<>();
-    private final Set<String> uses = new HashSet<>();
-    private final Map<String, Set<String>> provides = new HashMap<>();
-    GenModuleInfoSource() {
+    final Path sourceFile;
+    final List<Path> extraFiles;
+    final ModuleInfo extras;
+    final Set<String> modules;
+    final ModuleInfo moduleInfo;
+    GenModuleInfoSource(Path sourceFile, List<Path> extraFiles, Set<String> modules)
+        throws IOException
+    {
+        this.sourceFile = sourceFile;
+        this.extraFiles = extraFiles;
+        this.modules = modules;
+        this.moduleInfo = new ModuleInfo();
+        this.moduleInfo.parse(sourceFile);
+
+        // parse module-info.java.extra
+        this.extras = new ModuleInfo();
+        for (Path file : extraFiles) {
+            extras.parse(file);
+        }
+
+        // merge with module-info.java.extra
+        moduleInfo.augmentModuleInfo(extras, modules);
     }
 
-    private void export(String p) {
-        Objects.requireNonNull(p);
-        if (exports.contains(p) || exportsTo.containsKey(p)) {
-            throw new RuntimeException("duplicated exports: " + p);
+    void generate(Path output) throws IOException {
+        List<String> lines = Files.readAllLines(sourceFile);
+        try (BufferedWriter bw = Files.newBufferedWriter(output);
+             PrintWriter writer = new PrintWriter(bw)) {
+            // write the copyright header and lines up to module declaration
+            for (String l : lines) {
+                writer.println(l);
+                if (l.trim().startsWith("module ")) {
+                    writer.format("    // source file: %s%n", sourceFile);
+                    for (Path file: extraFiles) {
+                        writer.format("    //              %s%n", file);
+                    }
+                    break;
+                }
+            }
+
+            // requires
+            for (String l : lines) {
+                if (l.trim().startsWith("requires"))
+                    writer.println(l);
+            }
+
+            // write exports, opens, uses, and provides
+            moduleInfo.print(writer);
+
+            // close
+            writer.println("}");
         }
-        exports.add(p);
-    }
-    private void exportTo(String p, String mn) {
-        Objects.requireNonNull(p);
-        Objects.requireNonNull(mn);
-        if (exports.contains(p)) {
-            throw new RuntimeException("unqualified exports already exists: " + p);
-        }
-        exportsTo.computeIfAbsent(p, _k -> new HashSet<>()).add(mn);
     }
 
-    private void use(String service) {
-        uses.add(service);
-    }
 
-    private void provide(String s, String impl) {
-        provides.computeIfAbsent(s, _k -> new HashSet<>()).add(impl);
-    }
+    class ModuleInfo {
+        final Map<String, Statement> exports = new HashMap<>();
+        final Map<String, Statement> opens = new HashMap<>();
+        final Map<String, Statement> uses = new HashMap<>();
+        final Map<String, Statement> provides = new HashMap<>();
 
-    private void generate(Path sourcefile, Path outfile) throws IOException {
-        Path parent = outfile.getParent();
-        if (parent != null)
-            Files.createDirectories(parent);
+        Statement getStatement(String directive, String name) {
+            switch (directive) {
+                case "exports":
+                    if (moduleInfo.exports.containsKey(name) &&
+                        moduleInfo.exports.get(name).isUnqualified()) {
+                        throw new IllegalArgumentException(sourceFile +
+                            " already has " + directive + " " + name);
+                    }
+                    return exports.computeIfAbsent(name,
+                        _n -> new Statement("exports", "to", name));
 
-        List<String> lines = Files.readAllLines(sourcefile);
-        try (BufferedWriter bw = Files.newBufferedWriter(outfile);
-             PrintWriter writer = new PrintWriter(bw)) {
-            int lineNumber = 0;
-            for (String l : lines) {
+                case "opens":
+                    if (moduleInfo.opens.containsKey(name) &&
+                        moduleInfo.opens.get(name).isUnqualified()) {
+                        throw new IllegalArgumentException(sourceFile +
+                            " already has " + directive + " " + name);
+                    }
+
+                    if (moduleInfo.opens.containsKey(name)) {
+                        throw new IllegalArgumentException(sourceFile +
+                            " already has " + directive + " " + name);
+                    }
+                    return opens.computeIfAbsent(name,
+                        _n -> new Statement("opens", "to", name));
+
+                case "uses":
+                    return uses.computeIfAbsent(name,
+                        _n -> new Statement("uses", "", name));
+
+                case "provides":
+                    return provides.computeIfAbsent(name,
+                        _n -> new Statement("provides", "with", name, true));
+
+                default:
+                    throw new IllegalArgumentException(directive);
+            }
+
+        }
+
+        /*
+         * Augment this ModuleInfo with module-info.java.extra
+         */
+        void augmentModuleInfo(ModuleInfo extraFiles, Set<String> modules) {
+            // API package exported in the original module-info.java
+            extraFiles.exports.entrySet()
+                .stream()
+                .filter(e -> exports.containsKey(e.getKey()) &&
+                                e.getValue().filter(modules))
+                .forEach(e -> mergeExportsOrOpens(exports.get(e.getKey()),
+                                                  e.getValue(),
+                                                  modules));
+
+            // add exports that are not defined in the original module-info.java
+            extraFiles.exports.entrySet()
+                .stream()
+                .filter(e -> !exports.containsKey(e.getKey()) &&
+                                e.getValue().filter(modules))
+                .forEach(e -> addTargets(getStatement("exports", e.getKey()),
+                                         e.getValue(),
+                                         modules));
+
+            // API package opened in the original module-info.java
+            extraFiles.opens.entrySet()
+                .stream()
+                .filter(e -> opens.containsKey(e.getKey()) &&
+                                e.getValue().filter(modules))
+                .forEach(e -> mergeExportsOrOpens(opens.get(e.getKey()),
+                                                  e.getValue(),
+                                                  modules));
+
+            // add opens that are not defined in the original module-info.java
+            extraFiles.opens.entrySet()
+                .stream()
+                .filter(e -> !opens.containsKey(e.getKey()) &&
+                                e.getValue().filter(modules))
+                .forEach(e -> addTargets(getStatement("opens", e.getKey()),
+                                         e.getValue(),
+                                         modules));
+
+            // provides
+            extraFiles.provides.keySet()
+                .stream()
+                .filter(service -> provides.containsKey(service))
+                .forEach(service -> mergeProvides(service,
+                                                  extraFiles.provides.get(service)));
+            extraFiles.provides.keySet()
+                .stream()
+                .filter(service -> !provides.containsKey(service))
+                .forEach(service -> provides.put(service,
+                                                 extraFiles.provides.get(service)));
+
+            // uses
+            extraFiles.uses.keySet()
+                .stream()
+                .filter(service -> !uses.containsKey(service))
+                .forEach(service -> uses.put(service, extraFiles.uses.get(service)));
+        }
+
+        // add qualified exports or opens to known modules only
+        private void addTargets(Statement statement,
+                                Statement extra,
+                                Set<String> modules)
+        {
+            extra.targets.stream()
+                 .filter(mn -> modules.contains(mn))
+                 .forEach(mn -> statement.addTarget(mn));
+        }
+
+        private void mergeExportsOrOpens(Statement statement,
+                                         Statement extra,
+                                         Set<String> modules)
+        {
+            String pn = statement.name;
+            if (statement.isUnqualified() && extra.isQualified()) {
+                throw new RuntimeException("can't add qualified exports to " +
+                    "unqualified exports " + pn);
+            }
+
+            Set<String> mods = extra.targets.stream()
+                .filter(mn -> statement.targets.contains(mn))
+                .collect(toSet());
+            if (mods.size() > 0) {
+                throw new RuntimeException("qualified exports " + pn + " to " +
+                    mods.toString() + " already declared in " + sourceFile);
+            }
+
+            // add qualified exports or opens to known modules only
+            addTargets(statement, extra, modules);
+        }
+
+        private void mergeProvides(String service, Statement extra) {
+            Statement statement = provides.get(service);
+
+            Set<String> mods = extra.targets.stream()
+                .filter(mn -> statement.targets.contains(mn))
+                .collect(toSet());
+
+            if (mods.size() > 0) {
+                throw new RuntimeException("qualified exports " + service + " to " +
+                    mods.toString() + " already declared in " + sourceFile);
+            }
+
+            extra.targets.stream()
+                 .forEach(mn -> statement.addTarget(mn));
+        }
+
+
+        void print(PrintWriter writer) {
+            // print unqualified exports
+            exports.entrySet().stream()
+                .filter(e -> e.getValue().targets.isEmpty())
+                .sorted(Map.Entry.comparingByKey())
+                .forEach(e -> writer.println(e.getValue()));
+
+            // print qualified exports
+            exports.entrySet().stream()
+                .filter(e -> !e.getValue().targets.isEmpty())
+                .sorted(Map.Entry.comparingByKey())
+                .forEach(e -> writer.println(e.getValue()));
+
+            // print unqualified opens
+            opens.entrySet().stream()
+                .filter(e -> e.getValue().targets.isEmpty())
+                .sorted(Map.Entry.comparingByKey())
+                .forEach(e -> writer.println(e.getValue()));
+
+            // print qualified opens
+            opens.entrySet().stream()
+                .filter(e -> !e.getValue().targets.isEmpty())
+                .sorted(Map.Entry.comparingByKey())
+                .forEach(e -> writer.println(e.getValue()));
+
+            // uses and provides
+            writer.println();
+            uses.entrySet().stream()
+                .sorted(Map.Entry.comparingByKey())
+                .forEach(e -> writer.println(e.getValue()));
+            provides.entrySet().stream()
+                .sorted(Map.Entry.comparingByKey())
+                .forEach(e -> writer.println(e.getValue()));
+        }
+
+        private void parse(Path sourcefile) throws IOException {
+            List<String> lines = Files.readAllLines(sourcefile);
+            Statement statement = null;
+            boolean hasTargets = false;
+
+            for (int lineNumber = 1; lineNumber <= lines.size(); ) {
+                String l = lines.get(lineNumber-1).trim();
+                int index = 0;
+
+                if (l.isEmpty()) {
+                    lineNumber++;
+                    continue;
+                }
+
+                // comment block starts
+                if (l.startsWith("/*")) {
+                    while (l.indexOf("*/") == -1) { // end comment block
+                        l = lines.get(lineNumber++).trim();
+                    }
+                    index = l.indexOf("*/") + 2;
+                    if (index >= l.length()) {
+                        lineNumber++;
+                        continue;
+                    } else {
+                        // rest of the line
+                        l = l.substring(index, l.length()).trim();
+                        index = 0;
+                    }
+                }
+
+                // skip comment and annotations
+                if (l.startsWith("//") || l.startsWith("@")) {
+                    lineNumber++;
+                    continue;
+                }
+
+                int current = lineNumber;
+                int count = 0;
+                while (index < l.length()) {
+                    if (current == lineNumber && ++count > 20)
+                        throw new Error("Fail to parse line " + lineNumber + " " + sourcefile);
+
+                    int end = l.indexOf(';');
+                    if (end == -1)
+                        end = l.length();
+                    String content = l.substring(0, end).trim();
+                    if (content.isEmpty()) {
+                        index = end+1;
+                        if (index < l.length()) {
+                            // rest of the line
+                            l = l.substring(index, l.length()).trim();
+                            index = 0;
+                        }
+                        continue;
+                    }
+
+                    String[] s = content.split("\\s+");
+                    String keyword = s[0].trim();
+
+                    String name = s.length > 1 ? s[1].trim() : null;
+                    trace("%d: %s index=%d len=%d%n", lineNumber, l, index, l.length());
+                    switch (keyword) {
+                        case "module":
+                        case "requires":
+                        case "}":
+                            index = l.length();  // skip to the end
+                            continue;
+
+                        case "exports":
+                        case "opens":
+                        case "provides":
+                        case "uses":
+                            // assume name immediately after exports, opens, provides, uses
+                            statement = getStatement(keyword, name);
+                            hasTargets = false;
+
+                            int i = l.indexOf(name, keyword.length()+1) + name.length() + 1;
+                            l = i < l.length() ? l.substring(i, l.length()).trim() : "";
+                            index = 0;
+
+                            if (s.length >= 3) {
+                                if (!s[2].trim().equals(statement.qualifier)) {
+                                    throw new RuntimeException(sourcefile + ", line " +
+                                        lineNumber + ", is malformed: " + s[2]);
+                                }
+                            }
+
+                            break;
+
+                        case "to":
+                        case "with":
+                            if (statement == null) {
+                                throw new RuntimeException(sourcefile + ", line " +
+                                    lineNumber + ", is malformed");
+                            }
+
+                            hasTargets = true;
+                            String qualifier = statement.qualifier;
+                            i = l.indexOf(qualifier, index) + qualifier.length() + 1;
+                            l = i < l.length() ? l.substring(i, l.length()).trim() : "";
+                            index = 0;
+                            break;
+                    }
+
+                    if (index >= l.length()) {
+                        // skip to next line
+                        continue;
+                    }
+
+                        // comment block starts
+                    if (l.startsWith("/*")) {
+                        while (l.indexOf("*/") == -1) { // end comment block
+                            l = lines.get(lineNumber++).trim();
+                        }
+                        index = l.indexOf("*/") + 2;
+                        if (index >= l.length()) {
+                            continue;
+                        } else {
+                            // rest of the line
+                            l = l.substring(index, l.length()).trim();
+                            index = 0;
+                        }
+                    }
+
+                    if (l.startsWith("//")) {
+                        index = l.length();
+                        continue;
+                    }
+
+                    if (statement == null) {
+                        throw new RuntimeException(sourcefile + ", line " +
+                            lineNumber + ": missing keyword?");
+                    }
+
+                    if (!hasTargets) {
+                        continue;
+                    }
+
+                    if (index >= l.length()) {
+                        throw new RuntimeException(sourcefile + ", line " +
+                            lineNumber + ": " + l);
+                    }
+
+                    // parse the target module of exports, opens, or provides
+                    Statement stmt = statement;
+
+                    int terminal = l.indexOf(';', index);
+                    // determine up to which position to parse
+                    int pos = terminal != -1 ? terminal : l.length();
+                    // parse up to comments
+                    int pos1 = l.indexOf("//", index);
+                    if (pos1 != -1 && pos1 < pos) {
+                        pos = pos1;
+                    }
+                    int pos2 = l.indexOf("/*", index);
+                    if (pos2 != -1 && pos2 < pos) {
+                        pos = pos2;
+                    }
+                    // target module(s) for qualitifed exports or opens
+                    // or provider implementation class(es)
+                    String rhs = l.substring(index, pos).trim();
+                    index += rhs.length();
+                    trace("rhs: index=%d [%s] [line: %s]%n", index, rhs, l);
+
+                    String[] targets = rhs.split(",");
+                    for (String t : targets) {
+                        String n = t.trim();
+                        if (n.length() > 0)
+                            stmt.addTarget(n);
+                    }
+
+                    // start next statement
+                    if (pos == terminal) {
+                        statement = null;
+                        hasTargets = false;
+                        index = terminal + 1;
+                    }
+                    l = index < l.length() ? l.substring(index, l.length()).trim() : "";
+                    index = 0;
+                }
+
                 lineNumber++;
-                String[] s = l.trim().split("\\s+");
-                String keyword = s[0].trim();
-                int nextIndex = keyword.length();
-                String exp = null;
-                int n = l.length();
-                switch (keyword) {
-                    case "exports":
-                        boolean inExportsTo = false;
-                        // assume package name immediately after exports
-                        exp = s[1].trim();
-                        if (s.length >= 3) {
-                            nextIndex = l.indexOf(exp, nextIndex) + exp.length();
-                            if (s[2].trim().equals("to")) {
-                                inExportsTo = true;
-                                n = l.indexOf("to", nextIndex) + "to".length();
-                            } else {
-                                throw new RuntimeException(sourcefile + ", line " +
-                                    lineNumber + ", is malformed: " + s[2]);
-                            }
-                        }
-
-                        // inject the extra targets after "to"
-                        if (inExportsTo) {
-                            writer.println(injectExportTargets(exp, l, n));
-                        } else {
-                            writer.println(l);
-                        }
-                        break;
-                    case "to":
-                        if (exp == null) {
-                            throw new RuntimeException(sourcefile + ", line " +
-                                lineNumber + ", is malformed");
-                        }
-                        n = l.indexOf("to", nextIndex) + "to".length();
-                        writer.println(injectExportTargets(exp, l, n));
-                        break;
-                    case "}":
-                        doAugments(writer);
-                        // fall through
-                    default:
-                        writer.println(l);
-                        // reset exports
-                        exp = null;
-                }
             }
         }
     }
 
-    private String injectExportTargets(String pn, String exp, int pos) {
-        Set<String> targets = exportsTo.remove(pn);
-        if (targets != null) {
-            StringBuilder sb = new StringBuilder();
-            // inject the extra targets after the given pos
-            sb.append(exp.substring(0, pos))
-              .append("\n\t")
-              .append(targets.stream()
-                             .collect(Collectors.joining(",", "", ",")))
-              .append(" /* injected */");
-            if (pos < exp.length()) {
-                // print the remaining statement followed "to"
-                sb.append("\n\t")
-                  .append(exp.substring(pos+1, exp.length()));
+    static class Statement {
+        final String directive;
+        final String qualifier;
+        final String name;
+        final Set<String> targets = new LinkedHashSet<>();
+        final boolean ordered;
+
+        Statement(String directive, String qualifier, String name) {
+            this(directive, qualifier, name, false);
+        }
+
+        Statement(String directive, String qualifier, String name, boolean ordered) {
+            this.directive = directive;
+            this.qualifier = qualifier;
+            this.name = name;
+            this.ordered = ordered;
+        }
+
+        Statement addTarget(String mn) {
+            if (mn.isEmpty())
+                throw new IllegalArgumentException("empty module name");
+            targets.add(mn);
+            return this;
+        }
+
+        boolean isQualified() {
+            return targets.size() > 0;
+        }
+
+        boolean isUnqualified() {
+            return targets.isEmpty();
+        }
+
+        /**
+         * Returns true if this statement is unqualified or it has
+         * at least one target in the given names.
+         */
+        boolean filter(Set<String> names) {
+            if (isUnqualified()) {
+                return true;
+            } else {
+                return targets.stream()
+                    .filter(mn -> names.contains(mn))
+                    .findAny().isPresent();
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder("    ");
+            sb.append(directive).append(" ").append(name);
+            if (targets.isEmpty()) {
+                sb.append(";");
+            } else if (targets.size() == 1) {
+                sb.append(" ").append(qualifier)
+                  .append(orderedTargets().collect(joining(",", " ", ";")));
+            } else {
+                sb.append(" ").append(qualifier)
+                  .append(orderedTargets()
+                      .map(target -> String.format("        %s", target))
+                      .collect(joining(",\n", "\n", ";")));
             }
             return sb.toString();
-        } else {
-            return exp;
+        }
+
+        public Stream<String> orderedTargets() {
+            return ordered ? targets.stream()
+                           : targets.stream().sorted();
         }
     }
 
-    private void doAugments(PrintWriter writer) {
-        if ((exports.size() + exportsTo.size() + uses.size() + provides.size()) == 0)
-            return;
-
-        writer.println("    // augmented from module-info.java.extra");
-        exports.stream()
-            .sorted()
-            .forEach(e -> writer.format("    exports %s;%n", e));
-        // remaining injected qualified exports
-        exportsTo.entrySet().stream()
-            .sorted(Map.Entry.comparingByKey())
-            .map(e -> String.format("    exports %s to%n%s;", e.getKey(),
-                                    e.getValue().stream().sorted()
-                                        .map(mn -> String.format("        %s", mn))
-                                        .collect(Collectors.joining(",\n"))))
-            .forEach(writer::println);
-        uses.stream().sorted()
-            .forEach(s -> writer.format("    uses %s;%n", s));
-        provides.entrySet().stream()
-            .sorted(Map.Entry.comparingByKey())
-            .flatMap(e -> e.getValue().stream().sorted()
-                           .map(impl -> String.format("    provides %s with %s;",
-                                                      e.getKey(), impl)))
-            .forEach(writer::println);
+    static void trace(String fmt, Object... params) {
+        if (verbose) {
+            System.out.format(fmt, params);
+        }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/src/classes/build/tools/module/ModuleInfoExtraTest.java	Thu Dec 01 21:39:49 2016 +0000
@@ -0,0 +1,243 @@
+/*
+ * 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.  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 build.tools.module;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import build.tools.module.GenModuleInfoSource.Statement;
+
+/**
+ * Sanity test for GenModuleInfoSource tool
+ */
+public class ModuleInfoExtraTest {
+    private static final Path DIR = Paths.get("test");
+    public static void main(String... args) throws Exception {
+        if (args.length != 0)
+            GenModuleInfoSource.verbose = true;
+
+        ModuleInfoExtraTest test = new ModuleInfoExtraTest("m", "m1", "m2", "m3");
+        test.run();
+    }
+
+    String[] moduleInfo = new String[] {
+        "exports p",
+        "to",
+        "   // comment",
+        "   /* comment */ m1",
+        ",",
+        "m2,m3",
+        "   ;",
+        "exports q to m1;",
+        "provides s with /* ",
+        "  comment */ impl     ;    // comment",
+        "provides s1",
+        "    with  ",
+        "    impl1, impl2;"
+    };
+
+    String[] moduleInfoExtra = new String[] {
+        "exports q",
+        "to",
+        "   m2 // comment",
+        "   /* comment */;",
+        "   ;",
+        "opens p.q ",
+        "   to /* comment */ m3",
+        "   , // m1",
+        "   /* comment */, m4;",
+        "provides s1 with impl3;"
+    };
+
+    String[] test1 = new String[] {
+        "exports p1 to m1;",
+        "exports p2"
+    };
+
+    String[] test2 = new String[] {
+        "exports to m1;"
+    };
+
+    String[] test3 = new String[]{
+        "exports p3 to m1;",
+        "    m2, m3;"
+    };
+
+    String[] test4 = new String[]{
+        "provides s with impl1;",   // typo ; should be ,
+        "   impl2, impl3;"
+    };
+
+    String[] test5 = new String[]{
+        "uses s3",
+        "provides s3 with impl1,",
+        "   impl2, impl3;"
+    };
+
+    final Builder builder;
+    ModuleInfoExtraTest(String name, String... modules) {
+        this.builder = new Builder(name).modules(modules);
+    }
+
+    void run() throws IOException {
+        testModuleInfo();
+        errorCases();
+    }
+
+
+    void testModuleInfo() throws IOException {
+        GenModuleInfoSource source = builder.sourceFile(moduleInfo).build();
+        Set<String> targetsP = new HashSet<>();
+        targetsP.add("m1");
+        targetsP.add("m2");
+        targetsP.add("m3");
+
+        Set<String> targetsQ = new HashSet<>();
+        targetsQ.add("m1");
+
+        Set<String> providerS = new HashSet<>();
+        providerS.add("impl");
+
+        Set<String> providerS1 = new HashSet<>();
+        providerS1.add("impl1");
+        providerS1.add("impl2");
+
+        Set<String> opensPQ = new HashSet<>();
+
+        check(source, targetsP, targetsQ, opensPQ, providerS, providerS1);
+
+        // augment with extra
+        Path file = DIR.resolve("extra");
+        Files.write(file, Arrays.asList(moduleInfoExtra));
+        source = builder.build(file);
+
+        targetsQ.add("m2");
+        providerS1.add("impl3");
+
+        opensPQ.add("m3");
+        check(source, targetsP, targetsQ, opensPQ, providerS, providerS1);
+    }
+
+    void check(GenModuleInfoSource source,
+               Set<String> targetsP,
+               Set<String> targetsQ,
+               Set<String> opensPQ,
+               Set<String> providerS,
+               Set<String> providerS1) {
+        source.moduleInfo.print(new PrintWriter(System.out, true));
+        Statement export = source.moduleInfo.exports.get("p");
+        if (!export.targets.equals(targetsP)) {
+            throw new Error("unexpected: " + export);
+        }
+
+        export = source.moduleInfo.exports.get("q");
+        if (!export.targets.equals(targetsQ)) {
+            throw new Error("unexpected: " + export);
+        }
+
+        Statement provides = source.moduleInfo.provides.get("s");
+        if (!provides.targets.equals(providerS)) {
+            throw new Error("unexpected: " + provides);
+        }
+
+        provides = source.moduleInfo.provides.get("s1");
+        if (!provides.targets.equals(providerS1)) {
+            throw new Error("unexpected: " + provides);
+        }
+    }
+
+
+
+    void errorCases() throws IOException {
+        fail(test1);
+        fail(test2);
+        fail(test3);
+        fail(test4);
+        fail(test5);
+    }
+
+    void fail(String... extras) throws IOException {
+        Path file = DIR.resolve("test1");
+        Files.write(file, Arrays.asList(extras));
+        try {
+            builder.build(file);
+        } catch (RuntimeException e) {
+            if (!e.getMessage().matches("test/test1, line .* is malformed.*") &&
+                !e.getMessage().matches("test/test1, line .* missing keyword.*")) {
+                throw e;
+            }
+        }
+    }
+
+    static class Builder {
+        final String moduleName;
+        final Path sourceFile;
+        final Set<String> modules = new HashSet<>();
+        public Builder(String name) {
+            this.moduleName = name;
+            this.sourceFile = DIR.resolve(name).resolve("module-info.java");
+        }
+
+        public Builder modules(String... names) {
+            Arrays.stream(names).forEach(modules::add);
+            return this;
+        }
+
+        public Builder sourceFile(String... lines) throws IOException {
+            Files.createDirectories(sourceFile.getParent());
+            try (BufferedWriter bw = Files.newBufferedWriter(sourceFile);
+                 PrintWriter writer = new PrintWriter(bw)) {
+                writer.format("module %s {%n", moduleName);
+                for (String l : lines) {
+                    writer.println(l);
+                }
+                writer.println("}");
+            }
+            return this;
+        }
+
+        public GenModuleInfoSource build() throws IOException {
+            return build(Collections.emptyList());
+        }
+
+        public GenModuleInfoSource build(Path extraFile) throws IOException {
+            return build(Collections.singletonList(extraFile));
+        }
+
+        public GenModuleInfoSource build(List<Path> extraFiles) throws IOException {
+            return new GenModuleInfoSource(sourceFile, extraFiles, modules);
+        }
+    }
+
+}
--- a/src/java.base/share/classes/com/sun/java/util/jar/pack/intrinsic.properties	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/com/sun/java/util/jar/pack/intrinsic.properties	Thu Dec 01 21:39:49 2016 +0000
@@ -15,12 +15,12 @@
 pack.class.attribute.CompilationID = RUH
 
 # Module attributes, supported by the tool and not JSR-200
-pack.class.attribute.Module = NH[RUHFH]NH[RUHNH[RUH]]NH[RCH]NH[RCHRCH]
-pack.class.attribute.ConcealedPackages = NH[RUH]
-pack.class.attribute.Version = RUH
-pack.class.attribute.MainClass = RCH
-pack.class.attribute.TargetPlatform = RUHRUHRUH
-pack.class.attribute.Hashes = RUHNH[RUHRUH]
+pack.class.attribute.Module = RUHFHNH[RUHFH]NH[RUHFHNH[RUH]]NH[RUHFHNH[RUH]]NH[RCH]NH[RCHNH[RCH]]
+pack.class.attribute.ModulePackages = NH[RUH]
+pack.class.attribute.ModuleVersion = RUH
+pack.class.attribute.ModuleMainClass = RCH
+pack.class.attribute.ModuleTarget = RUHRUHRUH
+pack.class.attribute.ModuleHashes = RUHNH[RUHNH[B]]
 
 
 # Note:  Zero-length ("marker") attributes do not need to be specified here.
--- a/src/java.base/share/classes/java/io/FilePermission.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/io/FilePermission.java	Thu Dec 01 21:39:49 2016 +0000
@@ -216,19 +216,28 @@
             GetPropertyAction.privilegedGetProperty("user.dir"));
 
     /**
-     * A private constructor like a clone, only npath2 is not touched.
+     * A private constructor that clones some and updates some,
+     * always with a different name.
      * @param input
      */
-    private FilePermission(String name, FilePermission input) {
+    private FilePermission(String name,
+                           FilePermission input,
+                           Path npath,
+                           Path npath2,
+                           int mask,
+                           String actions) {
         super(name);
-        this.npath = input.npath;
-        this.actions = input.actions;
+        // Customizables
+        this.npath = npath;
+        this.npath2 = npath2;
+        this.actions = actions;
+        this.mask = mask;
+        // Cloneds
         this.allFiles = input.allFiles;
         this.invalid = input.invalid;
         this.recursive = input.recursive;
         this.directory = input.directory;
         this.cpath = input.cpath;
-        this.mask = input.mask;
     }
 
     /**
@@ -261,10 +270,12 @@
                             // different than the original so that when one is
                             // added to a FilePermissionCollection it will not
                             // be merged with the original one.
-                            FilePermission np = new FilePermission(
-                                    input.getName()+"#plus", input);
-                            np.npath2 = npath2;
-                            return np;
+                            return new FilePermission(input.getName() + "#plus",
+                                    input,
+                                    input.npath,
+                                    npath2,
+                                    input.mask,
+                                    input.actions);
                         }
                     }
                     return input;
@@ -274,10 +285,12 @@
                         Path npath2 = altPath(input.npath);
                         if (npath2 != null) {
                             // New name, see above.
-                            FilePermission np = new FilePermission(
-                                    input.getName()+"#using", input);
-                            np.npath = npath2;
-                            return np;
+                            return new FilePermission(input.getName() + "#using",
+                                    input,
+                                    npath2,
+                                    null,
+                                    input.mask,
+                                    input.actions);
                         }
                     }
                     return null;
@@ -981,6 +994,20 @@
         s.defaultReadObject();
         init(getMask(actions));
     }
+
+    /**
+     * Create a cloned FilePermission with a different actions.
+     * @param effective the new actions
+     * @return a new object
+     */
+    FilePermission withNewActions(int effective) {
+        return new FilePermission(this.getName(),
+                this,
+                this.npath,
+                this.npath2,
+                effective,
+                null);
+    }
 }
 
 /**
@@ -1048,8 +1075,7 @@
         FilePermission fp = (FilePermission)permission;
 
         // Add permission to map if it is absent, or replace with new
-        // permission if applicable. NOTE: cannot use lambda for
-        // remappingFunction parameter until JDK-8076596 is fixed.
+        // permission if applicable.
         perms.merge(fp.getName(), fp,
             new java.util.function.BiFunction<>() {
                 @Override
@@ -1063,7 +1089,8 @@
                             return newVal;
                         }
                         if (effective != oldMask) {
-                            return new FilePermission(fp.getName(), effective);
+                            return ((FilePermission)newVal)
+                                    .withNewActions(effective);
                         }
                     }
                     return existingVal;
--- a/src/java.base/share/classes/java/lang/Class.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/Class.java	Thu Dec 01 21:39:49 2016 +0000
@@ -26,46 +26,55 @@
 package java.lang;
 
 import java.lang.annotation.Annotation;
+import java.lang.module.ModuleDescriptor.Version;
+import java.lang.module.ModuleFinder;
 import java.lang.module.ModuleReader;
-import java.lang.reflect.AnnotatedElement;
-import java.lang.reflect.Array;
-import java.lang.reflect.GenericArrayType;
-import java.lang.reflect.GenericDeclaration;
-import java.lang.reflect.Member;
-import java.lang.reflect.Field;
-import java.lang.reflect.Executable;
-import java.lang.reflect.Method;
-import java.lang.reflect.Module;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Modifier;
-import java.lang.reflect.Type;
-import java.lang.reflect.TypeVariable;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.AnnotatedType;
-import java.lang.reflect.Proxy;
 import java.lang.ref.SoftReference;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.ObjectStreamField;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.GenericDeclaration;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Layer;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Module;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
 import java.net.URL;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
-import java.util.Map;
-import java.util.HashMap;
-import java.util.Objects;
 import java.util.StringJoiner;
+import java.util.stream.Collectors;
+
 import jdk.internal.HotSpotIntrinsicCandidate;
 import jdk.internal.loader.BootLoader;
 import jdk.internal.loader.BuiltinClassLoader;
+import jdk.internal.loader.ResourceHelper;
+import jdk.internal.misc.SharedSecrets;
 import jdk.internal.misc.Unsafe;
 import jdk.internal.misc.VM;
+import jdk.internal.module.ModuleHashes;
 import jdk.internal.reflect.CallerSensitive;
 import jdk.internal.reflect.ConstantPool;
 import jdk.internal.reflect.Reflection;
@@ -442,21 +451,10 @@
 
         PrivilegedAction<ClassLoader> pa = module::getClassLoader;
         ClassLoader cl = AccessController.doPrivileged(pa);
-        if (module.isNamed() && cl != null) {
-            return cl.loadLocalClass(module, name);
-        }
-
-        final Class<?> c;
         if (cl != null) {
-            c = cl.loadLocalClass(name);
+            return cl.loadClass(module, name);
         } else {
-            c = BootLoader.loadClassOrNull(name);
-        }
-
-        if (c != null && c.getModule() == module) {
-            return c;
-        } else {
-            return null;
+            return BootLoader.loadClass(module, name);
         }
     }
 
@@ -979,7 +977,7 @@
     }
 
     // cached package name
-    private String packageName;
+    private transient String packageName;
 
     /**
      * Returns the interfaces directly implemented by the class or interface
@@ -1976,6 +1974,22 @@
         return method;
     }
 
+    /**
+     * Returns a {@code Method} object that reflects the specified public
+     * member method of the class or interface represented by this
+     * {@code Class} object.
+     *
+     * @param name the name of the method
+     * @param parameterTypes the list of parameters
+     * @return the {@code Method} object that matches the specified
+     *         {@code name} and {@code parameterTypes}; {@code null}
+     *         if the method is not found or the name is
+     *         "&lt;init&gt;"or "&lt;clinit&gt;".
+     */
+    Method getMethodOrNull(String name, Class<?>... parameterTypes) {
+        return getMethod0(name, parameterTypes, true);
+    }
+
 
     /**
      * Returns a {@code Constructor} object that reflects the specified
@@ -2367,12 +2381,17 @@
     }
 
     /**
-     * Finds a resource with a given name. If this class is in a named {@link
-     * Module Module}, and the caller of this method is in the same module,
-     * then this method will attempt to find the resource in that module.
-     * Otherwise, the rules for searching resources
-     * associated with a given class are implemented by the defining
-     * {@linkplain ClassLoader class loader} of the class.  This method
+     * Finds a resource with a given name.
+     *
+     * <p> If this class is in a named {@link Module Module} then this method
+     * will attempt to find the resource in the module by means of the absolute
+     * resource name, subject to the rules for encapsulation specified in the
+     * {@code Module} {@link Module#getResourceAsStream getResourceAsStream}
+     * method.
+     *
+     * <p> Otherwise, if this class is not in a named module then the rules for
+     * searching resources associated with a given class are implemented by the
+     * defining {@linkplain ClassLoader class loader} of the class.  This method
      * delegates to this object's class loader.  If this object was loaded by
      * the bootstrap class loader, the method delegates to {@link
      * ClassLoader#getSystemResourceAsStream}.
@@ -2400,8 +2419,11 @@
      * </ul>
      *
      * @param  name name of the desired resource
-     * @return  A {@link java.io.InputStream} object or {@code null} if
-     *          no resource with this name is found
+     * @return  A {@link java.io.InputStream} object; {@code null} if no
+     *          resource with this name is found, the resource is in a package
+     *          that is not {@link Module#isOpen(String, Module) open} to at
+     *          least the caller module, or access to the resource is denied
+     *          by the security manager.
      * @throws  NullPointerException If {@code name} is {@code null}
      * @since  1.1
      */
@@ -2409,35 +2431,41 @@
     public InputStream getResourceAsStream(String name) {
         name = resolveName(name);
 
-        // if this Class and the caller are in the same named module
-        // then attempt to get an input stream to the resource in the
-        // module
         Module module = getModule();
         if (module.isNamed()) {
-            Class<?> caller = Reflection.getCallerClass();
-            if (caller != null && caller.getModule() == module) {
-                ClassLoader cl = getClassLoader0();
-                String mn = module.getName();
-                try {
-
-                    // special-case built-in class loaders to avoid the
-                    // need for a URL connection
-                    if (cl == null) {
-                        return BootLoader.findResourceAsStream(mn, name);
-                    } else if (cl instanceof BuiltinClassLoader) {
-                        return ((BuiltinClassLoader) cl).findResourceAsStream(mn, name);
-                    } else {
-                        URL url = cl.findResource(mn, name);
-                        return (url != null) ? url.openStream() : null;
+            if (!ResourceHelper.isSimpleResource(name)) {
+                Module caller = Reflection.getCallerClass().getModule();
+                if (caller != module) {
+                    Set<String> packages = module.getDescriptor().packages();
+                    String pn = ResourceHelper.getPackageName(name);
+                    if (packages.contains(pn) && !module.isOpen(pn, caller)) {
+                        // resource is in package not open to caller
+                        return null;
                     }
-
-                } catch (IOException | SecurityException e) {
-                    return null;
                 }
             }
+
+            String mn = module.getName();
+            ClassLoader cl = getClassLoader0();
+            try {
+
+                // special-case built-in class loaders to avoid the
+                // need for a URL connection
+                if (cl == null) {
+                    return BootLoader.findResourceAsStream(mn, name);
+                } else if (cl instanceof BuiltinClassLoader) {
+                    return ((BuiltinClassLoader) cl).findResourceAsStream(mn, name);
+                } else {
+                    URL url = cl.findResource(mn, name);
+                    return (url != null) ? url.openStream() : null;
+                }
+
+            } catch (IOException | SecurityException e) {
+                return null;
+            }
         }
 
-        // this Class and caller not in the same named module
+        // unnamed module
         ClassLoader cl = getClassLoader0();
         if (cl == null) {
             return ClassLoader.getSystemResourceAsStream(name);
@@ -2447,12 +2475,17 @@
     }
 
     /**
-     * Finds a resource with a given name. If this class is in a named {@link
-     * Module Module}, and the caller of this method is in the same module,
-     * then this method will attempt to find the resource in that module.
-     * Otherwise, the rules for searching resources
-     * associated with a given class are implemented by the defining
-     * {@linkplain ClassLoader class loader} of the class.  This method
+     * Finds a resource with a given name.
+     *
+     * <p> If this class is in a named {@link Module Module} then this method
+     * will attempt to find the resource in the module by means of the absolute
+     * resource name, subject to the rules for encapsulation specified in the
+     * {@code Module} {@link Module#getResourceAsStream getResourceAsStream}
+     * method.
+     *
+     * <p> Otherwise, if this class is not in a named module then the rules for
+     * searching resources associated with a given class are implemented by the
+     * defining {@linkplain ClassLoader class loader} of the class.  This method
      * delegates to this object's class loader. If this object was loaded by
      * the bootstrap class loader, the method delegates to {@link
      * ClassLoader#getSystemResource}.
@@ -2479,35 +2512,46 @@
      * </ul>
      *
      * @param  name name of the desired resource
-     * @return A {@link java.net.URL} object; {@code null} if no
-     *         resource with this name is found or the resource cannot
-     *         be located by a URL.
+     * @return A {@link java.net.URL} object; {@code null} if no resource with
+     *         this name is found, the resource cannot be located by a URL, the
+     *         resource is in a package that is not
+     *         {@link Module#isOpen(String, Module) open} to at least the caller
+     *         module, or access to the resource is denied by the security
+     *         manager.
+     * @throws NullPointerException If {@code name} is {@code null}
      * @since  1.1
      */
     @CallerSensitive
     public URL getResource(String name) {
         name = resolveName(name);
 
-        // if this Class and the caller are in the same named module
-        // then attempt to get URL to the resource in the module
         Module module = getModule();
         if (module.isNamed()) {
-            Class<?> caller = Reflection.getCallerClass();
-            if (caller != null && caller.getModule() == module) {
-                String mn = getModule().getName();
-                ClassLoader cl = getClassLoader0();
-                try {
-                    if (cl == null) {
-                        return BootLoader.findResource(mn, name);
-                    } else {
-                        return cl.findResource(mn, name);
+            if (!ResourceHelper.isSimpleResource(name)) {
+                Module caller = Reflection.getCallerClass().getModule();
+                if (caller != module) {
+                    Set<String> packages = module.getDescriptor().packages();
+                    String pn = ResourceHelper.getPackageName(name);
+                    if (packages.contains(pn) && !module.isOpen(pn, caller)) {
+                        // resource is in package not open to caller
+                        return null;
                     }
-                } catch (IOException ioe) {
-                    return null;
                 }
             }
+            String mn = getModule().getName();
+            ClassLoader cl = getClassLoader0();
+            try {
+                if (cl == null) {
+                    return BootLoader.findResource(mn, name);
+                } else {
+                    return cl.findResource(mn, name);
+                }
+            } catch (IOException ioe) {
+                return null;
+            }
         }
 
+        // unnamed module
         ClassLoader cl = getClassLoader0();
         if (cl == null) {
             return ClassLoader.getSystemResource(name);
@@ -2632,9 +2676,6 @@
      * if name is absolute
      */
     private String resolveName(String name) {
-        if (name == null) {
-            return name;
-        }
         if (!name.startsWith("/")) {
             Class<?> c = this;
             while (c.isArray()) {
--- a/src/java.base/share/classes/java/lang/ClassLoader.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/ClassLoader.java	Thu Dec 01 21:39:49 2016 +0000
@@ -42,15 +42,14 @@
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
 import java.util.Spliterator;
 import java.util.Spliterators;
 import java.util.Stack;
-import java.util.NoSuchElementException;
 import java.util.Vector;
 import java.util.WeakHashMap;
 import java.util.concurrent.ConcurrentHashMap;
@@ -59,7 +58,6 @@
 import java.util.stream.StreamSupport;
 
 import jdk.internal.perf.PerfCounter;
-import jdk.internal.module.ServicesCatalog;
 import jdk.internal.loader.BootLoader;
 import jdk.internal.loader.ClassLoaders;
 import jdk.internal.misc.SharedSecrets;
@@ -411,7 +409,6 @@
         this(checkCreateClassLoader(), null, parent);
     }
 
-
     /**
      * Creates a new class loader using the <tt>ClassLoader</tt> returned by
      * the method {@link #getSystemClassLoader()
@@ -431,7 +428,6 @@
         this(checkCreateClassLoader(), null, getSystemClassLoader());
     }
 
-
     /**
      * Returns the name of this class loader or {@code null} if
      * this class loader is not named.
@@ -581,7 +577,7 @@
      * @return The resulting {@code Class} object in a module defined by
      *         this class loader, or {@code null} if the class could not be found.
      */
-    final Class<?> loadLocalClass(Module module, String name) {
+    final Class<?> loadClass(Module module, String name) {
         synchronized (getClassLoadingLock(name)) {
             // First, check if the class has already been loaded
             Class<?> c = findLoadedClass(name);
@@ -597,34 +593,6 @@
     }
 
     /**
-     * Loads the class with the specified <a href="#name">binary name</a>
-     * defined by this class loader.  This method returns {@code null}
-     * if the class could not be found.
-     *
-     * @apiNote This method does not delegate to the parent class loader.
-     *
-     * @param  name
-     *         The <a href="#name">binary name</a> of the class
-     *
-     * @return The resulting {@code Class} object in a module defined by
-     *         this class loader, or {@code null} if the class could not be found.
-     */
-    final Class<?> loadLocalClass(String name) {
-        synchronized (getClassLoadingLock(name)) {
-            // First, check if the class has already been loaded
-            Class<?> c = findLoadedClass(name);
-            if (c == null) {
-                try {
-                    return findClass(name);
-                } catch (ClassNotFoundException e) {
-                    // ignore
-                }
-            }
-            return c;
-        }
-    }
-
-    /**
      * Returns the lock object for class loading operations.
      * For backward compatibility, the default implementation of this method
      * behaves as follows. If this ClassLoader object is registered as
@@ -724,12 +692,17 @@
      * should override this method.
      *
      * @apiNote This method returns {@code null} rather than throwing
-     *          {@code ClassNotFoundException} if the class could not be found
+     *          {@code ClassNotFoundException} if the class could not be found.
      *
-     * @implSpec The default implementation returns {@code null}.
+     * @implSpec The default implementation attempts to find the class by
+     * invoking {@link #findClass(String)} when the {@code moduleName} is
+     * {@code null}. It otherwise returns {@code null}.
      *
      * @param  moduleName
-     *         The module name
+     *         The module name; or {@code null} to find the class in the
+     *         {@linkplain #getUnnamedModule() unnamed module} for this
+     *         class loader
+
      * @param  name
      *         The <a href="#name">binary name</a> of the class
      *
@@ -739,6 +712,11 @@
      * @since 9
      */
     protected Class<?> findClass(String moduleName, String name) {
+        if (moduleName == null) {
+            try {
+                return findClass(name);
+            } catch (ClassNotFoundException ignore) { }
+        }
         return null;
     }
 
@@ -1286,10 +1264,20 @@
      * Class loader implementations that support the loading from modules
      * should override this method.
      *
-     * @implSpec The default implementation returns {@code null}.
+     * @apiNote This method is the basis for the {@code Class} {@link
+     * Class#getResource getResource} and {@link Class#getResourceAsStream
+     * getResourceAsStream} methods. It is not subject to the rules for
+     * encapsulation specified by {@code Module} {@link
+     * Module#getResourceAsStream getResourceAsStream}.
+     *
+     * @implSpec The default implementation attempts to find the resource by
+     * invoking {@link #findResource(String)} when the {@code moduleName} is
+     * {@code null}. It otherwise returns {@code null}.
      *
      * @param  moduleName
-     *         The module name
+     *         The module name; or {@code null} to find a resource in the
+     *         {@linkplain #getUnnamedModule() unnamed module} for this
+     *         class loader
      * @param  name
      *         The resource name
      *
@@ -1306,7 +1294,11 @@
      * @since 9
      */
     protected URL findResource(String moduleName, String name) throws IOException {
-        return null;
+        if (moduleName == null) {
+            return findResource(name);
+        } else {
+            return null;
+        }
     }
 
     /**
@@ -1314,9 +1306,6 @@
      * (images, audio, text, etc) that can be accessed by class code in a way
      * that is independent of the location of the code.
      *
-     * Resources in a named module are private to that module. This method does
-     * not find resource in named modules.
-     *
      * <p> The name of a resource is a '<tt>/</tt>'-separated path name that
      * identifies the resource.
      *
@@ -1325,16 +1314,31 @@
      * built-in to the virtual machine is searched.  That failing, this method
      * will invoke {@link #findResource(String)} to find the resource.  </p>
      *
-     * @apiNote When overriding this method it is recommended that an
-     * implementation ensures that any delegation is consistent with the {@link
+     * <p> Resources in named modules are subject to the encapsulation rules
+     * specified by {@link Module#getResourceAsStream Module.getResourceAsStream}.
+     * Additionally, and except for the special case where the resource has a
+     * name ending with "{@code .class}", this method will only find resources in
+     * packages of named modules when the package is {@link Module#isOpen(String)
+     * opened} unconditionally (even if the caller of this method is in the
+     * same module as the resource). </p>
+     *
+     * @apiNote Where several modules are defined to the same class loader,
+     * and where more than one module contains a resource with the given name,
+     * then the ordering that modules are searched is not specified and may be
+     * very unpredictable.
+     * When overriding this method it is recommended that an implementation
+     * ensures that any delegation is consistent with the {@link
      * #getResources(java.lang.String) getResources(String)} method.
      *
      * @param  name
      *         The resource name
      *
-     * @return  A <tt>URL</tt> object for reading the resource, or
-     *          <tt>null</tt> if the resource could not be found or the invoker
-     *          doesn't have adequate  privileges to get the resource.
+     * @return  {@code URL} object for reading the resource; {@code null} if
+     *          the resource could not be found, a {@code URL} could not be
+     *          constructed to locate the resource, the resource is in a package
+     *          that is not opened unconditionally, or access to the resource is
+     *          denied by the security manager.
+     *
      * @throws  NullPointerException If {@code name} is {@code null}
      *
      * @since  1.1
@@ -1358,16 +1362,24 @@
      * (images, audio, text, etc) that can be accessed by class code in a way
      * that is independent of the location of the code.
      *
-     * Resources in a named module are private to that module. This method does
-     * not find resources in named modules.
-     *
-     * <p>The name of a resource is a <tt>/</tt>-separated path name that
+     * <p> The name of a resource is a <tt>/</tt>-separated path name that
      * identifies the resource.
      *
-     * <p> The search order is described in the documentation for {@link
-     * #getResource(String)}.  </p>
+     * <p> The delegation order for searching is described in the documentation
+     * for {@link #getResource(String)}.  </p>
      *
-     * @apiNote When overriding this method it is recommended that an
+     * <p> Resources in named modules are subject to the encapsulation rules
+     * specified by {@link Module#getResourceAsStream Module.getResourceAsStream}.
+     * Additionally, and except for the special case where the resource has a
+     * name ending with "{@code .class}", this method will only find resources in
+     * packages of named modules when the package is {@link Module#isOpen(String)
+     * opened} unconditionally (even if the caller of this method is in the
+     * same module as the resource).</p>
+     *
+     * @apiNote Where several modules are defined to the same class loader,
+     * and where more than one module contains a resource with the given name,
+     * then the ordering is not specified and may be very unpredictable.
+     * When overriding this method it is recommended that an
      * implementation ensures that any delegation is consistent with the {@link
      * #getResource(java.lang.String) getResource(String)} method. This should
      * ensure that the first element returned by the Enumeration's
@@ -1378,9 +1390,11 @@
      *         The resource name
      *
      * @return  An enumeration of {@link java.net.URL <tt>URL</tt>} objects for
-     *          the resource.  If no resources could  be found, the enumeration
-     *          will be empty.  Resources that the class loader doesn't have
-     *          access to will not be in the enumeration.
+     *          the resource. If no resources could  be found, the enumeration
+     *          will be empty. Resources for which a {@code URL} cannot be
+     *          constructed, are in package that is not opened unconditionally,
+     *          or access to the resource is denied by the security manager,
+     *          are not returned in the enumeration.
      *
      * @throws  IOException
      *          If I/O errors occur
@@ -1410,9 +1424,6 @@
      * can be accessed by class code in a way that is independent of the
      * location of the code.
      *
-     * Resources in a named module are private to that module. This method does
-     * not find resources in named modules.
-     *
      * <p> The name of a resource is a {@code /}-separated path name that
      * identifies the resource.
      *
@@ -1424,6 +1435,14 @@
      * exception is wrapped in an {@link UncheckedIOException} that is then
      * thrown.
      *
+     * <p> Resources in named modules are subject to the encapsulation rules
+     * specified by {@link Module#getResourceAsStream Module.getResourceAsStream}.
+     * Additionally, and except for the special case where the resource has a
+     * name ending with "{@code .class}", this method will only find resources in
+     * packages of named modules when the package is {@link Module#isOpen(String)
+     * opened} unconditionally (even if the caller of this method is in the
+     * same module as the resource). </p>
+     *
      * @apiNote When overriding this method it is recommended that an
      * implementation ensures that any delegation is consistent with the {@link
      * #getResource(java.lang.String) getResource(String)} method. This should
@@ -1434,9 +1453,10 @@
      *         The resource name
      *
      * @return  A stream of resource {@link java.net.URL URL} objects. If no
-     *          resources could  be found, the stream will be empty.  Resources
-     *          that the class loader doesn't have access to will not be in the
-     *          stream.
+     *          resources could  be found, the stream will be empty. Resources
+     *          for which a {@code URL} cannot be constructed, are in a package
+     *          that is not opened unconditionally, or access to the resource
+     *          is denied by the security manager, will not be in the stream.
      *
      * @throws  NullPointerException If {@code name} is {@code null}
      *
@@ -1462,14 +1482,21 @@
      * Finds the resource with the given name. Class loader implementations
      * should override this method to specify where to find resources.
      *
-     * Resources in a named module are private to that module. This method does
-     * not find resources in named modules defined to this class loader.
+     * <p> For resources in named modules then the method must implement the
+     * rules for encapsulation specified in the {@code Module} {@link
+     * Module#getResourceAsStream getResourceAsStream} method. Additionally,
+     * it must not find non-"{@code .class}" resources in packages of named
+     * modules unless the package is {@link Module#isOpen(String) opened}
+     * unconditionally. </p>
      *
      * @param  name
      *         The resource name
      *
-     * @return  A <tt>URL</tt> object for reading the resource, or
-     *          <tt>null</tt> if the resource could not be found
+     * @return  {@code URL} object for reading the resource; {@code null} if
+     *          the resource could not be found, a {@code URL} could not be
+     *          constructed to locate the resource, the resource is in a package
+     *          that is not opened unconditionally, or access to the resource is
+     *          denied by the security manager.
      *
      * @since  1.2
      */
@@ -1483,14 +1510,22 @@
      * implementations should override this method to specify where to load
      * resources from.
      *
-     * Resources in a named module are private to that module. This method does
-     * not find resources in named modules defined to this class loader.
+     * <p> For resources in named modules then the method must implement the
+     * rules for encapsulation specified in the {@code Module} {@link
+     * Module#getResourceAsStream getResourceAsStream} method. Additionally,
+     * it must not find non-"{@code .class}" resources in packages of named
+     * modules unless the package is {@link Module#isOpen(String) opened}
+     * unconditionally. </p>
      *
      * @param  name
      *         The resource name
      *
      * @return  An enumeration of {@link java.net.URL <tt>URL</tt>} objects for
-     *          the resources
+     *          the resource. If no resources could  be found, the enumeration
+     *          will be empty. Resources for which a {@code URL} cannot be
+     *          constructed, are in a package that is not opened unconditionally,
+     *          or access to the resource is denied by the security manager,
+     *          are not returned in the enumeration.
      *
      * @throws  IOException
      *          If I/O errors occur
@@ -1498,7 +1533,7 @@
      * @since  1.2
      */
     protected Enumeration<URL> findResources(String name) throws IOException {
-        return java.util.Collections.emptyEnumeration();
+        return Collections.emptyEnumeration();
     }
 
     /**
@@ -1549,14 +1584,21 @@
      * classes.  This method locates the resource through the system class
      * loader (see {@link #getSystemClassLoader()}).
      *
-     * Resources in a named module are private to that module. This method does
-     * not find resources in named modules.
+     * <p> Resources in named modules are subject to the encapsulation rules
+     * specified by {@link Module#getResourceAsStream Module.getResourceAsStream}.
+     * Additionally, and except for the special case where the resource has a
+     * name ending with "{@code .class}", this method will only find resources in
+     * packages of named modules when the package is {@link Module#isOpen(String)
+     * opened} unconditionally. </p>
      *
      * @param  name
      *         The resource name
      *
-     * @return  A {@link java.net.URL <tt>URL</tt>} object for reading the
-     *          resource, or <tt>null</tt> if the resource could not be found
+     * @return  A {@link java.net.URL <tt>URL</tt>} to the resource; {@code
+     *          null} if the resource could not be found, a URL could not be
+     *          constructed to locate the resource, the resource is in a package
+     *          that is not opened unconditionally or access to the resource is
+     *          denied by the security manager.
      *
      * @since  1.1
      */
@@ -1570,17 +1612,25 @@
      * {@link java.util.Enumeration <tt>Enumeration</tt>} of {@link
      * java.net.URL <tt>URL</tt>} objects.
      *
-     * Resources in a named module are private to that module. This method does
-     * not find resources in named modules.
-     *
      * <p> The search order is described in the documentation for {@link
      * #getSystemResource(String)}.  </p>
      *
+     * <p> Resources in named modules are subject to the encapsulation rules
+     * specified by {@link Module#getResourceAsStream Module.getResourceAsStream}.
+     * Additionally, and except for the special case where the resource has a
+     * name ending with "{@code .class}", this method will only find resources in
+     * packages of named modules when the package is {@link Module#isOpen(String)
+     * opened} unconditionally. </p>
+     *
      * @param  name
      *         The resource name
      *
-     * @return  An enumeration of resource {@link java.net.URL <tt>URL</tt>}
-     *          objects
+     * @return  An enumeration of {@link java.net.URL <tt>URL</tt>} objects for
+     *          the resource. If no resources could  be found, the enumeration
+     *          will be empty. Resources for which a {@code URL} cannot be
+     *          constructed, are in a package that is not opened unconditionally,
+     *          or access to the resource is denied by the security manager,
+     *          are not returned in the enumeration.
      *
      * @throws  IOException
      *          If I/O errors occur
@@ -1596,17 +1646,24 @@
     /**
      * Returns an input stream for reading the specified resource.
      *
-     * Resources in a named module are private to that module. This method does
-     * not find resources in named modules.
-     *
      * <p> The search order is described in the documentation for {@link
      * #getResource(String)}.  </p>
      *
+     * <p> Resources in named modules are subject to the encapsulation rules
+     * specified by {@link Module#getResourceAsStream Module.getResourceAsStream}.
+     * Additionally, and except for the special case where the resource has a
+     * name ending with "{@code .class}", this method will only find resources in
+     * packages of named modules when the package is {@link Module#isOpen(String)
+     * opened} unconditionally. </p>
+     *
      * @param  name
      *         The resource name
      *
-     * @return  An input stream for reading the resource, or <tt>null</tt>
-     *          if the resource could not be found
+     * @return  An input stream for reading the resource; {@code null} if the
+     *          resource could not be found, the resource is in a package that
+     *          is not opened unconditionally, or access to the resource is
+     *          denied by the security manager.
+     *
      * @throws  NullPointerException If {@code name} is {@code null}
      *
      * @since  1.1
@@ -1626,14 +1683,20 @@
      * used to load classes.  This method locates the resource through the
      * system class loader (see {@link #getSystemClassLoader()}).
      *
-     * Resources in a named module are private to that module. This method does
-     * not find resources in named modules.
+     * <p> Resources in named modules are subject to the encapsulation rules
+     * specified by {@link Module#getResourceAsStream Module.getResourceAsStream}.
+     * Additionally, and except for the special case where the resource has a
+     * name ending with "{@code .class}", this method will only find resources in
+     * packages of named modules when the package is {@link Module#isOpen(String)
+     * opened} unconditionally. </p>
      *
      * @param  name
      *         The resource name
      *
-     * @return  An input stream for reading the resource, or <tt>null</tt>
-     *          if the resource could not be found
+     * @return  An input stream for reading the resource; {@code null} if the
+     *          resource could not be found, the resource is in a package that
+     *          is not opened unconditionally, or access to the resource is
+     *          denied by the security manager.
      *
      * @since  1.1
      */
@@ -2719,33 +2782,7 @@
     private static native AssertionStatusDirectives retrieveDirectives();
 
 
-    /**
-     * Returns the ServiceCatalog for modules defined to this class loader
-     * or {@code null} if this class loader does not have a services catalog.
-     */
-    ServicesCatalog getServicesCatalog() {
-        return servicesCatalog;
-    }
-
-    /**
-     * Returns the ServiceCatalog for modules defined to this class loader,
-     * creating it if it doesn't already exist.
-     */
-    ServicesCatalog createOrGetServicesCatalog() {
-        ServicesCatalog catalog = servicesCatalog;
-        if (catalog == null) {
-            catalog = ServicesCatalog.create();
-            boolean set = trySetObjectField("servicesCatalog", catalog);
-            if (!set) {
-                // beaten by someone else
-                catalog = servicesCatalog;
-            }
-        }
-        return catalog;
-    }
-
-    // the ServiceCatalog for modules associated with this class loader.
-    private volatile ServicesCatalog servicesCatalog;
+    // -- Misc --
 
     /**
      * Returns the ConcurrentHashMap used as a storage for ClassLoaderValue(s)
--- a/src/java.base/share/classes/java/lang/Deprecated.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/Deprecated.java	Thu Dec 01 21:39:49 2016 +0000
@@ -40,6 +40,11 @@
  * annotation on a local variable declaration or on a parameter declaration
  * or a package declaration has no effect on the warnings issued by a compiler.
  *
+ * <p>When a module is deprecated, the use of that module in {@code
+ * requires}, but not in {@code exports} or {@code opens} clauses causes
+ * a warning to be issued. A module being deprecated does <em>not</em> cause
+ * warnings to be issued for uses of types within the module.
+ *
  * <p>This annotation type has a string-valued element {@code since}. The value
  * of this element indicates the version in which the annotated program element
  * was first deprecated.
@@ -74,7 +79,7 @@
  */
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
-@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
+@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
 public @interface Deprecated {
     /**
      * Returns the version in which the annotated element became deprecated.
--- a/src/java.base/share/classes/java/lang/Iterable.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/Iterable.java	Thu Dec 01 21:39:49 2016 +0000
@@ -53,10 +53,13 @@
     /**
      * Performs the given action for each element of the {@code Iterable}
      * until all elements have been processed or the action throws an
-     * exception.  Unless otherwise specified by the implementing class,
-     * actions are performed in the order of iteration (if an iteration order
-     * is specified).  Exceptions thrown by the action are relayed to the
+     * exception.  Actions are performed in the order of iteration, if that
+     * order is specified.  Exceptions thrown by the action are relayed to the
      * caller.
+     * <p>
+     * The behavior of this method is unspecified if the action performs
+     * side-effects that modify the underlying source of elements, unless an
+     * overriding class has specified a concurrent modification policy.
      *
      * @implSpec
      * <p>The default implementation behaves as if:
--- a/src/java.base/share/classes/java/lang/Package.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/Package.java	Thu Dec 01 21:39:49 2016 +0000
@@ -398,10 +398,16 @@
         if (packageInfo == null) {
             // find package-info.class defined by loader
             String cn = packageName() + ".package-info";
-            PrivilegedAction<ClassLoader> pa = module()::getClassLoader;
+            Module module = module();
+            PrivilegedAction<ClassLoader> pa = module::getClassLoader;
             ClassLoader loader = AccessController.doPrivileged(pa);
-            Class<?> c = loader != null ? loader.loadLocalClass(cn)
-                                        : BootLoader.loadClassOrNull(cn);
+            Class<?> c;
+            if (loader != null) {
+                c = loader.loadClass(module, cn);
+            } else {
+                c = BootLoader.loadClass(module, cn);
+            }
+
             if (c != null) {
                 packageInfo = c;
             } else {
--- a/src/java.base/share/classes/java/lang/SuppressWarnings.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/SuppressWarnings.java	Thu Dec 01 21:39:49 2016 +0000
@@ -35,6 +35,9 @@
  * a superset of the warnings suppressed in all containing elements.  For
  * example, if you annotate a class to suppress one warning and annotate a
  * method to suppress another, both warnings will be suppressed in the method.
+ * However, note that if a warning is suppressed in a {@code
+ * module-info} file, the suppression applies to elements within the
+ * file and <em>not</em> to types contained within the module.
  *
  * <p>As a matter of style, programmers should always use this annotation
  * on the most deeply nested element where it is effective.  If you want to
@@ -49,7 +52,7 @@
  * @jls 5.5.2 Checked Casts and Unchecked Casts
  * @jls 9.6.4.5 @SuppressWarnings
  */
-@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
 @Retention(RetentionPolicy.SOURCE)
 public @interface SuppressWarnings {
     /**
--- a/src/java.base/share/classes/java/lang/System.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/System.java	Thu Dec 01 21:39:49 2016 +0000
@@ -38,6 +38,7 @@
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Executable;
 import java.lang.reflect.Layer;
+import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.lang.reflect.Module;
 import java.net.URL;
@@ -69,7 +70,6 @@
 import jdk.internal.logger.LocalizedLoggerWrapper;
 
 import jdk.internal.module.ModuleBootstrap;
-import jdk.internal.module.ServicesCatalog;
 
 /**
  * The <code>System</code> class contains several useful class fields
@@ -1987,7 +1987,10 @@
 
     private static void setJavaLangAccess() {
         // Allow privileged classes outside of java.lang
-        SharedSecrets.setJavaLangAccess(new JavaLangAccess(){
+        SharedSecrets.setJavaLangAccess(new JavaLangAccess() {
+            public Method getMethodOrNull(Class<?> klass, String name, Class<?>... parameterTypes) {
+                return klass.getMethodOrNull(name, parameterTypes);
+            }
             public jdk.internal.reflect.ConstantPool getConstantPool(Class<?> klass) {
                 return klass.getConstantPool();
             }
@@ -2031,12 +2034,6 @@
             public Layer getBootLayer() {
                 return bootLayer;
             }
-            public ServicesCatalog getServicesCatalog(ClassLoader cl) {
-                return cl.getServicesCatalog();
-            }
-            public ServicesCatalog createOrGetServicesCatalog(ClassLoader cl) {
-                return cl.createOrGetServicesCatalog();
-            }
             public ConcurrentHashMap<?, ?> createOrGetClassLoaderValueMap(ClassLoader cl) {
                 return cl.createOrGetClassLoaderValueMap();
             }
--- a/src/java.base/share/classes/java/lang/VersionProps.java.template	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/VersionProps.java.template	Thu Dec 01 21:39:49 2016 +0000
@@ -123,27 +123,25 @@
 
     /**
      * In case you were wondering this method is called by java -version.
-     * Sad that it prints to stderr; would be nicer if default printed on
-     * stdout.
      */
-    public static void print() {
-        print(System.err);
+    public static void print(boolean err) {
+        print(err, false);
     }
 
     /**
      * This is the same as print except that it adds an extra line-feed
      * at the end, typically used by the -showversion in the launcher
      */
-    public static void println() {
-        print(System.err);
-        System.err.println();
+    public static void println(boolean err) {
+        print(err, true);
     }
 
     /**
-     * Give a stream, it will print version info on it.
+     * Print version info.
      */
-    public static void print(PrintStream ps) {
+    private static void print(boolean err, boolean newln) {
         boolean isHeadless = false;
+        PrintStream ps = err ? System.err : System.out;
 
         /* Report that we're running headless if the property is true */
         String headless = System.getProperty("java.awt.headless");
@@ -152,10 +150,14 @@
         }
 
         /* First line: platform version. */
-        ps.println(launcher_name + " version \"" + java_version + "\"");
+        if (err) {
+            ps.println(launcher_name + " version \"" + java_version + "\"");
+        } else {
+            /* Use a format more in line with GNU conventions */
+            ps.println(launcher_name + " " + java_version);
+        }
 
         /* Second line: runtime version (ie, libraries). */
-
         String jdk_debug_level = System.getProperty("jdk.debug", "release");
         /* Debug level is not printed for "release" builds */
         if ("release".equals(jdk_debug_level)) {
--- a/src/java.base/share/classes/java/lang/annotation/ElementType.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/annotation/ElementType.java	Thu Dec 01 21:39:49 2016 +0000
@@ -37,10 +37,10 @@
  * <em>type contexts</em> , where annotations apply to types used in
  * declarations and expressions.
  *
- * <p>The constants {@link #ANNOTATION_TYPE} , {@link #CONSTRUCTOR} , {@link
- * #FIELD} , {@link #LOCAL_VARIABLE} , {@link #METHOD} , {@link #PACKAGE} ,
- * {@link #PARAMETER} , {@link #TYPE} , and {@link #TYPE_PARAMETER} correspond
- * to the declaration contexts in JLS 9.6.4.1.
+ * <p>The constants {@link #ANNOTATION_TYPE}, {@link #CONSTRUCTOR}, {@link
+ * #FIELD}, {@link #LOCAL_VARIABLE}, {@link #METHOD}, {@link #PACKAGE}, {@link
+ * #MODULE}, {@link #PARAMETER}, {@link #TYPE}, and {@link #TYPE_PARAMETER}
+ * correspond to the declaration contexts in JLS 9.6.4.1.
  *
  * <p>For example, an annotation whose type is meta-annotated with
  * {@code @Target(ElementType.FIELD)} may only be written as a modifier for a
@@ -107,5 +107,12 @@
      *
      * @since 1.8
      */
-    TYPE_USE
+    TYPE_USE,
+
+    /**
+     * Module declaration.
+     *
+     * @since 9
+     */
+    MODULE
 }
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java	Thu Dec 01 21:39:49 2016 +0000
@@ -1147,12 +1147,14 @@
 
         static
         MethodHandle bindCaller(MethodHandle mh, Class<?> hostClass) {
-            // Do not use this function to inject calls into system classes.
+            // Code in the the boot layer should now be careful while creating method handles or
+            // functional interface instances created from method references to @CallerSensitive  methods,
+            // it needs to be ensured the handles or interface instances are kept safe and are not passed
+            // from the boot layer to untrusted code.
             if (hostClass == null
                 ||    (hostClass.isArray() ||
                        hostClass.isPrimitive() ||
-                       hostClass.getName().startsWith("java.") ||
-                       hostClass.getName().startsWith("sun."))) {
+                       hostClass.getName().startsWith("java.lang.invoke."))) {
                 throw new InternalError();  // does not happen, and should not anyway
             }
             // For simplicity, convert mh to a varargs-like method.
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java	Thu Dec 01 21:39:49 2016 +0000
@@ -42,6 +42,7 @@
 import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.lang.reflect.Module;
 import java.lang.reflect.ReflectPermission;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
@@ -142,6 +143,59 @@
     }
 
     /**
+     * Returns a {@link Lookup lookup object} with full capabilities to emulate all
+     * supported bytecode behaviors, including <a href="MethodHandles.Lookup.html#privacc">
+     * private access</a>, on a target class.
+     * This method checks that a caller, specified as a {@code Lookup} object, is allowed to
+     * do <em>deep reflection</em> on the target class. If {@code m1} is the module containing
+     * the {@link Lookup#lookupClass() lookup class}, and {@code m2} is the module containing
+     * the target class, then this check ensures that
+     * <ul>
+     *     <li>{@code m1} {@link Module#canRead reads} {@code m2}.</li>
+     *     <li>{@code m2} {@link Module#isOpen(String,Module) opens} the package containing
+     *     the target class to at least {@code m1}.</li>
+     *     <li>The lookup has the {@link Lookup#MODULE MODULE} lookup mode.</li>
+     * </ul>
+     * <p>
+     * If there is a security manager, its {@code checkPermission} method is called to
+     * check {@code ReflectPermission("suppressAccessChecks")}.
+     * @apiNote The {@code MODULE} lookup mode serves to authenticate that the lookup object
+     * was created by code in the caller module (or derived from a lookup object originally
+     * created by the caller). A lookup object with the {@code MODULE} lookup mode can be
+     * shared with trusted parties without giving away {@code PRIVATE} and {@code PACKAGE}
+     * access to the caller.
+     * @param targetClass the target class
+     * @param lookup the caller lookup object
+     * @return a lookup object for the target class, with private access
+     * @throws IllegalArgumentException if {@code targetClass} is a primitve type or array class
+     * @throws NullPointerException if {@code targetClass} or {@code caller} is {@code null}
+     * @throws IllegalAccessException if the access check specified above fails
+     * @throws SecurityException if denied by the security manager
+     * @since 9
+     */
+    public static Lookup privateLookupIn(Class<?> targetClass, Lookup lookup) throws IllegalAccessException {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
+        if (targetClass.isPrimitive())
+            throw new IllegalArgumentException(targetClass + " is a primitive class");
+        if (targetClass.isArray())
+            throw new IllegalArgumentException(targetClass + " is an array class");
+        Module targetModule = targetClass.getModule();
+        Module callerModule = lookup.lookupClass().getModule();
+        if (callerModule != targetModule && targetModule.isNamed()) {
+            if (!callerModule.canRead(targetModule))
+                throw new IllegalAccessException(callerModule + " does not read " + targetModule);
+            String pn = targetClass.getPackageName();
+            assert pn != null && pn.length() > 0 : "unnamed package cannot be in named module";
+            if (!targetModule.isOpen(pn, callerModule))
+                throw new IllegalAccessException(targetModule + " does not open " + pn + " to " + callerModule);
+        }
+        if ((lookup.lookupModes() & Lookup.MODULE) == 0)
+            throw new IllegalAccessException("lookup does not have MODULE lookup mode");
+        return new Lookup(targetClass);
+    }
+
+    /**
      * Performs an unchecked "crack" of a
      * <a href="MethodHandleInfo.html#directmh">direct method handle</a>.
      * The result is as if the user had obtained a lookup object capable enough
@@ -1807,7 +1861,12 @@
             return callerClass;
         }
 
-        private boolean hasPrivateAccess() {
+        /**
+         * Returns {@code true} if this lookup has {@code PRIVATE} access.
+         * @return {@code true} if this lookup has {@code PRIVATE} acesss.
+         * @since 9
+         */
+        public boolean hasPrivateAccess() {
             return (allowedModes & PRIVATE) != 0;
         }
 
--- a/src/java.base/share/classes/java/lang/module/Configuration.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/module/Configuration.java	Thu Dec 01 21:39:49 2016 +0000
@@ -26,14 +26,20 @@
 package java.lang.module;
 
 import java.io.PrintStream;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * The configuration that is the result of resolution or resolution with
@@ -46,14 +52,14 @@
  * dependences expressed by {@code requires} clauses.
  *
  * The <em>dependence graph</em> is augmented with edges that take account of
- * implicitly declared dependences ({@code requires public}) to create a
+ * implicitly declared dependences ({@code requires transitive}) to create a
  * <em>readability graph</em>. A {@code Configuration} encapsulates the
  * resulting graph of {@link ResolvedModule resolved modules}.
  *
  * <p> Suppose we have the following observable modules: </p>
  * <pre> {@code
  *     module m1 { requires m2; }
- *     module m2 { requires public m3; }
+ *     module m2 { requires transitive m3; }
  *     module m3 { }
  *     module m4 { }
  * } </pre>
@@ -70,8 +76,10 @@
  * <p> Resolution is an additive process. When computing the transitive closure
  * then the dependence relation may include dependences on modules in parent
  * configurations. The result is a <em>relative configuration</em> that is
- * relative to a parent configuration and where the readability graph may have
- * edges from modules in the configuration to modules in a parent configuration.
+ * relative to one or more parent configurations and where the readability graph
+ * may have edges from modules in the configuration to modules in parent
+ * configurations.
+ *
  * </p>
  *
  * <p> Suppose we have the following observable modules: </p>
@@ -96,9 +104,9 @@
  * <p> {@link ModuleDescriptor#isAutomatic() Automatic} modules receive special
  * treatment during resolution. Each automatic module is resolved so that it
  * reads all other modules in the configuration and all parent configurations.
- * Each automatic module is also resolved as if it {@code requires public} all
- * other automatic modules in the configuration (and all automatic modules in
- * parent configurations). </p>
+ * Each automatic module is also resolved as if it {@code requires transitive}
+ * all other automatic modules in the configuration (and all automatic modules
+ * in parent configurations). </p>
 
  * <h2><a name="servicebinding">Service binding</a></h2>
  *
@@ -171,54 +179,157 @@
     // @see Configuration#empty()
     private static final Configuration EMPTY_CONFIGURATION = new Configuration();
 
-    private final Configuration parent;
+    // parent configurations, in search order
+    private final List<Configuration> parents;
 
     private final Map<ResolvedModule, Set<ResolvedModule>> graph;
     private final Set<ResolvedModule> modules;
     private final Map<String, ResolvedModule> nameToModule;
 
     private Configuration() {
-        this.parent = null;
+        this.parents = Collections.emptyList();
         this.graph = Collections.emptyMap();
         this.modules = Collections.emptySet();
         this.nameToModule = Collections.emptyMap();
     }
 
-    private Configuration(Configuration parent,
+    private Configuration(List<Configuration> parents,
                           Resolver resolver,
                           boolean check)
     {
         Map<ResolvedModule, Set<ResolvedModule>> g = resolver.finish(this, check);
 
-        Map<String, ResolvedModule> nameToModule = new HashMap<>();
+        @SuppressWarnings(value = {"rawtypes", "unchecked"})
+        Entry<String, ResolvedModule>[] nameEntries
+            = (Entry<String, ResolvedModule>[])new Entry[g.size()];
+        ResolvedModule[] moduleArray = new ResolvedModule[g.size()];
+        int i = 0;
         for (ResolvedModule resolvedModule : g.keySet()) {
-            nameToModule.put(resolvedModule.name(), resolvedModule);
+            moduleArray[i] = resolvedModule;
+            nameEntries[i] = Map.entry(resolvedModule.name(), resolvedModule);
+            i++;
         }
 
-        this.parent = parent;
+        this.parents = Collections.unmodifiableList(parents);
         this.graph = g;
-        this.modules = Collections.unmodifiableSet(g.keySet());
-        this.nameToModule = Collections.unmodifiableMap(nameToModule);
+        this.modules = Set.of(moduleArray);
+        this.nameToModule = Map.ofEntries(nameEntries);
     }
 
 
     /**
      * Resolves a collection of root modules, with this configuration as its
-     * parent, to create a new configuration.
+     * parent, to create a new configuration. This method works exactly as
+     * specified by the static {@link
+     * #resolveRequires(ModuleFinder,List,ModuleFinder,Collection) resolveRequires}
+     * method when invoked with this configuration as the parent. In other words,
+     * if this configuration is {@code cf} then this method is equivalent to
+     * invoking:
+     * <pre> {@code
+     *     Configuration.resolveRequires(before, List.of(cf), after, roots);
+     * }</pre>
+     *
+     * @param  before
+     *         The <em>before</em> module finder to find modules
+     * @param  after
+     *         The <em>after</em> module finder to locate modules when a
+     *         module cannot be located by the {@code before} module finder
+     *         and the module is not in this configuration
+     * @param  roots
+     *         The possibly-empty collection of module names of the modules
+     *         to resolve
+     *
+     * @return The configuration that is the result of resolving the given
+     *         root modules
+     *
+     * @throws ResolutionException
+     *         If resolution or the post-resolution checks fail
+     * @throws SecurityException
+     *         If locating a module is denied by the security manager
+     */
+    public Configuration resolveRequires(ModuleFinder before,
+                                         ModuleFinder after,
+                                         Collection<String> roots)
+    {
+        return resolveRequires(before, List.of(this), after, roots);
+    }
+
+
+    /**
+     * Resolves a collection of root modules, with service binding, and with
+     * this configuration as its parent, to create a new configuration.
+     * This method works exactly as specified by the static {@link
+     * #resolveRequiresAndUses(ModuleFinder,List,ModuleFinder,Collection)
+     * resolveRequiresAndUses} method when invoked with this configuration
+     * as the parent. In other words, if this configuration is {@code cf} then
+     * this method is equivalent to invoking:
+     * <pre> {@code
+     *     Configuration.resolveRequiresAndUses(before, List.of(cf), after, roots);
+     * }</pre>
+     *
+     *
+     * @param  before
+     *         The <em>before</em> module finder to find modules
+     * @param  after
+     *         The <em>after</em> module finder to locate modules when not
+     *         located by the {@code before} module finder and this
+     *         configuration
+     * @param  roots
+     *         The possibly-empty collection of module names of the modules
+     *         to resolve
+     *
+     * @return The configuration that is the result of resolving the given
+     *         root modules
+     *
+     * @throws ResolutionException
+     *         If resolution or the post-resolution checks fail
+     * @throws SecurityException
+     *         If locating a module is denied by the security manager
+     */
+    public Configuration resolveRequiresAndUses(ModuleFinder before,
+                                                ModuleFinder after,
+                                                Collection<String> roots)
+    {
+        return resolveRequiresAndUses(before, List.of(this), after, roots);
+    }
+
+
+    /**
+     * Resolves a collection of root modules, with service binding, and with
+     * the empty configuration as its parent. The post resolution checks
+     * are optionally run.
+     *
+     * This method is used to create the configuration for the boot layer.
+     */
+    static Configuration resolveRequiresAndUses(ModuleFinder finder,
+                                                Collection<String> roots,
+                                                boolean check,
+                                                PrintStream traceOutput)
+    {
+        List<Configuration> parents = List.of(empty());
+        Resolver resolver = new Resolver(finder, parents, ModuleFinder.of(), traceOutput);
+        resolver.resolveRequires(roots).resolveUses();
+
+        return new Configuration(parents, resolver, check);
+    }
+
+
+    /**
+     * Resolves a collection of root modules to create a configuration.
      *
      * <p> Each root module is located using the given {@code before} module
      * finder. If a module is not found then it is located in the parent
      * configuration as if by invoking the {@link #findModule(String)
-     * findModule} method. If not found then the module is located using the
-     * given {@code after} module finder. The same search order is used to
-     * locate transitive dependences. Root modules or dependences that are
-     * located in a parent configuration are resolved no further and are not
-     * included in the resulting configuration. </p>
+     * findModule} method on each parent in iteration order. If not found then
+     * the module is located using the given {@code after} module finder. The
+     * same search order is used to locate transitive dependences. Root modules
+     * or dependences that are located in a parent configuration are resolved
+     * no further and are not included in the resulting configuration. </p>
      *
      * <p> When all modules have been resolved then the resulting dependency
      * graph is checked to ensure that it does not contain cycles. A
-     * readability graph is constructed and then, in conjunction with the
-     * module exports and service use, checked for consistency. </p>
+     * readability graph is constructed and in conjunction with the module
+     * exports and service use, checked for consistency. </p>
      *
      * <p> Resolution and the (post-resolution) consistency checks may fail for
      * following reasons: </p>
@@ -262,6 +373,8 @@
      *
      * @param  before
      *         The <em>before</em> module finder to find modules
+     * @param  parents
+     *         The list parent configurations in search order
      * @param  after
      *         The <em>after</em> module finder to locate modules when not
      *         located by the {@code before} module finder or in parent
@@ -274,31 +387,37 @@
      *         root modules
      *
      * @throws ResolutionException
-     *         If resolution or the post-resolution checks fail for any of the
-     *         reasons listed
+     *         If resolution or the post-resolution checks fail
+     * @throws IllegalArgumentException
+     *         If the list of parents is empty
      * @throws SecurityException
      *         If locating a module is denied by the security manager
      */
-    public Configuration resolveRequires(ModuleFinder before,
-                                         ModuleFinder after,
-                                         Collection<String> roots)
+    public static Configuration resolveRequires(ModuleFinder before,
+                                                List<Configuration> parents,
+                                                ModuleFinder after,
+                                                Collection<String> roots)
     {
         Objects.requireNonNull(before);
         Objects.requireNonNull(after);
         Objects.requireNonNull(roots);
 
-        Resolver resolver = new Resolver(before, this, after, null);
+        List<Configuration> parentList = new ArrayList<>(parents);
+        if (parentList.isEmpty())
+            throw new IllegalArgumentException("'parents' is empty");
+
+        Resolver resolver = new Resolver(before, parentList, after, null);
         resolver.resolveRequires(roots);
 
-        return new Configuration(this, resolver, true);
+        return new Configuration(parentList, resolver, true);
     }
 
-
     /**
-     * Resolves a collection of root modules, with service binding, and with
-     * this configuration as its parent, to create a new configuration.
+     * Resolves a collection of root modules, with service binding, to create
+     * configuration.
      *
-     * <p> This method works exactly as specified by {@link #resolveRequires
+     * <p> This method works exactly as specified by {@link
+     * #resolveRequires(ModuleFinder,List,ModuleFinder,Collection)
      * resolveRequires} except that the graph of resolved modules is augmented
      * with modules induced by the service-use dependence relation. </p>
      *
@@ -319,6 +438,8 @@
      *
      * @param  before
      *         The <em>before</em> module finder to find modules
+     * @param  parents
+     *         The list parent configurations in search order
      * @param  after
      *         The <em>after</em> module finder to locate modules when not
      *         located by the {@code before} module finder or in parent
@@ -331,51 +452,35 @@
      *         root modules
      *
      * @throws ResolutionException
-     *         If resolution or the post-resolution checks fail for any of the
-     *         reasons listed
+     *         If resolution or the post-resolution checks fail
+     * @throws IllegalArgumentException
+     *         If the list of parents is empty
      * @throws SecurityException
      *         If locating a module is denied by the security manager
      */
-    public Configuration resolveRequiresAndUses(ModuleFinder before,
-                                                ModuleFinder after,
-                                                Collection<String> roots)
+    public static Configuration resolveRequiresAndUses(ModuleFinder before,
+                                                       List<Configuration> parents,
+                                                       ModuleFinder after,
+                                                       Collection<String> roots)
     {
         Objects.requireNonNull(before);
         Objects.requireNonNull(after);
         Objects.requireNonNull(roots);
 
-        Resolver resolver = new Resolver(before, this, after, null);
+        List<Configuration> parentList = new ArrayList<>(parents);
+        if (parentList.isEmpty())
+            throw new IllegalArgumentException("'parents' is empty");
+
+        Resolver resolver = new Resolver(before, parentList, after, null);
         resolver.resolveRequires(roots).resolveUses();
 
-        return new Configuration(this, resolver, true);
+        return new Configuration(parentList, resolver, true);
     }
 
 
     /**
-     * Resolves a collection of root modules, with service binding, and with
-     * the empty configuration as its parent. The post resolution checks
-     * are optionally run.
-     *
-     * This method is used to create the configuration for the boot layer.
-     */
-    static Configuration resolveRequiresAndUses(ModuleFinder finder,
-                                                Collection<String> roots,
-                                                boolean check,
-                                                PrintStream traceOutput)
-    {
-        Configuration parent = empty();
-
-        Resolver resolver
-            = new Resolver(finder, parent, ModuleFinder.of(), traceOutput);
-        resolver.resolveRequires(roots).resolveUses();
-
-        return new Configuration(parent, resolver, check);
-    }
-
-
-    /**
-     * Returns the <em>empty</em> configuration. The empty configuration does
-     * not contain any modules and does not have a parent.
+     * Returns the <em>empty</em> configuration. There are no modules in the
+     * empty configuration. It has no parents.
      *
      * @return The empty configuration
      */
@@ -385,13 +490,14 @@
 
 
     /**
-     * Returns this configuration's parent unless this is the {@linkplain #empty
-     * empty configuration}, which has no parent.
+     * Returns an unmodifiable list of this configuration's parents, in search
+     * order. If this is the {@linkplain #empty empty configuration} then an
+     * empty list is returned.
      *
-     * @return This configuration's parent
+     * @return A possibly-empty unmodifiable list of this parent configurations
      */
-    public Optional<Configuration> parent() {
-        return Optional.ofNullable(parent);
+    public List<Configuration> parents() {
+        return parents;
     }
 
 
@@ -408,23 +514,35 @@
 
     /**
      * Finds a resolved module in this configuration, or if not in this
-     * configuration, the {@linkplain #parent parent} configurations.
+     * configuration, the {@linkplain #parents parent} configurations.
+     * Finding a module in parent configurations is equivalent to invoking
+     * {@code findModule} on each parent, in search order, until the module
+     * is found or all parents have been searched. In a <em>tree of
+     * configurations</em> then this is equivalent to a depth-first search.
      *
      * @param  name
      *         The module name of the resolved module to find
      *
      * @return The resolved module with the given name or an empty {@code
      *         Optional} if there isn't a module with this name in this
-     *         configuration or any parent configuration
+     *         configuration or any parent configurations
      */
     public Optional<ResolvedModule> findModule(String name) {
         Objects.requireNonNull(name);
-        if (parent == null)
-            return Optional.empty();
         ResolvedModule m = nameToModule.get(name);
         if (m != null)
             return Optional.of(m);
-        return parent().flatMap(x -> x.findModule(name));
+
+        if (!parents.isEmpty()) {
+            return configurations()
+                    .skip(1)  // skip this configuration
+                    .map(cf -> cf.nameToModule)
+                    .filter(map -> map.containsKey(name))
+                    .map(map -> map.get(name))
+                    .findFirst();
+        }
+
+        return Optional.empty();
     }
 
 
@@ -444,9 +562,46 @@
     }
 
     /**
+     * Returns an ordered stream of configurations. The first element is this
+     * configuration, the remaining elements are the parent configurations
+     * in DFS order.
+     *
+     * @implNote For now, the assumption is that the number of elements will
+     * be very low and so this method does not use a specialized spliterator.
+     */
+    Stream<Configuration> configurations() {
+        List<Configuration> allConfigurations = this.allConfigurations;
+        if (allConfigurations == null) {
+            allConfigurations = new ArrayList<>();
+            Set<Configuration> visited = new HashSet<>();
+            Deque<Configuration> stack = new ArrayDeque<>();
+            visited.add(this);
+            stack.push(this);
+            while (!stack.isEmpty()) {
+                Configuration layer = stack.pop();
+                allConfigurations.add(layer);
+
+                // push in reverse order
+                for (int i = layer.parents.size() - 1; i >= 0; i--) {
+                    Configuration parent = layer.parents.get(i);
+                    if (!visited.contains(parent)) {
+                        visited.add(parent);
+                        stack.push(parent);
+                    }
+                }
+            }
+            this.allConfigurations = Collections.unmodifiableList(allConfigurations);
+        }
+        return allConfigurations.stream();
+    }
+
+    private volatile List<Configuration> allConfigurations;
+
+
+    /**
      * Returns a string describing this configuration.
      *
-     * @return A string describing this configuration
+     * @return A possibly empty string describing this configuration
      */
     @Override
     public String toString() {
--- a/src/java.base/share/classes/java/lang/module/Dependence.java	Thu Dec 01 21:01:53 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/*
- * Copyright (c) 2012, 2013, 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 java.lang.module;
-
-import java.util.*;
-import java.util.stream.*;
-
-
-class Dependence {
-
-    private Dependence() { }
-
-    static <T> Stream<String> toStringStream(Set<T> s) {
-        return s.stream().map(e -> e.toString().toLowerCase());
-    }
-
-    static <M> String toString(Set<M> mods, String what) {
-        return (Stream.concat(toStringStream(mods), Stream.of(what)))
-                      .collect(Collectors.joining(" "));
-    }
-
-}
--- a/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java	Thu Dec 01 21:39:49 2016 +0000
@@ -45,6 +45,8 @@
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static jdk.internal.module.Checks.*;
 import static java.util.Objects.*;
@@ -57,9 +59,11 @@
  * A module descriptor.
  *
  * <p> A {@code ModuleDescriptor} is typically created from the binary form
- * of a module declaration. The associated {@link ModuleDescriptor.Builder}
- * class can also be used to create a {@code ModuleDescriptor} from its
- * components. </p>
+ * of a module declaration. Alternatively, the {@link ModuleDescriptor.Builder}
+ * class can be used to create a {@code ModuleDescriptor} from its components.
+ * The {@link #module module}, {@link #openModule openModule}, and {@link
+ * #automaticModule automaticModule} methods create builders for building
+ * different kinds of modules. </p>
  *
  * <p> {@code ModuleDescriptor} objects are immutable and safe for use by
  * multiple concurrent threads.</p>
@@ -95,7 +99,13 @@
              * module</i> to have an implicitly declared dependence on the module
              * named by the {@code Requires}.
              */
-            PUBLIC,
+            TRANSITIVE,
+
+            /**
+             * The dependence is mandatory in the static phase, during compilation,
+             * but is optional in the dynamic phase, during execution.
+             */
+            STATIC,
 
             /**
              * The dependence was not explicitly or implicitly declared in the
@@ -115,16 +125,18 @@
         private final String name;
 
         private Requires(Set<Modifier> ms, String mn) {
-            this(ms, mn, true);
+            if (ms.isEmpty()) {
+                ms = Collections.emptySet();
+            } else {
+                ms = Collections.unmodifiableSet(EnumSet.copyOf(ms));
+            }
+            this.mods = ms;
+            this.name = mn;
         }
-        private Requires(Set<Modifier> ms, String mn, boolean check) {
-            if (ms == null || ms.isEmpty()) {
-                mods = Collections.emptySet();
-            } else {
-                mods = check ? Collections.unmodifiableSet(EnumSet.copyOf(ms))
-                             : ms;
-            }
-            this.name = check ? requireModuleName(mn) : mn;
+
+        private Requires(Set<Modifier> ms, String mn, boolean unused) {
+            this.mods = ms;
+            this.name = mn;
         }
 
         /**
@@ -223,9 +235,8 @@
          */
         @Override
         public String toString() {
-            return Dependence.toString(mods, name);
+            return ModuleDescriptor.toString(mods, name);
         }
-
     }
 
 
@@ -239,36 +250,61 @@
 
     public final static class Exports {
 
+        /**
+         * A modifier on a module export.
+         *
+         * @since 9
+         */
+        public static enum Modifier {
+
+            /**
+             * The export was not explicitly or implicitly declared in the
+             * source of the module declaration.
+             */
+            SYNTHETIC,
+
+            /**
+             * The export was implicitly declared in the source of the module
+             * declaration.
+             */
+            MANDATED;
+
+        }
+
+        private final Set<Modifier> mods;
         private final String source;
         private final Set<String> targets;  // empty if unqualified export
 
         /**
-         * Constructs a qualified export.
+         * Constructs an export
          */
-        private Exports(String source, Set<String> targets) {
-            this(source, targets, true);
+        private Exports(Set<Modifier> ms, String source, Set<String> targets) {
+            if (ms.isEmpty()) {
+                ms = Collections.emptySet();
+            } else {
+                ms = Collections.unmodifiableSet(EnumSet.copyOf(ms));
+            }
+            this.mods = ms;
+            this.source = source;
+            this.targets = emptyOrUnmodifiableSet(targets);
         }
 
-        private Exports(String source, Set<String> targets, boolean check) {
-            this.source = check ? requirePackageName(source) : source;
-            targets = check ? Collections.unmodifiableSet(new HashSet<>(targets))
-                            : Collections.unmodifiableSet(targets);
-            if (targets.isEmpty())
-                throw new IllegalArgumentException("Empty target set");
-            if (check)
-                targets.stream().forEach(Checks::requireModuleName);
+        private Exports(Set<Modifier> ms,
+                        String source,
+                        Set<String> targets,
+                        boolean unused) {
+            this.mods = ms;
+            this.source = source;
             this.targets = targets;
         }
 
         /**
-         * Constructs an unqualified export.
+         * Returns the set of modifiers.
+         *
+         * @return A possibly-empty unmodifiable set of modifiers
          */
-        private Exports(String source) {
-            this(source, true);
-        }
-        private Exports(String source, boolean check) {
-            this.source = check ? requirePackageName(source) : source;
-            this.targets = Collections.emptySet();
+        public Set<Modifier> modifiers() {
+            return mods;
         }
 
         /**
@@ -304,25 +340,27 @@
         /**
          * Computes a hash code for this module export.
          *
-         * <p> The hash code is based upon the package name, and for a
-         * qualified export, the set of modules names to which the package
-         * is exported. It satisfies the general contract of the {@link
-         * Object#hashCode Object.hashCode} method.
+         * <p> The hash code is based upon the modifiers, the package name,
+         * and for a qualified export, the set of modules names to which the
+         * package is exported. It satisfies the general contract of the
+         * {@link Object#hashCode Object.hashCode} method.
          *
          * @return The hash-code value for this module export
          */
         @Override
         public int hashCode() {
-            return hash(source, targets);
+            int hash = mods.hashCode();
+            hash = hash * 43 + source.hashCode();
+            return hash * 43 + targets.hashCode();
         }
 
         /**
          * Tests this module export for equality with the given object.
          *
          * <p> If the given object is not an {@code Exports} then this method
-         * returns {@code false}. Two module exports objects are equal if the
-         * package names are equal and the set of target module names is equal.
-         * </p>
+         * returns {@code false}. Two module exports objects are equal if their
+         * set of modifiers is equal, the package names are equal and the set
+         * of target module names is equal. </p>
          *
          * <p> This method satisfies the general contract of the {@link
          * java.lang.Object#equals(Object) Object.equals} method. </p>
@@ -338,8 +376,9 @@
             if (!(ob instanceof Exports))
                 return false;
             Exports other = (Exports)ob;
-            return Objects.equals(this.source, other.source) &&
-                Objects.equals(this.targets, other.targets);
+            return Objects.equals(this.mods, other.mods)
+                    && Objects.equals(this.source, other.source)
+                    && Objects.equals(this.targets, other.targets);
         }
 
         /**
@@ -349,15 +388,177 @@
          */
         @Override
         public String toString() {
+            String s = ModuleDescriptor.toString(mods, source);
             if (targets.isEmpty())
-                return source;
+                return s;
             else
-                return source + " to " + targets;
+                return s + " to " + targets;
+        }
+    }
+
+
+    /**
+     * <p> Represents a module <em>opens</em> directive, may be qualified or
+     * unqualified. </p>
+     *
+     * <p> The <em>opens</em> directive in a module declaration declares a
+     * package to be open to allow all types in the package, and all their
+     * members, not just public types and their public members to be reflected
+     * on by APIs that support private access or a way to bypass or suppress
+     * default Java language access control checks. </p>
+     *
+     * @see ModuleDescriptor#opens()
+     * @since 9
+     */
+
+    public final static class Opens {
+
+        /**
+         * A modifier on a module <em>opens</em> directive.
+         *
+         * @since 9
+         */
+        public static enum Modifier {
+
+            /**
+             * The opens was not explicitly or implicitly declared in the
+             * source of the module declaration.
+             */
+            SYNTHETIC,
+
+            /**
+             * The opens was implicitly declared in the source of the module
+             * declaration.
+             */
+            MANDATED;
+
         }
 
+        private final Set<Modifier> mods;
+        private final String source;
+        private final Set<String> targets;  // empty if unqualified export
+
+        /**
+         * Constructs an Opens
+         */
+        private Opens(Set<Modifier> ms, String source, Set<String> targets) {
+            if (ms.isEmpty()) {
+                ms = Collections.emptySet();
+            } else {
+                ms = Collections.unmodifiableSet(EnumSet.copyOf(ms));
+            }
+            this.mods = ms;
+            this.source = source;
+            this.targets = emptyOrUnmodifiableSet(targets);
+        }
+
+        private Opens(Set<Modifier> ms,
+                      String source,
+                      Set<String> targets,
+                      boolean unused) {
+            this.mods = ms;
+            this.source = source;
+            this.targets = targets;
+        }
+
+        /**
+         * Returns the set of modifiers.
+         *
+         * @return A possibly-empty unmodifiable set of modifiers
+         */
+        public Set<Modifier> modifiers() {
+            return mods;
+        }
+
+        /**
+         * Returns {@code true} if this is a qualified opens.
+         *
+         * @return {@code true} if this is a qualified opens
+         */
+        public boolean isQualified() {
+            return !targets.isEmpty();
+        }
+
+        /**
+         * Returns the package name.
+         *
+         * @return The package name
+         */
+        public String source() {
+            return source;
+        }
+
+        /**
+         * For a qualified opens, returns the non-empty and immutable set
+         * of the module names to which the package is open. For an
+         * unqualified opens, returns an empty set.
+         *
+         * @return The set of target module names or for an unqualified
+         *         opens, an empty set
+         */
+        public Set<String> targets() {
+            return targets;
+        }
+
+        /**
+         * Computes a hash code for this module opens.
+         *
+         * <p> The hash code is based upon the modifiers, the package name,
+         * and for a qualified opens, the set of modules names to which the
+         * package is opened. It satisfies the general contract of the
+         * {@link Object#hashCode Object.hashCode} method.
+         *
+         * @return The hash-code value for this module opens
+         */
+        @Override
+        public int hashCode() {
+            int hash = mods.hashCode();
+            hash = hash * 43 + source.hashCode();
+            return hash * 43 + targets.hashCode();
+        }
+
+        /**
+         * Tests this module opens for equality with the given object.
+         *
+         * <p> If the given object is not an {@code Opens} then this method
+         * returns {@code false}. Two {@code Opens} objects are equal if their
+         * set of modifiers is equal, the package names are equal and the set
+         * of target module names is equal. </p>
+         *
+         * <p> This method satisfies the general contract of the {@link
+         * java.lang.Object#equals(Object) Object.equals} method. </p>
+         *
+         * @param   ob
+         *          the object to which this object is to be compared
+         *
+         * @return  {@code true} if, and only if, the given object is a module
+         *          dependence that is equal to this module dependence
+         */
+        @Override
+        public boolean equals(Object ob) {
+            if (!(ob instanceof Opens))
+                return false;
+            Opens other = (Opens)ob;
+            return Objects.equals(this.mods, other.mods)
+                    && Objects.equals(this.source, other.source)
+                    && Objects.equals(this.targets, other.targets);
+        }
+
+        /**
+         * Returns a string describing module opens.
+         *
+         * @return A string describing module opens
+         */
+        @Override
+        public String toString() {
+            String s = ModuleDescriptor.toString(mods, source);
+            if (targets.isEmpty())
+                return s;
+            else
+                return s + " to " + targets;
+        }
     }
 
-
 
     /**
      * <p> A service that a module provides one or more implementations of. </p>
@@ -369,21 +570,15 @@
     public final static class Provides {
 
         private final String service;
-        private final Set<String> providers;
+        private final List<String> providers;
 
-        private Provides(String service, Set<String> providers) {
-            this(service, providers, true);
+        private Provides(String service, List<String> providers) {
+            this.service = service;
+            this.providers = Collections.unmodifiableList(providers);
         }
 
-        private Provides(String service, Set<String> providers, boolean check) {
-            this.service = check ? requireServiceTypeName(service) : service;
-            providers = check
-                ? Collections.unmodifiableSet(new LinkedHashSet<>(providers))
-                : Collections.unmodifiableSet(providers);
-            if (providers.isEmpty())
-                throw new IllegalArgumentException("Empty providers set");
-            if (check)
-                providers.forEach(Checks::requireServiceProviderName);
+        private Provides(String service, List<String> providers, boolean unused) {
+            this.service = service;
             this.providers = providers;
         }
 
@@ -395,12 +590,13 @@
         public String service() { return service; }
 
         /**
-         * Returns the set of the fully qualified class names of the providers.
+         * Returns the list of the fully qualified class names of the providers
+         * or provider factories.
          *
-         * @return A non-empty and unmodifiable set of the fully qualified class
-         *         names of the providers.
+         * @return A non-empty and unmodifiable list of the fully qualified class
+         *         names of the providers or provider factories
          */
-        public Set<String> providers() { return providers; }
+        public List<String> providers() { return providers; }
 
         /**
          * Computes a hash code for this provides.
@@ -413,7 +609,7 @@
          */
         @Override
         public int hashCode() {
-            return hash(service, providers);
+            return service.hashCode() * 43 + providers.hashCode();
         }
 
         /**
@@ -421,7 +617,7 @@
          *
          * <p> If the given object is not a {@code Provides} then this method
          * returns {@code false}. Two {@code Provides} objects are equal if the
-         * service type is equal and the set of providers is equal. </p>
+         * service type is equal and the list of providers is equal. </p>
          *
          * <p> This method satisfies the general contract of the {@link
          * java.lang.Object#equals(Object) Object.equals} method. </p>
@@ -774,10 +970,7 @@
 
     // From module declarations
     private final String name;
-    private final Set<Requires> requires;
-    private final Set<Exports> exports;
-    private final Set<String> uses;
-    private final Map<String, Provides> provides;
+    private final boolean open;
 
     // Indicates if synthesised for a JAR file found on the module path
     private final boolean automatic;
@@ -785,6 +978,12 @@
     // Not generated from a module-info.java
     private final boolean synthetic;
 
+    private final Set<Requires> requires;
+    private final Set<Exports> exports;
+    private final Set<Opens> opens;
+    private final Set<String> uses;
+    private final Set<Provides> provides;
+
     // "Extended" information, added post-compilation by tools
     private final Version version;
     private final String mainClass;
@@ -796,12 +995,14 @@
 
 
     private ModuleDescriptor(String name,
+                             boolean open,
                              boolean automatic,
                              boolean synthetic,
-                             Map<String, Requires> requires,
+                             Set<Requires> requires,
+                             Set<Exports> exports,
+                             Set<Opens> opens,
                              Set<String> uses,
-                             Map<String, Exports> exports,
-                             Map<String, Provides> provides,
+                             Set<Provides> provides,
                              Version version,
                              String mainClass,
                              String osName,
@@ -812,31 +1013,24 @@
     {
 
         this.name = name;
+        this.open = open;
         this.automatic = automatic;
         this.synthetic = synthetic;
 
-        Set<Requires> rqs = new HashSet<>(requires.values());
-        assert (rqs.stream().map(Requires::name).sorted().distinct().count()
-                == rqs.size())
-            : "Module " + name + " has duplicate requires";
-        this.requires = emptyOrUnmodifiableSet(rqs);
+        assert (requires.stream().map(Requires::name).distinct().count()
+                == requires.size());
+        this.requires = emptyOrUnmodifiableSet(requires);
 
-        Set<Exports> exs = new HashSet<>(exports.values());
-        assert (exs.stream().map(Exports::source).sorted().distinct().count()
-                == exs.size())
-            : "Module " + name + " has duplicate exports";
-        this.exports = emptyOrUnmodifiableSet(exs);
-
+        this.exports = emptyOrUnmodifiableSet(exports);
+        this.opens = emptyOrUnmodifiableSet(opens);
         this.uses = emptyOrUnmodifiableSet(uses);
-        this.provides = emptyOrUnmodifiableMap(provides);
-
+        this.provides = emptyOrUnmodifiableSet(provides);
         this.version = version;
         this.mainClass = mainClass;
         this.osName = osName;
         this.osArch = osArch;
         this.osVersion = osVersion;
         this.hashes = hashes;
-
         this.packages = emptyOrUnmodifiableSet(packages);
     }
 
@@ -845,11 +1039,13 @@
      */
     ModuleDescriptor(ModuleDescriptor md, Set<String> pkgs) {
         this.name = md.name;
+        this.open = md.open;
         this.automatic = md.automatic;
         this.synthetic = md.synthetic;
 
         this.requires = md.requires;
         this.exports = md.exports;
+        this.opens = md.opens;
         this.uses = md.uses;
         this.provides = md.provides;
 
@@ -866,38 +1062,44 @@
     }
 
     /**
-     * Creates a module descriptor from its components. This method is intended
-     * for use by the jlink plugin.
+     * Creates a module descriptor from its components.
+     * The arguments are pre-validated and sets are unmodifiable sets.
      */
     ModuleDescriptor(String name,
+                     boolean open,
                      boolean automatic,
                      boolean synthetic,
                      Set<Requires> requires,
+                     Set<Exports> exports,
+                     Set<Opens> opens,
                      Set<String> uses,
-                     Set<Exports> exports,
-                     Map<String, Provides> provides,
+                     Set<Provides> provides,
                      Version version,
                      String mainClass,
                      String osName,
                      String osArch,
                      String osVersion,
                      Set<String> packages,
-                     ModuleHashes hashes) {
+                     ModuleHashes hashes,
+                     int hashCode,
+                     boolean unused) {
         this.name = name;
+        this.open = open;
         this.automatic = automatic;
         this.synthetic = synthetic;
-        this.requires = Collections.unmodifiableSet(requires);
-        this.exports = Collections.unmodifiableSet(exports);
-        this.uses = Collections.unmodifiableSet(uses);
-        this.provides = Collections.unmodifiableMap(provides);
-        this.packages = Collections.unmodifiableSet(packages);
-
+        this.requires = requires;
+        this.exports = exports;
+        this.opens = opens;
+        this.uses = uses;
+        this.provides = provides;
+        this.packages = packages;
         this.version = version;
         this.mainClass = mainClass;
         this.osName = osName;
         this.osArch = osArch;
         this.osVersion = osVersion;
         this.hashes = hashes;
+        this.hash = hashCode;
     }
 
     /**
@@ -910,6 +1112,19 @@
     }
 
     /**
+     * <p> Returns {@code true} if this is an open module. </p>
+     *
+     * <p> An open module does not declare any open packages (the {@link #opens()
+     * opens} method returns an empty set) but the resulting module is treated
+     * as if all packages are open. </p>
+     *
+     * @return  {@code true} if this is an open module
+     */
+    public boolean isOpen() {
+        return open;
+    }
+
+    /**
      * <p> Returns {@code true} if this is an automatic module. </p>
      *
      * <p> An automatic module is defined implicitly rather than explicitly
@@ -933,8 +1148,6 @@
      *
      * @return  {@code true} if this module descriptor was not generated by
      *          an explicit or implicit module declaration
-     *
-     * @jvms 4.7.8 The {@code Synthetic} Attribute
      */
     public boolean isSynthetic() {
         return synthetic;
@@ -950,6 +1163,33 @@
     }
 
     /**
+     * <p> The module exports. </p>
+     *
+     * @return  A possibly-empty unmodifiable set of exported packages
+     */
+    public Set<Exports> exports() {
+        return exports;
+    }
+
+    /**
+     * <p> The module <em>opens</em> directives. </p>
+     *
+     * <p> Each {@code Opens} object in the set represents a package (and
+     * the set of target module names when qualified) where all types in the
+     * package, and all their members, not just public types and their public
+     * members, can be reflected on when using APIs that bypass or suppress
+     * default Java language access control checks. </p>
+     *
+     * <p> This method returns an empty set when invoked on {@link #isOpen()
+     * open} module. </p>
+     *
+     * @return  A possibly-empty unmodifiable set of open packages
+     */
+    public Set<Opens> opens() {
+        return opens;
+    }
+
+    /**
      * <p> The service dependences of this module. </p>
      *
      * @return  A possibly-empty unmodifiable set of the fully qualified class
@@ -962,24 +1202,14 @@
     /**
      * <p> The services that this module provides. </p>
      *
-     * @return The possibly-empty unmodifiable map of the services that this
-     *         module provides. The map key is fully qualified class name of
-     *         the service type.
+     * @return The possibly-empty unmodifiable set of the services that this
+     *         module provides
      */
-    public Map<String, Provides> provides() {
+    public Set<Provides> provides() {
         return provides;
     }
 
     /**
-     * <p> The module exports. </p>
-     *
-     * @return  A possibly-empty unmodifiable set of exported packages
-     */
-    public Set<Exports> exports() {
-        return exports;
-    }
-
-    /**
      * Returns this module's version.
      *
      * @return This module's version
@@ -1046,22 +1276,9 @@
     }
 
     /**
-     * Returns the names of the packages defined in, but not exported by, this
-     * module.
+     * Returns the names of all packages in this module.
      *
-     * @return A possibly-empty unmodifiable set of the concealed packages
-     */
-    public Set<String> conceals() {
-        Set<String> conceals = new HashSet<>(packages);
-        exports.stream().map(Exports::source).forEach(conceals::remove);
-        return emptyOrUnmodifiableSet(conceals);
-    }
-
-    /**
-     * Returns the names of all the packages defined in this module, whether
-     * exported or concealed.
-     *
-     * @return A possibly-empty unmodifiable set of the all packages
+     * @return A possibly-empty unmodifiable set of all packages in the module
      */
     public Set<String> packages() {
         return packages;
@@ -1078,31 +1295,39 @@
     /**
      * A builder used for building {@link ModuleDescriptor} objects.
      *
+     * <p> {@code ModuleDescriptor} defines the {@link #module module}, {@link
+     * #openModule openModule}, and {@link #automaticModule automaticModule}
+     * methods to create builders for building different kinds of modules. </p>
+     *
      * <p> Example usage: </p>
-     *
-     * <pre>{@code
-     *     ModuleDescriptor descriptor = new ModuleDescriptor.Builder("m1")
+     * <pre>{@code    ModuleDescriptor descriptor = ModuleDescriptor.module("m1")
+     *         .exports("p")
      *         .requires("m2")
-     *         .exports("p")
      *         .build();
      * }</pre>
      *
-     * @apiNote A {@code Builder} cannot be used to create an {@link
-     * ModuleDescriptor#isAutomatic() automatic} or a {@link
-     * ModuleDescriptor#isSynthetic() synthetic} module.
+     * @apiNote A {@code Builder} checks the components and invariants as
+     * components are added to the builder. The rational for this is to detect
+     * errors as early as possible and not defer all validation to the
+     * {@link #build build} method. A {@code Builder} cannot be used to create
+     * a {@link ModuleDescriptor#isSynthetic() synthetic} module.
      *
      * @since 9
      */
     public static final class Builder {
-
         final String name;
+        final boolean strict; // true if module names are checked
+        boolean open;
         boolean automatic;
         boolean synthetic;
         final Map<String, Requires> requires = new HashMap<>();
+
+        final Map<String, Exports> exports = new HashMap<>();
+        final Map<String, Opens> opens = new HashMap<>();
+        final Set<String> concealedPackages = new HashSet<>();
+
         final Set<String> uses = new HashSet<>();
-        final Map<String, Exports> exports = new HashMap<>();
         final Map<String, Provides> provides = new HashMap<>();
-        Set<String> conceals = Collections.emptySet();
         Version version;
         String osName;
         String osArch;
@@ -1113,29 +1338,30 @@
         /**
          * Initializes a new builder with the given module name.
          *
-         * @param  name
-         *         The module name
-         *
-         * @throws IllegalArgumentException
-         *         If the module name is {@code null} or is not a legal Java
-         *         identifier
+         * @param strict
+         *        Indicates whether module names are checked or not
          */
-        public Builder(String name) {
-            this.name = requireModuleName(name);
+        Builder(String name, boolean strict) {
+            this.strict = strict;
+            this.name = (strict) ? requireModuleName(name) : name;
         }
 
-        /**
-         * Updates the builder so that it builds an automatic module.
-         *
-         * @return This builder
-         *
-         * @see ModuleDescriptor#isAutomatic()
-         */
-        /* package */ Builder automatic() {
-            this.automatic = true;
+        /* package */ Builder open(boolean open) {
+            this.open = open;
             return this;
         }
 
+        /* package */ Builder automatic(boolean automatic) {
+            this.automatic = automatic;
+            return this;
+        }
+
+        /* package */ boolean isOpen() { return open; }
+
+        /* package */ boolean isAutomatic() {
+            return automatic;
+        }
+
         /**
          * Adds a dependence on a module.
          *
@@ -1165,7 +1391,7 @@
          * Adds a dependence on a module with the given (and possibly empty)
          * set of modifiers.
          *
-         * @param  mods
+         * @param  ms
          *         The set of modifiers
          * @param  mn
          *         The module name
@@ -1179,14 +1405,10 @@
          * @throws IllegalStateException
          *         If the dependence on the module has already been declared
          */
-        public Builder requires(Set<Requires.Modifier> mods, String mn) {
-            if (name.equals(mn))
-                throw new IllegalArgumentException("Dependence on self");
-            if (requires.containsKey(mn))
-                throw new IllegalStateException("Dependence upon " + mn
-                                                + " already declared");
-            requires.put(mn, new Requires(mods, mn)); // checks mn
-            return this;
+        public Builder requires(Set<Requires.Modifier> ms, String mn) {
+            if (strict)
+                mn = requireModuleName(mn);
+            return requires(new Requires(ms, mn));
         }
 
         /**
@@ -1209,62 +1431,6 @@
         }
 
         /**
-         * Adds a dependence on a module with the given modifier.
-         *
-         * @param  mod
-         *         The modifier
-         * @param  mn
-         *         The module name
-         *
-         * @return This builder
-         *
-         * @throws IllegalArgumentException
-         *         If the module name is {@code null}, is not a legal Java
-         *         identifier, or is equal to the module name that this builder
-         *         was initialized to build
-         * @throws IllegalStateException
-         *         If the dependence on the module has already been declared
-         */
-        public Builder requires(Requires.Modifier mod, String mn) {
-            return requires(EnumSet.of(mod), mn);
-        }
-
-        /**
-         * Adds a service dependence.
-         *
-         * @param  st
-         *         The service type
-         *
-         * @return This builder
-         *
-         * @throws IllegalArgumentException
-         *         If the service type is {@code null} or is not a legal Java
-         *         identifier
-         * @throws IllegalStateException
-         *         If a dependency on the service type has already been declared
-         */
-        public Builder uses(String st) {
-            if (uses.contains(requireServiceTypeName(st)))
-                throw new IllegalStateException("Dependence upon service "
-                                                + st + " already declared");
-            uses.add(st);
-            return this;
-        }
-
-        /**
-         * Ensures that the given package name has not been declared as an
-         * exported or concealed package.
-         */
-        private void ensureNotExportedOrConcealed(String pn) {
-            if (exports.containsKey(pn))
-                throw new IllegalStateException("Export of package "
-                                                + pn + " already declared");
-            if (conceals.contains(pn))
-                throw new IllegalStateException("Concealed package "
-                                                + pn + " already declared");
-        }
-
-        /**
          * Adds an export.
          *
          * @param  e
@@ -1273,18 +1439,90 @@
          * @return This builder
          *
          * @throws IllegalStateException
-         *         If the package is already declared as an exported or
-         *         concealed package
+         *         If the package is already declared as a package with the
+         *         {@link #contains contains} method or the package is already
+         *         declared as exported
          */
         public Builder exports(Exports e) {
-            String pn = e.source();
-            ensureNotExportedOrConcealed(pn);
-            exports.put(pn, e);
+            // can't be exported and concealed
+            String source = e.source();
+            if (concealedPackages.contains(source)) {
+                throw new IllegalStateException("Package " + source
+                                                 + " already declared");
+            }
+            if (exports.containsKey(source)) {
+                throw new IllegalStateException("Exported package " + source
+                                                 + " already declared");
+            }
+
+            exports.put(source, e);
             return this;
         }
 
         /**
-         * Adds an export to a set of target modules.
+         * Adds an export, with the given (and possibly empty) set of modifiers,
+         * to export a package to a set of target modules.
+         *
+         * @param  ms
+         *         The set of modifiers
+         * @param  pn
+         *         The package name
+         * @param  targets
+         *         The set of target modules names
+         *
+         * @return This builder
+         *
+         * @throws IllegalArgumentException
+         *         If the package name or any of the target modules is {@code
+         *         null} or is not a legal Java identifier, or the set of
+         *         targets is empty
+         * @throws IllegalStateException
+         *         If the package is already declared as a package with the
+         *         {@link #contains contains} method or the package is already
+         *         declared as exported
+         */
+        public Builder exports(Set<Exports.Modifier> ms,
+                               String pn,
+                               Set<String> targets)
+        {
+            Exports e = new Exports(ms, requirePackageName(pn), targets);
+
+            // check targets
+            targets = e.targets();
+            if (targets.isEmpty())
+                throw new IllegalArgumentException("Empty target set");
+            if (strict)
+                targets.stream().forEach(Checks::requireModuleName);
+
+            return exports(e);
+        }
+
+        /**
+         * Adds an unqualified export with the given (and possibly empty) set
+         * of modifiers.
+         *
+         * @param  ms
+         *         The set of modifiers
+         * @param  pn
+         *         The package name
+         *
+         * @return This builder
+         *
+         * @throws IllegalArgumentException
+         *         If the package name is {@code null} or is not a legal Java
+         *         identifier
+         * @throws IllegalStateException
+         *         If the package is already declared as a package with the
+         *         {@link #contains contains} method or the package is already
+         *         declared as exported
+         */
+        public Builder exports(Set<Exports.Modifier> ms, String pn) {
+            Exports e = new Exports(ms, requirePackageName(pn), Collections.emptySet());
+            return exports(e);
+        }
+
+        /**
+         * Adds an export to export a package to a set of target modules.
          *
          * @param  pn
          *         The package name
@@ -1298,38 +1536,16 @@
          *         null} or is not a legal Java identifier, or the set of
          *         targets is empty
          * @throws IllegalStateException
-         *         If the package is already declared as an exported or
-         *         concealed package
+         *         If the package is already declared as a package with the
+         *         {@link #contains contains} method or the package is already
+         *         declared as exported
          */
         public Builder exports(String pn, Set<String> targets) {
-            ensureNotExportedOrConcealed(pn);
-            exports.put(pn, new Exports(pn, targets)); // checks pn and targets
-            return this;
+            return exports(Collections.emptySet(), pn, targets);
         }
 
         /**
-         * Adds an export to a target module.
-         *
-         * @param  pn
-         *         The package name
-         * @param  target
-         *         The target module name
-         *
-         * @return This builder
-         *
-         * @throws IllegalArgumentException
-         *         If the package name or target module is {@code null} or is
-         *         not a legal Java identifier
-         * @throws IllegalStateException
-         *         If the package is already declared as an exported or
-         *         concealed package
-         */
-        public Builder exports(String pn, String target) {
-            return exports(pn, Collections.singleton(target));
-        }
-
-        /**
-         * Adds an export.
+         * Adds an unqualified export.
          *
          * @param  pn
          *         The package name
@@ -1340,18 +1556,186 @@
          *         If the package name is {@code null} or is not a legal Java
          *         identifier
          * @throws IllegalStateException
-         *         If the package is already declared as an exported or
-         *         concealed package
+         *         If the package is already declared as a package with the
+         *         {@link #contains contains} method or the package is already
+         *         declared as exported
          */
         public Builder exports(String pn) {
-            ensureNotExportedOrConcealed(pn);
-            exports.put(pn, new Exports(pn)); // checks pn
+            return exports(Collections.emptySet(), pn);
+        }
+
+        /**
+         * Adds an <em>opens</em> directive.
+         *
+         * @param  obj
+         *         The {@code Opens} object
+         *
+         * @return This builder
+         *
+         * @throws IllegalStateException
+         *         If the package is already declared as a package with the
+         *         {@link #contains contains} method, the package is already
+         *         declared as open, or this is a builder for an open module
+         */
+        public Builder opens(Opens obj) {
+            if (open) {
+                throw new IllegalStateException("open modules cannot declare"
+                                                + " open packages");
+            }
+
+            // can't be open and concealed
+            String source = obj.source();
+            if (concealedPackages.contains(source)) {
+                throw new IllegalStateException("Package " + source
+                                                + " already declared");
+            }
+            if (opens.containsKey(source)) {
+                throw new IllegalStateException("Open package " + source
+                                                + " already declared");
+            }
+
+            opens.put(source, obj);
             return this;
         }
 
+
+        /**
+         * Adds an <em>opens</em> directive, with the given (and possibly empty)
+         * set of modifiers, to open a package to a set of target modules.
+         *
+         * @param  ms
+         *         The set of modifiers
+         * @param  pn
+         *         The package name
+         * @param  targets
+         *         The set of target modules names
+         *
+         * @return This builder
+         *
+         * @throws IllegalArgumentException
+         *         If the package name or any of the target modules is {@code
+         *         null} or is not a legal Java identifier, or the set of
+         *         targets is empty
+         * @throws IllegalStateException
+         *         If the package is already declared as a package with the
+         *         {@link #contains contains} method, the package is already
+         *         declared as open, or this is a builder for an open module
+         */
+        public Builder opens(Set<Opens.Modifier> ms,
+                             String pn,
+                             Set<String> targets)
+        {
+            Opens e = new Opens(ms, requirePackageName(pn), targets);
+
+            // check targets
+            targets = e.targets();
+            if (targets.isEmpty())
+                throw new IllegalArgumentException("Empty target set");
+            if (strict)
+                targets.stream().forEach(Checks::requireModuleName);
+
+            return opens(e);
+        }
+
+        /**
+         * Adds an <em>opens</em> directive to open a package with the given (and
+         * possibly empty) set of modifiers.
+         *
+         * @param  ms
+         *         The set of modifiers
+         * @param  pn
+         *         The package name
+         *
+         * @return This builder
+         *
+         * @throws IllegalArgumentException
+         *         If the package name is {@code null} or is not a legal Java
+         *         identifier
+         * @throws IllegalStateException
+         *         If the package is already declared as a package with the
+         *         {@link #contains contains} method, the package is already
+         *         declared as open, or this is a builder for an open module
+         */
+        public Builder opens(Set<Opens.Modifier> ms, String pn) {
+            Opens e = new Opens(ms, requirePackageName(pn), Collections.emptySet());
+            return opens(e);
+        }
+
+        /**
+         * Adds an <em>opens</em> directive to open a package to a set of target
+         * modules.
+         *
+         * @param  pn
+         *         The package name
+         * @param  targets
+         *         The set of target modules names
+         *
+         * @return This builder
+         *
+         * @throws IllegalArgumentException
+         *         If the package name or any of the target modules is {@code
+         *         null} or is not a legal Java identifier, or the set of
+         *         targets is empty
+         * @throws IllegalStateException
+         *         If the package is already declared as a package with the
+         *         {@link #contains contains} method, the package is already
+         *         declared as open, or this is a builder for an open module
+         */
+        public Builder opens(String pn, Set<String> targets) {
+            return opens(Collections.emptySet(), pn, targets);
+        }
+
+        /**
+         * Adds an <em>opens</em> directive to open a package.
+         *
+         * @param  pn
+         *         The package name
+         *
+         * @return This builder
+         *
+         * @throws IllegalArgumentException
+         *         If the package name is {@code null} or is not a legal Java
+         *         identifier
+         * @throws IllegalStateException
+         *         If the package is already declared as a package with the
+         *         {@link #contains contains} method, the package is already
+         *         declared as open, or this is a builder for an open module
+         */
+        public Builder opens(String pn) {
+            return opens(Collections.emptySet(), pn);
+        }
+
+
         // Used by ModuleInfo, after a packageFinder is invoked
-        /* package */ Set<String> exportedPackages() {
-            return exports.keySet();
+        /* package */ Set<String> exportedAndOpenPackages() {
+            if (opens.isEmpty())
+                return exports.keySet();
+            Set<String> result = new HashSet<>();
+            result.addAll(exports.keySet());
+            result.addAll(opens.keySet());
+            return result;
+        }
+
+        /**
+         * Adds a service dependence.
+         *
+         * @param  service
+         *         The service type
+         *
+         * @return This builder
+         *
+         * @throws IllegalArgumentException
+         *         If the service type is {@code null} or is not a legal Java
+         *         identifier
+         * @throws IllegalStateException
+         *         If a dependency on the service type has already been declared
+         */
+        public Builder uses(String service) {
+            if (uses.contains(requireServiceTypeName(service)))
+                throw new IllegalStateException("Dependence upon service "
+                                                + service + " already declared");
+            uses.add(service);
+            return this;
         }
 
         /**
@@ -1376,38 +1760,47 @@
         }
 
         /**
-         * Provides service {@code st} with implementations {@code pcs}.
+         * Provides implementations of a service.
          *
-         * @param  st
+         * @param  service
          *         The service type
-         * @param  pcs
-         *         The set of provider class names
+         * @param  providers
+         *         The list of provider or provider factory class names
          *
          * @return This builder
          *
          * @throws IllegalArgumentException
          *         If the service type or any of the provider class names is
-         *         {@code null} or is not a legal Java identifier, or the set
+         *         {@code null} or is not a legal Java identifier, or the list
          *         of provider class names is empty
          * @throws IllegalStateException
          *         If the providers for the service type have already been
          *         declared
          */
-        public Builder provides(String st, Set<String> pcs) {
-            if (provides.containsKey(st))
+        public Builder provides(String service, List<String> providers) {
+            if (provides.containsKey(service))
                 throw new IllegalStateException("Providers of service "
-                                                + st + " already declared");
-            provides.put(st, new Provides(st, pcs)); // checks st and pcs
+                                                + service + " already declared by " + name);
+
+            Provides p = new Provides(requireServiceTypeName(service), providers);
+
+            // check providers after the set has been copied.
+            List<String> providerNames = p.providers();
+            if (providerNames.isEmpty())
+                throw new IllegalArgumentException("Empty providers set");
+            providerNames.forEach(Checks::requireServiceProviderName);
+
+            provides.put(service, p);
             return this;
         }
 
         /**
-         * Provides service {@code st} with implementation {@code pc}.
+         * Provides an implementation of a service.
          *
-         * @param  st
+         * @param  service
          *         The service type
-         * @param  pc
-         *         The provider class name
+         * @param  provider
+         *         The provider or provider factory class name
          *
          * @return This builder
          *
@@ -1418,15 +1811,17 @@
          *         If the providers for the service type have already been
          *         declared
          */
-        public Builder provides(String st, String pc) {
-            return provides(st, Collections.singleton(pc));
+        public Builder provides(String service, String provider) {
+            if (provider == null)
+                throw new IllegalArgumentException("'provider' is null");
+            return provides(service, List.of(provider));
         }
 
         /**
-         * Adds a set of (possible empty) concealed packages.
+         * Adds a (possible empty) set of packages to the module
          *
          * @param  pns
-         *         The set of package names of the concealed packages
+         *         The set of package names
          *
          * @return This builder
          *
@@ -1434,16 +1829,17 @@
          *         If any of the package names is {@code null} or is not a
          *         legal Java identifier
          * @throws IllegalStateException
-         *         If any of packages are already declared as a concealed or
-         *         exported package
+         *         If any of packages are already declared as packages in
+         *         the module. This includes packages that are already
+         *         declared as exported or open packages.
          */
-        public Builder conceals(Set<String> pns) {
-            pns.forEach(this::conceals);
+        public Builder contains(Set<String> pns) {
+            pns.forEach(this::contains);
             return this;
         }
 
         /**
-         * Adds a concealed package.
+         * Adds a package to the module.
          *
          * @param  pn
          *         The package name
@@ -1454,15 +1850,25 @@
          *         If the package name is {@code null}, or is not a legal Java
          *         identifier
          * @throws IllegalStateException
-         *         If the package is already declared as a concealed or exported
-         *         package
+         *         If the package is already declared as a package in the
+         *         module. This includes the package already declared as an
+         *         exported or open package.
          */
-        public Builder conceals(String pn) {
+        public Builder contains(String pn) {
             Checks.requirePackageName(pn);
-            ensureNotExportedOrConcealed(pn);
-            if (conceals.isEmpty())
-                conceals = new HashSet<>();
-            conceals.add(pn);
+            if (concealedPackages.contains(pn)) {
+                throw new IllegalStateException("Package " + pn
+                                                + " already declared");
+            }
+            if (exports.containsKey(pn)) {
+                throw new IllegalStateException("Exported package "
+                                                + pn + " already declared");
+            }
+            if (opens.containsKey(pn)) {
+                throw new IllegalStateException("Open package "
+                                                 + pn + " already declared");
+            }
+            concealedPackages.add(pn);
             return this;
         }
 
@@ -1473,13 +1879,8 @@
          *         The version
          *
          * @return This builder
-         *
-         * @throws IllegalStateException
-         *         If the module version is already set
          */
         public Builder version(Version v) {
-            if (version != null)
-                throw new IllegalStateException("module version already set");
             version = requireNonNull(v);
             return this;
         }
@@ -1494,16 +1895,11 @@
          *
          * @throws IllegalArgumentException
          *         If {@code v} is null or cannot be parsed as a version string
-         * @throws IllegalStateException
-         *         If the module version is already set
          *
          * @see Version#parse(String)
          */
         public Builder version(String v) {
-            if (version != null)
-                throw new IllegalStateException("module version already set");
-            version = Version.parse(v);
-            return this;
+            return version(Version.parse(v));
         }
 
         /**
@@ -1516,12 +1912,8 @@
          *
          * @throws IllegalArgumentException
          *         If {@code mainClass} is null or is not a legal Java identifier
-         * @throws IllegalStateException
-         *         If the module main class is already set
          */
         public Builder mainClass(String mc) {
-            if (mainClass != null)
-                throw new IllegalStateException("main class already set");
             mainClass = requireJavaIdentifier("main class name", mc);
             return this;
         }
@@ -1536,12 +1928,8 @@
          *
          * @throws IllegalArgumentException
          *         If {@code name} is null or the empty String
-         * @throws IllegalStateException
-         *         If the operating system name is already set
          */
         public Builder osName(String name) {
-            if (osName != null)
-                throw new IllegalStateException("OS name already set");
             if (name == null || name.isEmpty())
                 throw new IllegalArgumentException("OS name is null or empty");
             osName = name;
@@ -1558,12 +1946,8 @@
          *
          * @throws IllegalArgumentException
          *         If {@code name} is null or the empty String
-         * @throws IllegalStateException
-         *         If the operating system architecture is already set
          */
         public Builder osArch(String arch) {
-            if (osArch != null)
-                throw new IllegalStateException("OS arch already set");
             if (arch == null || arch.isEmpty())
                 throw new IllegalArgumentException("OS arch is null or empty");
             osArch = arch;
@@ -1580,12 +1964,8 @@
          *
          * @throws IllegalArgumentException
          *         If {@code name} is null or the empty String
-         * @throws IllegalStateException
-         *         If the operating system version is already set
          */
         public Builder osVersion(String version) {
-            if (osVersion != null)
-                throw new IllegalStateException("OS version already set");
             if (version == null || version.isEmpty())
                 throw new IllegalArgumentException("OS version is null or empty");
             osVersion = version;
@@ -1597,7 +1977,6 @@
             return this;
         }
 
-
         /* package */ Builder synthetic(boolean v) {
             this.synthetic = v;
             return this;
@@ -1609,16 +1988,24 @@
          * @return The module descriptor
          */
         public ModuleDescriptor build() {
-            assert name != null;
+            Set<Requires> requires = new HashSet<>(this.requires.values());
 
-            Set<String> packages = new HashSet<>(conceals);
-            packages.addAll(exportedPackages());
+            Set<String> packages = new HashSet<>(exportedAndOpenPackages());
+            packages.addAll(concealedPackages);
+
+            Set<Exports> exports = new HashSet<>(this.exports.values());
+            Set<Opens> opens = new HashSet<>(this.opens.values());
+
+            Set<Provides> provides = new HashSet<>(this.provides.values());
+
             return new ModuleDescriptor(name,
+                                        open,
                                         automatic,
                                         synthetic,
                                         requires,
+                                        exports,
+                                        opens,
                                         uses,
-                                        exports,
                                         provides,
                                         version,
                                         mainClass,
@@ -1631,7 +2018,6 @@
 
     }
 
-
     /**
      * Compares this module descriptor to another.
      *
@@ -1689,11 +2075,13 @@
             return false;
         ModuleDescriptor that = (ModuleDescriptor)ob;
         return (name.equals(that.name)
+                && open == that.open
                 && automatic == that.automatic
                 && synthetic == that.synthetic
                 && requires.equals(that.requires)
+                && exports.equals(that.exports)
+                && opens.equals(that.opens)
                 && uses.equals(that.uses)
-                && exports.equals(that.exports)
                 && provides.equals(that.provides)
                 && Objects.equals(version, that.version)
                 && Objects.equals(mainClass, that.mainClass)
@@ -1720,11 +2108,13 @@
         int hc = hash;
         if (hc == 0) {
             hc = name.hashCode();
+            hc = hc * 43 + Boolean.hashCode(open);
             hc = hc * 43 + Boolean.hashCode(automatic);
             hc = hc * 43 + Boolean.hashCode(synthetic);
             hc = hc * 43 + requires.hashCode();
+            hc = hc * 43 + exports.hashCode();
+            hc = hc * 43 + opens.hashCode();
             hc = hc * 43 + uses.hashCode();
-            hc = hc * 43 + exports.hashCode();
             hc = hc * 43 + provides.hashCode();
             hc = hc * 43 + Objects.hashCode(version);
             hc = hc * 43 + Objects.hashCode(mainClass);
@@ -1748,36 +2138,101 @@
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
-        sb.append("Module { name: ").append(toNameAndVersion());
+
+        if (isOpen())
+            sb.append("open ");
+        sb.append("module { name: ").append(toNameAndVersion());
         if (!requires.isEmpty())
             sb.append(", ").append(requires);
         if (!uses.isEmpty())
             sb.append(", ").append(uses);
         if (!exports.isEmpty())
             sb.append(", exports: ").append(exports);
+        if (!opens.isEmpty())
+            sb.append(", opens: ").append(opens);
         if (!provides.isEmpty()) {
-            sb.append(", provides: [");
-            for (Map.Entry<String, Provides> entry : provides.entrySet()) {
-                sb.append(entry.getKey())
-                   .append(" with ")
-                   .append(entry.getValue());
-            }
-            sb.append("]");
+            sb.append(", provides: ").append(provides);
         }
         sb.append(" }");
         return sb.toString();
     }
 
+
+    /**
+     * Instantiates a builder to build a module descriptor.
+     *
+     * @param  name
+     *         The module name
+     *
+     * @return A new builder
+     *
+     * @throws IllegalArgumentException
+     *         If the module name is {@code null} or is not a legal Java
+     *         identifier
+     */
+    public static Builder module(String name) {
+        return new Builder(name, true);
+    }
+
+    /**
+     * Instantiates a builder to build a module descriptor for an open module.
+     * An open module does not declare any open packages but the resulting
+     * module is treated as if all packages are open.
+     *
+     * <p> As an example, the following creates a module descriptor for an open
+     * name "{@code m}" containing two packages, one of which is exported. </p>
+     * <pre>{@code
+     *     ModuleDescriptor descriptor = ModuleDescriptor.openModule("m")
+     *         .requires("java.base")
+     *         .exports("p")
+     *         .contains("q")
+     *         .build();
+     * }</pre>
+     *
+     * @param  name
+     *         The module name
+     *
+     * @return A new builder that builds an open module
+     *
+     * @throws IllegalArgumentException
+     *         If the module name is {@code null} or is not a legal Java
+     *         identifier
+     */
+    public static Builder openModule(String name) {
+        return new Builder(name, true).open(true);
+    }
+
+    /**
+     * Instantiates a builder to build a module descriptor for an automatic
+     * module. Automatic modules receive special treatment during resolution
+     * (see {@link Configuration}) so that they read all other modules. When
+     * Instantiated in the Java virtual machine as a {@link java.lang.reflect.Module}
+     * then the Module reads every unnamed module in the Java virtual machine.
+     *
+     * @param  name
+     *         The module name
+     *
+     * @return A new builder that builds an automatic module
+     *
+     * @throws IllegalArgumentException
+     *         If the module name is {@code null} or is not a legal Java
+     *         identifier
+     *
+     * @see ModuleFinder#of(Path[])
+     */
+    public static Builder automaticModule(String name) {
+        return new Builder(name, true).automatic(true);
+    }
+
+
     /**
      * Reads the binary form of a module declaration from an input stream
      * as a module descriptor.
      *
      * <p> If the descriptor encoded in the input stream does not indicate a
-     * set of concealed packages then the {@code packageFinder} will be
-     * invoked.  The packages it returns, except for those indicated as
-     * exported in the encoded descriptor, will be considered to be concealed.
-     * If the {@code packageFinder} throws an {@link UncheckedIOException} then
-     * {@link IOException} cause will be re-thrown. </p>
+     * set of packages in the module then the {@code packageFinder} will be
+     * invoked. If the {@code packageFinder} throws an {@link UncheckedIOException}
+     * then {@link IOException} cause will be re-thrown. </p>
      *
      * <p> If there are bytes following the module descriptor then it is
      * implementation specific as to whether those bytes are read, ignored,
@@ -1789,12 +2244,12 @@
      *
      * @apiNote The {@code packageFinder} parameter is for use when reading
      * module descriptors from legacy module-artifact formats that do not
-     * record the set of concealed packages in the descriptor itself.
+     * record the set of packages in the descriptor itself.
      *
      * @param  in
      *         The input stream
      * @param  packageFinder
-     *         A supplier that can produce a set of package names
+     *         A supplier that can produce the set of packages
      *
      * @return The module descriptor
      *
@@ -1834,10 +2289,7 @@
      * as a module descriptor.
      *
      * <p> If the descriptor encoded in the byte buffer does not indicate a
-     * set of concealed packages then the {@code packageFinder} will be
-     * invoked.  The packages it returns, except for those indicated as
-     * exported in the encoded descriptor, will be considered to be
-     * concealed. </p>
+     * set of packages then the {@code packageFinder} will be invoked. </p>
      *
      * <p> The module descriptor is read from the buffer stating at index
      * {@code p}, where {@code p} is the buffer's {@link ByteBuffer#position()
@@ -1853,12 +2305,12 @@
      *
      * @apiNote The {@code packageFinder} parameter is for use when reading
      * module descriptors from legacy module-artifact formats that do not
-     * record the set of concealed packages in the descriptor itself.
+     * record the set of packages in the descriptor itself.
      *
      * @param  bb
      *         The byte buffer
      * @param  packageFinder
-     *         A supplier that can produce a set of package names
+     *         A supplier that can produce the set of packages
      *
      * @return The module descriptor
      *
@@ -1908,6 +2360,15 @@
         }
     }
 
+    /**
+     * Returns a string containing the given set of modifiers and label.
+     */
+    private static <M> String toString(Set<M> mods, String what) {
+        return (Stream.concat(mods.stream().map(e -> e.toString().toLowerCase()),
+                              Stream.of(what)))
+                .collect(Collectors.joining(" "));
+    }
+
     static {
         /**
          * Setup the shared secret to allow code in other packages access
@@ -1915,25 +2376,48 @@
          */
         jdk.internal.misc.SharedSecrets
             .setJavaLangModuleAccess(new jdk.internal.misc.JavaLangModuleAccess() {
+                @Override
+                public Builder newModuleBuilder(String mn, boolean strict) {
+                    return new Builder(mn, strict);
+                }
+
+                @Override
+                public Builder newOpenModuleBuilder(String mn, boolean strict) {
+                    return new Builder(mn, strict).open(true);
+                }
 
                 @Override
                 public Requires newRequires(Set<Requires.Modifier> ms, String mn) {
-                    return new Requires(ms, mn, false);
+                    return new Requires(ms, mn, true);
                 }
 
                 @Override
-                public Exports newExports(String source, Set<String> targets) {
-                    return new Exports(source, targets, false);
+                public Exports newExports(Set<Exports.Modifier> ms, String source) {
+                    return new Exports(ms, source, Collections.emptySet(), true);
                 }
 
                 @Override
-                public Exports newExports(String source) {
-                    return new Exports(source, false);
+                public Exports newExports(Set<Exports.Modifier> ms,
+                                          String source,
+                                          Set<String> targets) {
+                    return new Exports(ms, source, targets, true);
                 }
 
                 @Override
-                public Provides newProvides(String service, Set<String> providers) {
-                    return new Provides(service, providers, false);
+                public Opens newOpens(Set<Opens.Modifier> ms,
+                                      String source,
+                                      Set<String> targets) {
+                    return new Opens(ms, source, targets, true);
+                }
+
+                @Override
+                public Opens newOpens(Set<Opens.Modifier> ms, String source) {
+                    return new Opens(ms, source, Collections.emptySet(), true);
+                }
+
+                @Override
+                public Provides newProvides(String service, List<String> providers) {
+                    return new Provides(service, providers, true);
                 }
 
                 @Override
@@ -1949,25 +2433,30 @@
 
                 @Override
                 public ModuleDescriptor newModuleDescriptor(String name,
+                                                            boolean open,
                                                             boolean automatic,
                                                             boolean synthetic,
                                                             Set<Requires> requires,
+                                                            Set<Exports> exports,
+                                                            Set<Opens> opens,
                                                             Set<String> uses,
-                                                            Set<Exports> exports,
-                                                            Map<String, Provides> provides,
+                                                            Set<Provides> provides,
                                                             Version version,
                                                             String mainClass,
                                                             String osName,
                                                             String osArch,
                                                             String osVersion,
                                                             Set<String> packages,
-                                                            ModuleHashes hashes) {
+                                                            ModuleHashes hashes,
+                                                            int hashCode) {
                     return new ModuleDescriptor(name,
+                                                open,
                                                 automatic,
                                                 synthetic,
                                                 requires,
+                                                exports,
+                                                opens,
                                                 uses,
-                                                exports,
                                                 provides,
                                                 version,
                                                 mainClass,
@@ -1975,7 +2464,14 @@
                                                 osArch,
                                                 osVersion,
                                                 packages,
-                                                hashes);
+                                                hashes,
+                                                hashCode,
+                                                false);
+                }
+
+                @Override
+                public Optional<ModuleHashes> hashes(ModuleDescriptor descriptor) {
+                    return descriptor.hashes();
                 }
 
                 @Override
@@ -1995,11 +2491,6 @@
                 }
 
                 @Override
-                public Optional<ModuleHashes> hashes(ModuleDescriptor descriptor) {
-                    return descriptor.hashes();
-                }
-
-                @Override
                 public ModuleFinder newModulePath(Runtime.Version version,
                                                   boolean isLinkPhase,
                                                   Path... entries) {
--- a/src/java.base/share/classes/java/lang/module/ModuleFinder.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/module/ModuleFinder.java	Thu Dec 01 21:39:49 2016 +0000
@@ -233,10 +233,11 @@
      *         ModuleDescriptor.Version} and ignored if it cannot be parsed as
      *         a {@code Version}. </p></li>
      *
-     *         <li><p> For the module name, then all non-alphanumeric
-     *         characters ({@code [^A-Za-z0-9])} are replaced with a dot
-     *         ({@code "."}), all repeating dots are replaced with one dot,
-     *         and all leading and trailing dots are removed. </p></li>
+     *         <li><p> For the module name, then any trailing digits and dots
+     *         are removed, all non-alphanumeric characters ({@code [^A-Za-z0-9]})
+     *         are replaced with a dot ({@code "."}), all repeating dots are
+     *         replaced with one dot, and all leading and trailing dots are
+     *         removed. </p></li>
      *
      *         <li><p> As an example, a JAR file named {@code foo-bar.jar} will
      *         derive a module name {@code foo.bar} and no version. A JAR file
--- a/src/java.base/share/classes/java/lang/module/ModuleInfo.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/module/ModuleInfo.java	Thu Dec 01 21:39:49 2016 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 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
@@ -31,13 +31,17 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UncheckedIOException;
-import java.lang.module.ModuleDescriptor.Requires.Modifier;
+import java.lang.module.ModuleDescriptor.Builder;
+import java.lang.module.ModuleDescriptor.Requires;
+import java.lang.module.ModuleDescriptor.Exports;
+import java.lang.module.ModuleDescriptor.Opens;
 import java.nio.ByteBuffer;
 import java.nio.BufferUnderflowException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Supplier;
@@ -56,15 +60,12 @@
 
 final class ModuleInfo {
 
-    // supplies the set of packages when ConcealedPackages not present
+    // supplies the set of packages when ModulePackages attribute not present
     private final Supplier<Set<String>> packageFinder;
 
-    // indicates if the Hashes attribute should be parsed
+    // indicates if the ModuleHashes attribute should be parsed
     private final boolean parseHashes;
 
-    // the builder, created when parsing
-    private ModuleDescriptor.Builder builder;
-
     private ModuleInfo(Supplier<Set<String>> pf, boolean ph) {
         packageFinder = pf;
         parseHashes = ph;
@@ -86,9 +87,8 @@
     {
         try {
             return new ModuleInfo(pf).doRead(new DataInputStream(in));
-        } catch (IllegalArgumentException iae) {
-            // IllegalArgumentException means a malformed class
-            throw invalidModuleDescriptor(iae.getMessage());
+        } catch (IllegalArgumentException | IllegalStateException e) {
+            throw invalidModuleDescriptor(e.getMessage());
         } catch (EOFException x) {
             throw truncatedModuleDescriptor();
         }
@@ -105,9 +105,8 @@
     {
         try {
             return new ModuleInfo(pf).doRead(new DataInputWrapper(bb));
-        } catch (IllegalArgumentException iae) {
-            // IllegalArgumentException means a malformed class
-            throw invalidModuleDescriptor(iae.getMessage());
+        } catch (IllegalArgumentException | IllegalStateException e) {
+            throw invalidModuleDescriptor(e.getMessage());
         } catch (EOFException x) {
             throw truncatedModuleDescriptor();
         } catch (IOException ioe) {
@@ -117,7 +116,7 @@
 
     /**
      * Reads a {@code module-info.class} from the given byte buffer
-     * but ignore the {@code Hashes} attribute.
+     * but ignore the {@code ModuleHashes} attribute.
      *
      * @throws InvalidModuleDescriptorException
      * @throws UncheckedIOException
@@ -127,8 +126,8 @@
     {
         try {
             return new ModuleInfo(pf, false).doRead(new DataInputWrapper(bb));
-        } catch (IllegalArgumentException iae) {
-            throw invalidModuleDescriptor(iae.getMessage());
+        } catch (IllegalArgumentException | IllegalStateException e) {
+            throw invalidModuleDescriptor(e.getMessage());
         } catch (EOFException x) {
             throw truncatedModuleDescriptor();
         } catch (IOException ioe) {
@@ -164,12 +163,8 @@
             throw invalidModuleDescriptor("access_flags should be ACC_MODULE");
 
         int this_class = in.readUnsignedShort();
-        String mn = cpool.getClassName(this_class);
-        int suffix = mn.indexOf("/module-info");
-        if (suffix < 1)
-            throw invalidModuleDescriptor("this_class not of form name/module-info");
-        mn = mn.substring(0, suffix).replace('/', '.');
-        builder = new ModuleDescriptor.Builder(mn);
+        if (this_class != 0)
+            throw invalidModuleDescriptor("this_class must be 0");
 
         int super_class = in.readUnsignedShort();
         if (super_class > 0)
@@ -192,6 +187,13 @@
         // the names of the attributes found in the class file
         Set<String> attributes = new HashSet<>();
 
+        Builder builder = null;
+        Set<String> packages = null;
+        String version = null;
+        String mainClass = null;
+        String[] osValues = null;
+        ModuleHashes hashes = null;
+
         for (int i = 0; i < attributes_count ; i++) {
             int name_index = in.readUnsignedShort();
             String attribute_name = cpool.getUtf8(name_index);
@@ -206,28 +208,28 @@
             switch (attribute_name) {
 
                 case MODULE :
-                    readModuleAttribute(mn, in, cpool);
+                    builder = readModuleAttribute(in, cpool);
                     break;
 
-                case CONCEALED_PACKAGES :
-                    readConcealedPackagesAttribute(in, cpool);
+                case MODULE_PACKAGES :
+                    packages = readModulePackagesAttribute(in, cpool);
                     break;
 
-                case VERSION :
-                    readVersionAttribute(in, cpool);
+                case MODULE_VERSION :
+                    version = readModuleVersionAttribute(in, cpool);
                     break;
 
-                case MAIN_CLASS :
-                    readMainClassAttribute(in, cpool);
+                case MODULE_MAIN_CLASS :
+                    mainClass = readModuleMainClassAttribute(in, cpool);
                     break;
 
-                case TARGET_PLATFORM :
-                    readTargetPlatformAttribute(in, cpool);
+                case MODULE_TARGET :
+                    osValues = readModuleTargetAttribute(in, cpool);
                     break;
 
-                case HASHES :
+                case MODULE_HASHES :
                     if (parseHashes) {
-                        readHashesAttribute(in, cpool);
+                        hashes = readModuleHashesAttribute(in, cpool);
                     } else {
                         in.skipBytes(length);
                     }
@@ -245,53 +247,91 @@
         }
 
         // the Module attribute is required
-        if (!attributes.contains(MODULE)) {
+        if (builder == null) {
             throw invalidModuleDescriptor(MODULE + " attribute not found");
         }
 
-        // If the ConcealedPackages attribute is not present then the
-        // packageFinder is used to to find any non-exported packages.
-        if (!attributes.contains(CONCEALED_PACKAGES) && packageFinder != null) {
-            Set<String> pkgs;
+        // If the ModulePackages attribute is not present then the packageFinder
+        // is used to find the set of packages
+        boolean usedPackageFinder = false;
+        if (packages == null && packageFinder != null) {
             try {
-                pkgs = new HashSet<>(packageFinder.get());
+                packages = new HashSet<>(packageFinder.get());
             } catch (UncheckedIOException x) {
                 throw x.getCause();
             }
-            pkgs.removeAll(builder.exportedPackages());
-            builder.conceals(pkgs);
+            usedPackageFinder = true;
+        }
+        if (packages != null) {
+            for (String pn : builder.exportedAndOpenPackages()) {
+                if (!packages.contains(pn)) {
+                    String tail;
+                    if (usedPackageFinder) {
+                        tail = " not found by package finder";
+                    } else {
+                        tail = " missing from ModulePackages attribute";
+                    }
+                    throw invalidModuleDescriptor("Package " + pn + tail);
+                }
+                packages.remove(pn);
+            }
+            builder.contains(packages);
         }
 
-        // Was the Synthetic attribute present?
-        if (attributes.contains(SYNTHETIC))
-            builder.synthetic(true);
+        if (version != null)
+            builder.version(version);
+        if (mainClass != null)
+            builder.mainClass(mainClass);
+        if (osValues != null) {
+            if (osValues[0] != null) builder.osName(osValues[0]);
+            if (osValues[1] != null) builder.osArch(osValues[1]);
+            if (osValues[2] != null) builder.osVersion(osValues[2]);
+        }
+        if (hashes != null)
+            builder.hashes(hashes);
 
         return builder.build();
     }
 
     /**
-     * Reads the Module attribute.
+     * Reads the Module attribute, returning the ModuleDescriptor.Builder to
+     * build the corresponding ModuleDescriptor.
      */
-    private void readModuleAttribute(String mn, DataInput in, ConstantPool cpool)
+    private Builder readModuleAttribute(DataInput in, ConstantPool cpool)
         throws IOException
     {
+        // module_name
+        int module_name_index = in.readUnsignedShort();
+        String mn = cpool.getUtf8AsBinaryName(module_name_index);
+
+        Builder builder = new ModuleDescriptor.Builder(mn, /*strict*/ false);
+
+        int module_flags = in.readUnsignedShort();
+        boolean open = ((module_flags & ACC_OPEN) != 0);
+        if (open)
+            builder.open(true);
+        if ((module_flags & ACC_SYNTHETIC) != 0)
+            builder.synthetic(true);
+
         int requires_count = in.readUnsignedShort();
         boolean requiresJavaBase = false;
         for (int i=0; i<requires_count; i++) {
             int index = in.readUnsignedShort();
             int flags = in.readUnsignedShort();
-            String dn = cpool.getUtf8(index);
-            Set<Modifier> mods;
+            String dn = cpool.getUtf8AsBinaryName(index);
+            Set<Requires.Modifier> mods;
             if (flags == 0) {
                 mods = Collections.emptySet();
             } else {
                 mods = new HashSet<>();
-                if ((flags & ACC_PUBLIC) != 0)
-                    mods.add(Modifier.PUBLIC);
+                if ((flags & ACC_TRANSITIVE) != 0)
+                    mods.add(Requires.Modifier.TRANSITIVE);
+                if ((flags & ACC_STATIC_PHASE) != 0)
+                    mods.add(Requires.Modifier.STATIC);
                 if ((flags & ACC_SYNTHETIC) != 0)
-                    mods.add(Modifier.SYNTHETIC);
+                    mods.add(Requires.Modifier.SYNTHETIC);
                 if ((flags & ACC_MANDATED) != 0)
-                    mods.add(Modifier.MANDATED);
+                    mods.add(Requires.Modifier.MANDATED);
             }
             builder.requires(mods, dn);
             if (dn.equals("java.base"))
@@ -311,17 +351,66 @@
         if (exports_count > 0) {
             for (int i=0; i<exports_count; i++) {
                 int index = in.readUnsignedShort();
-                String pkg = cpool.getUtf8(index).replace('/', '.');
+                String pkg = cpool.getUtf8AsBinaryName(index);
+
+                Set<Exports.Modifier> mods;
+                int flags = in.readUnsignedShort();
+                if (flags == 0) {
+                    mods = Collections.emptySet();
+                } else {
+                    mods = new HashSet<>();
+                    if ((flags & ACC_SYNTHETIC) != 0)
+                        mods.add(Exports.Modifier.SYNTHETIC);
+                    if ((flags & ACC_MANDATED) != 0)
+                        mods.add(Exports.Modifier.MANDATED);
+                }
+
                 int exports_to_count = in.readUnsignedShort();
                 if (exports_to_count > 0) {
                     Set<String> targets = new HashSet<>(exports_to_count);
                     for (int j=0; j<exports_to_count; j++) {
                         int exports_to_index = in.readUnsignedShort();
-                        targets.add(cpool.getUtf8(exports_to_index));
+                        targets.add(cpool.getUtf8AsBinaryName(exports_to_index));
                     }
-                    builder.exports(pkg, targets);
+                    builder.exports(mods, pkg, targets);
                 } else {
-                    builder.exports(pkg);
+                    builder.exports(mods, pkg);
+                }
+            }
+        }
+
+        int opens_count = in.readUnsignedShort();
+        if (opens_count > 0) {
+            if (open) {
+                throw invalidModuleDescriptor("The opens table for an open"
+                                              + " module must be 0 length");
+            }
+            for (int i=0; i<opens_count; i++) {
+                int index = in.readUnsignedShort();
+                String pkg = cpool.getUtf8AsBinaryName(index);
+
+                Set<Opens.Modifier> mods;
+                int flags = in.readUnsignedShort();
+                if (flags == 0) {
+                    mods = Collections.emptySet();
+                } else {
+                    mods = new HashSet<>();
+                    if ((flags & ACC_SYNTHETIC) != 0)
+                        mods.add(Opens.Modifier.SYNTHETIC);
+                    if ((flags & ACC_MANDATED) != 0)
+                        mods.add(Opens.Modifier.MANDATED);
+                }
+
+                int open_to_count = in.readUnsignedShort();
+                if (open_to_count > 0) {
+                    Set<String> targets = new HashSet<>(open_to_count);
+                    for (int j=0; j<open_to_count; j++) {
+                        int opens_to_index = in.readUnsignedShort();
+                        targets.add(cpool.getUtf8AsBinaryName(opens_to_index));
+                    }
+                    builder.opens(mods, pkg, targets);
+                } else {
+                    builder.opens(mods, pkg);
                 }
             }
         }
@@ -330,111 +419,114 @@
         if (uses_count > 0) {
             for (int i=0; i<uses_count; i++) {
                 int index = in.readUnsignedShort();
-                String sn = cpool.getClassName(index).replace('/', '.');
+                String sn = cpool.getClassNameAsBinaryName(index);
                 builder.uses(sn);
             }
         }
 
         int provides_count = in.readUnsignedShort();
         if (provides_count > 0) {
-            Map<String, Set<String>> pm = new HashMap<>();
             for (int i=0; i<provides_count; i++) {
                 int index = in.readUnsignedShort();
-                int with_index = in.readUnsignedShort();
-                String sn = cpool.getClassName(index).replace('/', '.');
-                String cn = cpool.getClassName(with_index).replace('/', '.');
-                // computeIfAbsent
-                Set<String> providers = pm.get(sn);
-                if (providers == null) {
-                    providers = new LinkedHashSet<>(); // preserve order
-                    pm.put(sn, providers);
+                String sn = cpool.getClassNameAsBinaryName(index);
+                int with_count = in.readUnsignedShort();
+                List<String> providers = new ArrayList<>(with_count);
+                for (int j=0; j<with_count; j++) {
+                    index = in.readUnsignedShort();
+                    String pn = cpool.getClassNameAsBinaryName(index);
+                    providers.add(pn);
                 }
-                providers.add(cn);
-            }
-            for (Map.Entry<String, Set<String>> e : pm.entrySet()) {
-                builder.provides(e.getKey(), e.getValue());
+                builder.provides(sn, providers);
             }
         }
+
+        return builder;
     }
 
     /**
-     * Reads the ConcealedPackages attribute
+     * Reads the ModulePackages attribute
      */
-    private void readConcealedPackagesAttribute(DataInput in, ConstantPool cpool)
+    private Set<String> readModulePackagesAttribute(DataInput in, ConstantPool cpool)
         throws IOException
     {
         int package_count = in.readUnsignedShort();
+        Set<String> packages = new HashSet<>(package_count);
         for (int i=0; i<package_count; i++) {
             int index = in.readUnsignedShort();
-            String pn = cpool.getUtf8(index).replace('/', '.');
-            builder.conceals(pn);
+            String pn = cpool.getUtf8AsBinaryName(index);
+            packages.add(pn);
         }
+        return packages;
     }
 
     /**
-     * Reads the Version attribute
+     * Reads the ModuleVersion attribute
      */
-    private void readVersionAttribute(DataInput in, ConstantPool cpool)
+    private String readModuleVersionAttribute(DataInput in, ConstantPool cpool)
         throws IOException
     {
         int index = in.readUnsignedShort();
-        builder.version(cpool.getUtf8(index));
+        return cpool.getUtf8(index);
     }
 
     /**
-     * Reads the MainClass attribute
+     * Reads the ModuleMainClass attribute
      */
-    private void readMainClassAttribute(DataInput in, ConstantPool cpool)
+    private String readModuleMainClassAttribute(DataInput in, ConstantPool cpool)
         throws IOException
     {
         int index = in.readUnsignedShort();
-        builder.mainClass(cpool.getClassName(index).replace('/', '.'));
+        return cpool.getClassNameAsBinaryName(index);
     }
 
     /**
-     * Reads the TargetPlatform attribute
+     * Reads the ModuleTarget attribute
      */
-    private void readTargetPlatformAttribute(DataInput in, ConstantPool cpool)
+    private String[] readModuleTargetAttribute(DataInput in, ConstantPool cpool)
         throws IOException
     {
+        String[] values = new String[3];
+
         int name_index = in.readUnsignedShort();
         if (name_index != 0)
-            builder.osName(cpool.getUtf8(name_index));
+            values[0] = cpool.getUtf8(name_index);
 
         int arch_index = in.readUnsignedShort();
         if (arch_index != 0)
-            builder.osArch(cpool.getUtf8(arch_index));
+            values[1] = cpool.getUtf8(arch_index);
 
         int version_index = in.readUnsignedShort();
         if (version_index != 0)
-            builder.osVersion(cpool.getUtf8(version_index));
+            values[2] = cpool.getUtf8(version_index);
+
+        return values;
     }
 
 
     /**
-     * Reads the Hashes attribute
-     *
-     * @apiNote For now the hash is stored in base64 as a UTF-8 string, this
-     * should be changed to be an array of u1.
+     * Reads the ModuleHashes attribute
      */
-    private void readHashesAttribute(DataInput in, ConstantPool cpool)
+    private ModuleHashes readModuleHashesAttribute(DataInput in, ConstantPool cpool)
         throws IOException
     {
-        int index = in.readUnsignedShort();
-        String algorithm = cpool.getUtf8(index);
+        int algorithm_index = in.readUnsignedShort();
+        String algorithm = cpool.getUtf8(algorithm_index);
 
         int hash_count = in.readUnsignedShort();
-
-        Map<String, String> map = new HashMap<>(hash_count);
+        Map<String, byte[]> map = new HashMap<>(hash_count);
         for (int i=0; i<hash_count; i++) {
-            index = in.readUnsignedShort();
-            String dn = cpool.getUtf8(index);
-            index = in.readUnsignedShort();
-            String hash = cpool.getUtf8(index);
-            map.put(dn, hash);
+            int module_name_index = in.readUnsignedShort();
+            String mn = cpool.getUtf8AsBinaryName(module_name_index);
+            int hash_length = in.readUnsignedShort();
+            if (hash_length == 0) {
+                throw invalidModuleDescriptor("hash_length == 0");
+            }
+            byte[] hash = new byte[hash_length];
+            in.readFully(hash);
+            map.put(mn, hash);
         }
 
-        builder.hashes(new ModuleHashes(algorithm, map));
+        return new ModuleHashes(algorithm, map);
     }
 
 
@@ -447,11 +539,11 @@
         if (name.equals(MODULE) ||
                 name.equals(SOURCE_FILE) ||
                 name.equals(SDE) ||
-                name.equals(CONCEALED_PACKAGES) ||
-                name.equals(VERSION) ||
-                name.equals(MAIN_CLASS) ||
-                name.equals(TARGET_PLATFORM) ||
-                name.equals(HASHES))
+                name.equals(MODULE_PACKAGES) ||
+                name.equals(MODULE_VERSION) ||
+                name.equals(MODULE_MAIN_CLASS) ||
+                name.equals(MODULE_TARGET) ||
+                name.equals(MODULE_HASHES))
             return true;
 
         return false;
@@ -461,8 +553,8 @@
      * Return true if the given attribute name is the name of a pre-defined
      * attribute that is not allowed in the class file.
      *
-     * Except for Module, InnerClasses, Synthetic, SourceFile, SourceDebugExtension,
-     * and Deprecated, none of the pre-defined attributes in JVMS 4.7 may appear.
+     * Except for Module, InnerClasses, SourceFile, SourceDebugExtension, and
+     * Deprecated, none of the pre-defined attributes in JVMS 4.7 may appear.
      */
     private static boolean isAttributeDisallowed(String name) {
         Set<String> notAllowed = predefinedNotAllowed;
@@ -477,12 +569,11 @@
                     "LineNumberTable",
                     "LocalVariableTable",
                     "LocalVariableTypeTable",
-                    "RuntimeVisibleAnnotations",
-                    "RuntimeInvisibleAnnotations",
                     "RuntimeVisibleParameterAnnotations",
                     "RuntimeInvisibleParameterAnnotations",
                     "RuntimeVisibleTypeAnnotations",
                     "RuntimeInvisibleTypeAnnotations",
+                    "Synthetic",
                     "AnnotationDefault",
                     "BootstrapMethods",
                     "MethodParameters");
@@ -495,7 +586,6 @@
     private static volatile Set<String> predefinedNotAllowed;
 
 
-
     /**
      * The constant pool in a class file.
      */
@@ -628,6 +718,11 @@
             return getUtf8(((IndexEntry) e).index);
         }
 
+        String getClassNameAsBinaryName(int index) {
+            String value = getClassName(index);
+            return value.replace('/', '.');  // internal form -> binary name
+        }
+
         String getUtf8(int index) {
             checkIndex(index);
             Entry e = pool[index];
@@ -638,6 +733,11 @@
             return (String) (((ValueEntry) e).value);
         }
 
+        String getUtf8AsBinaryName(int index) {
+            String value = getUtf8(index);
+            return value.replace('/', '.');  // internal -> binary name
+        }
+
         void checkIndex(int index) {
             if (index < 1 || index >= pool.length)
                 throw invalidModuleDescriptor("Index into constant pool out of range");
--- a/src/java.base/share/classes/java/lang/module/ModulePath.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/module/ModulePath.java	Thu Dec 01 21:39:49 2016 +0000
@@ -40,9 +40,10 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -54,7 +55,6 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
-import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 import jdk.internal.jmod.JmodFile;
@@ -399,8 +399,8 @@
      *
      * 1. The module name (and optionally the version) is derived from the file
      *    name of the JAR file
-     * 2. The packages of all .class files in the JAR file are exported
-     * 3. It has no module-private/concealed packages
+     * 2. All packages are exported and open
+     * 3. It has no non-exported/non-open packages
      * 4. The contents of any META-INF/services configuration files are mapped
      *    to "provides" declarations
      * 5. The Main-Class attribute in the main attributes of the JAR manifest
@@ -440,8 +440,7 @@
 
         // Builder throws IAE if module name is empty or invalid
         ModuleDescriptor.Builder builder
-            = new ModuleDescriptor.Builder(mn)
-                .automatic()
+            = ModuleDescriptor.automaticModule(mn)
                 .requires(Set.of(Requires.Modifier.MANDATED), "java.base");
         if (vs != null)
             builder.version(vs);
@@ -455,13 +454,12 @@
 
         Set<String> resources = map.get(Boolean.FALSE);
         Set<String> configFiles = map.get(Boolean.TRUE);
-
-        // all packages are exported
+        // all packages are exported and open
         resources.stream()
                 .map(this::toPackageName)
                 .flatMap(Optional::stream)
                 .distinct()
-                .forEach(builder::exports);
+                .forEach(pn -> builder.exports(pn).opens(pn));
 
         // map names of service configuration files to service names
         Set<String> serviceNames = configFiles.stream()
@@ -472,7 +470,7 @@
         // parse each service configuration file
         for (String sn : serviceNames) {
             JarEntry entry = jf.getJarEntry(SERVICES_PREFIX + sn);
-            Set<String> providerClasses = new LinkedHashSet<>();
+            List<String> providerClasses = new ArrayList<>();
             try (InputStream in = jf.getInputStream(entry)) {
                 BufferedReader reader
                     = new BufferedReader(new InputStreamReader(in, "UTF-8"));
@@ -493,7 +491,7 @@
             Attributes attrs = man.getMainAttributes();
             String mainClass = attrs.getValue(Attributes.Name.MAIN_CLASS);
             if (mainClass != null)
-                builder.mainClass(mainClass);
+                builder.mainClass(mainClass.replace("/", "."));
         }
 
         return builder.build();
@@ -504,6 +502,7 @@
      */
     private static class Patterns {
         static final Pattern DASH_VERSION = Pattern.compile("-(\\d+(\\.|$))");
+        static final Pattern TRAILING_VERSION = Pattern.compile("(\\.|\\d)*$");
         static final Pattern NON_ALPHANUM = Pattern.compile("[^A-Za-z0-9]");
         static final Pattern REPEATING_DOTS = Pattern.compile("(\\.)(\\1)+");
         static final Pattern LEADING_DOTS = Pattern.compile("^\\.");
@@ -514,6 +513,9 @@
      * Clean up candidate module name derived from a JAR file name.
      */
     private static String cleanModuleName(String mn) {
+        // drop trailing version from name
+        mn = Patterns.TRAILING_VERSION.matcher(mn).replaceAll("");
+
         // replace non-alphanumeric
         mn = Patterns.NON_ALPHANUM.matcher(mn).replaceAll(".");
 
--- a/src/java.base/share/classes/java/lang/module/ModuleReference.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/module/ModuleReference.java	Thu Dec 01 21:39:49 2016 +0000
@@ -60,8 +60,8 @@
     // the function that computes the hash of this module reference
     private final HashSupplier hasher;
 
-    // cached hash string to avoid needing to compute it many times
-    private String cachedHash;
+    // cached hash to avoid needing to compute it many times
+    private byte[] cachedHash;
 
 
     /**
@@ -183,13 +183,13 @@
     }
 
     /**
-     * Computes the hash of this module, returning it as a hex string.
-     * Returns {@code null} if the hash cannot be computed.
+     * Computes the hash of this module. Returns {@code null} if the hash
+     * cannot be computed.
      *
      * @throws java.io.UncheckedIOException if an I/O error occurs
      */
-    String computeHash(String algorithm) {
-        String result = cachedHash;
+    byte[] computeHash(String algorithm) {
+        byte[] result = cachedHash;
         if (result != null)
             return result;
         if (hasher == null)
@@ -211,8 +211,11 @@
     public int hashCode() {
         int hc = hash;
         if (hc == 0) {
-            hc = Objects.hash(descriptor, location, readerSupplier, hasher,
-                    Boolean.valueOf(patched));
+            hc = descriptor.hashCode();
+            hc = 43 * hc + readerSupplier.hashCode();
+            hc = 43 * hc + Objects.hashCode(location);
+            hc = 43 * hc + Objects.hashCode(hasher);
+            hc = 43 * hc + Boolean.hashCode(patched);
             if (hc == 0)
                 hc = -1;
             hash = hc;
--- a/src/java.base/share/classes/java/lang/module/ModuleReferences.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/module/ModuleReferences.java	Thu Dec 01 21:39:49 2016 +0000
@@ -51,6 +51,7 @@
 import jdk.internal.jmod.JmodFile;
 import jdk.internal.misc.JavaLangAccess;
 import jdk.internal.misc.SharedSecrets;
+import jdk.internal.module.ModuleBootstrap;
 import jdk.internal.module.ModuleHashes;
 import jdk.internal.module.ModuleHashes.HashSupplier;
 import jdk.internal.module.ModulePatcher;
@@ -80,9 +81,8 @@
                                              HashSupplier hasher) {
 
         ModuleReference mref = new ModuleReference(md, uri, supplier, hasher);
-
         if (JLA.getBootLayer() == null)
-            mref = ModulePatcher.interposeIfNeeded(mref);
+            mref = ModuleBootstrap.patcher().patchIfNeeded(mref);
 
         return mref;
     }
@@ -93,7 +93,7 @@
     static ModuleReference newJarModule(ModuleDescriptor md, Path file) {
         URI uri = file.toUri();
         Supplier<ModuleReader> supplier = () -> new JarModuleReader(file, uri);
-        HashSupplier hasher = (a) -> ModuleHashes.computeHashAsString(file, a);
+        HashSupplier hasher = (a) -> ModuleHashes.computeHash(file, a);
         return newModule(md, uri, supplier, hasher);
     }
 
@@ -103,7 +103,7 @@
     static ModuleReference newJModModule(ModuleDescriptor md, Path file) {
         URI uri = file.toUri();
         Supplier<ModuleReader> supplier = () -> new JModModuleReader(file, uri);
-        HashSupplier hasher = (a) -> ModuleHashes.computeHashAsString(file, a);
+        HashSupplier hasher = (a) -> ModuleHashes.computeHash(file, a);
         return newModule(md, file.toUri(), supplier, hasher);
     }
 
--- a/src/java.base/share/classes/java/lang/module/Resolver.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/module/Resolver.java	Thu Dec 01 21:39:49 2016 +0000
@@ -26,9 +26,12 @@
 package java.lang.module;
 
 import java.io.PrintStream;
+import java.lang.module.ModuleDescriptor.Provides;
 import java.lang.module.ModuleDescriptor.Requires.Modifier;
+import java.lang.reflect.Layer;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Deque;
 import java.util.HashMap;
@@ -47,12 +50,15 @@
 /**
  * The resolver used by {@link Configuration#resolveRequires} and
  * {@link Configuration#resolveRequiresAndUses}.
+ *
+ * @implNote The resolver is used at VM startup and so deliberately avoids
+ * using lambda and stream usages in code paths used during startup.
  */
 
 final class Resolver {
 
     private final ModuleFinder beforeFinder;
-    private final Configuration parent;
+    private final List<Configuration> parents;
     private final ModuleFinder afterFinder;
     private final PrintStream traceOutput;
 
@@ -61,11 +67,11 @@
 
 
     Resolver(ModuleFinder beforeFinder,
-             Configuration parent,
+             List<Configuration> parents,
              ModuleFinder afterFinder,
              PrintStream traceOutput) {
         this.beforeFinder = beforeFinder;
-        this.parent = parent;
+        this.parents = parents;
         this.afterFinder = afterFinder;
         this.traceOutput = traceOutput;
     }
@@ -85,10 +91,12 @@
             // find root module
             ModuleReference mref = findWithBeforeFinder(root);
             if (mref == null) {
-                if (parent.findModule(root).isPresent()) {
+
+                if (findInParent(root) != null) {
                     // in parent, nothing to do
                     continue;
                 }
+
                 mref = findWithAfterFinder(root);
                 if (mref == null) {
                     fail("Module %s not found", root);
@@ -125,13 +133,21 @@
 
             // process dependences
             for (ModuleDescriptor.Requires requires : descriptor.requires()) {
+
+                // only required at compile-time
+                if (requires.modifiers().contains(Modifier.STATIC))
+                    continue;
+
                 String dn = requires.name();
 
                 // find dependence
                 ModuleReference mref = findWithBeforeFinder(dn);
                 if (mref == null) {
-                    if (parent.findModule(dn).isPresent())
+
+                    if (findInParent(dn) != null) {
+                        // dependence is in parent
                         continue;
+                    }
 
                     mref = findWithAfterFinder(dn);
                     if (mref == null) {
@@ -174,7 +190,9 @@
             ModuleDescriptor descriptor = mref.descriptor();
             if (!descriptor.provides().isEmpty()) {
 
-                for (String sn : descriptor.provides().keySet()) {
+                for (Provides provides :  descriptor.provides()) {
+                    String sn = provides.service();
+
                     // computeIfAbsent
                     Set<ModuleReference> providers = availableProviders.get(sn);
                     if (providers == null) {
@@ -191,19 +209,23 @@
         Deque<ModuleDescriptor> q = new ArrayDeque<>();
 
         // the initial set of modules that may use services
-        Set<ModuleDescriptor> candidateConsumers = new HashSet<>();
-        Configuration p = parent;
-        while (p != null) {
-            candidateConsumers.addAll(p.descriptors());
-            p = p.parent().orElse(null);
+        Set<ModuleDescriptor> initialConsumers;
+        if (Layer.boot() == null) {
+            initialConsumers = new HashSet<>();
+        } else {
+            initialConsumers = parents.stream()
+                    .flatMap(Configuration::configurations)
+                    .distinct()
+                    .flatMap(c -> c.descriptors().stream())
+                    .collect(Collectors.toSet());
         }
         for (ModuleReference mref : nameToReference.values()) {
-            candidateConsumers.add(mref.descriptor());
+            initialConsumers.add(mref.descriptor());
         }
 
-
         // Where there is a consumer of a service then resolve all modules
         // that provide an implementation of that service
+        Set<ModuleDescriptor> candidateConsumers = initialConsumers;
         do {
             for (ModuleDescriptor descriptor : candidateConsumers) {
                 if (!descriptor.uses().isEmpty()) {
@@ -234,7 +256,6 @@
             }
 
             candidateConsumers = resolve(q);
-
         } while (!candidateConsumers.isEmpty());
 
         return this;
@@ -429,21 +450,21 @@
             for (String dn : hashes.names()) {
                 ModuleReference other = nameToReference.get(dn);
                 if (other == null) {
-                    other = parent.findModule(dn)
-                            .map(ResolvedModule::reference)
-                            .orElse(null);
+                    ResolvedModule resolvedModule = findInParent(dn);
+                    if (resolvedModule != null)
+                        other = resolvedModule.reference();
                 }
 
                 // skip checking the hash if the module has been patched
                 if (other != null && !other.isPatched()) {
-                    String recordedHash = hashes.hashFor(dn);
-                    String actualHash = other.computeHash(algorithm);
+                    byte[] recordedHash = hashes.hashFor(dn);
+                    byte[] actualHash = other.computeHash(algorithm);
                     if (actualHash == null)
                         fail("Unable to compute the hash of module %s", dn);
-                    if (!recordedHash.equals(actualHash)) {
+                    if (!Arrays.equals(recordedHash, actualHash)) {
                         fail("Hash of %s (%s) differs to expected hash (%s)" +
-                             " recorded in %s", dn, actualHash, recordedHash,
-                             descriptor.name());
+                             " recorded in %s", dn, toHexString(actualHash),
+                             toHexString(recordedHash), descriptor.name());
                     }
                 }
             }
@@ -451,52 +472,68 @@
         }
     }
 
+    private static String toHexString(byte[] ba) {
+        StringBuilder sb = new StringBuilder(ba.length * 2);
+        for (byte b: ba) {
+            sb.append(String.format("%02x", b & 0xff));
+        }
+        return sb.toString();
+    }
+
 
     /**
      * Computes the readability graph for the modules in the given Configuration.
      *
      * The readability graph is created by propagating "requires" through the
-     * "public requires" edges of the module dependence graph. So if the module
-     * dependence graph has m1 requires m2 && m2 requires public m3 then the
-     * resulting readability graph will contain m1 reads m2, m1 reads m3, and
-     * m2 reads m3.
+     * "requires transitive" edges of the module dependence graph. So if the
+     * module dependence graph has m1 requires m2 && m2 requires transitive m3
+     * then the resulting readability graph will contain m1 reads m2, m1 reads m3,
+     * and m2 reads m3.
      */
     private Map<ResolvedModule, Set<ResolvedModule>> makeGraph(Configuration cf) {
 
+        // initial capacity of maps to avoid resizing
+        int capacity = 1 + (4 * nameToReference.size())/ 3;
+
         // the "reads" graph starts as a module dependence graph and
         // is iteratively updated to be the readability graph
-        Map<ResolvedModule, Set<ResolvedModule>> g1 = new HashMap<>();
+        Map<ResolvedModule, Set<ResolvedModule>> g1 = new HashMap<>(capacity);
 
-        // the "requires public" graph, contains requires public edges only
-        Map<ResolvedModule, Set<ResolvedModule>> g2 = new HashMap<>();
+        // the "requires transitive" graph, contains requires transitive edges only
+        Map<ResolvedModule, Set<ResolvedModule>> g2;
 
-
-        // need "requires public" from the modules in parent configurations as
-        // there may be selected modules that have a dependency on modules in
+        // need "requires transitive" from the modules in parent configurations
+        // as there may be selected modules that have a dependency on modules in
         // the parent configuration.
-
-        Configuration p = parent;
-        while (p != null) {
-            for (ModuleDescriptor descriptor : p.descriptors()) {
-                String name = descriptor.name();
-                ResolvedModule m1 = p.findModule(name)
-                    .orElseThrow(() -> new InternalError(name + " not found"));
-                for (ModuleDescriptor.Requires requires : descriptor.requires()) {
-                    if (requires.modifiers().contains(Modifier.PUBLIC)) {
-                        String dn = requires.name();
-                        ResolvedModule m2 = p.findModule(dn)
-                            .orElseThrow(() -> new InternalError(dn + " not found"));
-                        g2.computeIfAbsent(m1, k -> new HashSet<>()).add(m2);
-                    }
-                }
-            }
-
-            p = p.parent().orElse(null);
+        if (Layer.boot() == null) {
+            g2 = new HashMap<>(capacity);
+        } else {
+            g2 = parents.stream()
+                .flatMap(Configuration::configurations)
+                .distinct()
+                .flatMap(c ->
+                    c.modules().stream().flatMap(m1 ->
+                        m1.descriptor().requires().stream()
+                            .filter(r -> r.modifiers().contains(Modifier.TRANSITIVE))
+                            .flatMap(r -> {
+                                Optional<ResolvedModule> m2 = c.findModule(r.name());
+                                assert m2.isPresent()
+                                        || r.modifiers().contains(Modifier.STATIC);
+                                return m2.stream();
+                            })
+                            .map(m2 -> Map.entry(m1, m2))
+                    )
+                )
+                // stream of m1->m2
+                .collect(Collectors.groupingBy(Map.Entry::getKey,
+                        HashMap::new,
+                        Collectors.mapping(Map.Entry::getValue, Collectors.toSet())
+            ));
         }
 
         // populate g1 and g2 with the dependences from the selected modules
 
-        Map<String, ResolvedModule> nameToResolved = new HashMap<>();
+        Map<String, ResolvedModule> nameToResolved = new HashMap<>(capacity);
 
         for (ModuleReference mref : nameToReference.values()) {
             ModuleDescriptor descriptor = mref.descriptor();
@@ -505,20 +542,21 @@
             ResolvedModule m1 = computeIfAbsent(nameToResolved, name, cf, mref);
 
             Set<ResolvedModule> reads = new HashSet<>();
-            Set<ResolvedModule> requiresPublic = new HashSet<>();
+            Set<ResolvedModule> requiresTransitive = new HashSet<>();
 
             for (ModuleDescriptor.Requires requires : descriptor.requires()) {
                 String dn = requires.name();
 
-                ResolvedModule m2;
+                ResolvedModule m2 = null;
                 ModuleReference mref2 = nameToReference.get(dn);
                 if (mref2 != null) {
                     // same configuration
                     m2 = computeIfAbsent(nameToResolved, dn, cf, mref2);
                 } else {
                     // parent configuration
-                    m2 = parent.findModule(dn).orElse(null);
+                    m2 = findInParent(dn);
                     if (m2 == null) {
+                        assert requires.modifiers().contains(Modifier.STATIC);
                         continue;
                     }
                 }
@@ -526,9 +564,9 @@
                 // m1 requires m2 => m1 reads m2
                 reads.add(m2);
 
-                // m1 requires public m2
-                if (requires.modifiers().contains(Modifier.PUBLIC)) {
-                    requiresPublic.add(m2);
+                // m1 requires transitive m2
+                if (requires.modifiers().contains(Modifier.TRANSITIVE)) {
+                    requiresTransitive.add(m2);
                 }
 
             }
@@ -538,7 +576,7 @@
             if (descriptor.isAutomatic()) {
 
                 // reads all selected modules
-                // `requires public` all selected automatic modules
+                // `requires transitive` all selected automatic modules
                 for (ModuleReference mref2 : nameToReference.values()) {
                     ModuleDescriptor descriptor2 = mref2.descriptor();
                     String name2 = descriptor2.name();
@@ -548,40 +586,42 @@
                             = computeIfAbsent(nameToResolved, name2, cf, mref2);
                         reads.add(m2);
                         if (descriptor2.isAutomatic())
-                            requiresPublic.add(m2);
+                            requiresTransitive.add(m2);
                     }
                 }
 
                 // reads all modules in parent configurations
-                // `requires public` all automatic modules in parent configurations
-                p = parent;
-                while (p != null) {
-                    for (ResolvedModule m : p.modules()) {
-                        reads.add(m);
-                        if (m.reference().descriptor().isAutomatic())
-                            requiresPublic.add(m);
-                    }
-                    p = p.parent().orElse(null);
+                // `requires transitive` all automatic modules in parent
+                // configurations
+                for (Configuration parent : parents) {
+                    parent.configurations()
+                            .map(Configuration::modules)
+                            .flatMap(Set::stream)
+                            .forEach(m -> {
+                                reads.add(m);
+                                if (m.reference().descriptor().isAutomatic())
+                                    requiresTransitive.add(m);
+                            });
                 }
-
             }
 
             g1.put(m1, reads);
-            g2.put(m1, requiresPublic);
+            g2.put(m1, requiresTransitive);
         }
 
-        // Iteratively update g1 until there are no more requires public to propagate
+        // Iteratively update g1 until there are no more requires transitive
+        // to propagate
         boolean changed;
-        Set<ResolvedModule> toAdd = new HashSet<>();
+        List<ResolvedModule> toAdd = new ArrayList<>();
         do {
             changed = false;
             for (Set<ResolvedModule> m1Reads : g1.values()) {
                 for (ResolvedModule m2 : m1Reads) {
-                    Set<ResolvedModule> m2RequiresPublic = g2.get(m2);
-                    if (m2RequiresPublic != null) {
-                        for (ResolvedModule m3 : m2RequiresPublic) {
+                    Set<ResolvedModule> m2RequiresTransitive = g2.get(m2);
+                    if (m2RequiresTransitive != null) {
+                        for (ResolvedModule m3 : m2RequiresTransitive) {
                             if (!m1Reads.contains(m3)) {
-                                // m1 reads m2, m2 requires public m3
+                                // m1 reads m2, m2 requires transitive m3
                                 // => need to add m1 reads m3
                                 toAdd.add(m3);
                             }
@@ -655,7 +695,7 @@
                     // source is exported to descriptor2
                     String source = export.source();
                     ModuleDescriptor other
-                        = packageToExporter.put(source, descriptor2);
+                        = packageToExporter.putIfAbsent(source, descriptor2);
 
                     if (other != null && other != descriptor2) {
                         // package might be local to descriptor1
@@ -692,12 +732,8 @@
                 }
 
                 // provides S
-                for (Map.Entry<String, ModuleDescriptor.Provides> entry :
-                        descriptor1.provides().entrySet()) {
-                    String service = entry.getKey();
-                    ModuleDescriptor.Provides provides = entry.getValue();
-
-                    String pn = packageName(service);
+                for (ModuleDescriptor.Provides provides : descriptor1.provides()) {
+                    String pn = packageName(provides.service());
                     if (!packageToExporter.containsKey(pn)) {
                         fail("Module %s does not read a module that exports %s",
                              descriptor1.name(), pn);
@@ -717,6 +753,18 @@
 
     }
 
+    /**
+     * Find a module of the given name in the parent configurations
+     */
+    private ResolvedModule findInParent(String mn) {
+        for (Configuration parent : parents) {
+            Optional<ResolvedModule> om = parent.findModule(mn);
+            if (om.isPresent())
+                return om.get();
+        }
+        return null;
+    }
+
 
     /**
      * Invokes the beforeFinder to find method to find the given module.
@@ -755,15 +803,18 @@
             if (afterModules.isEmpty())
                 return beforeModules;
 
-            if (beforeModules.isEmpty() && parent == Configuration.empty())
+            if (beforeModules.isEmpty()
+                    && parents.size() == 1
+                    && parents.get(0) == Configuration.empty())
                 return afterModules;
 
             Set<ModuleReference> result = new HashSet<>(beforeModules);
             for (ModuleReference mref : afterModules) {
                 String name = mref.descriptor().name();
                 if (!beforeFinder.find(name).isPresent()
-                        && !parent.findModule(name).isPresent())
+                        && findInParent(name) == null) {
                     result.add(mref);
+                }
             }
 
             return result;
--- a/src/java.base/share/classes/java/lang/module/SystemModuleFinder.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/module/SystemModuleFinder.java	Thu Dec 01 21:39:49 2016 +0000
@@ -39,6 +39,7 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -53,6 +54,7 @@
 import jdk.internal.jimage.ImageReaderFactory;
 import jdk.internal.misc.JavaNetUriAccess;
 import jdk.internal.misc.SharedSecrets;
+import jdk.internal.module.ModuleBootstrap;
 import jdk.internal.module.ModuleHashes;
 import jdk.internal.module.ModuleHashes.HashSupplier;
 import jdk.internal.module.SystemModules;
@@ -64,7 +66,7 @@
  * run-time image.
  *
  * The modules linked into the run-time image are assumed to have the
- * ConcealedPackages attribute.
+ * Packages attribute.
  */
 
 class SystemModuleFinder implements ModuleFinder {
@@ -102,8 +104,11 @@
         int n = names.length;
         moduleCount.add(n);
 
-        Set<ModuleReference> mods = new HashSet<>(n);
-        Map<String, ModuleReference> map = new HashMap<>(n);
+        ModuleReference[] mods = new ModuleReference[n];
+
+        @SuppressWarnings(value = {"rawtypes", "unchecked"})
+        Entry<String, ModuleReference>[] map
+            = (Entry<String, ModuleReference>[])new Entry[n];
 
         for (int i = 0; i < n; i++) {
             ModuleDescriptor md = descriptors[i];
@@ -111,16 +116,16 @@
             // create the ModuleReference
             ModuleReference mref = toModuleReference(md, hashSupplier(i, names[i]));
 
-            mods.add(mref);
-            map.put(names[i], mref);
+            mods[i] = mref;
+            map[i] = Map.entry(names[i], mref);
 
             // counters
             packageCount.add(md.packages().size());
             exportsCount.add(md.exports().size());
         }
 
-        modules = Collections.unmodifiableSet(mods);
-        nameToModule = map;
+        modules = Set.of(mods);
+        nameToModule = Map.ofEntries(map);
 
         initTime.addElapsedTimeFrom(t0);
     }
@@ -190,7 +195,7 @@
             new ModuleReference(md, uri, readerSupplier, hash);
 
         // may need a reference to a patched module if --patch-module specified
-        mref = ModulePatcher.interposeIfNeeded(mref);
+        mref = ModuleBootstrap.patcher().patchIfNeeded(mref);
 
         return mref;
     }
@@ -199,7 +204,7 @@
         if (isFastPathSupported()) {
             return new HashSupplier() {
                 @Override
-                public String generate(String algorithm) {
+                public byte[] generate(String algorithm) {
                     return SystemModules.MODULES_TO_HASH[index];
                 }
             };
@@ -213,7 +218,7 @@
      * It will get the recorded hashes from module-info.class.
      */
     private static class Hashes {
-        static Map<String, String> hashes = new HashMap<>();
+        static Map<String, byte[]> hashes = new HashMap<>();
 
         static void add(ModuleDescriptor descriptor) {
             Optional<ModuleHashes> ohashes = descriptor.hashes();
@@ -228,7 +233,7 @@
 
             return new HashSupplier() {
                 @Override
-                public String generate(String algorithm) {
+                public byte[] generate(String algorithm) {
                     return hashes.get(name);
                 }
             };
--- a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java	Thu Dec 01 21:39:49 2016 +0000
@@ -25,12 +25,12 @@
 
 package java.lang.reflect;
 
+import java.lang.annotation.Annotation;
 import java.security.AccessController;
 
 import jdk.internal.reflect.CallerSensitive;
 import jdk.internal.reflect.Reflection;
 import jdk.internal.reflect.ReflectionFactory;
-import java.lang.annotation.Annotation;
 
 /**
  * The AccessibleObject class is the base class for Field, Method and
@@ -81,8 +81,10 @@
      * <p>This method cannot be used to enable access to an object that is a
      * {@link Member member} of a class in a different module to the caller and
      * where the class is in a package that is not exported to the caller's
-     * module. Additionally, this method cannot be used to enable access to
-     * non-public members of {@code AccessibleObject} or {@link Module}.
+     * module. Additionally, if the member is non-public or its declaring
+     * class is non-public, then this method can only be used to enable access
+     * if the package is {@link Module#isOpen(String,Module) open} to at least
+     * the caller's module.
      *
      * <p>If there is a security manager, its
      * {@code checkPermission} method is first called with a
@@ -126,8 +128,10 @@
      * <p>This method cannot be used to enable access to an object that is a
      * {@link Member member} of a class in a different module to the caller and
      * where the class is in a package that is not exported to the caller's
-     * module. Additionally, this method cannot be used to enable access to
-     * non-public members of {@code AccessibleObject} or {@link Module}.
+     * module. Additionally, if the member is non-public or its declaring
+     * class is non-public, then this method can only be used to enable access
+     * if the package is {@link Module#isOpen(String,Module) open} to at least
+     * the caller's module.
      *
      * <p>If there is a security manager, its
      * {@code checkPermission} method is first called with a
@@ -138,6 +142,7 @@
      * @throws SecurityException if the request is denied
      * @see SecurityManager#checkPermission
      * @see ReflectPermission
+     * @see java.lang.invoke.MethodHandles#privateLookupIn
      */
     public void setAccessible(boolean flag) {
         AccessibleObject.checkPermission();
@@ -161,35 +166,39 @@
         Module callerModule = caller.getModule();
         Module declaringModule = declaringClass.getModule();
 
-        if (callerModule != declaringModule
-                && callerModule != Object.class.getModule()) {
+        if (callerModule == declaringModule) return;
+        if (callerModule == Object.class.getModule()) return;
+        if (!declaringModule.isNamed()) return;
 
-            // check exports to target module
-            String pn = packageName(declaringClass);
-            if (!declaringModule.isExported(pn, callerModule)) {
-                String msg = "Unable to make member of "
-                        + declaringClass + " accessible:  "
-                        + declaringModule + " does not export "
-                        + pn + " to " + callerModule;
-                Reflection.throwInaccessibleObjectException(msg);
-            }
+        // package is open to caller
+        String pn = packageName(declaringClass);
+        if (declaringModule.isOpen(pn, callerModule))
+            return;
 
+        // package is exported to caller and class/member is public
+        boolean isExported = declaringModule.isExported(pn, callerModule);
+        boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers());
+        int modifiers;
+        if (this instanceof Executable) {
+            modifiers = ((Executable) this).getModifiers();
+        } else {
+            modifiers = ((Field) this).getModifiers();
         }
+        boolean isMemberPublic = Modifier.isPublic(modifiers);
+        if (isExported && isClassPublic && isMemberPublic)
+            return;
 
-        if (declaringClass == Module.class
-                || declaringClass == AccessibleObject.class) {
-            int modifiers;
-            if (this instanceof Executable) {
-                modifiers = ((Executable) this).getModifiers();
-            } else {
-                modifiers = ((Field) this).getModifiers();
-            }
-            if (!Modifier.isPublic(modifiers)) {
-                String msg = "Cannot make a non-public member of "
-                        + declaringClass + " accessible";
-                Reflection.throwInaccessibleObjectException(msg);
-            }
-        }
+        // not accessible
+        String msg = "Unable to make ";
+        if (this instanceof Field)
+            msg += "field ";
+        msg += this + " accessible: " + declaringModule + " does not \"";
+        if (isClassPublic && isMemberPublic)
+            msg += "exports";
+        else
+            msg += "opens";
+        msg += " " + pn + "\" to " + callerModule;
+        Reflection.throwInaccessibleObjectException(msg);
     }
 
     /**
--- a/src/java.base/share/classes/java/lang/reflect/Layer.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/reflect/Layer.java	Thu Dec 01 21:39:49 2016 +0000
@@ -27,23 +27,29 @@
 
 import java.lang.module.Configuration;
 import java.lang.module.ModuleDescriptor;
-import java.lang.module.ModuleDescriptor.Provides;
 import java.lang.module.ResolvedModule;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
+import jdk.internal.loader.ClassLoaderValue;
 import jdk.internal.loader.Loader;
 import jdk.internal.loader.LoaderPool;
 import jdk.internal.misc.SharedSecrets;
+import jdk.internal.module.Modules;
 import jdk.internal.module.ServicesCatalog;
-import jdk.internal.module.ServicesCatalog.ServiceProvider;
 import sun.security.util.SecurityConstants;
 
 
@@ -55,7 +61,7 @@
  * Creating a layer informs the Java virtual machine about the classes that
  * may be loaded from modules so that the Java virtual machine knows which
  * module that each class is a member of. Each layer, except the {@link
- * #empty() empty} layer, has a {@link #parent() parent}. </p>
+ * #empty() empty} layer, has at least one {@link #parents() parent}. </p>
  *
  * <p> Creating a layer creates a {@link Module} object for each {@link
  * ResolvedModule} in the configuration. For each resolved module that is
@@ -71,7 +77,11 @@
  * mapped to a single class loader or where each module is mapped to its own
  * class loader. The {@link #defineModules defineModules} method is for more
  * advanced cases where modules are mapped to custom class loaders by means of
- * a function specified to the method. </p>
+ * a function specified to the method. Each of these methods has an instance
+ * and static variant. The instance methods create a layer with the receiver
+ * as the parent layer. The static methods are for more advanced cases where
+ * there can be more than one parent layer or a {@link Layer.Controller
+ * Controller} is needed to control modules in the layer. </p>
  *
  * <p> A Java virtual machine has at least one non-empty layer, the {@link
  * #boot() boot} layer, that is created when the Java virtual machine is
@@ -80,7 +90,7 @@
  * The modules in the boot layer are mapped to the bootstrap class loader and
  * other class loaders that are <a href="../ClassLoader.html#builtinLoaders">
  * built-in</a> into the Java virtual machine. The boot layer will often be
- * the {@link #parent() parent} when creating additional layers. </p>
+ * the {@link #parents() parent} when creating additional layers. </p>
  *
  * <p> As when creating a {@code Configuration},
  * {@link ModuleDescriptor#isAutomatic() automatic} modules receive
@@ -123,30 +133,29 @@
 
     // the empty Layer
     private static final Layer EMPTY_LAYER
-        = new Layer(Configuration.empty(), null, null);
+        = new Layer(Configuration.empty(), List.of(), null);
 
     // the configuration from which this Layer was created
     private final Configuration cf;
 
-    // parent layer, null in the case of the empty layer
-    private final Layer parent;
+    // parent layers, empty in the case of the empty layer
+    private final List<Layer> parents;
 
     // maps module name to jlr.Module
     private final Map<String, Module> nameToModule;
 
-
     /**
      * Creates a new Layer from the modules in the given configuration.
      */
     private Layer(Configuration cf,
-                  Layer parent,
+                  List<Layer> parents,
                   Function<String, ClassLoader> clf)
     {
         this.cf = cf;
-        this.parent = parent;
+        this.parents = parents; // no need to do defensive copy
 
         Map<String, Module> map;
-        if (parent == null) {
+        if (parents.isEmpty()) {
             map = Collections.emptyMap();
         } else {
             map = Module.defineModules(cf, clf, this);
@@ -154,12 +163,230 @@
         this.nameToModule = map; // no need to do defensive copy
     }
 
+    /**
+     * Controls a layer. The static methods defined by {@link Layer} to create
+     * module layers return a {@code Controller} that can be used to control
+     * modules in the layer.
+     *
+     * @apiNote Care should be taken with {@code Controller} objects, they
+     * should never be shared with untrusted code.
+     *
+     * @since 9
+     */
+    public static final class Controller {
+        private final Layer layer;
+
+        Controller(Layer layer) {
+            this.layer = layer;
+        }
+
+        /**
+         * Returns the layer that this object controls.
+         *
+         * @return the layer
+         */
+        public Layer layer() {
+            return layer;
+        }
+
+        private void ensureInLayer(Module source) {
+            if (!layer.modules().contains(source))
+                throw new IllegalArgumentException(source + " not in layer");
+        }
+
+
+        /**
+         * Updates module {@code source} in the layer to read module
+         * {@code target}. This method is a no-op if {@code source} already
+         * reads {@code target}.
+         *
+         * @implNote <em>Read edges</em> added by this method are <em>weak</em>
+         * and do not prevent {@code target} from being GC'ed when {@code source}
+         * is strongly reachable.
+         *
+         * @param  source
+         *         The source module
+         * @param  target
+         *         The target module to read
+         *
+         * @return This controller
+         *
+         * @throws IllegalArgumentException
+         *         If {@code source} is not in the layer
+         *
+         * @see Module#addReads
+         */
+        public Controller addReads(Module source, Module target) {
+            Objects.requireNonNull(source);
+            Objects.requireNonNull(target);
+            ensureInLayer(source);
+            Modules.addReads(source, target);
+            return this;
+        }
+
+        /**
+         * Updates module {@code source} in the layer to open a package to
+         * module {@code target}. This method is a no-op if {@code source}
+         * already opens the package to at least {@code target}.
+         *
+         * @param  source
+         *         The source module
+         * @param  pn
+         *         The package name
+         * @param  target
+         *         The target module to read
+         *
+         * @return This controller
+         *
+         * @throws IllegalArgumentException
+         *         If {@code source} is not in the layer or the package is not
+         *         in the source module
+         *
+         * @see Module#addOpens
+         */
+        public Controller addOpens(Module source, String pn, Module target) {
+            Objects.requireNonNull(source);
+            Objects.requireNonNull(source);
+            Objects.requireNonNull(target);
+            ensureInLayer(source);
+            Modules.addOpens(source, pn, target);
+            return this;
+        }
+    }
+
 
     /**
      * Creates a new layer, with this layer as its parent, by defining the
      * modules in the given {@code Configuration} to the Java virtual machine.
      * This method creates one class loader and defines all modules to that
-     * class loader.
+     * class loader. The {@link ClassLoader#getParent() parent} of each class
+     * loader is the given parent class loader. This method works exactly as
+     * specified by the static {@link
+     * #defineModulesWithOneLoader(Configuration,List,ClassLoader)
+     * defineModulesWithOneLoader} method when invoked with this layer as the
+     * parent. In other words, if this layer is {@code thisLayer} then this
+     * method is equivalent to invoking:
+     * <pre> {@code
+     *     Layer.defineModulesWithOneLoader(cf, List.of(thisLayer), parentLoader).layer();
+     * }</pre>
+     *
+     * @param  cf
+     *         The configuration for the layer
+     * @param  parentLoader
+     *         The parent class loader for the class loader created by this
+     *         method; may be {@code null} for the bootstrap class loader
+     *
+     * @return The newly created layer
+     *
+     * @throws IllegalArgumentException
+     *         If the parent of the given configuration is not the configuration
+     *         for this layer
+     * @throws LayerInstantiationException
+     *         If all modules cannot be defined to the same class loader for any
+     *         of the reasons listed above or the layer cannot be created because
+     *         the configuration contains a module named "{@code java.base}" or
+     *         a module with a package name starting with "{@code java.}"
+     * @throws SecurityException
+     *         If {@code RuntimePermission("createClassLoader")} or
+     *         {@code RuntimePermission("getClassLoader")} is denied by
+     *         the security manager
+     *
+     * @see #findLoader
+     */
+    public Layer defineModulesWithOneLoader(Configuration cf,
+                                            ClassLoader parentLoader) {
+        return defineModulesWithOneLoader(cf, List.of(this), parentLoader).layer();
+    }
+
+
+    /**
+     * Creates a new layer, with this layer as its parent, by defining the
+     * modules in the given {@code Configuration} to the Java virtual machine.
+     * Each module is defined to its own {@link ClassLoader} created by this
+     * method. The {@link ClassLoader#getParent() parent} of each class loader
+     * is the given parent class loader. This method works exactly as specified
+     * by the static {@link
+     * #defineModulesWithManyLoaders(Configuration,List,ClassLoader)
+     * defineModulesWithManyLoaders} method when invoked with this layer as the
+     * parent. In other words, if this layer is {@code thisLayer} then this
+     * method is equivalent to invoking:
+     * <pre> {@code
+     *     Layer.defineModulesWithManyLoaders(cf, List.of(thisLayer), parentLoader).layer();
+     * }</pre>
+     *
+     * @param  cf
+     *         The configuration for the layer
+     * @param  parentLoader
+     *         The parent class loader for each of the class loaders created by
+     *         this method; may be {@code null} for the bootstrap class loader
+     *
+     * @return The newly created layer
+     *
+     * @throws IllegalArgumentException
+     *         If the parent of the given configuration is not the configuration
+     *         for this layer
+     * @throws LayerInstantiationException
+     *         If the layer cannot be created because the configuration contains
+     *         a module named "{@code java.base}" or a module with a package
+     *         name starting with "{@code java.}"
+     * @throws SecurityException
+     *         If {@code RuntimePermission("createClassLoader")} or
+     *         {@code RuntimePermission("getClassLoader")} is denied by
+     *         the security manager
+     *
+     * @see #findLoader
+     */
+    public Layer defineModulesWithManyLoaders(Configuration cf,
+                                              ClassLoader parentLoader) {
+        return defineModulesWithManyLoaders(cf, List.of(this), parentLoader).layer();
+    }
+
+
+    /**
+     * Creates a new layer, with this layer as its parent, by defining the
+     * modules in the given {@code Configuration} to the Java virtual machine.
+     * Each module is mapped, by name, to its class loader by means of the
+     * given function. This method works exactly as specified by the static
+     * {@link #defineModules(Configuration,List,Function) defineModules}
+     * method when invoked with this layer as the parent. In other words, if
+     * this layer is {@code thisLayer} then this method is equivalent to
+     * invoking:
+     * <pre> {@code
+     *     Layer.defineModules(cf, List.of(thisLayer), clf).layer();
+     * }</pre>
+     *
+     * @param  cf
+     *         The configuration for the layer
+     * @param  clf
+     *         The function to map a module name to a class loader
+     *
+     * @return The newly created layer
+     *
+     * @throws IllegalArgumentException
+     *         If the parent of the given configuration is not the configuration
+     *         for this layer
+     * @throws LayerInstantiationException
+     *         If creating the {@code Layer} fails for any of the reasons
+     *         listed above, the layer cannot be created because the
+     *         configuration contains a module named "{@code java.base}",
+     *         a module with a package name starting with "{@code java.}" is
+     *         mapped to a class loader other than the {@link
+     *         ClassLoader#getPlatformClassLoader() platform class loader},
+     *         or the function to map a module name to a class loader returns
+     *         {@code null}
+     * @throws SecurityException
+     *         If {@code RuntimePermission("getClassLoader")} is denied by
+     *         the security manager
+     */
+    public Layer defineModules(Configuration cf,
+                               Function<String, ClassLoader> clf) {
+        return defineModules(cf, List.of(this), clf).layer();
+    }
+
+    /**
+     * Creates a new layer by defining the modules in the given {@code
+     * Configuration} to the Java virtual machine. This method creates one
+     * class loader and defines all modules to that class loader.
      *
      * <p> The class loader created by this method implements <em>direct
      * delegation</em> when loading types from modules. When its {@link
@@ -180,7 +407,7 @@
      * <ul>
      *
      *     <li><p> <em>Overlapping packages</em>: Two or more modules in the
-     *     configuration have the same package (exported or concealed). </p></li>
+     *     configuration have the same package. </p></li>
      *
      *     <li><p> <em>Split delegation</em>: The resulting class loader would
      *     need to delegate to more than one class loader in order to load types
@@ -194,19 +421,22 @@
      *
      * @param  cf
      *         The configuration for the layer
+     * @param  parentLayers
+     *         The list parent layers in search order
      * @param  parentLoader
      *         The parent class loader for the class loader created by this
      *         method; may be {@code null} for the bootstrap class loader
      *
-     * @return The newly created layer
+     * @return A controller that controls the newly created layer
      *
      * @throws IllegalArgumentException
-     *         If the parent of the given configuration is not the configuration
-     *         for this layer
+     *         If the parent configurations do not match the configuration of
+     *         the parent layers, including order
      * @throws LayerInstantiationException
      *         If all modules cannot be defined to the same class loader for any
      *         of the reasons listed above or the layer cannot be created because
-     *         the configuration contains a module named "{@code java.base}"
+     *         the configuration contains a module named "{@code java.base}" or
+     *         a module with a package name starting with "{@code java.}"
      * @throws SecurityException
      *         If {@code RuntimePermission("createClassLoader")} or
      *         {@code RuntimePermission("getClassLoader")} is denied by
@@ -214,29 +444,32 @@
      *
      * @see #findLoader
      */
-    public Layer defineModulesWithOneLoader(Configuration cf,
-                                            ClassLoader parentLoader)
+    public static Controller defineModulesWithOneLoader(Configuration cf,
+                                                        List<Layer> parentLayers,
+                                                        ClassLoader parentLoader)
     {
-        checkConfiguration(cf);
+        List<Layer> parents = new ArrayList<>(parentLayers);
+        checkConfiguration(cf, parents);
+
         checkCreateClassLoaderPermission();
         checkGetClassLoaderPermission();
 
         try {
             Loader loader = new Loader(cf.modules(), parentLoader);
-            loader.initRemotePackageMap(cf, this);
-            return new Layer(cf, this, mn -> loader);
+            loader.initRemotePackageMap(cf, parents);
+            Layer layer =  new Layer(cf, parents, mn -> loader);
+            return new Controller(layer);
         } catch (IllegalArgumentException e) {
             throw new LayerInstantiationException(e.getMessage());
         }
     }
 
-
     /**
-     * Creates a new layer, with this layer as its parent, by defining the
-     * modules in the given {@code Configuration} to the Java virtual machine.
-     * Each module is defined to its own {@link ClassLoader} created by this
-     * method. The {@link ClassLoader#getParent() parent} of each class loader
-     * is the given parent class loader.
+     * Creates a new layer by defining the modules in the given {@code
+     * Configuration} to the Java virtual machine. Each module is defined to
+     * its own {@link ClassLoader} created by this method. The {@link
+     * ClassLoader#getParent() parent} of each class loader is the given parent
+     * class loader.
      *
      * <p> The class loaders created by this method implement <em>direct
      * delegation</em> when loading types from modules. When {@link
@@ -258,18 +491,21 @@
      *
      * @param  cf
      *         The configuration for the layer
+     * @param  parentLayers
+     *         The list parent layers in search order
      * @param  parentLoader
      *         The parent class loader for each of the class loaders created by
      *         this method; may be {@code null} for the bootstrap class loader
      *
-     * @return The newly created layer
+     * @return A controller that controls the newly created layer
      *
      * @throws IllegalArgumentException
-     *         If the parent of the given configuration is not the configuration
-     *         for this layer
+     *         If the parent configurations do not match the configuration of
+     *         the parent layers, including order
      * @throws LayerInstantiationException
      *         If the layer cannot be created because the configuration contains
-     *         a module named "{@code java.base}"
+     *         a module named "{@code java.base}" or a module with a package
+     *         name starting with "{@code java.}"
      * @throws SecurityException
      *         If {@code RuntimePermission("createClassLoader")} or
      *         {@code RuntimePermission("getClassLoader")} is denied by
@@ -277,37 +513,43 @@
      *
      * @see #findLoader
      */
-    public Layer defineModulesWithManyLoaders(Configuration cf,
-                                              ClassLoader parentLoader)
+    public static Controller defineModulesWithManyLoaders(Configuration cf,
+                                                          List<Layer> parentLayers,
+                                                          ClassLoader parentLoader)
     {
-        checkConfiguration(cf);
+        List<Layer> parents = new ArrayList<>(parentLayers);
+        checkConfiguration(cf, parents);
+
         checkCreateClassLoaderPermission();
         checkGetClassLoaderPermission();
 
-        LoaderPool pool = new LoaderPool(cf, this, parentLoader);
+        LoaderPool pool = new LoaderPool(cf, parents, parentLoader);
         try {
-            return new Layer(cf, this, pool::loaderFor);
+            Layer layer = new Layer(cf, parents, pool::loaderFor);
+            return new Controller(layer);
         } catch (IllegalArgumentException e) {
             throw new LayerInstantiationException(e.getMessage());
         }
     }
 
-
     /**
-     * Creates a new layer, with this layer as its parent, by defining the
-     * modules in the given {@code Configuration} to the Java virtual machine.
+     * Creates a new layer by defining the modules in the given {@code
+     * Configuration} to the Java virtual machine.
      * Each module is mapped, by name, to its class loader by means of the
      * given function. The class loader delegation implemented by these class
-     * loaders must respect module readability. In addition, the caller needs
-     * to arrange that the class loaders are ready to load from these module
-     * before there are any attempts to load classes or resources.
+     * loaders must respect module readability. The class loaders should be
+     * {@link ClassLoader#registerAsParallelCapable parallel-capable} so as to
+     * avoid deadlocks during class loading. In addition, the entity creating
+     * a new layer with this method should arrange that the class loaders are
+     * ready to load from these module before there are any attempts to load
+     * classes or resources.
      *
      * <p> Creating a {@code Layer} can fail for the following reasons: </p>
      *
      * <ul>
      *
-     *     <li><p> Two or more modules with the same package (exported or
-     *     concealed) are mapped to the same class loader. </p></li>
+     *     <li><p> Two or more modules with the same package are mapped to the
+     *     same class loader. </p></li>
      *
      *     <li><p> A module is mapped to a class loader that already has a
      *     module of the same name defined to it. </p></li>
@@ -328,26 +570,35 @@
      *
      * @param  cf
      *         The configuration for the layer
+     * @param  parentLayers
+     *         The list parent layers in search order
      * @param  clf
      *         The function to map a module name to a class loader
      *
-     * @return The newly created layer
+     * @return A controller that controls the newly created layer
      *
      * @throws IllegalArgumentException
-     *         If the parent of the given configuration is not the configuration
-     *         for this layer
+     *         If the parent configurations do not match the configuration of
+     *         the parent layers, including order
      * @throws LayerInstantiationException
      *         If creating the {@code Layer} fails for any of the reasons
-     *         listed above or the layer cannot be created because the
-     *         configuration contains a module named "{@code java.base}"
+     *         listed above, the layer cannot be created because the
+     *         configuration contains a module named "{@code java.base}",
+     *         a module with a package name starting with "{@code java.}" is
+     *         mapped to a class loader other than the {@link
+     *         ClassLoader#getPlatformClassLoader() platform class loader},
+     *         or the function to map a module name to a class loader returns
+     *         {@code null}
      * @throws SecurityException
      *         If {@code RuntimePermission("getClassLoader")} is denied by
      *         the security manager
      */
-    public Layer defineModules(Configuration cf,
-                               Function<String, ClassLoader> clf)
+    public static Controller defineModules(Configuration cf,
+                                           List<Layer> parentLayers,
+                                           Function<String, ClassLoader> clf)
     {
-        checkConfiguration(cf);
+        List<Layer> parents = new ArrayList<>(parentLayers);
+        checkConfiguration(cf, parents);
         Objects.requireNonNull(clf);
 
         checkGetClassLoaderPermission();
@@ -362,7 +613,8 @@
         }
 
         try {
-            return new Layer(cf, this, clf);
+            Layer layer = new Layer(cf, parents, clf);
+            return new Controller(layer);
         } catch (IllegalArgumentException iae) {
             // IAE is thrown by VM when defining the module fails
             throw new LayerInstantiationException(iae.getMessage());
@@ -370,13 +622,26 @@
     }
 
 
-    private void checkConfiguration(Configuration cf) {
+    /**
+     * Checks that the parent configurations match the configuration of
+     * the parent layers.
+     */
+    private static void checkConfiguration(Configuration cf,
+                                           List<Layer> parentLayers)
+    {
         Objects.requireNonNull(cf);
 
-        Optional<Configuration> oparent = cf.parent();
-        if (!oparent.isPresent() || oparent.get() != this.configuration()) {
-            throw new IllegalArgumentException(
-                    "Parent of configuration != configuration of this Layer");
+        List<Configuration> parentConfigurations = cf.parents();
+        if (parentLayers.size() != parentConfigurations.size())
+            throw new IllegalArgumentException("wrong number of parents");
+
+        int index = 0;
+        for (Layer parent : parentLayers) {
+            if (parent.configuration() != parentConfigurations.get(index)) {
+                throw new IllegalArgumentException(
+                        "Parent of configuration != configuration of this Layer");
+            }
+            index++;
         }
     }
 
@@ -463,18 +728,57 @@
 
 
     /**
-     * Returns this layer's parent unless this is the {@linkplain #empty empty
-     * layer}, which has no parent.
+     * Returns the list of this layer's parents unless this is the
+     * {@linkplain #empty empty layer}, which has no parents and so an
+     * empty list is returned.
      *
-     * @return This layer's parent
+     * @return The list of this layer's parents
      */
-    public Optional<Layer> parent() {
-        return Optional.ofNullable(parent);
+    public List<Layer> parents() {
+        return parents;
     }
 
 
     /**
-     * Returns a set of the modules in this layer.
+     * Returns an ordered stream of layers. The first element is is this layer,
+     * the remaining elements are the parent layers in DFS order.
+     *
+     * @implNote For now, the assumption is that the number of elements will
+     * be very low and so this method does not use a specialized spliterator.
+     */
+    Stream<Layer> layers() {
+        List<Layer> allLayers = this.allLayers;
+        if (allLayers != null)
+            return allLayers.stream();
+
+        allLayers = new ArrayList<>();
+        Set<Layer> visited = new HashSet<>();
+        Deque<Layer> stack = new ArrayDeque<>();
+        visited.add(this);
+        stack.push(this);
+
+        while (!stack.isEmpty()) {
+            Layer layer = stack.pop();
+            allLayers.add(layer);
+
+            // push in reverse order
+            for (int i = layer.parents.size() - 1; i >= 0; i--) {
+                Layer parent = layer.parents.get(i);
+                if (!visited.contains(parent)) {
+                    visited.add(parent);
+                    stack.push(parent);
+                }
+            }
+        }
+
+        this.allLayers = allLayers = Collections.unmodifiableList(allLayers);
+        return allLayers.stream();
+    }
+
+    private volatile List<Layer> allLayers;
+
+    /**
+     * Returns the set of the modules in this layer.
      *
      * @return A possibly-empty unmodifiable set of the modules in this layer
      */
@@ -486,7 +790,11 @@
 
     /**
      * Returns the module with the given name in this layer, or if not in this
-     * layer, the {@linkplain #parent parent} layer.
+     * layer, the {@linkplain #parents parents} layers. Finding a module in
+     * parent layers is equivalent to invoking {@code findModule} on each
+     * parent, in search order, until the module is found or all parents have
+     * been searched. In a <em>tree of layers</em>  then this is equivalent to
+     * a depth-first search.
      *
      * @param  name
      *         The name of the module to find
@@ -496,17 +804,25 @@
      *         parent layer
      */
     public Optional<Module> findModule(String name) {
-        Module m = nameToModule.get(Objects.requireNonNull(name));
+        Objects.requireNonNull(name);
+        Module m = nameToModule.get(name);
         if (m != null)
             return Optional.of(m);
-        return parent().flatMap(l -> l.findModule(name));
+
+        return layers()
+                .skip(1)  // skip this layer
+                .map(l -> l.nameToModule)
+                .filter(map -> map.containsKey(name))
+                .map(map -> map.get(name))
+                .findAny();
     }
 
 
     /**
      * Returns the {@code ClassLoader} for the module with the given name. If
-     * a module of the given name is not in this layer then the {@link #parent}
-     * layer is checked.
+     * a module of the given name is not in this layer then the {@link #parents
+     * parent} layers are searched in the manner specified by {@link
+     * #findModule(String) findModule}.
      *
      * <p> If there is a security manager then its {@code checkPermission}
      * method is called with a {@code RuntimePermission("getClassLoader")}
@@ -527,20 +843,32 @@
      * @throws SecurityException if denied by the security manager
      */
     public ClassLoader findLoader(String name) {
-        Module m = nameToModule.get(Objects.requireNonNull(name));
-        if (m != null)
-            return m.getClassLoader();
-        Optional<Layer> ol = parent();
-        if (ol.isPresent())
-            return ol.get().findLoader(name);
-        throw new IllegalArgumentException("Module " + name
-                                           + " not known to this layer");
+        Optional<Module> om = findModule(name);
+
+        // can't use map(Module::getClassLoader) as class loader can be null
+        if (om.isPresent()) {
+            return om.get().getClassLoader();
+        } else {
+            throw new IllegalArgumentException("Module " + name
+                                               + " not known to this layer");
+        }
     }
 
+    /**
+     * Returns a string describing this layer.
+     *
+     * @return A possibly empty string describing this layer
+     */
+    @Override
+    public String toString() {
+        return modules().stream()
+                .map(Module::getName)
+                .collect(Collectors.joining(", "));
+    }
 
     /**
      * Returns the <em>empty</em> layer. There are no modules in the empty
-     * layer. It has no parent.
+     * layer. It has no parents.
      *
      * @return The empty layer
      */
@@ -572,39 +900,12 @@
         if (servicesCatalog != null)
             return servicesCatalog;
 
-        Map<String, Set<ServiceProvider>> map = new HashMap<>();
-        for (Module m : nameToModule.values()) {
-            ModuleDescriptor descriptor = m.getDescriptor();
-            for (Provides provides : descriptor.provides().values()) {
-                String service = provides.service();
-                Set<ServiceProvider> providers
-                    = map.computeIfAbsent(service, k -> new HashSet<>());
-                for (String pn : provides.providers()) {
-                    providers.add(new ServiceProvider(m, pn));
-                }
-            }
-        }
-
-        ServicesCatalog catalog = new ServicesCatalog() {
-            @Override
-            public void register(Module module) {
-                throw new UnsupportedOperationException();
-            }
-            @Override
-            public Set<ServiceProvider> findServices(String service) {
-                Set<ServiceProvider> providers = map.get(service);
-                if (providers == null) {
-                    return Collections.emptySet();
-                } else {
-                    return Collections.unmodifiableSet(providers);
-                }
-            }
-        };
-
         synchronized (this) {
             servicesCatalog = this.servicesCatalog;
             if (servicesCatalog == null) {
-                this.servicesCatalog = servicesCatalog = catalog;
+                servicesCatalog = ServicesCatalog.create();
+                nameToModule.values().forEach(servicesCatalog::register);
+                this.servicesCatalog = servicesCatalog;
             }
         }
 
@@ -612,4 +913,36 @@
     }
 
     private volatile ServicesCatalog servicesCatalog;
+
+
+    /**
+     * Record that this layer has at least one module defined to the given
+     * class loader.
+     */
+    void bindToLoader(ClassLoader loader) {
+        // CLV.computeIfAbsent(loader, (cl, clv) -> new CopyOnWriteArrayList<>())
+        List<Layer> list = CLV.get(loader);
+        if (list == null) {
+            list = new CopyOnWriteArrayList<>();
+            List<Layer> previous = CLV.putIfAbsent(loader, list);
+            if (previous != null) list = previous;
+        }
+        list.add(this);
+    }
+
+    /**
+     * Returns a stream of the layers that have at least one module defined to
+     * the given class loader.
+     */
+    static Stream<Layer> layers(ClassLoader loader) {
+        List<Layer> list = CLV.get(loader);
+        if (list != null) {
+            return list.stream();
+        } else {
+            return Stream.empty();
+        }
+    }
+
+    // the list of layers with modules defined to a class loader
+    private static final ClassLoaderValue<List<Layer>> CLV = new ClassLoaderValue<>();
 }
--- a/src/java.base/share/classes/java/lang/reflect/Module.java	Thu Dec 01 21:01:53 2016 +0000
+++ b/src/java.base/share/classes/java/lang/reflect/Module.java	Thu Dec 01 21:39:49 2016 +0000
@@ -27,15 +27,18 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.annotation.Annotation;
 import java.lang.module.Configuration;
 import java.lang.module.ModuleReference;
 import java.lang.module.ModuleDescriptor;
 import java.lang.module.ModuleDescriptor.Exports;
-import java.lang.module.ModuleDescriptor.Provides;
+import java.lang.module.ModuleDescriptor.Opens;
 import java.lang.module.ModuleDescriptor.Version;
 import java.lang.module.ResolvedModule;
 import java.net.URI;
 import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -49,9 +52,17 @@
 
 import jdk.internal.loader.BuiltinClassLoader;
 import jdk.internal.loader.BootLoader;
+import jdk.internal.loader.ResourceHelper;
+import jdk.internal.misc.JavaLangAccess;
 import jdk.internal.misc.JavaLangReflectModuleAccess;
 import jdk.internal.misc.SharedSecrets;
 import jdk.internal.module.ServicesCatalog;
+import jdk.internal.org.objectweb.asm.AnnotationVisitor;
+import jdk.internal.org.objectweb.asm.Attribute;
+import jdk.internal.org.objectweb.asm.ClassReader;
+import jdk.internal.org.objectweb.asm.ClassVisitor;
+import jdk.internal.org.objectweb.asm.ClassWriter;
+import jdk.internal.org.objectweb.asm.Opcodes;
 import jdk.internal.reflect.CallerSensitive;
 import jdk.internal.reflect.Reflection;
 import sun.security.util.SecurityConstants;
@@ -83,7 +94,7 @@
  * @see java.lang.Class#getModule
  */
 
-public final class Module {
+public final class Module implements AnnotatedElement {
 
     // the layer that contains this module, can be null
     private final Layer layer;
@@ -113,6 +124,10 @@
 
         // define module to VM
 
+        boolean isOpen = descriptor.isOpen();
+        Version version = descriptor.version().orElse(null);
+        String vs = Objects.toString(version, null);
+        String loc = Objects.toString(uri, null);
         Set<String> packages = descriptor.packages();
         int n = packages.size();
         String[] array = new String[n];
@@ -120,11 +135,7 @@
         for (String pn : packages) {
             array[i++] = pn.replace('.', '/');
         }
-        Version version = descriptor.version().orElse(null);
-        String vs = Objects.toString(version, null);
-        String loc = Objects.toString(uri, null);
-
-        defineModule0(this, vs, loc, array);
+        defineModule0(this, isOpen, vs, loc, array);
     }
 
 
@@ -240,24 +251,24 @@
 
     // --
 
-    // the special Module to mean reads or exported to "all unnamed modules"
+    // special Module to mean "all unnamed modules"
     private static final Module ALL_UNNAMED_MODULE = new Module(null);
 
-    // special Module to mean exported to "everyone"
+    // special Module to mean "everyone"
     private static final Module EVERYONE_MODULE = new Module(null);
 
-    // exported to all modules
-    private static final Set<Module> EVERYONE = Collections.singleton(EVERYONE_MODULE);
+    // set contains EVERYONE_MODULE, used when a package is opened or
+    // exported unconditionally
+    private static final Set<Module> EVERYONE_SET = Set.of(EVERYONE_MODULE);
 
 
     // -- readability --
 
-    // the modules that this module permanently reads
-    // (will be final when the modules are defined in reverse topology order)
+    // the modules that this module reads
     private volatile Set<Module> reads;
 
     // additional module (2nd key) that some module (1st key) reflectively reads
-    private static final WeakPairMap<Module, Module, Boolean> transientReads
+    private static final WeakPairMap<Module, Module, Boolean> reflectivelyReads
         = new WeakPairMap<>();
 
 
@@ -293,13 +304,13 @@
         }
 
         // check if this module reads the other module reflectively
-        if (transientReads.containsKeyPair(this, other))
+        if (reflectivelyReads.containsKeyPair(this, other))
             return true;
 
         // if other is an unnamed module then check if this module reads
         // all unnamed modules
         if (!other.isNamed()
-            && transientReads.containsKeyPair(this, ALL_UNNAMED_MODULE))
+            && reflectivelyReads.containsKeyPair(this, ALL_UNNAMED_MODULE))
             return true;
 
         return false;
@@ -309,9 +320,13 @@
      * If the caller's module is this module then update this module to read
      * the given module.
      *
-     * This method is a no-op if {@code other} is this module (all modules can
-     * read themselves) or this module is an unnamed module (as unnamed modules
-     * read all modules).
+     * This method is a no-op if {@code other} is this module (all modules read
+     * themselves), this module is an unnamed module (as unnamed modules read
+     * all modules), or this module already reads {@code other}.
+     *
+     * @implNote <em>Read edges</em> added by this method are <em>weak</em> and
+     * do not prevent {@code other} from being GC'ed when this module is
+     * strongly reachable.
      *
      * @param  other
      *         The other module
@@ -381,30 +396,39 @@
         }
 
         // add reflective read
-        transientReads.putIfAbsent(this, other, Boolean.TRUE);
+        reflectivelyReads.putIfAbsent(this, other, Boolean.TRUE);
     }
 
 
-    // -- exports --
+    // -- exported and open packages --
 
-    // the packages that are permanently exported
-    // (will be final when the modules are defined in reverse topology order)
-    private volatile Map<String, Set<Module>> exports;
+    // the packages are open to other modules, can be null
+    // if the value contains EVERYONE_MODULE then the package is open to all
+    private volatile Map<String, Set<Module>> openPackages;
 
-    // additional exports added at run-time
-    // this module (1st key), other module (2nd key), exported packages (value)
+    // the packages that are exported, can be null
+    // if the value contains EVERYONE_MODULE then the package is exported to all
+    private volatile Map<String, Set<Module>> exportedPackages;
+
+    // additional exports or opens added at run-time
+    // this module (1st key), other module (2nd key)
+    // (package name, open?) (value)
     private static final WeakPairMap<Module, Module, Map<String, Boolean>>
-        transientExports = new WeakPairMap<>();
+        reflectivelyExports = new WeakPairMap<>();
 
 
     /**
      * Returns {@code true} if this module exports the given package to at
      * least the given module.
      *
-     * <p> This method always return {@code true} when invoked on an unnamed
+     * <p> This method returns {@code true} if invoked to test if a package in
+     * this module is exported to itself. It always returns {@code true} when
+     * invoked on an unnamed module. A package that is {@link #isOpen open} to
+     * the given module is considered exported to that module at run-time and
+     * so this method returns {@code true} if the package is open to the given
      * module. </p>
      *
-     * <p> This method does not check if the given module reads this module </p>
+     * <p> This method does not check if the given module reads this module. </p>
      *
      * @param  pn
      *         The package name
@@ -413,93 +437,196 @@
      *
      * @return {@code true} if this module exports the package to at least the
      *         given module
+     *
+     * @see ModuleDescriptor#exports()
+     * @see #addExports(String,Module)
      */
     public boolean isExported(String pn, Module other) {
         Objects.requireNonNull(pn);
         Objects.requireNonNull(other);
-        return implIsExported(pn, other);
+        return implIsExportedOrOpen(pn, other, /*open*/false);
+    }
+
+    /**
+     * Returns {@code true} if this module has <em>opened</em> a package to at
+     * least the given module.
+     *
+     * <p> This method returns {@code true} if invoked to test if a package in
+     * this module is open to itself. It returns {@code true} when invoked on an
+     * {@link ModuleDescriptor#isOpen open} module with a package in the module.
+     * It always returns {@code true} when invoked on an unnamed module. </p>
+     *
+     * <p> This method does not check if the given module reads this module. </p>
+     *
+     * @param  pn
+     *         The package name
+     * @param  other
+     *         The other module
+     *
+     * @return {@code true} if this module has <em>opened</em> the package
+     *         to at least the given module
+     *
+     * @see ModuleDescriptor#opens()
+     * @see #addOpens(String,Module)
+     * @see AccessibleObject#setAccessible(boolean)
+     * @see java.lang.invoke.MethodHandles#privateLookupIn
+     */
+    public boolean isOpen(String pn, Module other) {
+        Objects.requireNonNull(pn);
+        Objects.requireNonNull(other);
+        return implIsExportedOrOpen(pn, other, /*open*/true);
     }
 
     /**
      * Returns {@code true} if this module exports the given package
      * unconditionally.
      *
-     * <p> This method always return {@code true} when invoked on an unnamed
-     * module. </p>
+     * <p> This method always returns {@code true} when invoked on an unnamed
+     * module. A package that is {@link #isOpen(String) opened} unconditionally
+     * is considered exported unconditionally at run-time and so this method
+     * returns {@code true} if the package is opened unconditionally. </p>
      *
-     * <p> This method does not check if the given module reads this module </p>
+     * <p> This method does not check if the given module reads this module. </p>
      *
      * @param  pn
      *         The package name
      *
      * @return {@code true} if this module exports the package unconditionally
+     *
+     * @see ModuleDescriptor#exports()
      */
     public boolean isExported(String pn) {
         Objects.requireNonNull(pn);
-        return implIsExported(pn, EVERYONE_MODULE);
+        return implIsExportedOrOpen(pn, EVERYONE_MODULE, /*open*/false);
     }
 
     /**
-     * Returns {@code true} if this module exports the given package to the
-     * given module. If the other module is {@code EVERYONE_MODULE} then
-     * this method tests if the package is exported unconditionally.
+     * Returns {@code true} if this module has <em>opened</em> a package
+     * unconditionally.
+     *
+     * <p> This method always returns {@code true} when invoked on an unnamed
+     * module. Additionally, it always returns {@code true} when invoked on an
+     * {@link ModuleDescriptor#isOpen open} module with a package in the
+     * module. </p>
+     *
+     * <p> This method does not check if the given module reads this module. </p>
+     *
+     * @param  pn
+     *         The package name
+     *
+     * @return {@code true} if this module has <em>opened</em> the package
+     *         unconditionally
+     *
+     * @see ModuleDescriptor#opens()
      */
-    private boolean implIsExported(String pn, Module other) {
+    public boolean isOpen(String pn) {
+        Objects.requireNonNull(pn);
+        return implIsExportedOrOpen(pn, EVERYONE_MODULE, /*open*/true);
+    }
 
-        // all packages are exported by unnamed modules
+
+    /**
+     * Returns {@code true} if this module exports or opens the given package
+     * to the given module. If the other module is {@code EVERYONE_MODULE} then
+     * this method tests if the package is exported or opened unconditionally.
+     */
+    private boolean implIsExportedOrOpen(String pn, Module other, boolean open) {
+        // all packages in unnamed modules are open
         if (!isNamed())
             return true;
 
-        // exported via module declaration/descriptor
-        if (isExportedPermanently(pn, other))
+        // all packages are exported/open to self
+        if (other == this && containsPackage(pn))
             return true;
 
-        // exported via addExports
-        if (isExportedReflectively(pn, other))
+        // all packages in open modules are open
+        if (descriptor.isOpen())
+            return containsPackage(pn);
+
+        // exported/opened via module declaration/descriptor
+        if (isStaticallyExportedOrOpen(pn, other, open))
             return true;
 
-        // not exported or not exported to other
+        // exported via addExports/addOpens
+        if (isReflectivelyExportedOrOpen(pn, other, open))
+            return true;
+
+        // not exported or open to other
         return false;
     }
 
     /**
-     * Returns {@code true} if this module permanently exports the given
-     * package to the given module.
+     * Returns {@code true} if this module exports or opens a package to
+     * the given module via its module declaration.
      */
-    private boolean isExportedPermanently(String pn, Module other) {
-        Map<String, Set<Module>> exports = this.exports;
-        if (exports != null) {
-            Set<Module> targets = exports.get(pn);
+    private boolean isStaticallyExportedOrOpen(String pn, Module other, boolean open) {
+        // package is open to everyone or <other>
+        Map<String, Set<Module>> openPackages = this.openPackages;
+        if (openPackages != null) {
+            Set<Module> targets = openPackages.get(pn);
+            if (targets != null) {
+                if (targets.contains(EVERYONE_MODULE))
+                    return true;
+                if (other != EVERYONE_MODULE && targets.contains(other))
+                    return true;
+            }
+        }
 
-            if ((targets != null)
-                && (targets.contains(other) || targets.contains(EVERYONE_MODULE)))
-                return true;
+        if (!open) {
+            // package is exported to everyone or <other>
+            Map<String, Set<Module>> exportedPackages = this.exportedPackages;
+            if (exportedPackages != null) {
+                Set<Module> targets = exportedPackages.get(pn);
+                if (targets != null) {
+                    if (targets.contains(EVERYONE_MODULE))
+                        return true;
+                    if (other != EVERYONE_MODULE && targets.contains(other))
+                        return true;
+                }
+            }
         }
+
         return false;
     }
 
+
     /**
-     * Returns {@code true} if this module reflectively exports the given
+     * Returns {@code true} if this module reflectively exports or opens given
      * package package to the given module.
      */
-    private boolean isExportedReflectively(String pn, Module other) {
-        // exported to all modules
-        Map<String, ?> exports = transientExports.get(this, EVERYONE_MODULE);
-        if (exports != null && exports.containsKey(pn))
-            return true;
+    private boolean isReflectivelyExportedOrOpen(String pn, Module other, boolean open) {
+        // exported or open to all modules
+        Map<String, Boolean> exports = reflectivelyExports.get(this, EVERYONE_MODULE);
+        if (exports != null) {
+            Boolean b = exports.get(pn);
+            if (b != null) {
+                boolean isOpen = b.booleanValue();
+                if (!open || isOpen) return true;
+            }
+        }
 
         if (other != EVERYONE_MODULE) {
 
-            // exported to other
-            exports = transientExports.get(this, other);
-            if (exports != null && exports.containsKey(pn))
-                return true;
+            // exported or open to other
+            exports = reflectivelyExports.get(this, other);
+            if (exports != null) {
+                Boolean b = exports.get(pn);
+                if (b != null) {
+                    boolean isOpen = b.booleanValue();
+                    if (!open || isOpen) return true;
+                }
+            }
 
-            // other is an unnamed module && exported to all unnamed
+            // other is an unnamed module && exported or open to all unnamed
             if (!other.isNamed()) {
-                exports = transientExports.get(this, ALL_UNNAMED_MODULE);
-                if (exports != null && exports.containsKey(pn))
-                    return true;
+                exports = reflectivelyExports.get(this, ALL_UNNAMED_MODULE);
+                if (exports != null) {
+                    Boolean b = exports.get(pn);
+                    if (b != null) {
+                        boolean isOpen = b.booleanValue();
+                        if (!open || isOpen) return true;
+                    }
+                }
             }
 
         }
@@ -510,11 +637,11 @@
 
     /**
      * If the caller's module is this module then update this module to export
-     * package {@code pn} to the given module.
+     * the given package to the given module.
      *
-     * <p> This method has no effect if the package is already exported to the
-     * given module. It also has no effect if invoked on an unnamed module (as
-     * unnamed modules export all packages). </p>
+     * <p> This method has no effect if the package is already exported (or
+     * <em>open</em>) to the given module. It also has no effect if
+     * invoked on an {@link ModuleDescriptor#isOpen open} module. </p>
      *
      * @param  pn
      *         The package name
@@ -528,6 +655,8 @@
      *         package {@code pn} is not a package in this module
      * @throws IllegalStateException
      *         If this is a named module and the caller is not this module
+     *
+     * @see #isExported(String,Module)
      */
     @CallerSensitive
     public Module addExports(String pn, Module other) {
@@ -535,18 +664,66 @@
             throw new IllegalArgumentException("package is null");
         Objects.requireNonNull(other);
 
-        if (isNamed()) {
+        if (isNamed() && !descriptor.isOpen()) {
             Module caller = Reflection.getCallerClass().getModule();
             if (caller != this) {
                 throw new IllegalStateException(caller + " != " + this);
             }
-            implAddExports(pn, other, true);
+            implAddExportsOrOpens(pn, other, /*open*/false, /*syncVM*/true);
         }
 
         return this;
     }
 
     /**
+     * If the caller's module is this module then update this module to
+     * <em>open</em> the given package to the given module.
+     * Opening a package with this method allows all types in the package,
+     * and all their members, not just public types and their public members,
+     * to be reflected on by the given module when using APIs that support
+     * private access or a way to bypass or suppress default Java language
+     * access control checks.
+     *
+     * <p> This method has no effect if the package is already <em>open</em>
+     * to the given module. It also has no effect if invoked on an {@link
+     * ModuleDescriptor#isOpen open} module. </p>
+     *
+     * @param  pn
+     *         The package name
+     * @param  other
+     *         The module
+     *
+     * @return this module
+     *
+     * @throws IllegalArgumentException
+     *         If {@code pn} is {@code null}, or this is a named module and the
+     *         package {@code pn} is not a package in this module
+     * @throws IllegalStateException
+     *         If this is a named module and the caller is not this module
+     *
+     * @see #isOpen(String,Module)
+     * @see AccessibleObject#setAccessible(boolean)
+     * @see java.lang.invoke.MethodHandles#privateLookupIn
+     */
+    @CallerSensitive
+    public Module addOpens(String pn, Module other) {
+        if (pn == null)
+            throw new IllegalArgumentException("package is null");
+        Objects.requireNonNull(other);
+
+        if (isNamed() && !descriptor.isOpen()) {
+            Module caller = Reflection.getCallerClass().getModule();
+            if (caller != this) {
+                throw new IllegalStateException(caller + " != " + this);
+            }
+            implAddExportsOrOpens(pn, other, /*open*/true, /*syncVM*/true);
+        }
+
+        return this;
+    }
+
+
+    /**
      * Updates the exports so that package {@code pn} is exported to module
      * {@code other} but without notifying the VM.
      *
@@ -555,7 +732,7 @@
     void implAddExportsNoSync(String pn, Module other) {
         if (other == null)
             other = EVERYONE_MODULE;
-        implAddExports(pn.replace('/', '.'), other, false);
+        implAddExportsOrOpens(pn.replace('/', '.'), other, false, false);
     }
 
     /**
@@ -565,25 +742,36 @@
      * @apiNote This method is for white-box testing.
      */
     void implAddExports(String pn, Module other) {
-        implAddExports(pn, other, true);
+        implAddExportsOrOpens(pn, other, false, true);
     }
 
     /**
-     * Updates the exports so that package {@code pn} is exported to module
-     * {@code other}.
+     * Updates the module to open package {@code pn} to module {@code other}.
+     *
+     * @apiNote This method is for white-box tests and jtreg
+     */
+    void implAddOpens(String pn, Module other) {
+        implAddExportsOrOpens(pn, other, true, true);
+    }
+
+    /**
+     * Updates a module to export or open a module to another module.
      *
      * If {@code syncVM} is {@code true} then the VM is notified.
      */
-    private void implAddExports(String pn, Module other, boolean syncVM) {
+    private void implAddExportsOrOpens(String pn,
+                                       Module other,
+                                       boolean open,
+                                       boolean syncVM) {
         Objects.requireNonNull(other);
         Objects.requireNonNull(pn);
 
-        // unnamed modules export all packages
-        if (!isNamed())
+        // all packages are open in unnamed and open modules
+        if (!isNamed() || descriptor.isOpen())
             return;
 
-        // nothing to do if already exported to other
-        if (implIsExported(pn, other))
+        // nothing to do if already exported/open to other
+        if (implIsExportedOrOpen(pn, other, open))
             return;
 
         // can only export a package in the module
@@ -604,18 +792,23 @@
             }
         }
 
-        // add package name to transientExports if absent
-        transientExports
+        // add package name to reflectivelyExports if absent
+        Map<String, Boolean> map = reflectivelyExports
             .computeIfAbsent(this, other,
-                             (_this, _other) -> new ConcurrentHashMap<>())
-            .putIfAbsent(pn, Boolean.TRUE);
+                             (m1, m2) -> new ConcurrentHashMap<>());
+
+        if (open) {
+            map.put(pn, Boolean.TRUE);  // may need to promote from FALSE to TRUE
+        } else {
+            map.putIfAbsent(pn, Boolean.FALSE);
+        }
     }
 
 
     // -- services --
 
     // additional service type (2nd key) that some module (1st key) uses
-    private static final WeakPairMap<Module, Class<?>, Boolean> transientUses
+    private static final WeakPairMap<Module, Class<?>, Boolean> reflectivelyUses
         = new WeakPairMap<>();
 
     /**
@@ -624,13 +817,13 @@
      * for use by frameworks that invoke {@link java.util.ServiceLoader
      * ServiceLoader} on behalf of other modules or where the framework is
      * passed a reference to the service type by other code. This method is
-     * a no-op when invoked on an unnamed module.
+     * a no-op when invoked on an unnamed module or an automatic module.
      *
      * <p> This method does not cause {@link
      * Configuration#resolveRequiresAndUses resolveRequiresAndUses} to be
      * re-run. </p>
      *
-     * @param  st