This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-compress.git
The following commit(s) were added to refs/heads/master by this push: new dab9c48 Add Path support to ZipArchiveOutputStream with an eye on implementing (#123) dab9c48 is described below commit dab9c48811f90c92daf3dd095623f5f18010a553 Author: Gary Gregory <garydgreg...@users.noreply.github.com> AuthorDate: Sat Aug 8 16:06:00 2020 -0400 Add Path support to ZipArchiveOutputStream with an eye on implementing (#123) additional ArchiveOutputStream.createArchiveEntry(Path, String, LinkOption...) methods. --- .../compress/archivers/ArchiveOutputStream.java | 22 +++++++ .../compress/archivers/zip/ZipArchiveEntry.java | 44 ++++++++++++- .../archivers/zip/ZipArchiveOutputStream.java | 69 +++++++++++++++---- .../commons/compress/archivers/ZipTestCase.java | 77 +++++++++++++++++++--- 4 files changed, 186 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/apache/commons/compress/archivers/ArchiveOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/ArchiveOutputStream.java index 4377b6d..c7c4a52 100644 --- a/src/main/java/org/apache/commons/compress/archivers/ArchiveOutputStream.java +++ b/src/main/java/org/apache/commons/compress/archivers/ArchiveOutputStream.java @@ -21,6 +21,8 @@ package org.apache.commons.compress.archivers; 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 /** diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java index d7aaefb..687615d 100644 --- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java +++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java @@ -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. @@ -229,6 +234,39 @@ public class ZipArchiveEntry extends java.util.zip.ZipEntry } /** + * 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 */ diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java index 16ad3b7..a926531 100644 --- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java +++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java @@ -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.io.OutputStream; 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.ArchiveEntry; 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 class ZipArchiveOutputStream extends ArchiveOutputStream { * @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 class ZipArchiveOutputStream extends ArchiveOutputStream { } 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; @@ -1758,6 +1773,32 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { } /** + * 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. * diff --git a/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java b/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java index e66a30a..dcb61fd 100644 --- a/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java +++ b/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java @@ -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.io.OutputStream; 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 final class ZipTestCase extends AbstractTestCase { 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 final class ZipTestCase extends AbstractTestCase { 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 final class ZipTestCase extends AbstractTestCase { 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); } }