8156497: Add jar tool support for Multi-Release Modular JARs
authorchegar
Mon, 23 May 2016 21:28:39 +0100
changeset 38468 d459a0f8fe72
parent 38467 172e0c9007a6
child 38469 0e4ababda6a9
8156497: Add jar tool support for Multi-Release Modular JARs Reviewed-by: alanb
jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java
jdk/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties
jdk/test/tools/jar/modularJar/Basic.java
jdk/test/tools/jar/modularJar/src/bar/jdk/test/bar/Bar.java
jdk/test/tools/jar/modularJar/src/bar/jdk/test/bar/internal/Message.java
jdk/test/tools/jar/modularJar/src/bar/module-info.java
jdk/test/tools/jar/modularJar/src/baz/jdk/test/baz/BazService.java
jdk/test/tools/jar/modularJar/src/baz/jdk/test/baz/internal/BazServiceImpl.java
jdk/test/tools/jar/modularJar/src/baz/module-info.java
jdk/test/tools/jar/modularJar/src/foo/jdk/test/foo/Foo.java
jdk/test/tools/jar/modularJar/src/foo/jdk/test/foo/internal/Message.java
jdk/test/tools/jar/modularJar/src/foo/module-info.java
--- a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java	Mon May 23 12:44:55 2016 -0700
+++ b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java	Mon May 23 21:28:39 2016 +0100
@@ -38,6 +38,7 @@
 import java.lang.module.ResolutionException;
 import java.lang.module.ResolvedModule;
 import java.net.URI;
+import java.nio.ByteBuffer;
 import java.nio.file.Path;
 import java.nio.file.Files;
 import java.nio.file.Paths;
@@ -62,6 +63,7 @@
 import static jdk.internal.util.jar.JarIndex.INDEX_NAME;
 import static java.util.jar.JarFile.MANIFEST_NAME;
 import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toSet;
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 
 /**
@@ -129,10 +131,8 @@
 
     private static final String MODULE_INFO = "module-info.class";
 
-    Path moduleInfo;
-    private boolean isModularJar() { return moduleInfo != null; }
-
     static final String MANIFEST_DIR = "META-INF/";
+    static final String VERSIONS_DIR = MANIFEST_DIR + "versions/";
     static final String VERSION = "1.0";
 
     private static ResourceBundle rsrc;
@@ -242,12 +242,27 @@
                         addMainClass(manifest, ename);
                     }
                 }
-                expand(null, files, false);
+                Map<String,Path> moduleInfoPaths = new HashMap<>();
+                expand(null, files, false, moduleInfoPaths);
+
+                Map<String,byte[]> moduleInfos = new LinkedHashMap<>();
+                if (!moduleInfoPaths.isEmpty()) {
+                    if (!checkModuleInfos(moduleInfoPaths))
+                        return false;
 
-                byte[] moduleInfoBytes = null;
-                if (isModularJar()) {
-                    moduleInfoBytes = addExtendedModuleAttributes(
-                            readModuleInfo(moduleInfo));
+                    // root module-info first
+                    byte[] b = readModuleInfo(moduleInfoPaths.get(MODULE_INFO));
+                    moduleInfos.put(MODULE_INFO, b);
+                    for (Map.Entry<String,Path> e : moduleInfoPaths.entrySet())
+                        moduleInfos.putIfAbsent(e.getKey(), readModuleInfo(e.getValue()));
+
+                    if (!addExtendedModuleAttributes(moduleInfos))
+                        return false;
+
+                    // Basic consistency checks for modular jars.
+                    if (!checkServices(moduleInfos.get(MODULE_INFO)))
+                        return false;
+
                 } else if (moduleVersion != null || modulesToHash != null) {
                     error(getMsg("error.module.options.without.info"));
                     return false;
@@ -274,13 +289,7 @@
                     tmpfile = createTemporaryFile(tmpbase, ".jar");
                     out = new FileOutputStream(tmpfile);
                 }
-                create(new BufferedOutputStream(out, 4096), manifest, moduleInfoBytes);
-
-                // Consistency checks for modular jars.
-                if (isModularJar()) {
-                    if (!checkServices(moduleInfoBytes))
-                        return false;
-                }
+                create(new BufferedOutputStream(out, 4096), manifest, moduleInfos);
 
                 if (in != null) {
                     in.close();
@@ -337,19 +346,20 @@
                 }
                 InputStream manifest = (!Mflag && (mname != null)) ?
                     (new FileInputStream(mname)) : null;
-                expand(null, files, true);
+
+                Map<String,Path> moduleInfoPaths = new HashMap<>();
+                expand(null, files, true, moduleInfoPaths);
 
-                byte[] moduleInfoBytes = null;
-                if (isModularJar()) {
-                    moduleInfoBytes = readModuleInfo(moduleInfo);
-                }
+                Map<String,byte[]> moduleInfos = new HashMap<>();
+                for (Map.Entry<String,Path> e : moduleInfoPaths.entrySet())
+                    moduleInfos.put(e.getKey(), readModuleInfo(e.getValue()));
 
                 boolean updateOk = update(in, new BufferedOutputStream(out),
-                                          manifest, moduleInfoBytes, null);
+                                          manifest, moduleInfos, null);
 
                 // Consistency checks for modular jars.
-                if (isModularJar()) {
-                    if(!checkServices(moduleInfoBytes))
+                if (!moduleInfos.isEmpty()) {
+                    if(!checkServices(moduleInfos.get(MODULE_INFO)))
                         return false;
                 }
 
@@ -638,7 +648,12 @@
      * Expands list of files to process into full list of all files that
      * can be found by recursively descending directories.
      */
-    void expand(File dir, String[] files, boolean isUpdate) throws IOException {
+    void expand(File dir,
+                String[] files,
+                boolean isUpdate,
+                Map<String,Path> moduleInfoPaths)
+        throws IOException
+    {
         if (files == null)
             return;
 
@@ -651,18 +666,17 @@
 
             if (f.isFile()) {
                 String path = f.getPath();
-                if (entryName(path).equals(MODULE_INFO)) {
-                    if (moduleInfo != null && vflag)
-                        output(formatMsg("error.unexpected.module-info", path));
-                    moduleInfo = f.toPath();
+                String entryName = entryName(path);
+                if (entryName.endsWith(MODULE_INFO)) {
+                    moduleInfoPaths.put(entryName, f.toPath());
                     if (isUpdate)
-                        entryMap.put(entryName(path), f);
+                        entryMap.put(entryName, f);
                 } else if (entries.add(f)) {
-                    jarEntries.add(entryName(path));
-                    if (path.endsWith(".class"))
-                        packages.add(toPackageName(entryName(path)));
+                    jarEntries.add(entryName);
+                    if (path.endsWith(".class") && !entryName.startsWith(VERSIONS_DIR))
+                        packages.add(toPackageName(entryName));
                     if (isUpdate)
-                        entryMap.put(entryName(f.getPath()), f);
+                        entryMap.put(entryName, f);
                 }
             } else if (f.isDirectory()) {
                 if (entries.add(f)) {
@@ -672,7 +686,7 @@
                             (dirPath + File.separator);
                         entryMap.put(entryName(dirPath), f);
                     }
-                    expand(f, f.list(), isUpdate);
+                    expand(f, f.list(), isUpdate, moduleInfoPaths);
                 }
             } else {
                 error(formatMsg("error.nosuch.fileordir", String.valueOf(f)));
@@ -684,7 +698,7 @@
     /**
      * Creates a new JAR file.
      */
-    void create(OutputStream out, Manifest manifest, byte[] moduleInfoBytes)
+    void create(OutputStream out, Manifest manifest, Map<String,byte[]> moduleInfos)
         throws IOException
     {
         ZipOutputStream zos = new JarOutputStream(out);
@@ -710,17 +724,19 @@
             manifest.write(zos);
             zos.closeEntry();
         }
-        if (moduleInfoBytes != null) {
+        for (Map.Entry<String,byte[]> mi : moduleInfos.entrySet()) {
+            String entryName = mi.getKey();
+            byte[] miBytes = mi.getValue();
             if (vflag) {
-                output(getMsg("out.added.module-info"));
+                output(formatMsg("out.added.module-info", entryName));
             }
-            ZipEntry e = new ZipEntry(MODULE_INFO);
+            ZipEntry e = new ZipEntry(mi.getKey());
             e.setTime(System.currentTimeMillis());
             if (flag0) {
-                crc32ModuleInfo(e, moduleInfoBytes);
+                crc32ModuleInfo(e, miBytes);
             }
             zos.putNextEntry(e);
-            ByteArrayInputStream in = new ByteArrayInputStream(moduleInfoBytes);
+            ByteArrayInputStream in = new ByteArrayInputStream(miBytes);
             in.transferTo(zos);
             zos.closeEntry();
         }
@@ -755,18 +771,41 @@
     }
 
     /**
+     * Returns true of the given module-info's are located in acceptable
+     * locations.  Otherwise, outputs an appropriate message and returns false.
+     */
+    private boolean checkModuleInfos(Map<String,?> moduleInfos) {
+        // there must always be, at least, a root module-info
+        if (!moduleInfos.containsKey(MODULE_INFO)) {
+            error(getMsg("error.versioned.info.without.root"));
+            return false;
+        }
+
+        // module-info can only appear in the root, or a versioned section
+        Optional<String> other = moduleInfos.keySet().stream()
+                .filter(x -> !x.equals(MODULE_INFO))
+                .filter(x -> !x.startsWith(VERSIONS_DIR))
+                .findFirst();
+
+        if (other.isPresent()) {
+            error(formatMsg("error.unexpected.module-info", other.get()));
+            return false;
+        }
+        return true;
+    }
+
+    /**
      * Updates an existing jar file.
      */
     boolean update(InputStream in, OutputStream out,
                    InputStream newManifest,
-                   byte[] newModuleInfoBytes,
+                   Map<String,byte[]> moduleInfos,
                    JarIndex jarIndex) throws IOException
     {
         ZipInputStream zis = new ZipInputStream(in);
         ZipOutputStream zos = new JarOutputStream(out);
         ZipEntry e = null;
         boolean foundManifest = false;
-        boolean foundModuleInfo = false;
         boolean updateOk = true;
 
         if (jarIndex != null) {
@@ -778,7 +817,7 @@
             String name = e.getName();
 
             boolean isManifestEntry = equalsIgnoreCase(name, MANIFEST_NAME);
-            boolean isModuleInfoEntry = name.equals(MODULE_INFO);
+            boolean isModuleInfoEntry = name.endsWith(MODULE_INFO);
 
             if ((jarIndex != null && equalsIgnoreCase(name, INDEX_NAME))
                 || (Mflag && isManifestEntry)) {
@@ -806,13 +845,8 @@
                 if (!updateManifest(old, zos)) {
                     return false;
                 }
-            } else if (isModuleInfoEntry
-                       && ((newModuleInfoBytes != null) || (ename != null)
-                           || moduleVersion != null || modulesToHash != null)) {
-                if (newModuleInfoBytes == null) {
-                    // Update existing module-info.class
-                    newModuleInfoBytes = readModuleInfo(zis);
-                }
+            } else if (moduleInfos != null && isModuleInfoEntry) {
+                moduleInfos.putIfAbsent(name, readModuleInfo(zis));
             } else {
                 if (!entryMap.containsKey(name)) { // copy the old stuff
                     // do our own compression
@@ -860,13 +894,22 @@
             }
         }
 
-        // write the module-info.class
-        if (newModuleInfoBytes != null) {
-            newModuleInfoBytes = addExtendedModuleAttributes(newModuleInfoBytes);
+        if (moduleInfos != null && !moduleInfos.isEmpty()) {
+            if (!checkModuleInfos(moduleInfos))
+                updateOk = false;
+
+            if (updateOk) {
+                if (!addExtendedModuleAttributes(moduleInfos))
+                    updateOk = false;
+            }
 
             // TODO: check manifest main classes, etc
-            if (!updateModuleInfo(newModuleInfoBytes, zos)) {
-                updateOk = false;
+
+            if (updateOk) {
+                for (Map.Entry<String,byte[]> mi : moduleInfos.entrySet()) {
+                    if (!updateModuleInfo(mi.getValue(), zos, mi.getKey()))
+                        updateOk = false;
+                }
             }
         } else if (moduleVersion != null || modulesToHash != null) {
             error(getMsg("error.module.options.without.info"));
@@ -894,10 +937,10 @@
         zos.closeEntry();
     }
 
-    private boolean updateModuleInfo(byte[] moduleInfoBytes, ZipOutputStream zos)
+    private boolean updateModuleInfo(byte[] moduleInfoBytes, ZipOutputStream zos, String entryName)
         throws IOException
     {
-        ZipEntry e = new ZipEntry(MODULE_INFO);
+        ZipEntry e = new ZipEntry(entryName);
         e.setTime(System.currentTimeMillis());
         if (flag0) {
             crc32ModuleInfo(e, moduleInfoBytes);
@@ -905,7 +948,7 @@
         zos.putNextEntry(e);
         zos.write(moduleInfoBytes);
         if (vflag) {
-            output(getMsg("out.update.module-info"));
+            output(formatMsg("out.update.module-info", entryName));
         }
         return true;
     }
@@ -1710,13 +1753,11 @@
         return (classname.replace('.', '/')) + ".class";
     }
 
+    /* A module must have the implementation class of the services it 'provides'. */
     private boolean checkServices(byte[] moduleInfoBytes)
         throws IOException
     {
-        ModuleDescriptor md;
-        try (InputStream in = new ByteArrayInputStream(moduleInfoBytes)) {
-            md = ModuleDescriptor.read(in);
-        }
+        ModuleDescriptor md = ModuleDescriptor.read(ByteBuffer.wrap(moduleInfoBytes));
         Set<String> missing = md.provides()
                                 .values()
                                 .stream()
@@ -1732,63 +1773,140 @@
     }
 
     /**
-     * Returns a byte array containing the module-info.class.
+     * Adds extended modules attributes to the given module-info's.  The given
+     * Map values are updated in-place. Returns false if an error occurs.
+     */
+    private boolean addExtendedModuleAttributes(Map<String,byte[]> moduleInfos)
+        throws IOException
+    {
+        assert !moduleInfos.isEmpty() && moduleInfos.get(MODULE_INFO) != null;
+
+        ByteBuffer bb = ByteBuffer.wrap(moduleInfos.get(MODULE_INFO));
+        ModuleDescriptor rd = ModuleDescriptor.read(bb);
+
+        Set<String> exports = rd.exports()
+                                .stream()
+                                .map(Exports::source)
+                                .collect(toSet());
+
+        Set<String> conceals = packages.stream()
+                                       .filter(p -> !exports.contains(p))
+                                       .collect(toSet());
+
+        for (Map.Entry<String,byte[]> e: moduleInfos.entrySet()) {
+            ModuleDescriptor vd = ModuleDescriptor.read(ByteBuffer.wrap(e.getValue()));
+            if (!(isValidVersionedDescriptor(vd, rd)))
+                return false;
+            e.setValue(extendedInfoBytes(rd, vd, e.getValue(), conceals));
+        }
+        return true;
+    }
+
+    private static boolean isPlatformModule(String name) {
+        return name.startsWith("java.") || name.startsWith("jdk.");
+    }
+
+    /**
+     * Tells whether or not the given versioned module descriptor's attributes
+     * are valid when compared against the given root module descriptor.
+     *
+     * A versioned module descriptor must be identical to the root module
+     * descriptor, with two exceptions:
+     *  - A versioned descriptor can have different non-public `requires`
+     *    clauses of platform ( `java.*` and `jdk.*` ) modules, and
+     *  - A versioned descriptor can have different `uses` clauses, even of
+     *    service types defined outside of the platform modules.
+     */
+    private boolean isValidVersionedDescriptor(ModuleDescriptor vd,
+                                               ModuleDescriptor rd)
+        throws IOException
+    {
+        if (!rd.name().equals(vd.name())) {
+            fatalError(getMsg("error.versioned.info.name.notequal"));
+            return false;
+        }
+        if (!rd.requires().equals(vd.requires())) {
+            Set<Requires> rootRequires = rd.requires();
+            for (Requires r : vd.requires()) {
+                if (rootRequires.contains(r)) {
+                    continue;
+                } else if (r.modifiers().contains(Requires.Modifier.PUBLIC)) {
+                    fatalError(getMsg("error.versioned.info.requires.public"));
+                    return false;
+                } else if (!isPlatformModule(r.name())) {
+                    fatalError(getMsg("error.versioned.info.requires.added"));
+                    return false;
+                }
+            }
+            for (Requires r : rootRequires) {
+                Set<Requires> mdRequires = vd.requires();
+                if (mdRequires.contains(r)) {
+                    continue;
+                } else if (!isPlatformModule(r.name())) {
+                    fatalError(getMsg("error.versioned.info.requires.dropped"));
+                    return false;
+                }
+            }
+        }
+        if (!rd.exports().equals(vd.exports())) {
+            fatalError(getMsg("error.versioned.info.exports.notequal"));
+            return false;
+        }
+        if (!rd.provides().equals(vd.provides())) {
+            fatalError(getMsg("error.versioned.info.provides.notequal"));
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns a byte array containing the given module-info.class plus any
+     * extended attributes.
      *
      * If --module-version, --main-class, or other options were provided
      * then the corresponding class file attributes are added to the
      * module-info here.
      */
-    private byte[] addExtendedModuleAttributes(byte[] moduleInfoBytes)
+    private byte[] extendedInfoBytes(ModuleDescriptor rootDescriptor,
+                                     ModuleDescriptor md,
+                                     byte[] miBytes,
+                                     Set<String> conceals)
         throws IOException
     {
-        assert isModularJar();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        InputStream is = new ByteArrayInputStream(miBytes);
+        ModuleInfoExtender extender = ModuleInfoExtender.newExtender(is);
 
-        ModuleDescriptor md;
-        try (InputStream in = new ByteArrayInputStream(moduleInfoBytes)) {
-            md = ModuleDescriptor.read(in);
-        }
-        String name = md.name();
-        Set<String> exported = md.exports()
-                                 .stream()
-                                 .map(ModuleDescriptor.Exports::source)
-                                 .collect(Collectors.toSet());
+        // Add (or replace) the ConcealedPackages attribute
+        extender.conceals(conceals);
 
-        // copy the module-info.class into the jmod with the additional
-        // attributes for the version, main class and other meta data
-        try (InputStream in = new ByteArrayInputStream(moduleInfoBytes);
-             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
-            ModuleInfoExtender extender = ModuleInfoExtender.newExtender(in);
-
-            // Add (or replace) the ConcealedPackages attribute
-            Set<String> conceals = packages.stream()
-                                            .filter(p -> !exported.contains(p))
-                                            .collect(Collectors.toSet());
+        // --main-class
+        if (ename != null)
+            extender.mainClass(ename);
+        else if (rootDescriptor.mainClass().isPresent())
+            extender.mainClass(rootDescriptor.mainClass().get());
 
-            extender.conceals(conceals);
-
-            // --main-class
-            if (ename != null)
-                extender.mainClass(ename);
-
-            // --module-version
-            if (moduleVersion != null)
-                extender.version(moduleVersion);
+        // --module-version
+        if (moduleVersion != null)
+            extender.version(moduleVersion);
+        else if (rootDescriptor.version().isPresent())
+            extender.version(rootDescriptor.version().get());
 
-            // --hash-modules
-            if (modulesToHash != null) {
-                Hasher hasher = new Hasher(md, fname);
-                ModuleHashes moduleHashes = hasher.computeHashes(name);
-                if (moduleHashes != null) {
-                    extender.hashes(moduleHashes);
-                } else {
-                    // should it issue warning or silent?
-                    System.out.println("warning: no module is recorded in hash in " + name);
-                }
+        // --hash-modules
+        if (modulesToHash != null) {
+            String mn = md.name();
+            Hasher hasher = new Hasher(md, fname);
+            ModuleHashes moduleHashes = hasher.computeHashes(mn);
+            if (moduleHashes != null) {
+                extender.hashes(moduleHashes);
+            } else {
+                // should it issue warning or silent?
+                System.out.println("warning: no module is recorded in hash in " + mn);
             }
+        }
 
-            extender.write(baos);
-            return baos.toByteArray();
-        }
+        extender.write(baos);
+        return baos.toByteArray();
     }
 
     /**
@@ -1865,8 +1983,8 @@
             deque.add(name);
             Set<String> mods = visitNodes(graph, deque);
 
-            // filter modules matching the pattern specified --hash-modules
-            // as well as itself as the jmod file is being generated
+            // filter modules matching the pattern specified in --hash-modules,
+            // as well as the modular jar file that is being created / updated
             Map<String, Path> modulesForHash = mods.stream()
                 .filter(mn -> !mn.equals(name) && modules.contains(mn))
                 .collect(Collectors.toMap(Function.identity(), moduleNameToPath::get));
--- a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties	Mon May 23 12:44:55 2016 -0700
+++ b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties	Mon May 23 21:28:39 2016 +0100
@@ -62,16 +62,33 @@
         Unexpected module descriptor {0}
 error.module.descriptor.not.found=\
         Module descriptor not found
+error.versioned.info.without.root=\
+        module-info.class found in versioned section without module-info.class \
+        in the root
+error.versioned.info.name.notequal=\
+        module-info.class in versioned section contains incorrect name
+error.versioned.info.requires.public=\
+        module-info.class in versioned section contains additional requires public
+error.versioned.info.requires.added=\
+        module-info.class in versioned section contains additional requires
+error.versioned.info.requires.dropped=\
+        module-info.class in versioned section contains missing requires
+error.versioned.info.exports.notequal=\
+        module-info.class in versioned section contains different exports
+error.versioned.info.provides.notequal=\
+        module-info.class in versioned section contains different provides
+error.invalid.versioned.module.attribute=\
+        Invalid module descriptor attribute {0}
 error.missing.provider=\
         Service provider not found: {0}
 out.added.manifest=\
         added manifest
 out.added.module-info=\
-        added module-info.class
+        added module-info: {0}
 out.update.manifest=\
         updated manifest
 out.update.module-info=\
-        updated module-info.class
+        updated module-info: {0}
 out.ignore.entry=\
         ignoring entry {0}
 out.adding=\
--- a/jdk/test/tools/jar/modularJar/Basic.java	Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/Basic.java	Mon May 23 21:28:39 2016 +0100
@@ -33,6 +33,7 @@
 import java.util.function.Consumer;
 import java.util.jar.JarEntry;
 import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
 import java.util.regex.Pattern;
 import java.util.stream.Stream;
 import javax.tools.JavaCompiler;
@@ -52,45 +53,64 @@
 /*
  * @test
  * @library /lib/testlibrary
+ * @modules jdk.compiler
+ *          jdk.jartool
  * @build jdk.testlibrary.FileUtils jdk.testlibrary.JDKToolFinder
  * @compile Basic.java
  * @run testng Basic
- * @summary Basic test for Modular jars
+ * @summary Tests for plain Modular jars & Multi-Release Modular jars
  */
 
 public class Basic {
     static final Path TEST_SRC = Paths.get(System.getProperty("test.src", "."));
     static final Path TEST_CLASSES = Paths.get(System.getProperty("test.classes", "."));
     static final Path MODULE_CLASSES = TEST_CLASSES.resolve("build");
+    static final Path MRJAR_DIR = MODULE_CLASSES.resolve("mrjar");
 
     // Details based on the checked in module source
     static TestModuleData FOO = new TestModuleData("foo",
                                                    "1.123",
                                                    "jdk.test.foo.Foo",
-                                                   "Hello World!!!", null,
-                                                   "jdk.test.foo.internal");
+                                                   "Hello World!!!",
+                                                   null, // no hashes
+                                                   Set.of("java.base"),
+                                                   Set.of("jdk.test.foo"),
+                                                   null, // no uses
+                                                   null, // no provides
+                                                   Set.of("jdk.test.foo.internal"));
     static TestModuleData BAR = new TestModuleData("bar",
                                                    "4.5.6.7",
                                                    "jdk.test.bar.Bar",
-                                                   "Hello from Bar!", null,
-                                                   "jdk.test.bar",
-                                                   "jdk.test.bar.internal");
+                                                   "Hello from Bar!",
+                                                   null, // no hashes
+                                                   Set.of("java.base", "foo"),
+                                                   null, // no exports
+                                                   null, // no uses
+                                                   null, // no provides
+                                                   Set.of("jdk.test.bar",
+                                                          "jdk.test.bar.internal"));
 
     static class TestModuleData {
         final String moduleName;
+        final Set<String> requires;
+        final Set<String> exports;
+        final Set<String> uses;
+        final Set<String> provides;
         final String mainClass;
         final String version;
         final String message;
         final String hashes;
         final Set<String> conceals;
-        TestModuleData(String mn, String v, String mc, String m, String h, String... pkgs) {
+
+        TestModuleData(String mn, String v, String mc, String m, String h,
+                       Set<String> requires, Set<String> exports, Set<String> uses,
+                       Set<String> provides, Set<String> conceals) {
             moduleName = mn; mainClass = mc; version = v; message = m; hashes = h;
-            conceals = new HashSet<>();
-            Stream.of(pkgs).forEach(conceals::add);
-        }
-        TestModuleData(String mn, String v, String mc, String m, String h, Set<String> pkgs) {
-            moduleName = mn; mainClass = mc; version = v; message = m; hashes = h;
-            conceals = pkgs;
+            this.requires = requires;
+            this.exports = exports;
+            this.uses = uses;
+            this.provides = provides;
+            this.conceals = conceals;
         }
         static TestModuleData from(String s) {
             try {
@@ -99,7 +119,8 @@
                 String message = null;
                 String name = null, version = null, mainClass = null;
                 String hashes = null;
-                Set<String> conceals = null;
+                Set<String> requires, exports, uses, provides, conceals;
+                requires = exports = uses = provides = conceals = null;
                 while ((line = reader.readLine()) != null) {
                     if (line.startsWith("message:")) {
                         message = line.substring("message:".length());
@@ -114,28 +135,46 @@
                         }
                     } else if (line.startsWith("mainClass:")) {
                         mainClass = line.substring("mainClass:".length());
+                    } else if (line.startsWith("requires:")) {
+                        line = line.substring("requires:".length());
+                        requires = stringToSet(line);
+                    } else if (line.startsWith("exports:")) {
+                        line = line.substring("exports:".length());
+                        exports = stringToSet(line);
+                    } else if (line.startsWith("uses:")) {
+                        line = line.substring("uses:".length());
+                        uses = stringToSet(line);
+                    } else if (line.startsWith("provides:")) {
+                        line = line.substring("provides:".length());
+                        provides = stringToSet(line);
                     } else if (line.startsWith("hashes:")) {
                         hashes = line.substring("hashes:".length());
-                    }  else if (line.startsWith("conceals:")) {
+                    } else if (line.startsWith("conceals:")) {
                         line = line.substring("conceals:".length());
-                        conceals = new HashSet<>();
-                        int i = line.indexOf(',');
-                        if (i != -1) {
-                            String[] p = line.split(",");
-                            Stream.of(p).forEach(conceals::add);
-                        } else {
-                            conceals.add(line);
-                        }
+                        conceals = stringToSet(line);
                     } else {
                         throw new AssertionError("Unknown value " + line);
                     }
                 }
 
-                return new TestModuleData(name, version, mainClass, message, hashes, conceals);
+                return new TestModuleData(name, version, mainClass, message,
+                                          hashes, requires, exports, uses,
+                                          provides, conceals);
             } catch (IOException x) {
                 throw new UncheckedIOException(x);
             }
         }
+        static Set<String> stringToSet(String commaList) {
+            Set<String> s = new HashSet<>();
+            int i = commaList.indexOf(',');
+            if (i != -1) {
+                String[] p = commaList.split(",");
+                Stream.of(p).forEach(s::add);
+            } else {
+                s.add(commaList);
+            }
+            return s;
+        }
     }
 
     static void assertModuleData(Result r, TestModuleData expected) {
@@ -150,10 +189,19 @@
                    "Expected version: ", expected.version, ", got:", received.version);
         assertTrue(expected.mainClass.equals(received.mainClass),
                    "Expected mainClass: ", expected.mainClass, ", got:", received.mainClass);
-        expected.conceals.forEach(p -> assertTrue(received.conceals.contains(p),
-                                                  "Expected ", p, ", in ", received.conceals));
-        received.conceals.forEach(p -> assertTrue(expected.conceals.contains(p),
-                                                  "Expected ", p, ", in ", expected.conceals));
+        assertSetsEqual(expected.requires, received.requires);
+        assertSetsEqual(expected.exports, received.exports);
+        assertSetsEqual(expected.uses, received.uses);
+        assertSetsEqual(expected.provides, received.provides);
+        assertSetsEqual(expected.conceals, received.conceals);
+    }
+
+    static void assertSetsEqual(Set<String> s1, Set<String> s2) {
+        if (s1 == null && s2 == null) // none expected, or received
+            return;
+        assertTrue(s1.size() == s2.size(),
+                   "Unexpected set size difference: ", s1.size(), ", ", s2.size());
+        s1.forEach(p -> assertTrue(s2.contains(p), "Expected ", p, ", in ", s2));
     }
 
     @BeforeTest
@@ -161,6 +209,10 @@
         compileModule(FOO.moduleName);
         compileModule(BAR.moduleName, MODULE_CLASSES);
         compileModule("baz");  // for service provider consistency checking
+
+        setupMRJARModuleInfo(FOO.moduleName);
+        setupMRJARModuleInfo(BAR.moduleName);
+        setupMRJARModuleInfo("baz");
     }
 
     @Test
@@ -180,7 +232,6 @@
         java(mp, FOO.moduleName + "/" + FOO.mainClass)
             .assertSuccess()
             .resultChecker(r -> assertModuleData(r, FOO));
-
         try (InputStream fis = Files.newInputStream(modularJar);
              JarInputStream jis = new JarInputStream(fis)) {
             assertTrue(!jarContains(jis, "./"),
@@ -188,6 +239,30 @@
         }
     }
 
+    /** Similar to createFoo, but with a Multi-Release Modular jar. */
+    @Test
+    public void createMRMJarFoo() throws IOException {
+        Path mp = Paths.get("createMRMJarFoo");
+        createTestDir(mp);
+        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
+        Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName);
+        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
+
+        // Positive test, create
+        jar("--create",
+            "--file=" + modularJar.toString(),
+            "--main-class=" + FOO.mainClass,
+            "--module-version=" + FOO.version,
+            "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
+            "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class",
+            "-C", modClasses.toString(), ".")
+            .assertSuccess();
+        java(mp, FOO.moduleName + "/" + FOO.mainClass)
+            .assertSuccess()
+            .resultChecker(r -> assertModuleData(r, FOO));
+    }
+
+
     @Test
     public void updateFoo() throws IOException {
         Path mp = Paths.get("updateFoo");
@@ -213,6 +288,32 @@
     }
 
     @Test
+    public void updateMRMJarFoo() throws IOException {
+        Path mp = Paths.get("updateMRMJarFoo");
+        createTestDir(mp);
+        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
+        Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName);
+        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
+
+        jar("--create",
+            "--file=" + modularJar.toString(),
+            "--no-manifest",
+            "-C", modClasses.toString(), "jdk")
+            .assertSuccess();
+        jar("--update",
+            "--file=" + modularJar.toString(),
+            "--main-class=" + FOO.mainClass,
+            "--module-version=" + FOO.version,
+            "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
+            "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class",
+            "-C", modClasses.toString(), "module-info.class")
+            .assertSuccess();
+        java(mp, FOO.moduleName + "/" + FOO.mainClass)
+            .assertSuccess()
+            .resultChecker(r -> assertModuleData(r, FOO));
+    }
+
+    @Test
     public void partialUpdateFooMainClass() throws IOException {
         Path mp = Paths.get("partialUpdateFooMainClass");
         createTestDir(mp);
@@ -290,6 +391,30 @@
     }
 
     @Test
+    public void partialUpdateMRMJarFooNotAllFiles() throws IOException {
+        Path mp = Paths.get("partialUpdateMRMJarFooNotAllFiles");
+        createTestDir(mp);
+        Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
+        Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName);
+        Path modularJar = mp.resolve(FOO.moduleName + ".jar");
+
+        jar("--create",
+            "--file=" + modularJar.toString(),
+            "--module-version=" + FOO.version,
+            "-C", modClasses.toString(), ".")
+            .assertSuccess();
+        jar("--update",
+            "--file=" + modularJar.toString(),
+            "--main-class=" + FOO.mainClass,
+            "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
+            "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class")
+            .assertSuccess();
+        java(mp, FOO.moduleName + "/" + FOO.mainClass)
+            .assertSuccess()
+            .resultChecker(r -> assertModuleData(r, FOO));
+    }
+
+    @Test
     public void partialUpdateFooAllFilesAndAttributes() throws IOException {
         Path mp = Paths.get("partialUpdateFooAllFilesAndAttributes");
         createTestDir(mp);
@@ -528,6 +653,24 @@
     }
 
     @Test
+    public void servicesCreateWithoutFailureMRMJAR() throws IOException {
+        Path mp = Paths.get("servicesCreateWithoutFailureMRMJAR");
+        createTestDir(mp);
+        Path modClasses = MODULE_CLASSES.resolve("baz");
+        Path mrjarDir = MRJAR_DIR.resolve("baz");
+        Path modularJar = mp.resolve("baz" + ".jar");
+
+        jar("--create",
+            "--file=" + modularJar.toString(),
+            "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
+            "-C", modClasses.toString(), "module-info.class",
+            "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class",
+            "-C", modClasses.toString(), "jdk/test/baz/BazService.class",
+            "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class")
+            .assertSuccess();
+    }
+
+    @Test
     public void printModuleDescriptorFoo() throws IOException {
         Path mp = Paths.get("printModuleDescriptorFoo");
         createTestDir(mp);
@@ -611,6 +754,24 @@
         return build;
     }
 
+    static void setupMRJARModuleInfo(String moduleName) throws IOException {
+        Path modClasses = MODULE_CLASSES.resolve(moduleName);
+        Path metaInfDir = MRJAR_DIR.resolve(moduleName).resolve("META-INF");
+        Path versionSection = metaInfDir.resolve("versions").resolve("9");
+        createTestDir(versionSection);
+
+        Path versionModuleInfo = versionSection.resolve("module-info.class");
+        System.out.println("copying " + modClasses.resolve("module-info.class") + " to " + versionModuleInfo);
+        Files.copy(modClasses.resolve("module-info.class"), versionModuleInfo);
+
+        Manifest manifest = new Manifest();
+        manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
+        manifest.getMainAttributes().putValue("Multi-Release", "true");
+        try (OutputStream os = Files.newOutputStream(metaInfDir.resolve("MANIFEST.MF"))) {
+            manifest.write(os);
+        }
+    }
+
     // Re-enable when there is support in javax.tools for module path
 //    static void javac(Path dest, Path... sourceFiles) throws IOException {
 //        out.printf("Compiling %d source files %s%n", sourceFiles.length,
@@ -690,7 +851,7 @@
     static void createTestDir(Path p) throws IOException{
         if (Files.exists(p))
             FileUtils.deleteFileTreeWithRetry(p);
-        Files.createDirectory(p);
+        Files.createDirectories(p);
     }
 
     static boolean jarContains(JarInputStream jis, String entryName)
--- a/jdk/test/tools/jar/modularJar/src/bar/jdk/test/bar/Bar.java	Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/bar/jdk/test/bar/Bar.java	Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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.
+ * published by the Free Software Foundation.
  *
  * This code is distributed in the hope that it will be useful, but WITHOUT
  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
@@ -26,6 +24,8 @@
 package jdk.test.bar;
 
 import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleDescriptor.Exports;
+import java.lang.module.ModuleDescriptor.Requires;
 import java.lang.reflect.Method;
 import java.util.Optional;
 import java.util.StringJoiner;
@@ -39,18 +39,36 @@
 
         ModuleDescriptor md = Bar.class.getModule().getDescriptor();
         System.out.println("nameAndVersion:" + md.toNameAndVersion());
-        System.out.println("mainClass:" + md.mainClass().get());
+        md.mainClass().ifPresent(mc -> System.out.println("mainClass:" + mc));
+
+        StringJoiner sj = new StringJoiner(",");
+        md.requires().stream().map(ModuleDescriptor.Requires::name).sorted().forEach(sj::add);
+        System.out.println("requires:" + sj.toString());
+
+        sj = new StringJoiner(",");
+        md.exports().stream().map(ModuleDescriptor.Exports::source).sorted().forEach(sj::add);
+        if (!sj.toString().equals(""))
+            System.out.println("exports:" + sj.toString());
+
+        sj = new StringJoiner(",");
+        md.uses().stream().sorted().forEach(sj::add);
+        if (!sj.toString().equals(""))
+            System.out.println("uses:" + sj.toString());
+
+        sj = new StringJoiner(",");
+        md.provides().keySet().stream().sorted().forEach(sj::add);
+        if (!sj.toString().equals(""))
+            System.out.println("provides:" + sj.toString());
+
+        sj = new StringJoiner(",");
+        md.conceals().forEach(sj::add);
+        if (!sj.toString().equals(""))
+            System.out.println("conceals:" + sj.toString());
 
         Method m = ModuleDescriptor.class.getDeclaredMethod("hashes");
         m.setAccessible(true);
         ModuleDescriptor foo = jdk.test.foo.Foo.class.getModule().getDescriptor();
-        Optional<ModuleHashes> oHashes =
-                (Optional<ModuleHashes>) m.invoke(foo);
-
+        Optional<ModuleHashes> oHashes = (Optional<ModuleHashes>) m.invoke(foo);
         System.out.println("hashes:" + oHashes.get().hashFor("bar"));
-
-        StringJoiner sj = new StringJoiner(",");
-        md.conceals().forEach(sj::add);
-        System.out.println("conceals:" + sj.toString());
     }
 }
--- a/jdk/test/tools/jar/modularJar/src/bar/jdk/test/bar/internal/Message.java	Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/bar/jdk/test/bar/internal/Message.java	Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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.
+ * published by the Free Software Foundation.
  *
  * This code is distributed in the hope that it will be useful, but WITHOUT
  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
--- a/jdk/test/tools/jar/modularJar/src/bar/module-info.java	Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/bar/module-info.java	Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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.
+ * published by the Free Software Foundation.
  *
  * This code is distributed in the hope that it will be useful, but WITHOUT
  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
--- a/jdk/test/tools/jar/modularJar/src/baz/jdk/test/baz/BazService.java	Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/baz/jdk/test/baz/BazService.java	Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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.
+ * published by the Free Software Foundation.
  *
  * This code is distributed in the hope that it will be useful, but WITHOUT
  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
--- a/jdk/test/tools/jar/modularJar/src/baz/jdk/test/baz/internal/BazServiceImpl.java	Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/baz/jdk/test/baz/internal/BazServiceImpl.java	Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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.
+ * published by the Free Software Foundation.
  *
  * This code is distributed in the hope that it will be useful, but WITHOUT
  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
--- a/jdk/test/tools/jar/modularJar/src/baz/module-info.java	Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/baz/module-info.java	Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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.
+ * published by the Free Software Foundation.
  *
  * This code is distributed in the hope that it will be useful, but WITHOUT
  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
--- a/jdk/test/tools/jar/modularJar/src/foo/jdk/test/foo/Foo.java	Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/foo/jdk/test/foo/Foo.java	Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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.
+ * published by the Free Software Foundation.
  *
  * This code is distributed in the hope that it will be useful, but WITHOUT
  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
@@ -26,6 +24,8 @@
 package jdk.test.foo;
 
 import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleDescriptor.Exports;
+import java.lang.module.ModuleDescriptor.Requires;
 import java.util.StringJoiner;
 
 import jdk.test.foo.internal.Message;
@@ -36,10 +36,30 @@
 
         ModuleDescriptor md = Foo.class.getModule().getDescriptor();
         System.out.println("nameAndVersion:" + md.toNameAndVersion());
-        System.out.println("mainClass:" + md.mainClass().get());
+        md.mainClass().ifPresent(mc -> System.out.println("mainClass:" + mc));
 
         StringJoiner sj = new StringJoiner(",");
+        md.requires().stream().map(Requires::name).sorted().forEach(sj::add);
+        System.out.println("requires:" + sj.toString());
+
+        sj = new StringJoiner(",");
+        md.exports().stream().map(Exports::source).sorted().forEach(sj::add);
+        if (!sj.toString().equals(""))
+            System.out.println("exports:" + sj.toString());
+
+        sj = new StringJoiner(",");
+        md.uses().stream().sorted().forEach(sj::add);
+        if (!sj.toString().equals(""))
+            System.out.println("uses:" + sj.toString());
+
+        sj = new StringJoiner(",");
+        md.provides().keySet().stream().sorted().forEach(sj::add);
+        if (!sj.toString().equals(""))
+            System.out.println("provides:" + sj.toString());
+
+        sj = new StringJoiner(",");
         md.conceals().forEach(sj::add);
-        System.out.println("conceals:" + sj.toString());
+        if (!sj.toString().equals(""))
+            System.out.println("conceals:" + sj.toString());
     }
 }
--- a/jdk/test/tools/jar/modularJar/src/foo/jdk/test/foo/internal/Message.java	Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/foo/jdk/test/foo/internal/Message.java	Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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.
+ * published by the Free Software Foundation.
  *
  * This code is distributed in the hope that it will be useful, but WITHOUT
  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
--- a/jdk/test/tools/jar/modularJar/src/foo/module-info.java	Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/foo/module-info.java	Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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.
+ * published by the Free Software Foundation.
  *
  * This code is distributed in the hope that it will be useful, but WITHOUT
  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or