--- old/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java 2019-09-23 18:40:38.000000000 -0400 +++ new/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java 2019-09-23 18:40:38.000000000 -0400 @@ -233,7 +233,7 @@ } public void setMultiReleaseValue(String multiReleaseValue) { - fsEnv = Collections.singletonMap("multi-release", multiReleaseValue); + fsEnv = Collections.singletonMap("releaseVersion", multiReleaseValue); } private boolean contains(Collection searchPath, Path file) throws IOException { --- old/src/jdk.zipfs/share/classes/jdk/nio/zipfs/JarFileSystem.java 2019-09-23 18:40:39.000000000 -0400 +++ new/src/jdk.zipfs/share/classes/jdk/nio/zipfs/JarFileSystem.java 2019-09-23 18:40:39.000000000 -0400 @@ -60,18 +60,20 @@ JarFileSystem(ZipFileSystemProvider provider, Path zfpath, Map env) throws IOException { super(provider, zfpath, env); - if (isMultiReleaseJar()) { + Object o = getRuntimeVersion(env); + if (isMultiReleaseJar() && (o != null)) { int version; - Object o = env.get("multi-release"); if (o instanceof String) { String s = (String)o; if (s.equals("runtime")) { version = Runtime.version().feature(); + } else if (s.matches("^[1-9][0-9]*$")) { + version = Version.parse(s).feature(); } else { - version = Integer.parseInt(s); + throw new IllegalArgumentException("Invalid runtime version"); } } 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 +85,24 @@ } } + /** + * 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 = null; + if( env.containsKey(ZipFileSystemProvider.RELEASE_VERSION)) { + o = env.get(ZipFileSystemProvider.RELEASE_VERSION); + } else { + o = env.get(ZipFileSystemProvider.MULTI_RELEASE); + } + return o; + } + 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/ZipFileSystem.java 2019-09-23 18:40:41.000000000 -0400 +++ new/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java 2019-09-23 18:40:40.000000000 -0400 @@ -88,6 +88,12 @@ private static final Set DEFAULT_PERMISSIONS = PosixFilePermissions.fromString("rwxrwxrwx"); + // Property used to specify the compression mode to use + private static final String COMPRESSION_METHOD = "compressionMethod"; + // Value specified for compressionMethod property to compress Zip entries + public static final String DEFLATED_COMPRESSION_METHOD = "DEFLATED"; + // Value specified for compressionMethod property to not compress Zip entries + public static final String STORED_COMPRESSION_METHOD = "STORED"; private final ZipFileSystemProvider provider; private final Path zfpath; @@ -124,7 +130,7 @@ this.noExtt = "false".equals(env.get("zipinfo-time")); this.useTempFile = isTrue(env, "useTempFile"); this.forceEnd64 = isTrue(env, "forceZIP64End"); - this.defaultCompressionMethod = isTrue(env, "noCompression") ? METHOD_STORED : METHOD_DEFLATED; + this.defaultCompressionMethod = getDefaultCompressionMethod(env); this.supportPosix = isTrue(env, OPT_POSIX); this.defaultOwner = initOwner(zfpath, env); this.defaultGroup = initGroup(zfpath, env); @@ -163,6 +169,49 @@ this.zfpath = zfpath; } + /** + * Return the compression method to use(STORED or DEFLATED). If the + * property {@code commpressionMethod} is set use its value to determine + * the compression method to use. If the property is not set, then the + * default compression is DEFLATED unless the property {@code noCompression} + * is set which is supported for backwards compatability. + * @param env Zip FS map of properties + * @return The Compression method to use + */ + private int getDefaultCompressionMethod(Map env) { + int result = + isTrue(env, "noCompression") ? METHOD_STORED : METHOD_DEFLATED; + if(env.containsKey(COMPRESSION_METHOD)) { + Object compressionMethod = env.get(COMPRESSION_METHOD); + if(compressionMethod != null) { + if(compressionMethod instanceof String) { + switch(((String) compressionMethod).toUpperCase()) { + case STORED_COMPRESSION_METHOD: + result = METHOD_STORED; + break; + case DEFLATED_COMPRESSION_METHOD: + result = METHOD_DEFLATED; + break; + default: + throw new IllegalArgumentException(String.format( + "The value for the %s property must be %s or %s", + COMPRESSION_METHOD, STORED_COMPRESSION_METHOD, + DEFLATED_COMPRESSION_METHOD)); + } + } else { + throw new IllegalArgumentException(String.format( + "The Object type for the %s property must be a String")); + } + } else { + throw new IllegalArgumentException(String.format( + "The value for the %s property must be %s or %s", + COMPRESSION_METHOD, STORED_COMPRESSION_METHOD, + DEFLATED_COMPRESSION_METHOD)); + } + } + return result; + } + // returns true if there is a name=true/"true" setting in env private static boolean isTrue(Map env, String name) { return "true".equals(env.get(name)) || TRUE.equals(env.get(name)); --- old/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystemProvider.java 2019-09-23 18:40:42.000000000 -0400 +++ new/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystemProvider.java 2019-09-23 18:40:42.000000000 -0400 @@ -53,6 +53,11 @@ */ public class ZipFileSystemProvider extends FileSystemProvider { + // Property used to specify the entry version to use for a multi-release JAR + protected static final String RELEASE_VERSION = "releaseVersion"; + // Original property used to specify the entry version to use for a + // multi-release JAR which is kept for backwards compatability. + protected static final String MULTI_RELEASE = "multi-release"; private final Map filesystems = new HashMap<>(); public ZipFileSystemProvider() {} @@ -104,20 +109,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 +123,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-23 18:40:43.000000000 -0400 +++ new/src/jdk.zipfs/share/classes/module-info.java 2019-09-23 18:40:43.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,66 @@ * a String that is parsed by {@link java.nio.file.attribute.PosixFilePermissions#fromString PosixFilePermissions::fromString} * * - * + * + * compressionMethod + * {@link java.lang.String} + * "DEFLATED" + * + * The value representing the compression method to use when writing entries + * to the Zip file system. + *
    + *
  • + * If the value is {@code "STORED"}, the Zip file system provider will + * not compress entries when writing to the Zip file system. + *
  • + *
  • + * If the value is {@code "DEFLATED"} or the property is not set, + * the Zip file system provider will use data compression when + * writing entries to the Zip file system. + *
  • + *
  • + * If the value is not {@code "STORED"} or {@code "DEFLATED"}, an + * {@code IllegalArgumentException} will be thrown when the Zip + * filesystem is created. + *
  • + *
+ * + * + * + * releaseVersion + * {@link java.lang.String} or {@link java.lang.Integer} + * 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. + *

+ * The value must represent a valid + * {@linkplain Runtime.Version Java SE Platform version number}, + * such as {@code 9}, or {@code 14} in order to determine the version entry. + * + *

    + *
  • + * If the value is {@code null} or the property is not set, + * then the JAR will be treated as an un-versioned JAR. + *
  • + *
  • + * If the value is {@code "runtime"}, the + * version entry will be determined by invoking + * {@linkplain Runtime.Version#feature() Runtime.Version.feature()}. + *
  • + *
  • + * 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 +284,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-23 18:40:44.000000000 -0400
+++ new/test/jdk/jdk/nio/zipfs/jarfs/MultiReleaseJarTest.java	2019-09-23 18:40:44.000000000 -0400
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 8144355 8144062 8176709 8194070 8193802
+ * @bug 8144355 8144062 8176709 8194070 8193802 8231093
  * @summary Test aliasing additions to ZipFileSystem for multi-release jar files
  * @library /lib/testlibrary/java/util/jar
  * @modules jdk.compiler
@@ -88,8 +88,7 @@
     public Object[][] createStrings() {
         return new Object[][]{
                 {"runtime", MAJOR_VERSION},
-                {"-20", 8},
-                {"0", 8},
+                {null, 8},
                 {"8", 8},
                 {"9", 9},
                 {Integer.toString(MAJOR_VERSION), MAJOR_VERSION},
@@ -101,8 +100,7 @@
     @DataProvider(name="integers")
     public Object[][] createIntegers() {
         return new Object[][] {
-                {Integer.valueOf(-5), 8},
-                {Integer.valueOf(0), 8},
+                {null, 8},
                 {Integer.valueOf(8), 8},
                 {Integer.valueOf(9), 9},
                 {Integer.valueOf(MAJOR_VERSION), MAJOR_VERSION},
@@ -114,6 +112,7 @@
     @DataProvider(name="versions")
     public Object[][] createVersions() {
         return new Object[][] {
+                {null,    8},
                 {Version.parse("8"),    8},
                 {Version.parse("9"),    9},
                 {Version.parse(Integer.toString(MAJOR_VERSION)),  MAJOR_VERSION},
@@ -122,6 +121,19 @@
         };
     }
 
+    @DataProvider(name="invalidVersions")
+    public Object[][] invalidVersions() {
+        return new Object[][] {
+                {Map.of("releaseVersion", "")},
+                {Map.of("releaseVersion", "invalid")},
+                {Map.of("releaseVersion", "0")},
+                {Map.of("releaseVersion", "-1")},
+                {Map.of("releaseVersion", "11.0.1")},
+                {Map.of("releaseVersion",Integer.valueOf(0))},
+                {Map.of("releaseVersion",Integer.valueOf(-1))}
+        };
+    }
+
     // 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 +143,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 +162,67 @@
 
     @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);
     }
 
+    /**
+     * Validate that an invalid value for the "releaseVersion" property throws
+     * an {@code IllegalArgumentException}
+     * @param env Zip FS map
+     * @throws Throwable  Exception thrown for anything other than the expected
+     * IllegalArgumentException
+     */
+    @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 {
+        stringEnv.clear();
+        stringEnv.put("multi-release", value);
+        runTest(stringEnv, expected);
+    }
+
+    @Test(dataProvider="integers")
+    public void testMRIntegers(Integer value, int expected) throws Throwable {
+        integerEnv.clear();
+        integerEnv.put("multi-release", value);
+        runTest(integerEnv, expected);
+    }
+
+    @Test(dataProvider="versions")
+    public void testMRVersions(Version value, int expected) throws Throwable {
+        versionEnv.clear();
+        versionEnv.put("multi-release", value);
+        runTest(versionEnv, expected);
+    }
+
     private void runTest(Map env, int expected) throws Throwable {
         runTest(mruri, env, expected);
     }
@@ -213,7 +262,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 +277,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);
--- /dev/null	2019-09-23 18:40:46.000000000 -0400
+++ new/test/jdk/jdk/nio/zipfs/CompressionModeTest.java	2019-09-23 18:40:45.000000000 -0400
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+import org.testng.annotations.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import static java.lang.String.format;
+import static java.util.stream.Collectors.joining;
+import static org.testng.Assert.*;
+
+/**
+ * @test
+ * @bug 8231093
+ * @summary Test Zip FS compressionMethod property
+ * @modules jdk.zipfs
+ * @run testng CompressionModeTest
+ */
+public class CompressionModeTest {
+
+    private static final Path HERE = Path.of(".");
+
+    /**
+     * Number of ZIP entries to create
+     */
+    private static final int ENTRIES = 5;
+
+    /**
+     * Value used for creating the required entries in a ZIP or JAR file
+     */
+    private static final String ZIP_FILE_VALUE = "US Open 2019";
+    private static final byte[] ZIP_FILE_ENTRY =
+            ZIP_FILE_VALUE.getBytes(StandardCharsets.UTF_8);
+
+    private static final SecureRandom random = new SecureRandom();
+
+    /**
+     * Validate that you can create a ZIP file with and without compression
+     * and that entries are created with the specified compression method.
+     *
+     * @param env         Properties used for creating the ZIP Filesystem
+     * @param compression Indicates whether the files are DEFLATED(default)
+     *                    or STORED
+     * @throws Exception If an error occurs during the creation, verification or
+     *                   deletion of the ZIP file
+     */
+    @Test(dataProvider = "validCompressionMethods", enabled = true)
+    public void testValidCompressionMehods(Map env,
+                                           int compression) throws Exception {
+
+        System.out.printf("ZIP FS Map = %s, Compression mode= %s%n ",
+                formatMap(env), compression);
+
+        Path zipfile = generatePath(HERE, "test", ".zip");
+        Files.deleteIfExists(zipfile);
+        createZipFile(zipfile, env, ENTRIES);
+        verify(zipfile, compression, ENTRIES, 0);
+        Files.deleteIfExists(zipfile);
+    }
+
+    /**
+     * Validate that an IllegalArgumentException is thrown when an invalid
+     * value is specified for the compressionMethod property.
+     *
+     * @param env Properties used for creating the ZIP Filesystem
+     * @throws Exception if an error occurs other than the expected
+     * IllegalArgumentException
+     */
+    @Test(dataProvider = "invalidCompressionMethod")
+    public void testInvalidCompressionMethod(Map env) throws Exception {
+        System.out.printf("ZIP FS Map = %s%n ", formatMap(env));
+        Path zipfile = generatePath(HERE, "test", ".zip");
+        Files.deleteIfExists(zipfile);
+        assertThrows(IllegalArgumentException.class, () ->
+                createZipFile(zipfile, env, ENTRIES));
+        Files.deleteIfExists(zipfile);
+    }
+
+    /**
+     * Create a ZIP File System using the specified properties and a ZIP file
+     * with the specified number of entries
+     *
+     * @param zipFile Path to the ZIP File to create
+     * @param env     Properties used for creating the ZIP Filesystem
+     * @param entries Number of entries to add to the ZIP File
+     * @throws IOException If an error occurs while creating the ZIP file
+     */
+    private void createZipFile(Path zipFile, Map env,
+                               int entries) throws IOException {
+        System.out.printf("Creating file = %s%n", zipFile);
+        try (FileSystem zipfs =
+                     FileSystems.newFileSystem(zipFile, env)) {
+
+            for (int i = 0; i < entries; i++) {
+                Files.writeString(zipfs.getPath("Entry-" + i), ZIP_FILE_VALUE);
+            }
+        }
+    }
+
+    /**
+     * DataProvider used to validate that you can create a ZIP file with and
+     * without compression.
+     */
+    @DataProvider(name = "validCompressionMethods")
+    private Object[][] validCompressionMethods() {
+        return new Object[][]{
+                {Map.of("create", "true"), ZipEntry.DEFLATED},
+                {Map.of("create", "true", "noCompression", "true"),
+                        ZipEntry.STORED},
+                {Map.of("create", "true", "noCompression", "false"),
+                        ZipEntry.DEFLATED},
+                {Map.of("create", "true", "compressionMethod", "STORED"),
+                        ZipEntry.STORED},
+                {Map.of("create", "true", "compressionMethod", "DEFLATED"),
+                        ZipEntry.DEFLATED},
+                {Map.of("create", "true", "compressionMethod", "stored"),
+                        ZipEntry.STORED},
+                {Map.of("create", "true", "compressionMethod", "deflated"),
+                        ZipEntry.DEFLATED}
+
+        };
+    }
+
+    /**
+     * DataProvider used to validate that an IllegalArgumentException is thrown
+     * for an invalid value for the compressionMethod property.
+     */
+    @DataProvider(name = "invalidCompressionMethod")
+    private Object[][] invalidCompressionMethod() {
+        HashMap map = new HashMap<>();
+        map.put("create", "true");
+        map.put("compressionMethod", null);
+        return new Object[][]{
+                {map},
+                {Map.of("create", "true", "compressionMethod", "")},
+                {Map.of("create", "true", "compressionMethod", "invalid")}
+
+        };
+    }
+
+    /**
+     * Verify that the given path is a ZIP file containing the
+     * expected entries.
+     *
+     * @param zipfile ZIP file to be validated
+     * @param method  Expected Compression method: STORED or DEFLATED
+     * @param entries Number of expected entries
+     * @param start   Starting number for verifying entries
+     * @throws Exception If an error occurs while examining the ZIP file
+     */
+    private static void verify(Path zipfile, int method, int entries,
+                               int start) throws Exception {
+        // check entries with ZIP API
+        try (ZipFile zf = new ZipFile(zipfile.toFile())) {
+            // check entry count
+            assertEquals(entries, zf.size());
+
+            // check compression method and content of each entry
+            for (int i = start; i < entries; i++) {
+                ZipEntry ze = zf.getEntry("Entry-" + i);
+                assertNotNull(ze);
+                assertEquals(method, ze.getMethod());
+                try (InputStream is = zf.getInputStream(ze)) {
+                    byte[] bytes = is.readAllBytes();
+                    assertTrue(Arrays.equals(bytes, ZIP_FILE_ENTRY));
+                }
+            }
+        }
+        // check entries with FileSystem API
+        try (FileSystem fs = FileSystems.newFileSystem(zipfile)) {
+
+            // check entry count
+            Path top = fs.getPath("/");
+            long count = Files.find(top, Integer.MAX_VALUE, (path, attrs) ->
+                    attrs.isRegularFile() || (attrs.isDirectory() &&
+                            path.getFileName() != null &&
+                            path.getFileName().toString().equals("META-INF")))
+                    .count();
+            assertEquals(entries, count);
+
+            // check content of each entry
+            for (int i = start; i < entries; i++) {
+                Path file = fs.getPath("Entry-" + i);
+                byte[] bytes = Files.readAllBytes(file);
+                assertTrue(Arrays.equals(bytes, ZIP_FILE_ENTRY));
+            }
+        }
+    }
+
+    /**
+     * Generate a temporary file Path
+     *
+     * @param dir    Directory used to create the path
+     * @param prefix The prefix string used to create the path
+     * @param suffix The suffix string used to create the path
+     * @return Path that was generated
+     */
+    private static Path generatePath(Path dir, String prefix, String suffix) {
+        long n = random.nextLong();
+        String s = prefix + Long.toUnsignedString(n) + suffix;
+        Path name = dir.getFileSystem().getPath(s);
+        // the generated name should be a simple file name
+        if (name.getParent() != null)
+            throw new IllegalArgumentException("Invalid prefix or suffix");
+        return dir.resolve(name);
+    }
+
+    /**
+     * Utility method to return a formatted String of the key:value entries for
+     * a Map
+     *
+     * @param env Map to format
+     * @return Formatted string of the Map entries
+     */
+    private static String formatMap(Map env) {
+        return env.entrySet().stream()
+                .map(e -> format("(%s:%s)", e.getKey(), e.getValue()))
+                .collect(joining(", "));
+    }
+}