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 6c0ffcb32 [COMPRESS-695] Ability to use different InputStreams for Zstd (#655) 6c0ffcb32 is described below commit 6c0ffcb329c69c721b2104a2dd349643c80dd5b8 Author: mehmet-karaman <mehmet.kara...@advantest.com> AuthorDate: Sun Mar 23 15:04:33 2025 +0100 [COMPRESS-695] Ability to use different InputStreams for Zstd (#655) * Adjusted the ZipFile and the Builder. Added a Test for checking both. * Implemented PR Comments. * Fix Javadoc and checkstyle problem --- .../commons/compress/archivers/zip/ZipFile.java | 43 ++++++++++++++-- .../harmony/unpack200/bytecode/Attribute.java | 2 +- .../compress/archivers/zip/ZipFileTest.java | 57 ++++++++++++++++++++++ 3 files changed, 97 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java index cb5e482ab..c0bfd7c80 100644 --- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java +++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java @@ -65,6 +65,7 @@ import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.build.AbstractOrigin.ByteArrayOrigin; import org.apache.commons.io.build.AbstractStreamBuilder; +import org.apache.commons.io.function.IOFunction; import org.apache.commons.io.function.IOStream; import org.apache.commons.io.input.BoundedInputStream; @@ -138,6 +139,7 @@ public static class Builder extends AbstractStreamBuilder<ZipFile, Builder> { private boolean useUnicodeExtraFields = true; private boolean ignoreLocalFileHeader; private long maxNumberOfDisks = 1; + private IOFunction<InputStream, InputStream> zstdInputStream = ZstdCompressorInputStream::new; /** * Constructs a new instance. @@ -167,7 +169,24 @@ public ZipFile get() throws IOException { actualDescription = path.toString(); } final boolean closeOnError = seekableByteChannel != null; - return new ZipFile(actualChannel, actualDescription, getCharset(), useUnicodeExtraFields, closeOnError, ignoreLocalFileHeader); + return new ZipFile(actualChannel, actualDescription, getCharset(), useUnicodeExtraFields, closeOnError, ignoreLocalFileHeader, zstdInputStream); + } + + /** + * This method sets the {@link IOFunction} which will be used the create an + * {@link InputStream}, which could be used to replace the officially supported + * ZSTD compression library. + * + * @param zstdInpStreamFactory the IOFunction which gives the ability to return + * a different InputStream for Zstd compression, if + * parameter is null than it will be set to default. + * @return {@code this} instance + * @since 1.28.0 + */ + public Builder setZstdInputStreamFactory(IOFunction<InputStream, InputStream> zstdInpStreamFactory) { + this.zstdInputStream = zstdInpStreamFactory == null ? ZstdCompressorInputStream::new : zstdInpStreamFactory; + + return this; } /** @@ -721,6 +740,8 @@ private static boolean tryToLocateSignature(final SeekableByteChannel channel, f private final ByteBuffer shortBbuf = ByteBuffer.wrap(shortBuf); + private final IOFunction<InputStream, InputStream> zstdInputStream; + private long centralDirectoryStartDiskNumber; private long centralDirectoryStartRelativeOffset; @@ -892,12 +913,13 @@ public ZipFile(final SeekableByteChannel channel, final String encoding) throws } private ZipFile(final SeekableByteChannel channel, final String channelDescription, final Charset encoding, final boolean useUnicodeExtraFields, - final boolean closeOnError, final boolean ignoreLocalFileHeader) throws IOException { + final boolean closeOnError, final boolean ignoreLocalFileHeader, IOFunction<InputStream, InputStream> zstdInputStream) throws IOException { this.isSplitZipArchive = channel instanceof ZipSplitReadOnlySeekableByteChannel; this.encoding = Charsets.toCharset(encoding, Builder.DEFAULT_CHARSET); this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); this.useUnicodeExtraFields = useUnicodeExtraFields; this.archive = channel; + this.zstdInputStream = zstdInputStream; boolean success = false; try { final Map<ZipArchiveEntry, NameAndComment> entriesWithoutUTF8Flag = populateFromCentralDirectory(); @@ -966,7 +988,8 @@ public ZipFile(final SeekableByteChannel channel, final String channelDescriptio private ZipFile(final SeekableByteChannel channel, final String channelDescription, final String encoding, final boolean useUnicodeExtraFields, final boolean closeOnError, final boolean ignoreLocalFileHeader) throws IOException { - this(channel, channelDescription, Charsets.toCharset(encoding), useUnicodeExtraFields, closeOnError, ignoreLocalFileHeader); + this(channel, channelDescription, Charsets.toCharset(encoding), useUnicodeExtraFields, closeOnError, + ignoreLocalFileHeader, ZstdCompressorInputStream::new); } /** @@ -1234,7 +1257,7 @@ public void close() throws IOException { return new Deflate64CompressorInputStream(is); case ZSTD: case ZSTD_DEPRECATED: - return new ZstdCompressorInputStream(is); + return createZstdInputStream(is); case XZ: return new XZCompressorInputStream(is); case AES_ENCRYPTED: @@ -1254,6 +1277,18 @@ public void close() throws IOException { } } + /** + * Creates the appropriate InputStream for the ZSTD compression method. + * + * @param is the input stream which should be used for compression. + * @return the {@link InputStream} for handling the Zstd compression. + * @throws IOException if an I/O error occurs. + * @since 1.28.0 + */ + protected InputStream createZstdInputStream(final InputStream is) throws IOException { + return zstdInputStream.apply(is); + } + /** * Gets the raw stream of the archive entry (compressed form). * <p> diff --git a/src/main/java/org/apache/commons/compress/harmony/unpack200/bytecode/Attribute.java b/src/main/java/org/apache/commons/compress/harmony/unpack200/bytecode/Attribute.java index 1ba085b0a..d18ed20a6 100644 --- a/src/main/java/org/apache/commons/compress/harmony/unpack200/bytecode/Attribute.java +++ b/src/main/java/org/apache/commons/compress/harmony/unpack200/bytecode/Attribute.java @@ -126,7 +126,7 @@ protected void resolve(final ClassConstantPool pool) { * Writes this body to the given output stream. * * @param out the output. - * @throws IOException + * @throws IOException if an I/O error occurs. */ protected abstract void writeBody(DataOutputStream out) throws IOException; diff --git a/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java b/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java index 8f4e013f4..ebc4b7c0d 100644 --- a/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java +++ b/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java @@ -66,8 +66,36 @@ import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; +import io.airlift.compress.zstd.ZstdInputStream; + public class ZipFileTest extends AbstractTest { + /** + * This Class simulates the case where the Zip File uses the aircompressors {@link ZstdInputStream} + */ + private final class AirliftZstdZipFile extends ZipFile { + private boolean used; + + private AirliftZstdZipFile(final File file) throws IOException { + super(file); + } + + protected InputStream createZstdInputStream(final InputStream is) throws IOException { + return new ZstdInputStream(is) { + + @Override + public int read(final byte[] outputBuffer, final int outputOffset, final int outputLength) throws IOException { + used = true; + return super.read(outputBuffer, outputOffset, outputLength); + } + }; + } + + public boolean isUsed() { + return used; + } + } + private static final int OUT_OF_MEMORY = 137; private static void assertEntryName(final ArrayList<ZipArchiveEntry> entries, final int index, final String expectedName) { @@ -382,6 +410,35 @@ public void testDuplicateEntry() throws Exception { } } + @Test + public void testAlternativeZstdInputStream() throws Exception { + final File archive = getFile("COMPRESS-692/compress-692.zip"); + try (AirliftZstdZipFile zf = new AirliftZstdZipFile(archive)) { + final byte[] buffer = new byte[7000]; + final ZipArchiveEntry ze = zf.getEntry("dolor.txt"); + assertNotNull(ze); + try (InputStream inputStream = zf.getInputStream(ze)) { + assertNotNull(inputStream); + assertFalse(zf.isUsed()); + final int bytesRead = org.apache.commons.compress.utils.IOUtils.readFully(inputStream, buffer); + assertEquals(6066, bytesRead); + assertTrue(zf.isUsed()); + } + } + + try (ZipFile builtZipFile = ZipFile.builder().setPath(archive.getAbsolutePath()).setZstdInputStreamFactory(ZstdInputStream::new).get()) { + final byte[] buffer = new byte[7000]; + final ZipArchiveEntry ze = builtZipFile.getEntry("dolor.txt"); + assertNotNull(ze); + try (InputStream inputStream = builtZipFile.getInputStream(ze)) { + assertTrue(inputStream instanceof ZstdInputStream); + assertNotNull(inputStream); + final int bytesRead = org.apache.commons.compress.utils.IOUtils.readFully(inputStream, buffer); + assertEquals(6066, bytesRead); + } + } + } + /** * Test entries alignment. */