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:02:35 UTC

[commons-compress] branch master updated (8e34ee4 -> 2c85b2c)

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/commons-compress.git.


    from 8e34ee4  Make the private static final instance public under the name "DEFAULT" since it uses the platform default encoding.
     new 379f75a  Add SevenZOutputFile.write(InputStream).
     new fee64bc  Add SevenZOutputFile.write(Path, OpenOption...).
     new 2c85b2c  Reimplement org.apache.commons.compress.archivers.examples.Archiver using Java NIO tree walking instead custom file system traversal which was painful when dealing with large directories and calls to File.listFiles().

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/changes/changes.xml                            |   6 +
 .../compress/archivers/examples/Archiver.java      | 402 ++++++++++++---------
 .../archivers/sevenz/SevenZOutputFile.java         |  34 +-
 .../archivers/sevenz/SevenZOutputFileTest.java     |  75 ++++
 4 files changed, 350 insertions(+), 167 deletions(-)


[commons-compress] 01/03: Add SevenZOutputFile.write(InputStream).

Posted by gg...@apache.org.
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

commit 379f75ace59e52853aae91c591aeaee20e3f3c79
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Aug 8 15:45:56 2020 -0400

    Add SevenZOutputFile.write(InputStream).
---
 src/changes/changes.xml                            |  3 ++
 .../archivers/sevenz/SevenZOutputFile.java         | 17 +++++++
 .../archivers/sevenz/SevenZOutputFileTest.java     | 55 ++++++++++++++++++++++
 3 files changed, 75 insertions(+)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index f1f303d..58d23c5 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -130,6 +130,9 @@ The <action> type attribute can be add,update,fix,remove.
       <action issue="COMPRESS-539" type="update" date="2020-07-04" due-to="Robin Schimpf">
         Reuse the record buffer in TarArchiveInputStream.
       </action>
+      <action type="update" date="2020-08-08" due-to="Gary Gregory" dev="ggregory">
+        Add SevenZOutputFile.write(InputStream).
+      </action>
       <action type="update" date="2020-07-23" due-to="Dependabot" dev="ggregory">
         Update GitHub actions/checkout from v1 to v2.3.1 #114.
       </action>
diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java
index 0e43769..28a7e6c 100644
--- a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java
@@ -17,12 +17,14 @@
  */
 package org.apache.commons.compress.archivers.sevenz;
 
+import java.io.BufferedInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.DataOutput;
 import java.io.DataOutputStream;
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -243,6 +245,20 @@ public class SevenZOutputFile implements Closeable {
     }
 
     /**
+     * Writes all of the given input stream to the current archive entry.
+     * @param inputStream the data source.
+     * @throws IOException if an I/O error occurs.
+     * @since 1.21
+     */
+    public void write(final InputStream inputStream) throws IOException {
+        final byte[] buffer = new byte[8024];
+        int n = 0;
+        while (-1 != (n = inputStream.read(buffer))) {
+            write(buffer, 0, n);
+        }
+    }
+
+    /**
      * Finishes the addition of entries to this archive, without closing it.
      *
      * @throws IOException if archive is already closed.
@@ -810,4 +826,5 @@ public class SevenZOutputFile implements Closeable {
             // the file will be closed by the containing class's close method
         }
     }
+
 }
diff --git a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java
index 63836ae..638d9d2 100644
--- a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java
@@ -20,6 +20,7 @@ package org.apache.commons.compress.archivers.sevenz;
 import static org.junit.Assert.*;
 import org.junit.Test;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.util.Arrays;
@@ -77,6 +78,30 @@ public class SevenZOutputFileTest extends AbstractTestCase {
             outArchive.closeArchiveEntry();
 
             entry = new SevenZArchiveEntry();
+            entry.setName("foo/bar/boo0");
+            entry.setCreationDate(creationDate);
+            entry.setAccessDate(accessDate);
+            outArchive.putArchiveEntry(entry);
+            outArchive.write(new ByteArrayInputStream(new byte[0]));
+            outArchive.closeArchiveEntry();
+
+            entry = new SevenZArchiveEntry();
+            entry.setName("foo/bar/boo1");
+            entry.setCreationDate(creationDate);
+            entry.setAccessDate(accessDate);
+            outArchive.putArchiveEntry(entry);
+            outArchive.write(new ByteArrayInputStream(new byte[] {'a'}));
+            outArchive.closeArchiveEntry();
+
+            entry = new SevenZArchiveEntry();
+            entry.setName("foo/bar/boo10000");
+            entry.setCreationDate(creationDate);
+            entry.setAccessDate(accessDate);
+            outArchive.putArchiveEntry(entry);
+            outArchive.write(new ByteArrayInputStream(new byte[10000]));
+            outArchive.closeArchiveEntry();
+
+            entry = new SevenZArchiveEntry();
             entry.setName("xyzzy");
             outArchive.putArchiveEntry(entry);
             outArchive.write(0);
@@ -118,6 +143,36 @@ public class SevenZOutputFileTest extends AbstractTestCase {
 
             entry = archive.getNextEntry();
             assert (entry != null);
+            assertEquals("foo/bar/boo0", entry.getName());
+            assertFalse(entry.isDirectory());
+            assertFalse(entry.isAntiItem());
+            assertEquals(0, entry.getSize());
+            assertFalse(entry.getHasLastModifiedDate());
+            assertEquals(accessDate, entry.getAccessDate());
+            assertEquals(creationDate, entry.getCreationDate());
+
+            entry = archive.getNextEntry();
+            assert (entry != null);
+            assertEquals("foo/bar/boo1", entry.getName());
+            assertFalse(entry.isDirectory());
+            assertFalse(entry.isAntiItem());
+            assertEquals(1, entry.getSize());
+            assertFalse(entry.getHasLastModifiedDate());
+            assertEquals(accessDate, entry.getAccessDate());
+            assertEquals(creationDate, entry.getCreationDate());
+
+            entry = archive.getNextEntry();
+            assert (entry != null);
+            assertEquals("foo/bar/boo10000", entry.getName());
+            assertFalse(entry.isDirectory());
+            assertFalse(entry.isAntiItem());
+            assertEquals(10000, entry.getSize());
+            assertFalse(entry.getHasLastModifiedDate());
+            assertEquals(accessDate, entry.getAccessDate());
+            assertEquals(creationDate, entry.getCreationDate());
+
+            entry = archive.getNextEntry();
+            assert (entry != null);
             assertEquals("xyzzy", entry.getName());
             assertEquals(1, entry.getSize());
             assertFalse(entry.getHasAccessDate());


[commons-compress] 03/03: Reimplement org.apache.commons.compress.archivers.examples.Archiver using Java NIO tree walking instead custom file system traversal which was painful when dealing with large directories and calls to File.listFiles().

Posted by gg...@apache.org.
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

commit 2c85b2cd40889166c0341a3049cd769b22d7687e
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Aug 8 16:02:26 2020 -0400

    Reimplement org.apache.commons.compress.archivers.examples.Archiver
    using Java NIO tree walking instead custom file system traversal which
    was painful when dealing with large directories and calls to
    File.listFiles().
    
    Also provide Java NIO Path APIs.
    
    There is some cruft still, due to the fact that this component is still
    on Java 7 and Java 8 lambdas cannot be used.
---
 .../compress/archivers/examples/Archiver.java      | 402 ++++++++++++---------
 1 file changed, 236 insertions(+), 166 deletions(-)

diff --git a/src/main/java/org/apache/commons/compress/archivers/examples/Archiver.java b/src/main/java/org/apache/commons/compress/archivers/examples/Archiver.java
index b35ecc2..cf6bfa0 100644
--- a/src/main/java/org/apache/commons/compress/archivers/examples/Archiver.java
+++ b/src/main/java/org/apache/commons/compress/archivers/examples/Archiver.java
@@ -18,16 +18,21 @@
  */
 package org.apache.commons.compress.archivers.examples;
 
-import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.channels.Channels;
 import java.nio.channels.FileChannel;
 import java.nio.channels.SeekableByteChannel;
+import java.nio.file.FileVisitOption;
+import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
 import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.EnumSet;
+import java.util.Objects;
 
 import org.apache.commons.compress.archivers.ArchiveEntry;
 import org.apache.commons.compress.archivers.ArchiveException;
@@ -35,63 +40,178 @@ import org.apache.commons.compress.archivers.ArchiveOutputStream;
 import org.apache.commons.compress.archivers.ArchiveStreamFactory;
 import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
 import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
-import org.apache.commons.compress.utils.IOUtils;
 
 /**
  * Provides a high level API for creating archives.
+ *
  * @since 1.17
+ * @since 1.21 Supports {@link Path}.
  */
 public class Archiver {
 
-    private interface ArchiveEntryCreator {
-        ArchiveEntry create(File f, String entryName) throws IOException;
+    private static final class ArchiverFileVisitor extends SimpleFileVisitor<Path> {
+
+        private final ArchiveOutputStream target;
+        private final Path directory;
+
+        private ArchiverFileVisitor(final ArchiveOutputStream target,
+            final Path directory /* BiConsumer<Path,T> fileConsumer */) {
+            this.target = target;
+            this.directory = directory;
+        }
+
+        @Override
+        public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException {
+            return visit(dir, attrs, false);
+        }
+
+        private FileVisitResult visit(final Path path, final BasicFileAttributes attrs, final boolean isFile)
+            throws IOException {
+            Objects.requireNonNull(path);
+            Objects.requireNonNull(attrs);
+            final String name = directory.relativize(path).toString().replace('\\', '/');
+            if (!name.isEmpty()) {
+                final ArchiveEntry archiveEntry = target.createArchiveEntry(path.toFile(),
+                    isFile || name.endsWith("/") ? name : name + "/");
+                target.putArchiveEntry(archiveEntry);
+                if (isFile) {
+                    // Refactor this as a BiConsumer on Java 8
+                    Files.copy(path, target);
+                }
+                target.closeArchiveEntry();
+            }
+            return FileVisitResult.CONTINUE;
+        }
+
+        @Override
+        public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
+            return visit(file, attrs, true);
+        }
+    }
+
+    /**
+     * No {@link FileVisitOption}.
+     */
+    public static final EnumSet<FileVisitOption> EMPTY_FileVisitOption = EnumSet.noneOf(FileVisitOption.class);
+
+    /**
+     * Creates an archive {@code target} by recursively including all files and directories in {@code directory}.
+     *
+     * @param target the stream to write the new archive to.
+     * @param directory the directory that contains the files to archive.
+     * @throws IOException if an I/O error occurs
+     * @throws ArchiveException if the archive cannot be created for other reasons
+     */
+    public void create(final ArchiveOutputStream target, final File directory) throws IOException, ArchiveException {
+        create(target, directory.toPath(), EMPTY_FileVisitOption);
+    }
+
+    /**
+     * Creates an archive {@code target} by recursively including all files and directories in {@code directory}.
+     *
+     * @param target the stream to write the new archive to.
+     * @param directory the directory that contains the files to archive.
+     * @param fileVisitOptions options to configure the traversal of the source {@code directory}.
+     * @throws IOException if an I/O error occurs or the archive cannot be created for other reasons.
+     * @since 1.21
+     */
+    public void create(final ArchiveOutputStream target, final Path directory,
+        final EnumSet<FileVisitOption> fileVisitOptions) throws IOException {
+        Files.walkFileTree(directory, fileVisitOptions, Integer.MAX_VALUE, new ArchiverFileVisitor(target, directory));
+        target.finish();
+    }
+
+    /**
+     * Creates an archive {@code target} by recursively including all files and directories in {@code directory}.
+     *
+     * @param target the stream to write the new archive to.
+     * @param directory the directory that contains the files to archive.
+     * @throws IOException if an I/O error occurs or the archive cannot be created for other reasons.
+     * @since 1.21
+     */
+    public void create(final ArchiveOutputStream target, final Path directory) throws IOException {
+        create(target, directory, EMPTY_FileVisitOption);
     }
 
-    private interface ArchiveEntryConsumer {
-        void accept(File source, ArchiveEntry entry) throws IOException;
+    /**
+     * Creates an archive {@code target} by recursively including all files and directories in {@code directory}.
+     *
+     * @param target the file to write the new archive to.
+     * @param directory the directory that contains the files to archive.
+     * @throws IOException if an I/O error occurs
+     */
+    public void create(final SevenZOutputFile target, final File directory) throws IOException {
+        create(target, directory.toPath());
     }
 
-    private interface Finisher {
-        void finish() throws IOException;
+    /**
+     * Creates an archive {@code target} by recursively including all files and directories in {@code directory}.
+     *
+     * @param target the file to write the new archive to.
+     * @param directory the directory that contains the files to archive.
+     * @throws IOException if an I/O error occurs
+     * @since 1.21
+     */
+    public void create(final SevenZOutputFile target, final Path directory) throws IOException {
+        // This custom SimpleFileVisitor goes away with Java 8's BiConsumer.
+        Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
+            @Override
+            public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs)
+                throws IOException {
+                return visit(dir, attrs, false);
+            }
+
+            private FileVisitResult visit(final Path path, final BasicFileAttributes attrs, final boolean isFile)
+                throws IOException {
+                Objects.requireNonNull(path);
+                Objects.requireNonNull(attrs);
+                final String name = directory.relativize(path).toString().replace('\\', '/');
+                if (!name.isEmpty()) {
+                    final ArchiveEntry archiveEntry = target.createArchiveEntry(path.toFile(),
+                        isFile || name.endsWith("/") ? name : name + "/");
+                    target.putArchiveEntry(archiveEntry);
+                    if (isFile) {
+                        // Refactor this as a BiConsumer on Java 8
+                        target.write(path);
+                    }
+                    target.closeArchiveEntry();
+                }
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
+                return visit(file, attrs, true);
+            }
+        });
+        target.finish();
     }
 
     /**
      * Creates an archive {@code target} using the format {@code
-     * format} by recursively including all files and directories in
-     * {@code directory}.
+     * format} by recursively including all files and directories in {@code directory}.
      *
-     * @param format the archive format. This uses the same format as
-     * accepted by {@link ArchiveStreamFactory}.
+     * @param format the archive format. This uses the same format as accepted by {@link ArchiveStreamFactory}.
      * @param target the file to write the new archive to.
      * @param directory the directory that contains the files to archive.
      * @throws IOException if an I/O error occurs
      * @throws ArchiveException if the archive cannot be created for other reasons
      */
-    public void create(String format, File target, File directory) throws IOException, ArchiveException {
-        if (prefersSeekableByteChannel(format)) {
-            try (SeekableByteChannel c = FileChannel.open(target.toPath(), StandardOpenOption.WRITE,
-                StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
-                create(format, c, directory, CloseableConsumer.CLOSING_CONSUMER);
-            }
-            return;
-        }
-        try (OutputStream o = Files.newOutputStream(target.toPath())) {
-            create(format, o, directory, CloseableConsumer.CLOSING_CONSUMER);
-        }
+    public void create(final String format, final File target, final File directory)
+        throws IOException, ArchiveException {
+        create(format, target.toPath(), directory.toPath());
     }
 
     /**
      * Creates an archive {@code target} using the format {@code
-     * format} by recursively including all files and directories in
-     * {@code directory}.
+     * format} by recursively including all files and directories in {@code directory}.
      *
-     * <p>This method creates a wrapper around the target stream
-     * which is never closed and thus leaks resources, please use
-     * {@link #create(String,OutputStream,File,CloseableConsumer)}
-     * instead.</p>
+     * <p>
+     * This method creates a wrapper around the target stream which is never closed and thus leaks resources, please use
+     * {@link #create(String,OutputStream,File,CloseableConsumer)} instead.
+     * </p>
      *
-     * @param format the archive format. This uses the same format as
-     * accepted by {@link ArchiveStreamFactory}.
+     * @param format the archive format. This uses the same format as accepted by {@link ArchiveStreamFactory}.
      * @param target the stream to write the new archive to.
      * @param directory the directory that contains the files to archive.
      * @throws IOException if an I/O error occurs
@@ -99,24 +219,23 @@ public class Archiver {
      * @deprecated this method leaks resources
      */
     @Deprecated
-    public void create(String format, OutputStream target, File directory) throws IOException, ArchiveException {
+    public void create(final String format, final OutputStream target, final File directory)
+        throws IOException, ArchiveException {
         create(format, target, directory, CloseableConsumer.NULL_CONSUMER);
     }
 
     /**
      * Creates an archive {@code target} using the format {@code
-     * format} by recursively including all files and directories in
-     * {@code directory}.
+     * format} by recursively including all files and directories in {@code directory}.
      *
-     * <p>This method creates a wrapper around the archive stream and
-     * the caller of this method is responsible for closing it -
-     * probably at the same time as closing the stream itself. The
-     * caller is informed about the wrapper object via the {@code
-     * closeableConsumer} callback as soon as it is no longer needed
-     * by this class.</p>
+     * <p>
+     * This method creates a wrapper around the archive stream and the caller of this method is responsible for closing
+     * it - probably at the same time as closing the stream itself. The caller is informed about the wrapper object via
+     * the {@code
+     * closeableConsumer} callback as soon as it is no longer needed by this class.
+     * </p>
      *
-     * @param format the archive format. This uses the same format as
-     * accepted by {@link ArchiveStreamFactory}.
+     * @param format the archive format. This uses the same format as accepted by {@link ArchiveStreamFactory}.
      * @param target the stream to write the new archive to.
      * @param directory the directory that contains the files to archive.
      * @param closeableConsumer is informed about the stream wrapped around the passed in stream
@@ -124,172 +243,123 @@ public class Archiver {
      * @throws ArchiveException if the archive cannot be created for other reasons
      * @since 1.19
      */
-    public void create(String format, OutputStream target, File directory,
-        CloseableConsumer closeableConsumer) throws IOException, ArchiveException {
+    public void create(final String format, final OutputStream target, final File directory,
+        final CloseableConsumer closeableConsumer) throws IOException, ArchiveException {
         try (CloseableConsumerAdapter c = new CloseableConsumerAdapter(closeableConsumer)) {
-            create(c.track(ArchiveStreamFactory.DEFAULT.createArchiveOutputStream(format, target)),
-                directory);
+            create(c.track(ArchiveStreamFactory.DEFAULT.createArchiveOutputStream(format, target)), directory);
         }
     }
 
     /**
      * Creates an archive {@code target} using the format {@code
-     * format} by recursively including all files and directories in
-     * {@code directory}.
+     * format} by recursively including all files and directories in {@code directory}.
      *
-     * <p>This method creates a wrapper around the target channel
-     * which is never closed and thus leaks resources, please use
-     * {@link #create(String,SeekableByteChannel,File,CloseableConsumer)}
-     * instead.</p>
-     *
-     * @param format the archive format. This uses the same format as
-     * accepted by {@link ArchiveStreamFactory}.
-     * @param target the channel to write the new archive to.
+     * @param format the archive format. This uses the same format as accepted by {@link ArchiveStreamFactory}.
+     * @param target the file to write the new archive to.
      * @param directory the directory that contains the files to archive.
      * @throws IOException if an I/O error occurs
      * @throws ArchiveException if the archive cannot be created for other reasons
-     * @deprecated this method leaks resources
+     * @since 1.21
      */
-    @Deprecated
-    public void create(String format, SeekableByteChannel target, File directory)
+    public void create(final String format, final Path target, final Path directory)
         throws IOException, ArchiveException {
-        create(format, target, directory, CloseableConsumer.NULL_CONSUMER);
+        if (prefersSeekableByteChannel(format)) {
+            try (SeekableByteChannel channel = FileChannel.open(target, StandardOpenOption.WRITE,
+                StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+                create(format, channel, directory);
+                return;
+            }
+        }
+        try (@SuppressWarnings("resource") // ArchiveOutputStream wraps newOutputStream result
+        ArchiveOutputStream outputStream = ArchiveStreamFactory.DEFAULT.createArchiveOutputStream(format,
+            Files.newOutputStream(target))) {
+            create(outputStream, directory, EMPTY_FileVisitOption);
+        }
     }
 
     /**
      * Creates an archive {@code target} using the format {@code
-     * format} by recursively including all files and directories in
-     * {@code directory}.
+     * format} by recursively including all files and directories in {@code directory}.
      *
-     * <p>This method creates a wrapper around the archive channel and
-     * the caller of this method is responsible for closing it -
-     * probably at the same time as closing the channel itself. The
-     * caller is informed about the wrapper object via the {@code
-     * closeableConsumer} callback as soon as it is no longer needed
-     * by this class.</p>
+     * <p>
+     * This method creates a wrapper around the target channel which is never closed and thus leaks resources, please
+     * use {@link #create(String,SeekableByteChannel,File,CloseableConsumer)} instead.
+     * </p>
      *
-     * @param format the archive format. This uses the same format as
-     * accepted by {@link ArchiveStreamFactory}.
+     * @param format the archive format. This uses the same format as accepted by {@link ArchiveStreamFactory}.
      * @param target the channel to write the new archive to.
      * @param directory the directory that contains the files to archive.
-     * @param closeableConsumer is informed about the stream wrapped around the passed in stream
      * @throws IOException if an I/O error occurs
      * @throws ArchiveException if the archive cannot be created for other reasons
-     * @since 1.19
+     * @deprecated this method leaks resources
      */
-    public void create(String format, SeekableByteChannel target, File directory,
-        CloseableConsumer closeableConsumer)
+    @Deprecated
+    public void create(final String format, final SeekableByteChannel target, final File directory)
         throws IOException, ArchiveException {
-        try (CloseableConsumerAdapter c = new CloseableConsumerAdapter(closeableConsumer)) {
-        if (!prefersSeekableByteChannel(format)) {
-            create(format, c.track(Channels.newOutputStream(target)), directory);
-        } else if (ArchiveStreamFactory.ZIP.equalsIgnoreCase(format)) {
-            create(c.track(new ZipArchiveOutputStream(target)), directory);
-        } else if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(format)) {
-            create(c.track(new SevenZOutputFile(target)), directory);
-        } else {
-            // never reached as prefersSeekableByteChannel only returns true for ZIP and 7z
-            throw new ArchiveException("Don't know how to handle format " + format);
-        }
-        }
+        create(format, target, directory, CloseableConsumer.NULL_CONSUMER);
     }
 
     /**
-     * Creates an archive {@code target} by recursively including all
-     * files and directories in {@code directory}.
+     * Creates an archive {@code target} using the format {@code
+     * format} by recursively including all files and directories in {@code directory}.
      *
-     * @param target the stream to write the new archive to.
+     * <p>
+     * This method creates a wrapper around the archive channel and the caller of this method is responsible for closing
+     * it - probably at the same time as closing the channel itself. The caller is informed about the wrapper object via
+     * the {@code
+     * closeableConsumer} callback as soon as it is no longer needed by this class.
+     * </p>
+     *
+     * @param format the archive format. This uses the same format as accepted by {@link ArchiveStreamFactory}.
+     * @param target the channel to write the new archive to.
      * @param directory the directory that contains the files to archive.
+     * @param closeableConsumer is informed about the stream wrapped around the passed in stream
      * @throws IOException if an I/O error occurs
      * @throws ArchiveException if the archive cannot be created for other reasons
+     * @since 1.19
      */
-    public void create(final ArchiveOutputStream target, File directory)
-        throws IOException, ArchiveException {
-        create(directory, new ArchiveEntryCreator() {
-            @Override
-            public ArchiveEntry create(File f, String entryName) throws IOException {
-                return target.createArchiveEntry(f, entryName);
-            }
-        }, new ArchiveEntryConsumer() {
-            @Override
-            public void accept(File source, ArchiveEntry e) throws IOException {
-                target.putArchiveEntry(e);
-                if (!e.isDirectory()) {
-                    try (InputStream in = new BufferedInputStream(Files.newInputStream(source.toPath()))) {
-                        IOUtils.copy(in, target);
-                    }
-                }
-                target.closeArchiveEntry();
-            }
-        }, new Finisher() {
-            @Override
-            public void finish() throws IOException {
-                target.finish();
+    public void create(final String format, final SeekableByteChannel target, final File directory,
+        final CloseableConsumer closeableConsumer) throws IOException, ArchiveException {
+        try (CloseableConsumerAdapter c = new CloseableConsumerAdapter(closeableConsumer)) {
+            if (!prefersSeekableByteChannel(format)) {
+                create(format, c.track(Channels.newOutputStream(target)), directory);
+            } else if (ArchiveStreamFactory.ZIP.equalsIgnoreCase(format)) {
+                create(c.track(new ZipArchiveOutputStream(target)), directory);
+            } else if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(format)) {
+                create(c.track(new SevenZOutputFile(target)), directory);
+            } else {
+                // never reached as prefersSeekableByteChannel only returns true for ZIP and 7z
+                throw new ArchiveException("Don't know how to handle format " + format);
             }
-        });
+        }
     }
 
     /**
-     * Creates an archive {@code target} by recursively including all
-     * files and directories in {@code directory}.
+     * Creates an archive {@code target} using the format {@code
+     * format} by recursively including all files and directories in {@code directory}.
      *
-     * @param target the file to write the new archive to.
+     * @param format the archive format. This uses the same format as accepted by {@link ArchiveStreamFactory}.
+     * @param target the channel to write the new archive to.
      * @param directory the directory that contains the files to archive.
      * @throws IOException if an I/O error occurs
+     * @throws IllegalStateException if the format does not support {@code SeekableByteChannel}.
      */
-    public void create(final SevenZOutputFile target, File directory) throws IOException {
-        create(directory, new ArchiveEntryCreator() {
-            @Override
-            public ArchiveEntry create(File f, String entryName) throws IOException {
-                return target.createArchiveEntry(f, entryName);
-            }
-        }, new ArchiveEntryConsumer() {
-            @Override
-            public void accept(File source, ArchiveEntry e) throws IOException {
-                target.putArchiveEntry(e);
-                if (!e.isDirectory()) {
-                    final byte[] buffer = new byte[8024];
-                    int n = 0;
-                    long count = 0;
-                    try (InputStream in = new BufferedInputStream(Files.newInputStream(source.toPath()))) {
-                        while (-1 != (n = in.read(buffer))) {
-                            target.write(buffer, 0, n);
-                            count += n;
-                        }
-                    }
-                }
-                target.closeArchiveEntry();
+    public void create(final String format, final SeekableByteChannel target, final Path directory) throws IOException {
+        if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(format)) {
+            try (SevenZOutputFile sevenZFile = new SevenZOutputFile(target)) {
+                create(sevenZFile, directory);
             }
-        }, new Finisher() {
-            @Override
-            public void finish() throws IOException {
-                target.finish();
+        } else if (ArchiveStreamFactory.ZIP.equalsIgnoreCase(format)) {
+            try (ArchiveOutputStream archiveOutputStream = new ZipArchiveOutputStream(target)) {
+                create(archiveOutputStream, directory, EMPTY_FileVisitOption);
             }
-        });
-    }
-
-    private boolean prefersSeekableByteChannel(String format) {
-        return ArchiveStreamFactory.ZIP.equalsIgnoreCase(format) || ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(format);
-    }
-
-    private void create(File directory, ArchiveEntryCreator creator, ArchiveEntryConsumer consumer,
-        Finisher finisher) throws IOException {
-        create("", directory, creator, consumer);
-        finisher.finish();
+        } else {
+            throw new IllegalStateException(format);
+        }
     }
 
-    private void create(String prefix, File directory, ArchiveEntryCreator creator, ArchiveEntryConsumer consumer)
-        throws IOException {
-        File[] children = directory.listFiles();
-        if (children == null) {
-            return;
-        }
-        for (File f : children) {
-            String entryName = prefix + f.getName() + (f.isDirectory() ? "/" : "");
-            consumer.accept(f, creator.create(f, entryName));
-            if (f.isDirectory()) {
-                create(entryName, f, creator, consumer);
-            }
-        }
+    private boolean prefersSeekableByteChannel(final String format) {
+        return ArchiveStreamFactory.ZIP.equalsIgnoreCase(format)
+            || ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(format);
     }
 }


[commons-compress] 02/03: Add SevenZOutputFile.write(Path, OpenOption...).

Posted by gg...@apache.org.
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

commit fee64bc2cdd017fdb756fc9cb0cd13521503957a
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Aug 8 15:54:58 2020 -0400

    Add SevenZOutputFile.write(Path, OpenOption...).
---
 src/changes/changes.xml                              |  3 +++
 .../compress/archivers/sevenz/SevenZOutputFile.java  | 17 ++++++++++++++++-
 .../archivers/sevenz/SevenZOutputFileTest.java       | 20 ++++++++++++++++++++
 3 files changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 58d23c5..5eefcd9 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -133,6 +133,9 @@ The <action> type attribute can be add,update,fix,remove.
       <action type="update" date="2020-08-08" due-to="Gary Gregory" dev="ggregory">
         Add SevenZOutputFile.write(InputStream).
       </action>
+      <action type="update" date="2020-08-08" due-to="Gary Gregory" dev="ggregory">
+        Add SevenZOutputFile.write(Path, OpenOption...).
+      </action>
       <action type="update" date="2020-07-23" due-to="Dependabot" dev="ggregory">
         Update GitHub actions/checkout from v1 to v2.3.1 #114.
       </action>
diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java
index 28a7e6c..fe9db09 100644
--- a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java
@@ -31,6 +31,8 @@ import java.nio.ByteOrder;
 import java.nio.channels.SeekableByteChannel;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
 import java.util.BitSet;
@@ -38,8 +40,8 @@ import java.util.Collections;
 import java.util.Date;
 import java.util.EnumSet;
 import java.util.HashMap;
-import java.util.List;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.zip.CRC32;
 
@@ -259,6 +261,19 @@ public class SevenZOutputFile implements Closeable {
     }
 
     /**
+     * Writes all of the given input stream to the current archive entry.
+     * @param path the data source.
+     * @param options options specifying how the file is opened.
+     * @throws IOException if an I/O error occurs.
+     * @since 1.21
+     */
+    public void write(final Path path, OpenOption... options) throws IOException {
+        try (InputStream in = new BufferedInputStream(Files.newInputStream(path, options))) {
+            write(in);
+        }
+    }
+
+    /**
      * Finishes the addition of entries to this archive, without closing it.
      *
      * @throws IOException if archive is already closed.
diff --git a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java
index 638d9d2..bebc26b 100644
--- a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java
@@ -23,6 +23,8 @@ import org.junit.Test;
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Calendar;
@@ -102,6 +104,14 @@ public class SevenZOutputFileTest extends AbstractTestCase {
             outArchive.closeArchiveEntry();
 
             entry = new SevenZArchiveEntry();
+            entry.setName("foo/bar/test.txt");
+            entry.setCreationDate(creationDate);
+            entry.setAccessDate(accessDate);
+            outArchive.putArchiveEntry(entry);
+            outArchive.write(Paths.get("src/test/resources/test.txt"));
+            outArchive.closeArchiveEntry();
+
+            entry = new SevenZArchiveEntry();
             entry.setName("xyzzy");
             outArchive.putArchiveEntry(entry);
             outArchive.write(0);
@@ -173,6 +183,16 @@ public class SevenZOutputFileTest extends AbstractTestCase {
 
             entry = archive.getNextEntry();
             assert (entry != null);
+            assertEquals("foo/bar/test.txt", entry.getName());
+            assertFalse(entry.isDirectory());
+            assertFalse(entry.isAntiItem());
+            assertEquals(Files.size(Paths.get("src/test/resources/test.txt")), entry.getSize());
+            assertFalse(entry.getHasLastModifiedDate());
+            assertEquals(accessDate, entry.getAccessDate());
+            assertEquals(creationDate, entry.getCreationDate());
+
+            entry = archive.getNextEntry();
+            assert (entry != null);
             assertEquals("xyzzy", entry.getName());
             assertEquals(1, entry.getSize());
             assertFalse(entry.getHasAccessDate());