--- old/src/jdk.zipfs/share/classes/jdk/nio/zipfs/JarFileSystem.java 2019-09-19 10:01:16.000000000 -0400 +++ new/src/jdk.zipfs/share/classes/jdk/nio/zipfs/JarFileSystem.java 2019-09-19 10:01:16.000000000 -0400 @@ -62,16 +62,16 @@ super(provider, zfpath, env); if (isMultiReleaseJar()) { int version; - Object o = env.get("multi-release"); + Object o = getRuntimeVersion(env); if (o instanceof String) { String s = (String)o; if (s.equals("runtime")) { version = Runtime.version().feature(); } else { - version = Integer.parseInt(s); + version = Version.parse(s).feature(); } } else if (o instanceof Integer) { - version = (Integer)o; + version = Version.parse(((Integer)o).toString()).feature(); } else if (o instanceof Version) { version = ((Version)o).feature(); } else { @@ -83,6 +83,18 @@ } } + /** + * Utility method to get the release version for a multi-release JAR. It + * first checks the documented property {@code releaseVersion} and if not + * found checks the original property {@code multi-release} + * @param env ZIP FS map + * @return release version or null if it is not specified + */ + private Object getRuntimeVersion(Map env) { + Object o = env.get(ZipFileSystemProvider.RELEASE_VERSION); + return o != null ? o : env.get(ZipFileSystemProvider.MULTI_RELEASE); + } + private boolean isMultiReleaseJar() throws IOException { try (InputStream is = newInputStream(getBytes("/META-INF/MANIFEST.MF"))) { String multiRelease = new Manifest(is).getMainAttributes() --- old/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystemProvider.java 2019-09-19 10:01:18.000000000 -0400 +++ new/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystemProvider.java 2019-09-19 10:01:18.000000000 -0400 @@ -53,6 +53,8 @@ */ public class ZipFileSystemProvider extends FileSystemProvider { + protected static final String RELEASE_VERSION = "releaseVersion"; + protected static final String MULTI_RELEASE = "multi-release"; private final Map filesystems = new HashMap<>(); public ZipFileSystemProvider() {} @@ -104,20 +106,7 @@ if (filesystems.containsKey(realPath)) throw new FileSystemAlreadyExistsException(); } - ZipFileSystem zipfs; - try { - if (env.containsKey("multi-release")) { - zipfs = new JarFileSystem(this, path, env); - } else { - zipfs = new ZipFileSystem(this, path, env); - } - } catch (ZipException ze) { - String pname = path.toString(); - if (pname.endsWith(".zip") || pname.endsWith(".jar")) - throw ze; - // assume NOT a zip/jar file - throw new UnsupportedOperationException(); - } + ZipFileSystem zipfs = getZipFileSystem(path, env); if (realPath == null) { // newly created realPath = path.toRealPath(); } @@ -131,20 +120,26 @@ throws IOException { ensureFile(path); + ZipFileSystem zipfs = getZipFileSystem(path, env); + return zipfs; + } + + private ZipFileSystem getZipFileSystem(Path path, Map env) throws IOException { + ZipFileSystem zipfs; try { - ZipFileSystem zipfs; - if (env.containsKey("multi-release")) { + if (env.containsKey(RELEASE_VERSION) || + env.containsKey(MULTI_RELEASE)) { zipfs = new JarFileSystem(this, path, env); } else { zipfs = new ZipFileSystem(this, path, env); } - return zipfs; } catch (ZipException ze) { String pname = path.toString(); if (pname.endsWith(".zip") || pname.endsWith(".jar")) throw ze; throw new UnsupportedOperationException(); } + return zipfs; } @Override --- old/src/jdk.zipfs/share/classes/module-info.java 2019-09-19 10:01:20.000000000 -0400 +++ new/src/jdk.zipfs/share/classes/module-info.java 2019-09-19 10:01:19.000000000 -0400 @@ -147,7 +147,7 @@ * * * create - * java.lang.String + * {@link java.lang.String} or {@link java.lang.Boolean} * false * * If the value is {@code true}, the Zip file system provider @@ -156,7 +156,7 @@ * * * encoding - * java.lang.String + * {@link java.lang.String} * UTF-8 * * The value indicates the encoding scheme for the @@ -164,8 +164,8 @@ * * * - * enablePosixFileAttributes - * java.lang.String + * enablePosixFileAttributes + * {@link java.lang.String} or {@link java.lang.Boolean} * false * * If the value is {@code true}, the Zip file system will support @@ -173,8 +173,9 @@ * * * - * defaultOwner - * {@link java.nio.file.attribute.UserPrincipal UserPrincipal}
or java.lang.String + * defaultOwner + * {@link java.nio.file.attribute.UserPrincipal UserPrincipal}
or + * {@link java.lang.String} * null/unset * * Override the default owner for entries in the Zip file system.
@@ -182,8 +183,9 @@ * * * - * defaultGroup - * {@link java.nio.file.attribute.GroupPrincipal GroupPrincipal}
or java.lang.String + * defaultGroup + * {@link java.nio.file.attribute.GroupPrincipal GroupPrincipal}
or + * {@link java.lang.String} * null/unset * * Override the the default group for entries in the Zip file system.
@@ -191,9 +193,9 @@ * * * - * defaultPermissions + * defaultPermissions * {@link java.util.Set Set}<{@link java.nio.file.attribute.PosixFilePermission PosixFilePermission}>
- * or java.lang.String + * or {@link java.lang.String} * null/unset * * Override the default Set of permissions for entries in the Zip file system.
@@ -201,7 +203,51 @@ * a String that is parsed by {@link java.nio.file.attribute.PosixFilePermissions#fromString PosixFilePermissions::fromString} * * - * + * + * noCompression + * {@link java.lang.String} or {@link java.lang.Boolean}n + * false + * + * If the value is {@code true}, the Zip file system provider will + * not compress entries when writing to the Zip file system. + * If the value is {@code false}, the Zip file system provider will + * use data compression when writing entries to the Zip file system. + * + * + * + * releaseVersion + * {@link java.lang.String} or {@link java.lang.Integer} or + * {@link java.lang.Runtime.Version} + * null/unset + * + * A value representing the version entry to use when accessing a + * multi-release JAR. If the JAR is not a multi-release JAR, the value + * will be ignored and the JAR will considered un-versioned. + *

+ * + *

    + *
  • + * If the value is {@code "runtime"} or is a + * {@linkplain java.lang.Runtime.Version Version} Object, the + * version entry will be determined by invoking + * {@linkplain Runtime.Version#feature() Version.feature()}. + *
  • + *
  • + * If the Object is a {@linkplain java.lang.String} or an + * {@linkplain java.lang.Integer}, its value must represent a valid + * {@linkplain Runtime.Version Java SE Platform version number}, + * such as {@code 9}, {@code 11.0.1}, or {@code 14} in order to + * determine the version entry. + *
  • + *
  • + * If the value does not represent a valid + * {@linkplain Runtime.Version Java SE Platform version number}, + * an {@code IllegalArgumentException} will be thrown. + *
  • + *
+ * + * + * * * *

Examples:

@@ -223,7 +269,7 @@ *
  * {@code
  *
- *     FileSystem zipfs = FileSystems.newFileSystem(Path.of("helloworld.jar"), null);
+ *     FileSystem zipfs = FileSystems.newFileSystem(Path.of("helloworld.jar"));
  *     Path rootDir = zipfs.getPath("/");
  *     Files.walk(rootDir)
  *            .forEach(System.out::println);
--- old/test/jdk/jdk/nio/zipfs/jarfs/MultiReleaseJarTest.java	2019-09-19 10:01:21.000000000 -0400
+++ new/test/jdk/jdk/nio/zipfs/jarfs/MultiReleaseJarTest.java	2019-09-19 10:01:21.000000000 -0400
@@ -88,8 +88,8 @@
     public Object[][] createStrings() {
         return new Object[][]{
                 {"runtime", MAJOR_VERSION},
-                {"-20", 8},
-                {"0", 8},
+                {"9.0.1", 9},
+                {MAJOR_VERSION + ".0.1", MAJOR_VERSION},
                 {"8", 8},
                 {"9", 9},
                 {Integer.toString(MAJOR_VERSION), MAJOR_VERSION},
@@ -101,8 +101,6 @@
     @DataProvider(name="integers")
     public Object[][] createIntegers() {
         return new Object[][] {
-                {Integer.valueOf(-5), 8},
-                {Integer.valueOf(0), 8},
                 {Integer.valueOf(8), 8},
                 {Integer.valueOf(9), 9},
                 {Integer.valueOf(MAJOR_VERSION), MAJOR_VERSION},
@@ -122,6 +120,15 @@
         };
     }
 
+    @DataProvider(name="invalidVersions")
+    public Object[][] invalidVersions() {
+        return new Object[][] {
+                {Map.of("releaseVersion", "0")},
+                {Map.of("releaseVersion", "-1")},
+                {Map.of("releaseVersion",Integer.valueOf(-5))}
+        };
+    }
+
     // Not the best test but all I can do since ZipFileSystem and JarFileSystem
     // are not public, so I can't use (fs instanceof ...)
     @Test
@@ -131,7 +138,7 @@
         try (FileSystem fs = FileSystems.newFileSystem(mruri, env)) {
             Assert.assertTrue(readAndCompare(fs, 8));
         }
-        env.put("multi-release", "runtime");
+        env.put("releaseVersion", "runtime");
         // a configuration and jar file is multi-release
         try (FileSystem fs = FileSystems.newFileSystem(mruri, env)) {
             Assert.assertTrue(readAndCompare(fs, MAJOR_VERSION));
@@ -150,30 +157,54 @@
 
     @Test(dataProvider="strings")
     public void testStrings(String value, int expected) throws Throwable {
-        stringEnv.put("multi-release", value);
+        stringEnv.put("releaseVersion", value);
         runTest(stringEnv, expected);
     }
 
     @Test(dataProvider="integers")
     public void testIntegers(Integer value, int expected) throws Throwable {
-        integerEnv.put("multi-release", value);
+        integerEnv.put("releaseVersion", value);
         runTest(integerEnv, expected);
     }
 
     @Test(dataProvider="versions")
     public void testVersions(Version value, int expected) throws Throwable {
-        versionEnv.put("multi-release", value);
+        versionEnv.put("releaseVersion", value);
         runTest(versionEnv, expected);
     }
 
     @Test
     public void testShortJar() throws Throwable {
-        integerEnv.put("multi-release", Integer.valueOf(MAJOR_VERSION));
+        integerEnv.put("releaseVersion", Integer.valueOf(MAJOR_VERSION));
         runTest(smruri, integerEnv, MAJOR_VERSION);
-        integerEnv.put("multi-release", Integer.valueOf(9));
+        integerEnv.put("releaseVersion", Integer.valueOf(9));
         runTest(smruri, integerEnv, 8);
     }
 
+    @Test(dataProvider="invalidVersions")
+    public void testInvalidVersions(Map env) throws Throwable {
+        Assert.assertThrows(IllegalArgumentException.class, () ->
+                FileSystems.newFileSystem(Path.of(userdir,
+                        "multi-release.jar"), env));
+    }
+
+    // The following tests are for backwards compatibility to validate that
+    // the original property still works
+    @Test(dataProvider="strings")
+    public void testMRStrings(String value, int expected) throws Throwable {
+        runTest(Map.of("multi-release", value), expected);
+    }
+
+    @Test(dataProvider="integers")
+    public void testMRIntegers(Integer value, int expected) throws Throwable {
+        runTest(Map.of("multi-release", value), expected);
+    }
+
+    @Test(dataProvider="versions")
+    public void testMRVersions(Version value, int expected) throws Throwable {
+        runTest(Map.of("multi-release", value), expected);
+    }
+
     private void runTest(Map env, int expected) throws Throwable {
         runTest(mruri, env, expected);
     }
@@ -213,7 +244,7 @@
         JarBuilder jb = new JarBuilder(jfname);
         jb.addAttribute("Multi-Release", "true");
         jb.build();
-        Map env = Map.of("multi-release", "runtime");
+        Map env = Map.of("releaseVersion", "runtime");
         try (FileSystem fs = FileSystems.newFileSystem(uri, env)) {
             Assert.assertTrue(true);
         }
@@ -228,7 +259,7 @@
         creator.buildCustomMultiReleaseJar(fileName, value, Map.of(),
                 /*addEntries*/true);
 
-        Map env = Map.of("multi-release", "runtime");
+        Map env = Map.of("releaseVersion", "runtime");
         Path filePath = Paths.get(userdir, fileName);
         String ssp = filePath.toUri().toString();
         URI customJar = new URI("jar", ssp , null);