You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2020/08/08 20:06:07 UTC

[commons-compress] branch master updated: Add Path support to ZipArchiveOutputStream with an eye on implementing (#123)

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 <ga...@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);
         }
     }