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 55ecb7f1b Add org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream.builder/Builder() 55ecb7f1b is described below commit 55ecb7f1b8f58408c525fef742dcf27d51efebf3 Author: Gary D. Gregory <garydgreg...@gmail.com> AuthorDate: Wed Apr 16 14:52:26 2025 -0400 Add org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream.builder/Builder() - Add org.apache.commons.compress.compressors.lzma.LZMACompressorOutputStream.builder/Builder() - Add LZMACompressorOutputStreamTest - Deprecate org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream.LZMACompressorInputStream(InputStream, int) --- src/changes/changes.xml | 2 + .../compressors/CompressorStreamFactory.java | 2 +- .../lzma/LZMACompressorInputStream.java | 76 +++++++++++++++++--- .../lzma/LZMACompressorOutputStream.java | 71 ++++++++++++++++++- .../lzma/LZMACompressorOutputStreamTest.java | 82 ++++++++++++++++++++++ 5 files changed, 222 insertions(+), 11 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 0ad2a5b33..6532f4e8d 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -109,6 +109,8 @@ The <action> type attribute can be add,update,fix,remove. <action type="add" dev="ggregory" due-to="Gary Gregory" issue="COMPRESS-695">Add ZipArchiveInputStream.createZstdInputStream(InputStream) to provide a different InputStream implementation for Zstandard (Zstd) #649.</action> <action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.compress.harmony.pack200.Pack200Exception.Pack200Exception(String, Throwable).</action> <action type="add" issue="COMPRESS-697" dev="ggregory" due-to="Fredrik Kjellberg, Gary Gregory">Move BitStream.nextBit() method to BitInputStream #663.</action> + <action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream.builder/Builder().</action> + <action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.compress.compressors.lzma.LZMACompressorOutputStream.builder/Builder().</action> <!-- UPDATE --> <action type="update" dev="sebb">Bump Commons Parent from 79 to 81</action> <action type="update" dev="ggregory" due-to="Dependabot, Gary Gregory">Bump org.apache.commons:commons-parent from 72 to 79 #563, #567, #574, #582, #587, #595.</action> diff --git a/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java b/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java index e54b15179..c6b1b0993 100644 --- a/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java +++ b/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java @@ -619,7 +619,7 @@ public CompressorInputStream createCompressorInputStream(final String name, fina if (!LZMAUtils.isLZMACompressionAvailable()) { throw new CompressorException("LZMA compression is not available" + YOU_NEED_XZ_JAVA); } - return new LZMACompressorInputStream(in, memoryLimitInKb); + return LZMACompressorInputStream.builder().setInputStream(in).setMemoryLimitKiB(memoryLimitInKb).get(); } if (PACK200.equalsIgnoreCase(name)) { return new Pack200CompressorInputStream(in); diff --git a/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java index 48e5b3cd2..0f5c04362 100644 --- a/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java +++ b/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java @@ -24,7 +24,9 @@ import org.apache.commons.compress.MemoryLimitException; import org.apache.commons.compress.compressors.CompressorInputStream; import org.apache.commons.compress.utils.InputStreamStatistics; +import org.apache.commons.io.build.AbstractStreamBuilder; import org.apache.commons.io.input.BoundedInputStream; +import org.tukaani.xz.LZMA2Options; import org.tukaani.xz.LZMAInputStream; /** @@ -34,6 +36,57 @@ */ public class LZMACompressorInputStream extends CompressorInputStream implements InputStreamStatistics { + // @formatter:off + /** + * Builds a new {@link LZMACompressorInputStream}. + * + * <p> + * For example: + * </p> + * <pre>{@code + * LZMACompressorOutputStream s = LZMACompressorInputStream.builder() + * .setPath(path) + * .get(); + * } + * </pre> + * + * @see #get() + * @see LZMA2Options + * @since 1.28.0 + */ + // @formatter:on + public static class Builder extends AbstractStreamBuilder<LZMACompressorInputStream, Builder> { + + private int memoryLimitKiB = -1; + + @Override + public LZMACompressorInputStream get() throws IOException { + return new LZMACompressorInputStream(this); + } + + /** + * Sets a working memory threshold in kibibytes (KiB). + * + * @param memoryLimitKiB Sets a working memory threshold in kibibytes (KiB). Processing throws MemoryLimitException if memory use is above this + * threshold. + * @return this instance. + */ + public Builder setMemoryLimitKiB(int memoryLimitKiB) { + this.memoryLimitKiB = memoryLimitKiB; + return this; + } + } + + /** + * Constructs a new builder of {@link LZMACompressorOutputStream}. + * + * @return a new builder of {@link LZMACompressorOutputStream}. + * @since 1.28.0 + */ + public static Builder builder() { + return new Builder(); + } + /** * Checks if the signature matches what is expected for an LZMA file. * @@ -50,6 +103,16 @@ public static boolean matches(final byte[] signature, final int length) { private final InputStream in; + @SuppressWarnings("resource") // Caller closes + private LZMACompressorInputStream(final Builder builder) throws IOException { + try { + in = new LZMAInputStream(countingStream = BoundedInputStream.builder().setInputStream(builder.getInputStream()).get(), builder.memoryLimitKiB); + } catch (final org.tukaani.xz.MemoryLimitException e) { + // convert to Commons Compress exception + throw new MemoryLimitException(e.getMemoryNeeded(), e.getMemoryLimit(), (Throwable) e); + } + } + /** * Creates a new input stream that decompresses LZMA-compressed data from the specified input stream. * @@ -58,26 +121,23 @@ public static boolean matches(final byte[] signature, final int length) { * this implementation, or the underlying {@code inputStream} throws an exception */ public LZMACompressorInputStream(final InputStream inputStream) throws IOException { - in = new LZMAInputStream(countingStream = BoundedInputStream.builder().setInputStream(inputStream).get(), -1); + this(builder().setInputStream(inputStream)); } /** * Creates a new input stream that decompresses LZMA-compressed data from the specified input stream. * * @param inputStream where to read the compressed data - * @param memoryLimitKiB calculated memory use threshold in kibibytes (KiB). Throws MemoryLimitException if calculate memory use is above this threshold + * @param memoryLimitKiB Sets a working memory threshold in kibibytes (KiB). Processing throws MemoryLimitException if memory use is above this threshold. * @throws IOException if the input is not in the .lzma format, the input is corrupt or truncated, the .lzma headers specify sizes that are not supported by * this implementation, or the underlying {@code inputStream} throws an exception * * @since 1.14 + * @deprecated Use {@link #builder()}. */ + @Deprecated public LZMACompressorInputStream(final InputStream inputStream, final int memoryLimitKiB) throws IOException { - try { - in = new LZMAInputStream(countingStream = BoundedInputStream.builder().setInputStream(inputStream).get(), memoryLimitKiB); - } catch (final org.tukaani.xz.MemoryLimitException e) { - // convert to commons-compress exception - throw new MemoryLimitException(e.getMemoryNeeded(), e.getMemoryLimit(), (Throwable) e); - } + this(builder().setInputStream(inputStream).setMemoryLimitKiB(memoryLimitKiB)); } /** {@inheritDoc} */ diff --git a/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorOutputStream.java b/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorOutputStream.java index 082fe81b1..4aae9eb89 100644 --- a/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorOutputStream.java +++ b/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorOutputStream.java @@ -22,6 +22,7 @@ import java.io.OutputStream; import org.apache.commons.compress.compressors.CompressorOutputStream; +import org.apache.commons.io.build.AbstractStreamBuilder; import org.tukaani.xz.LZMA2Options; import org.tukaani.xz.LZMAOutputStream; @@ -32,15 +33,81 @@ */ public class LZMACompressorOutputStream extends CompressorOutputStream<LZMAOutputStream> { + // @formatter:off + /** + * Builds a new {@link LZMACompressorOutputStream}. + * + * <p> + * For example: + * </p> + * <pre>{@code + * LZMACompressorOutputStream s = LZMACompressorOutputStream.builder() + * .setPath(path) + * .setLzma2Options(new LZMA2Options(...)) + * .get(); + * } + * </pre> + * + * @see #get() + * @see LZMA2Options + * @since 1.28.0 + */ + // @formatter:on + public static class Builder extends AbstractStreamBuilder<LZMACompressorOutputStream, Builder> { + + private LZMA2Options lzma2Options = new LZMA2Options(); + + /** + * Constructs a new builder of {@link LZMACompressorOutputStream}. + */ + public Builder() { + // empty + } + + @Override + public LZMACompressorOutputStream get() throws IOException { + return new LZMACompressorOutputStream(this); + } + + /** + * Sets LZMA options. + * <p> + * Passing {@code null} resets to the default value {@link LZMA2Options#LZMA2Options()}. + * </p> + * + * @param lzma2Options LZMA options. + * @return this instance. + */ + public Builder setLzma2Options(final LZMA2Options lzma2Options) { + this.lzma2Options = lzma2Options != null ? lzma2Options : new LZMA2Options(); + return this; + } + + } + + /** + * Constructs a new builder of {@link LZMACompressorOutputStream}. + * + * @return a new builder of {@link LZMACompressorOutputStream}. + * @since 1.28.0 + */ + public static Builder builder() { + return new Builder(); + } + + @SuppressWarnings("resource") // Caller closes + private LZMACompressorOutputStream(final Builder builder) throws IOException { + super(new LZMAOutputStream(builder.getOutputStream(), builder.lzma2Options, -1)); + } + /** * Creates a LZMA compressor. * * @param outputStream the stream to wrap * @throws IOException on error */ - @SuppressWarnings("resource") // Caller closes public LZMACompressorOutputStream(final OutputStream outputStream) throws IOException { - super(new LZMAOutputStream(outputStream, new LZMA2Options(), -1)); + this(builder().setOutputStream(outputStream)); } /** diff --git a/src/test/java/org/apache/commons/compress/compressors/lzma/LZMACompressorOutputStreamTest.java b/src/test/java/org/apache/commons/compress/compressors/lzma/LZMACompressorOutputStreamTest.java new file mode 100644 index 000000000..7b8287702 --- /dev/null +++ b/src/test/java/org/apache/commons/compress/compressors/lzma/LZMACompressorOutputStreamTest.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.commons.compress.compressors.lzma; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; + +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.tukaani.xz.LZMA2Options; + +/** + * Tests {@link LZMACompressorOutputStream}. + */ +public class LZMACompressorOutputStreamTest { + + @TempDir + static Path tempDir; + + private void roundtrip(final Path outPath, final LZMA2Options options) throws IOException { + final String data = "Hello World!"; + try (LZMACompressorOutputStream out = LZMACompressorOutputStream.builder().setPath(outPath).setLzma2Options(options).get()) { + out.writeUtf8(data); + } + try (LZMACompressorInputStream out = LZMACompressorInputStream.builder().setPath(outPath).setMemoryLimitKiB(-1).get()) { + assertEquals(data, IOUtils.toString(out, StandardCharsets.UTF_8)); + } + } + + @Test + public void testBuilderOptionsAll() throws IOException { + final int dictSize = LZMA2Options.DICT_SIZE_MAX; + final int lc = LZMA2Options.LC_LP_MAX - 4; + final int lp = LZMA2Options.LC_LP_MAX - 4; + final int pb = LZMA2Options.PB_MAX; + final int mode = LZMA2Options.MODE_NORMAL; + final int niceLen = LZMA2Options.NICE_LEN_MAX; + final int mf = LZMA2Options.MF_BT4; + final int depthLimit = 50; + roundtrip(tempDir.resolve("out.lzma"), new LZMA2Options(dictSize, lc, lp, pb, mode, niceLen, mf, depthLimit)); + } + + @Test + public void testBuilderOptionsDefault() throws IOException { + roundtrip(tempDir.resolve("out.lzma"), new LZMA2Options()); + } + + @Test + public void testBuilderOptionsPreset() throws IOException { + roundtrip(tempDir.resolve("out.lzma"), new LZMA2Options(LZMA2Options.PRESET_MAX)); + } + + @Test + public void testBuilderPath() throws IOException { + // This test does not use LZMA2Options + final Path outPath = tempDir.resolve("out.lzma"); + try (LZMACompressorOutputStream out = LZMACompressorOutputStream.builder().setPath(outPath).get()) { + out.writeUtf8("Hello World!"); + } + } +}