Skip to content
Permalink
Browse files
Add Path support to ZipArchiveOutputStream with an eye on implementing (
#123)

additional ArchiveOutputStream.createArchiveEntry(Path, String,
LinkOption...) methods.
  • Loading branch information
garydgregory committed Aug 8, 2020
1 parent 2c85b2c commit dab9c48811f90c92daf3dd095623f5f18010a553
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 26 deletions.
@@ -21,6 +21,8 @@
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.LinkOption;
import java.nio.file.Path;

/**
* Archive output stream implementations are expected to override the
@@ -90,6 +92,26 @@ public abstract class ArchiveOutputStream extends OutputStream {
*/
public abstract ArchiveEntry createArchiveEntry(File inputFile, String entryName) throws IOException;

/**
* Create an archive entry using the inputPath and entryName provided.
*
* The default implementation calls simply delegates as:
* <pre>return createArchiveEntry(inputFile.toFile(), entryName);</pre>
*
* Subclasses should override this method.
*
* @param inputPath the file to create the entry from
* @param entryName name to use for the entry
* @param options options indicating how symbolic links are handled.
* @return the ArchiveEntry set up with details from the file
*
* @throws IOException if an I/O error occurs
* @since 1.21
*/
public ArchiveEntry createArchiveEntry(Path inputPath, String entryName, LinkOption... options) throws IOException {
return createArchiveEntry(inputPath.toFile(), entryName);
}

// Generic implementations of OutputStream methods that may be useful to sub-classes

/**
@@ -17,16 +17,21 @@
*/
package org.apache.commons.compress.archivers.zip;

import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.EntryStreamOffsets;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.zip.ZipException;

import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.EntryStreamOffsets;

/**
* Extension that adds better handling of extra fields and provides
* access to the internal and external file attributes.
@@ -228,6 +233,39 @@ public ZipArchiveEntry(final File inputFile, final String entryName) {
// TODO are there any other fields we can set here?
}

/**
* Creates a new zip entry taking some information from the given
* path and using the provided name.
*
* <p>The name will be adjusted to end with a forward slash "/" if
* the file is a directory. If the file is not a directory a
* potential trailing forward slash will be stripped from the
* entry name.</p>
* @param inputPath path to create the entry from.
* @param entryName name of the entry.
* @param options options indicating how symbolic links are handled.
* @throws IOException if an I/O error occurs.
* @since 1.21
*/
public ZipArchiveEntry(final Path inputPath, final String entryName, LinkOption... options) throws IOException {
this(Files.isDirectory(inputPath, options) && !entryName.endsWith("/") ?
entryName + "/" : entryName);
if (Files.isRegularFile(inputPath, options)){
setSize(Files.size(inputPath));
}
setTime(Files.getLastModifiedTime(inputPath, options));
// TODO are there any other fields we can set here?
}

/**
* Sets the modification time of the entry.
* @param fileTime the entry modification time.
* @since 1.21
*/
public void setTime(final FileTime fileTime) {
setTime(fileTime.toMillis());
}

/**
* Overwrite clone.
* @return a cloned copy of this ZipArchiveEntry
@@ -17,6 +17,18 @@
*/
package org.apache.commons.compress.archivers.zip;

import static org.apache.commons.compress.archivers.zip.ZipConstants.DATA_DESCRIPTOR_MIN_VERSION;
import static org.apache.commons.compress.archivers.zip.ZipConstants.DEFLATE_MIN_VERSION;
import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD;
import static org.apache.commons.compress.archivers.zip.ZipConstants.INITIAL_VERSION;
import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT;
import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD;
import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC;
import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC_SHORT;
import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MIN_VERSION;
import static org.apache.commons.compress.archivers.zip.ZipLong.putLong;
import static org.apache.commons.compress.archivers.zip.ZipShort.putShort;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
@@ -26,6 +38,9 @@
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Calendar;
import java.util.EnumSet;
@@ -40,18 +55,6 @@
import org.apache.commons.compress.archivers.ArchiveOutputStream;
import org.apache.commons.compress.utils.IOUtils;

import static org.apache.commons.compress.archivers.zip.ZipConstants.DATA_DESCRIPTOR_MIN_VERSION;
import static org.apache.commons.compress.archivers.zip.ZipConstants.DEFLATE_MIN_VERSION;
import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD;
import static org.apache.commons.compress.archivers.zip.ZipConstants.INITIAL_VERSION;
import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT;
import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD;
import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC;
import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC_SHORT;
import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MIN_VERSION;
import static org.apache.commons.compress.archivers.zip.ZipLong.putLong;
import static org.apache.commons.compress.archivers.zip.ZipShort.putShort;

/**
* Reimplementation of {@link java.util.zip.ZipOutputStream
* java.util.zip.ZipOutputStream} that does handle the extended
@@ -307,12 +310,24 @@ public ZipArchiveOutputStream(final OutputStream out) {
* @throws IOException on error
*/
public ZipArchiveOutputStream(final File file) throws IOException {
this(file.toPath());
}

/**
* Creates a new ZIP OutputStream writing to a Path. Will use
* random access if possible.
* @param file the file to zip to
* @param options options specifying how the file is opened.
* @throws IOException on error
* @since 1.21
*/
public ZipArchiveOutputStream(final Path file, OpenOption... options) throws IOException {
def = new Deflater(level, true);
OutputStream o = null;
SeekableByteChannel _channel = null;
StreamCompressor _streamCompressor = null;
try {
_channel = Files.newByteChannel(file.toPath(),
_channel = Files.newByteChannel(file,
EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE,
StandardOpenOption.READ,
StandardOpenOption.TRUNCATE_EXISTING));
@@ -321,7 +336,7 @@ public ZipArchiveOutputStream(final File file) throws IOException {
} catch (final IOException e) { // NOSONAR
IOUtils.closeQuietly(_channel);
_channel = null;
o = new FileOutputStream(file);
o = Files.newOutputStream(file, options);
_streamCompressor = StreamCompressor.create(o, def);
}
out = o;
@@ -1757,6 +1772,32 @@ public ArchiveEntry createArchiveEntry(final File inputFile, final String entryN
return new ZipArchiveEntry(inputFile, entryName);
}

/**
* Creates a new zip entry taking some information from the given
* file and using the provided name.
*
* <p>The name will be adjusted to end with a forward slash "/" if
* the file is a directory. If the file is not a directory a
* potential trailing forward slash will be stripped from the
* entry name.</p>
*
* <p>Must not be used if the stream has already been closed.</p>
* @param inputPath path to create the entry from.
* @param entryName name of the entry.
* @param options options indicating how symbolic links are handled.
* @return a new instance.
* @throws IOException if an I/O error occurs.
* @since 1.21
*/
@Override
public ArchiveEntry createArchiveEntry(final Path inputPath, final String entryName, LinkOption... options)
throws IOException {
if (finished) {
throw new IOException("Stream has already been finished");
}
return new ZipArchiveEntry(inputPath, entryName);
}

/**
* Get the existing ZIP64 extended information extra field or
* create a new one and add it to the entry.
@@ -18,7 +18,13 @@
*/
package org.apache.commons.compress.archivers;

import static org.junit.Assert.*;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.ByteArrayInputStream;
import java.io.File;
@@ -30,6 +36,7 @@
import java.nio.channels.Channels;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
@@ -498,14 +505,16 @@ public void testFileEntryFromFile() throws Exception {
ZipArchiveOutputStream zos = null;
ZipFile zf = null;
FileInputStream fis = null;
File tmpDir = tmp[0];
File tmpFile = tmp[1];
try {
archive = File.createTempFile("test.", ".zip", tmp[0]);
archive = File.createTempFile("test.", ".zip", tmpDir);
archive.deleteOnExit();
zos = new ZipArchiveOutputStream(archive);
final ZipArchiveEntry in = new ZipArchiveEntry(tmp[1], "foo");
final ZipArchiveEntry in = new ZipArchiveEntry(tmpFile, "foo");
zos.putArchiveEntry(in);
final byte[] b = new byte[(int) tmp[1].length()];
fis = new FileInputStream(tmp[1]);
final byte[] b = new byte[(int) tmpFile.length()];
fis = new FileInputStream(tmpFile);
while (fis.read(b) > 0) {
zos.write(b);
}
@@ -518,8 +527,8 @@ public void testFileEntryFromFile() throws Exception {
final ZipArchiveEntry out = zf.getEntry("foo");
assertNotNull(out);
assertEquals("foo", out.getName());
assertEquals(tmp[1].length(), out.getSize());
assertEquals(tmp[1].lastModified() / 2000,
assertEquals(tmpFile.length(), out.getSize());
assertEquals(tmpFile.lastModified() / 2000,
out.getLastModifiedDate().getTime() / 2000);
assertFalse(out.isDirectory());
} finally {
@@ -531,8 +540,58 @@ public void testFileEntryFromFile() throws Exception {
if (fis != null) {
fis.close();
}
tryHardToDelete(tmp[1]);
rmdir(tmp[0]);
tryHardToDelete(tmpFile);
rmdir(tmpDir);
}
}

@Test
public void testZipArchiveEntryNewFromPath() throws Exception {
final File[] tmp = createTempDirAndFile();
File archiveFile = null;
Path archivePath = null;
ZipArchiveOutputStream zos = null;
ZipFile zf = null;
FileInputStream fis = null;
File tmpDir = tmp[0];
File tmpFile = tmp[1];
Path tmpFilePath = tmpFile.toPath();
try {
archiveFile = File.createTempFile("test.", ".zip", tmpDir);
archivePath = archiveFile.toPath();
archiveFile.deleteOnExit();
zos = new ZipArchiveOutputStream(archivePath);
final ZipArchiveEntry in = (ZipArchiveEntry) zos.createArchiveEntry(tmpFilePath, "foo");
zos.putArchiveEntry(in);
final byte[] b = new byte[(int) tmpFile.length()];
fis = new FileInputStream(tmpFile);
while (fis.read(b) > 0) {
zos.write(b);
}
fis.close();
fis = null;
zos.closeArchiveEntry();
zos.close();
zos = null;
zf = new ZipFile(archiveFile);
final ZipArchiveEntry out = zf.getEntry("foo");
assertNotNull(out);
assertEquals("foo", out.getName());
assertEquals(tmpFile.length(), out.getSize());
assertEquals(tmpFile.lastModified() / 2000,
out.getLastModifiedDate().getTime() / 2000);
assertFalse(out.isDirectory());
} finally {
ZipFile.closeQuietly(zf);
if (zos != null) {
zos.close();
}
tryHardToDelete(archiveFile);
if (fis != null) {
fis.close();
}
tryHardToDelete(tmpFile);
rmdir(tmpDir);
}
}

0 comments on commit dab9c48

Please sign in to comment.