8211917: Zip FS should add META-INF/MANIFEST.FS at the start of the Zip/JAR

Reviewed-by: clanger, jpai
This commit is contained in:
Lance Andersen 2020-03-05 13:56:40 -05:00
parent db91be2ee1
commit 3607ddd55a
4 changed files with 815 additions and 1 deletions

View File

@ -1718,8 +1718,33 @@ class ZipFileSystem extends FileSystem {
byte[] buf = null;
Entry e;
final IndexNode manifestInode = inodes.get(
IndexNode.keyOf(getBytes("/META-INF/MANIFEST.MF")));
final Iterator<IndexNode> inodeIterator = inodes.values().iterator();
boolean manifestProcessed = false;
// write loc
for (IndexNode inode : inodes.values()) {
while (inodeIterator.hasNext()) {
final IndexNode inode;
// write the manifest inode (if any) first so that
// java.util.jar.JarInputStream can find it
if (manifestInode == null) {
inode = inodeIterator.next();
} else {
if (manifestProcessed) {
// advance to next node, filtering out the manifest
// which was already written
inode = inodeIterator.next();
if (inode == manifestInode) {
continue;
}
} else {
inode = manifestInode;
manifestProcessed = true;
}
}
if (inode instanceof Entry) { // an updated inode
e = (Entry)inode;
try {

View File

@ -0,0 +1,4 @@
# Zip FS unit tests uses TestNG
modules = jdk.zipfs
TestNG.dirs = .

View File

@ -0,0 +1,528 @@
/*
* Copyright (c) 2020, 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.
*
*/
package test;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.Test;
import util.ZipFsBaseTest;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.*;
import java.util.jar.Attributes.Name;
import java.util.spi.ToolProvider;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import static org.testng.Assert.*;
/**
* @test
* @bug 8211917
* @summary Validate that Zip FS will always add META-INF/MANIFEST.MF to the
* beginning of a Zip file allowing the Manifest be found and processed
* by java.util.jar.JarInputStream.
*/
public class ManifestOrderTest extends ZipFsBaseTest {
// Manifest PATH within a JAR
public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
// Manifests used by the tests
private static String MANIFEST_ENTRY;
private static String MANIFEST_ENTRY2;
// Manifest Attributes used by the tests
private static Map<Name, String> MANIFEST_ATTRS;
private static Map<Name, String> MANIFEST_ATTRS2;
// Used when the test does not expect to find a Manifest
private static final Map<Name, String> NO_ATTRIBUTES = Map.of();
// JAR Tool via ToolProvider API
private static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar")
.orElseThrow(() -> new RuntimeException("jar tool not found")
);
/**
* Create the Manifests and Map of attributes included in the Manifests
*/
@BeforeSuite
public void setup() {
String jdkVendor = System.getProperty("java.vendor");
String jdkVersion = System.getProperty("java.version");
String attributeKey = "Player";
String attributeValue = "Rafael Nadal";
String attributeKey2 = "Country";
String attributeValue2 = "Spain";
String jdkVendorVersion = jdkVersion + " (" + jdkVendor + ")";
MANIFEST_ENTRY = "Manifest-Version: 1.0"
+ System.lineSeparator()
+ "Created-By: " + jdkVendorVersion
+ System.lineSeparator()
+ attributeKey + ": " + attributeValue
+ System.lineSeparator();
MANIFEST_ENTRY2 = MANIFEST_ENTRY
+ attributeKey2 + ": " + attributeValue2
+ System.lineSeparator();
MANIFEST_ATTRS =
Map.of(Name.MANIFEST_VERSION, "1.0",
new Name("Created-By"), jdkVendorVersion,
new Name(attributeKey), attributeValue);
MANIFEST_ATTRS2 = new HashMap<>();
MANIFEST_ATTRS.forEach(MANIFEST_ATTRS2::put);
MANIFEST_ATTRS2.put(new Name(attributeKey2), attributeValue2);
}
/**
* Validate that JarInputStream can find META-INF/MANIFEST.MF when its written
* as the first entry within a JAR using Zip FS
*
* @param env Zip FS properties to use when creating the Zip File
* @param compression The compression used when writing the entries
* @throws Exception If an error occurs
*/
@Test(dataProvider = "zipfsMap")
public void testJarWithManifestAddedFirst(final Map<String, String> env,
final int compression)
throws Exception {
final Path jarPath = generatePath(HERE, "test", ".jar");
Files.deleteIfExists(jarPath);
// Create the initial JAR writing out the Manifest first
final Entry[] entries = newEntries(compression);
Entry manifest = Entry.of(MANIFEST_NAME, compression, MANIFEST_ENTRY);
zip(jarPath, env, manifest, entries[0], entries[1], entries[2]);
verify(jarPath, MANIFEST_ATTRS, compression, entries);
// Add an additional entry and re-verify
Entry e00 = Entry.of("Entry-00", compression, "Roger Federer");
zip(jarPath, Map.of("noCompression", compression == ZipEntry.STORED),
e00);
verify(jarPath, MANIFEST_ATTRS, compression, entries[0], entries[1],
entries[2], e00);
}
/**
* Validate that JarInputStream can find META-INF/MANIFEST.MF when its written
* as the last entry within a JAR using Zip FS
*
* @param env Zip FS properties to use when creating the Zip File
* @param compression The compression used when writing the entries
* @throws Exception If an error occurs
*/
@Test(dataProvider = "zipfsMap")
public void testJarWithManifestAddedLast(final Map<String, String> env,
final int compression) throws Exception {
final Path jarPath = generatePath(HERE, "test", ".jar");
Files.deleteIfExists(jarPath);
// Create the initial JAR writing out the Manifest last
final Entry[] entries = newEntries(compression);
Entry manifest = Entry.of(MANIFEST_NAME, compression, MANIFEST_ENTRY);
zip(jarPath, env, entries[0], entries[1], entries[2], manifest);
verify(jarPath, MANIFEST_ATTRS, compression, entries);
// Add an additional entry and re-verify
Entry e00 = Entry.of("Entry-00", compression, "Roger Federer");
zip(jarPath, Map.of("noCompression", compression == ZipEntry.STORED),
e00);
verify(jarPath, MANIFEST_ATTRS, compression, entries[0], entries[1],
entries[2], e00);
}
/**
* Validate that JarInputStream can find META-INF/MANIFEST.MF when its written
* between other entries within a JAR using Zip FS
*
* @param env Zip FS properties to use when creating the Zip File
* @param compression The compression used when writing the entries
* @throws Exception If an error occurs
*/
@Test(dataProvider = "zipfsMap")
public void testJarWithManifestAddedInBetween(final Map<String, String> env,
final int compression)
throws Exception {
final Path jarPath = generatePath(HERE, "test", ".jar");
Files.deleteIfExists(jarPath);
// Create the initial JAR writing out the Manifest in between other entries
final Entry[] entries = newEntries(compression);
Entry manifest = Entry.of(MANIFEST_NAME, compression, MANIFEST_ENTRY);
zip(jarPath, env, entries[0], entries[1], manifest, entries[2]);
verify(jarPath, MANIFEST_ATTRS, compression, entries);
// Add an additional entry and re-verify
Entry e00 = Entry.of("Entry-00", compression, "Roger Federer");
zip(jarPath, Map.of("noCompression", compression == ZipEntry.STORED),
e00);
verify(jarPath, MANIFEST_ATTRS, compression, entries[0], entries[1],
entries[2], e00);
}
/**
* Validate that JarInputStream can read all entries from a JAR created
* using Zip FS without adding a Manifest
*
* @param env Zip FS properties to use when creating the Zip File
* @param compression The compression used when writing the entries
* @throws Exception If an error occurs
*/
@Test(dataProvider = "zipfsMap")
public void testJarWithNoManifest(final Map<String, String> env,
final int compression) throws Exception {
final Path jarPath = generatePath(HERE, "test", ".jar");
Files.deleteIfExists(jarPath);
// Create the initial JAR writing without a Manifest
final Entry[] entries = newEntries(compression);
zip(jarPath, env, entries[0], entries[1], entries[2]);
verify(jarPath, NO_ATTRIBUTES, compression, entries);
// Add an additional entry and re-verify
Entry e00 = Entry.of("Entry-00", compression, "Roger Federer");
zip(jarPath, Map.of("noCompression", compression == ZipEntry.STORED),
e00);
verify(jarPath, NO_ATTRIBUTES, compression, entries[0], entries[1],
entries[2], e00);
}
/**
* Validate that JarInputStream can read META-INF/MANIFEST.MF when the
* the Manfiest is copied to the JAR using Files::copy
*
* @param env Zip FS properties to use when creating the Zip File
* @param compression The compression used when writing the entries
* @throws Exception If an error occurs
*/
@Test(dataProvider = "zipfsMap")
public void testManifestCopiedFromOSFile(final Map<String, String> env,
final int compression) throws Exception {
final Path jarPath = generatePath(HERE, "test", ".jar");
Files.deleteIfExists(jarPath);
final Path manifest = Paths.get(".", "test-MANIFEST.MF");
Files.deleteIfExists(manifest);
// Create initial JAR without a Manifest
Files.writeString(manifest, MANIFEST_ENTRY);
final Entry[] entries = newEntries(compression);
zip(jarPath, env, entries[0], entries[1], entries[2]);
verify(jarPath, NO_ATTRIBUTES, compression, entries);
// Add the Manifest via Files::copy and verify
try (FileSystem zipfs =
FileSystems.newFileSystem(jarPath, env)) {
Files.copy(manifest, zipfs.getPath("META-INF", "MANIFEST.MF"));
}
verify(jarPath, MANIFEST_ATTRS, compression, entries);
}
/**
* Validate that JarInputStream can find META-INF/MANIFEST.MF when the
* entries are copied from one JAR to another JAR using Zip FS and Files::copy
*
* @param env Zip FS properties to use when creating the Zip File
* @param compression The compression used when writing the entries
* @throws Exception If an error occurs
*/
@Test(dataProvider = "zipfsMap")
public void copyJarToJarTest(final Map<String, String> env, final int compression)
throws Exception {
final Path jarPath = generatePath(HERE, "test", ".jar");
final Path jarPath2 = generatePath(HERE, "test", ".jar");
Files.deleteIfExists(jarPath);
Files.deleteIfExists(jarPath2);
// Create initial JAR with a Manifest
final Entry[] entries = newEntries(compression);
Entry manifest = Entry.of(MANIFEST_NAME, compression, MANIFEST_ENTRY);
zip(jarPath, env, manifest, entries[0], entries[1], entries[2]);
verify(jarPath, MANIFEST_ATTRS, compression, entries);
// Create the another JAR via Files::copy and verify
try (FileSystem zipfs = FileSystems.newFileSystem(jarPath, env);
FileSystem zipfsTarget = FileSystems.newFileSystem(jarPath2,
Map.of("create", "true", "noCompression",
compression == ZipEntry.STORED))) {
Path mPath = zipfsTarget.getPath(manifest.name);
if (mPath.getParent() != null) {
Files.createDirectories(mPath.getParent());
}
Files.copy(zipfs.getPath(manifest.name), mPath);
for (Entry e : entries) {
Path target = zipfsTarget.getPath(e.name);
if (target.getParent() != null) {
Files.createDirectories(target.getParent());
}
Files.copy(zipfs.getPath(e.name), target);
}
}
verify(jarPath2, MANIFEST_ATTRS, compression, entries);
}
/**
* Validate a JAR created using the jar tool and is updated using Zip FS
* contains the expected entries and Manifest
*
* @param compression The compression used when writing the entries
* @throws Exception If an error occurs
*/
@Test(dataProvider = "compressionMethods")
public void testJarToolGeneratedJarWithManifest(final int compression)
throws Exception {
final Path jarPath = generatePath(HERE, "test", ".jar");
Files.deleteIfExists(jarPath);
final Entry[] entries = newEntries(compression);
final Path tmpdir = Paths.get("tmp");
rmdir(tmpdir);
Files.createDirectory(tmpdir);
// Create a directory to hold the files to bad added to the JAR
for (final Entry entry : entries) {
Path p = Path.of("tmp", entry.name);
if (p.getParent() != null) {
Files.createDirectories(p.getParent());
}
Files.write(Path.of("tmp", entry.name), entry.bytes);
}
// Create a file containing the Manifest
final Path manifestFile = Paths.get(".", "test-jar-MANIFEST.MF");
Files.deleteIfExists(manifestFile);
Files.writeString(manifestFile, MANIFEST_ENTRY);
// Create a JAR via the jar tool and verify
final int exitCode = JAR_TOOL.run(System.out, System.err, "cvfm"
+ (compression == ZipEntry.STORED ? "0" : ""),
jarPath.getFileName().toString(),
manifestFile.toAbsolutePath().toString(),
"-C", tmpdir.toAbsolutePath().toString(), ".");
assertEquals(exitCode, 0, "jar tool exited with failure");
verify(jarPath, MANIFEST_ATTRS, compression, entries);
// Add an additional entry and re-verify
Entry e00 = Entry.of("Entry-00", compression, "Roger Federer");
zip(jarPath, Map.of("noCompression", compression == ZipEntry.STORED),
entries[0], entries[1], e00, entries[2]);
verify(jarPath, MANIFEST_ATTRS, compression, entries[0], entries[1],
e00, entries[2]);
}
/**
* Validate that JarInputStream can read all entries from a JAR created
* using Zip FS with a Manifest created by java.util.jar.Manifest
*
* @param env Zip FS properties to use when creating the Zip File
* @param compression The compression used when writing the entries
* @throws Exception If an error occurs
*/
@Test(dataProvider = "zipfsMap")
public void createWithManifestTest(final Map<String, String> env,
final int compression) throws Exception {
Path jarPath = generatePath(HERE, "test", ".jar");
Files.deleteIfExists(jarPath);
// Create the JAR and verify
Entry e00 = Entry.of("Entry-00", compression, "Indian Wells");
try (FileSystem zipfs = FileSystems.newFileSystem(jarPath, env)) {
// Add our Manifest using Manifest::write
Path manifestPath = zipfs.getPath("META-INF", "MANIFEST.MF");
if (manifestPath.getParent() != null) {
Files.createDirectories(manifestPath.getParent());
}
try (final OutputStream os = Files.newOutputStream(manifestPath)) {
final Manifest manifest = new Manifest();
final Attributes attributes = manifest.getMainAttributes();
// Populate the Manifest Attributes
MANIFEST_ATTRS.forEach(attributes::put);
manifest.write(os);
}
Files.write(zipfs.getPath(e00.name), e00.bytes);
}
verify(jarPath, MANIFEST_ATTRS, compression, e00);
}
/**
* Validate that JarInputStream can find META-INF/MANIFEST.MF when it has
* been updated by Zip FS
*
* @param env Zip FS properties to use when creating the Zip File
* @param compression The compression used when writing the entries
* @throws Exception If an error occurs
*/
@Test(dataProvider = "zipfsMap")
public void updateManifestTest(final Map<String, String> env,
final int compression) throws Exception {
final Path jarPath = generatePath(HERE, "test", ".jar");
Files.deleteIfExists(jarPath);
// Create the initial JAR with a Manifest
final Entry[] entries = newEntries(compression);
Entry manifest = Entry.of(MANIFEST_NAME, compression, MANIFEST_ENTRY);
zip(jarPath, env, manifest, entries[0], entries[1], entries[2]);
verify(jarPath, MANIFEST_ATTRS, compression, entries);
// Add an additional entry, update the Manifest and re-verify
Entry e00 = Entry.of("Entry-00", compression, "Roger Federer");
Entry revisedManifest = manifest.content(MANIFEST_ENTRY2);
zip(jarPath, Map.of("noCompression", compression == ZipEntry.STORED),
revisedManifest, e00);
verify(jarPath, MANIFEST_ATTRS2, compression, entries[0], entries[1],
entries[2], e00);
}
/**
* Create an Entry array used when creating a JAR using Zip FS
*
* @param compressionMethod The compression method used by the test
* @return the Entry array
*/
private static Entry[] newEntries(final int compressionMethod) {
return new Entry[]{new Entry("hello.txt", compressionMethod, "hello"),
new Entry("META-INF/bar/world.txt", compressionMethod, "world"),
new Entry("META-INF/greeting.txt", compressionMethod, "greeting")};
}
/**
* Verify the entries including the Manifest in a JAR
*
* @param jar Path to the JAR
* @param attributes A Map containing the attributes expected in the Manifest;
* otherwise empty
* @param entries Entries to validate in the JAR
* @param compression The compression used when writing the entries
* @throws Exception If an error occurs
*/
private static void verify(final Path jar, final Map<?, ?> attributes,
final int compression, Entry... entries)
throws Exception {
// If the compression method is not STORED, then use JarInputStream
// to validate the entries in the JAR. The current JAR/ZipInputStream
// implementation supports PKZIP version 2.04g which only supports
// bit 8 for DEFLATED entries(JDK-8143613). Zip FS will set this bit
// for STORED entries as is now allowed by the PKZIP spec, resulting
// in the error "only DEFLATED entries can have EXT descriptor"
if (ZipEntry.STORED != compression) {
try (final JarInputStream jis =
new JarInputStream(Files.newInputStream(jar))) {
// Verify the Manifest
validateManifest(attributes, jis.getManifest());
// Verify the rest of the expected entries are present
final Map<String, Entry> expected = Arrays.stream(entries)
.collect(Collectors.toMap(entry -> entry.name, entry -> entry));
JarEntry je = jis.getNextJarEntry();
assertNotNull(je, "Jar is empty");
while (je != null) {
if (je.isDirectory()) {
// skip directories
je = jis.getNextJarEntry();
continue;
}
final Entry e = expected.remove(je.getName());
assertNotNull(e, "Unexpected entry in jar ");
assertEquals(je.getMethod(), e.method, "Compression method mismatch");
assertEquals(jis.readAllBytes(), e.bytes);
je = jis.getNextJarEntry();
}
assertEquals(expected.size(), 0, "Missing entries in jar!");
}
}
// Verify using JarFile
try (final JarFile jf = new JarFile(jar.toFile())) {
// Validate Manifest
validateManifest(attributes, jf.getManifest());
for (Entry e : entries) {
JarEntry je = jf.getJarEntry(e.name);
assertNotNull(je, "Entry does not exist");
if (DEBUG) {
System.out.printf("Entry Name: %s, method: %s, Expected Method: %s%n",
e.name, je.getMethod(), e.method);
}
assertEquals(e.method, je.getMethod(), "Compression methods mismatch");
try (InputStream in = jf.getInputStream(je)) {
byte[] bytes = in.readAllBytes();
if (DEBUG) {
System.out.printf("bytes= %s, actual=%s%n",
new String(bytes), new String(e.bytes));
}
assertTrue(Arrays.equals(bytes, e.bytes), "Entries do not match");
}
}
}
// Check entries with FileSystem API
try (FileSystem fs = FileSystems.newFileSystem(jar)) {
// Check entry count
Path top = fs.getPath("/");
long count = Files.find(top, Integer.MAX_VALUE,
(path, attrs) -> attrs.isRegularFile()).count();
assertEquals(entries.length + (!attributes.isEmpty() ? 1 : 0), count);
Path mf = fs.getPath("META-INF", "MANIFEST.MF");
Manifest m = null;
if (!attributes.isEmpty()) {
assertTrue(Files.exists(mf));
m = new Manifest(Files.newInputStream(mf));
}
validateManifest(attributes, m);
// Check content of each entry
for (Entry e : entries) {
Path file = fs.getPath(e.name);
if (DEBUG) {
System.out.printf("Entry name = %s, bytes= %s, actual=%s%n", e.name,
new String(Files.readAllBytes(file)), new String(e.bytes));
}
assertEquals(Files.readAllBytes(file), e.bytes);
}
}
}
/**
* Validate whether the Manifest contains the expected attributes
*
* @param attributes A Map containing the attributes expected in the Manifest;
* otherwise empty
* @param m The Manifest to validate
*/
private static void validateManifest(Map<?, ?> attributes, Manifest m) {
if (!attributes.isEmpty()) {
assertNotNull(m, "Manifest is missing!");
Attributes attrs = m.getMainAttributes();
attributes.forEach((k, v) ->
{
if (DEBUG) {
System.out.printf("Key: %s, Value: %s%n", k, v);
}
assertTrue(attrs.containsKey(k));
assertEquals(v, attrs.get(k));
});
} else {
assertNull(m, "Manifest was found!");
}
}
}

View File

@ -0,0 +1,257 @@
/*
* Copyright (c) 2020, 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.
*
*/
package util;
import org.testng.annotations.DataProvider;
import java.io.File;
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.Comparator;
import java.util.Map;
import java.util.stream.Stream;
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.*;
public class ZipFsBaseTest {
protected static final Path HERE = Path.of(".");
// Enable for permissions output
protected static final boolean DEBUG = false;
private static final SecureRandom random = new SecureRandom();
/**
* DataProvider used to specify the Zip FS properties to use when creating
* the Zip File along with the compression method used
*
* @return Zip FS properties and compression method used by the tests
*/
@DataProvider(name = "zipfsMap")
protected Object[][] zipfsMap() {
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}
};
}
/**
* DataProvider with the compression methods to be used for a given test run
*
* @return Compression methods to test with
*/
@DataProvider(name = "compressionMethods")
protected Object[][] compressionMethods() {
return new Object[][]{
{ZipEntry.DEFLATED},
{ZipEntry.STORED}
};
}
/**
* 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<String, ?> env) {
return env.entrySet().stream()
.map(e -> format("(%s:%s)", e.getKey(), e.getValue()))
.collect(joining(", "));
}
/**
* 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
*/
protected 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);
}
/**
* Verify that the given path is a Zip file containing exactly the
* given entries.
*/
protected static void verify(Path zipfile, Entry... entries) throws IOException {
// check entries with Zip API
try (ZipFile zf = new ZipFile(zipfile.toFile())) {
// check entry count
assertEquals(entries.length, zf.size());
// Check compression method and content of each entry
for (Entry e : entries) {
ZipEntry ze = zf.getEntry(e.name);
assertNotNull(ze);
if (DEBUG) {
System.out.printf("Entry Name: %s, method: %s, Expected Method: %s%n",
e.name, ze.getMethod(), e.method);
}
assertEquals(e.method, ze.getMethod());
try (InputStream in = zf.getInputStream(ze)) {
byte[] bytes = in.readAllBytes();
if (DEBUG) {
System.out.printf("bytes= %s, actual=%s%n",
new String(bytes), new String(e.bytes));
}
assertTrue(Arrays.equals(bytes, e.bytes));
}
}
}
// Check entries with FileSystem API
try (FileSystem fs = FileSystems.newFileSystem(zipfile)) {
// cCheck entry count
Path top = fs.getPath("/");
long count = Files.find(top, Integer.MAX_VALUE,
(path, attrs) -> attrs.isRegularFile()).count();
assertEquals(entries.length, count);
// Check content of each entry
for (Entry e : entries) {
Path file = fs.getPath(e.name);
if (DEBUG) {
System.out.printf("Entry name = %s, bytes= %s, actual=%s%n", e.name,
new String(Files.readAllBytes(file)), new String(e.bytes));
}
assertEquals(Files.readAllBytes(file), e.bytes);
}
}
}
/**
* 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/update
* @param env Properties used for creating the Zip Filesystem
* @param source The path of the file to add to the Zip File
* @throws IOException If an error occurs while creating/updating the Zip file
*/
protected void zip(Path zipFile, Map<String, String> env, Path source) throws IOException {
if (DEBUG) {
System.out.printf("File:%s, adding:%s%n", zipFile.toAbsolutePath(), source);
}
try (FileSystem zipfs =
FileSystems.newFileSystem(zipFile, env)) {
Files.copy(source, zipfs.getPath(source.getFileName().toString()));
}
}
/**
* 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 The entries to add to the Zip File
* @throws IOException If an error occurs while creating the Zip file
*/
protected void zip(Path zipFile, Map<String, ?> env,
Entry... entries) throws IOException {
if (DEBUG) {
System.out.printf("Creating file: %s, env: %s%n", zipFile, formatMap(env));
}
try (FileSystem zipfs = FileSystems.newFileSystem(zipFile, env)) {
for (Entry e : entries) {
Path path = zipfs.getPath(e.name);
if (path.getParent() != null) {
Files.createDirectories(path.getParent());
}
Files.write(path, e.bytes);
}
}
}
/**
* Recursively remove a Directory
*
* @param dir Directory to delete
* @throws IOException If an error occurs
*/
protected static void rmdir(Path dir) throws IOException {
// Nothing to do if the the file does not exist
if (!Files.exists(dir)) {
return;
}
try (Stream<Path> walk = Files.walk(dir)) {
walk.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.peek(System.out::println)
.forEach(File::delete);
}
}
/**
* Represents an entry in a Zip file. An entry encapsulates a name, a
* compression method, and its contents/data.
*/
public static class Entry {
public final String name;
public final int method;
public final byte[] bytes;
public Entry(String name, int method, String contents) {
this.name = name;
this.method = method;
this.bytes = contents.getBytes(StandardCharsets.UTF_8);
}
public static Entry of(String name, int method, String contents) {
return new Entry(name, method, contents);
}
/**
* Returns a new Entry with the same name and compression method as this
* Entry but with the given content.
*/
public Entry content(String contents) {
return new Entry(name, method, contents);
}
}
}