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 061828c78 Add a builder for the TarFile class and deprecate some constructors 061828c78 is described below commit 061828c78c3487c7f8193d48c3cba099c1d02414 Author: Gary D. Gregory <garydgreg...@gmail.com> AuthorDate: Sun Sep 21 22:50:59 2025 -0400 Add a builder for the TarFile class and deprecate some constructors --- src/changes/changes.xml | 1 + .../compress/archivers/tar/AbstractTarBuilder.java | 87 +++++++++++++++++++ .../archivers/tar/TarArchiveInputStream.java | 46 +--------- .../commons/compress/archivers/tar/TarFile.java | 97 ++++++++++++++++++++-- .../compress/archivers/tar/TarFileTest.java | 16 ++++ 5 files changed, 198 insertions(+), 49 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 221a07693..7f46fc8c5 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -122,6 +122,7 @@ The <action> type attribute can be add,update,fix,remove. <action type="add" dev="ggregory" due-to="Gary Gregory">Add ArchiveInputStream.ArchiveInputStream(InputStream, Charset) as a public constructor, it was private.</action> <action type="add" dev="ggregory" due-to="Gary Gregory, Piotr P. Karwasz">Introduce builders for all ArchiveInputStream implementations and deprecate some constructors.</action> <action type="add" dev="ggregory" due-to="Gary Gregory">TarFile now implements IOIterable<TarArchiveEntry>.</action> + <action type="add" dev="ggregory" due-to="Gary Gregory">Add a builder for the TarFile class and deprecate some constructors.</action> <!-- UPDATE --> <action type="update" dev="ggregory" due-to="Gary Gregory">Bump org.apache.commons:commons-parent from 85 to 88 #707.</action> <action type="update" dev="ppkarwasz" due-to="Raeps">Extract duplicate code in org.apache.commons.compress.harmony.pack200.IntList.</action> diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/AbstractTarBuilder.java b/src/main/java/org/apache/commons/compress/archivers/tar/AbstractTarBuilder.java new file mode 100644 index 000000000..8eae8dd43 --- /dev/null +++ b/src/main/java/org/apache/commons/compress/archivers/tar/AbstractTarBuilder.java @@ -0,0 +1,87 @@ +/* + * 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.archivers.tar; + +import org.apache.commons.io.build.AbstractStreamBuilder; + +/** + * Abstracts TAR builder operations. + * + * @since 1.29.0 + */ +public abstract class AbstractTarBuilder<T, B extends AbstractTarBuilder<T, B>> extends AbstractStreamBuilder<T, B> { + + private int blockSize = TarConstants.DEFAULT_BLKSIZE; + private int recordSize = TarConstants.DEFAULT_RCDSIZE; + private boolean lenient; + + /** + * Constructs a new instance. + */ + protected AbstractTarBuilder() { + // empty + } + + int getBlockSize() { + return blockSize; + } + + int getRecordSize() { + return recordSize; + } + + boolean isLenient() { + return lenient; + } + + /** + * Sets the block size. + * + * @param blockSize the block size. + * @return {@code this} instance. + */ + public B setBlockSize(final int blockSize) { + this.blockSize = blockSize; + return asThis(); + } + + /** + * Sets whether illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to {@link TarArchiveEntry#UNKNOWN}. + * When set to false such illegal fields cause an exception instead. + * + * @param lenient whether illegal values throw exceptions. + * @return {@code this} instance. + */ + public B setLenient(final boolean lenient) { + this.lenient = lenient; + return asThis(); + } + + /** + * Sets the record size. + * + * @param recordSize the record size. + * @return {@code this} instance. + */ + public B setRecordSize(final int recordSize) { + this.recordSize = recordSize; + return asThis(); + } +} diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveInputStream.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveInputStream.java index ecf4f0738..80dd6859f 100644 --- a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveInputStream.java +++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveInputStream.java @@ -71,11 +71,7 @@ public class TarArchiveInputStream extends ArchiveInputStream<TarArchiveEntry> { * @since 1.29.0 */ // @formatter:on - public static final class Builder extends AbstractBuilder<TarArchiveInputStream, Builder> { - - private int blockSize = TarConstants.DEFAULT_BLKSIZE; - private int recordSize = TarConstants.DEFAULT_RCDSIZE; - private boolean lenient; + public static final class Builder extends AbstractTarBuilder<TarArchiveInputStream, Builder> { /** * Constructs a new instance. @@ -89,40 +85,6 @@ public TarArchiveInputStream get() throws IOException { return new TarArchiveInputStream(this); } - /** - * Sets the block size. - * - * @param blockSize the block size. - * @return {@code this} instance. - */ - public Builder setBlockSize(final int blockSize) { - this.blockSize = blockSize; - return this; - } - - /** - * Sets whether illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to - * {@link TarArchiveEntry#UNKNOWN}. When set to false such illegal fields cause an exception instead. - * - * @param lenient whether illegal values throw exceptions. - * @return {@code this} instance. - */ - public Builder setLenient(final boolean lenient) { - this.lenient = lenient; - return this; - } - - /** - * Sets the record size. - * - * @param recordSize the record size. - * @return {@code this} instance. - */ - public Builder setRecordSize(final int recordSize) { - this.recordSize = recordSize; - return this; - } - } /** @@ -244,9 +206,9 @@ public TarArchiveInputStream(final InputStream inputStream, final boolean lenien private TarArchiveInputStream(final InputStream inputStream, final Builder builder) { super(inputStream, builder.getCharset()); this.zipEncoding = ZipEncodingHelper.getZipEncoding(builder.getCharset()); - this.recordBuffer = new byte[builder.recordSize]; - this.blockSize = builder.blockSize; - this.lenient = builder.lenient; + this.recordBuffer = new byte[builder.getRecordSize()]; + this.blockSize = builder.getBlockSize(); + this.lenient = builder.isLenient(); } /** diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarFile.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarFile.java index 9c1d13a62..9ebb6c1d6 100644 --- a/src/main/java/org/apache/commons/compress/archivers/tar/TarFile.java +++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarFile.java @@ -36,6 +36,7 @@ import org.apache.commons.compress.archivers.ArchiveException; import org.apache.commons.compress.archivers.zip.ZipEncoding; import org.apache.commons.compress.archivers.zip.ZipEncodingHelper; +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; import org.apache.commons.compress.utils.ArchiveUtils; import org.apache.commons.compress.utils.BoundedArchiveInputStream; import org.apache.commons.compress.utils.BoundedSeekableByteChannelInputStream; @@ -138,6 +139,64 @@ private int readSparse(final long pos, final ByteBuffer buf, final int numToRead } } + // @formatter:off + /** + * Builds a new {@link GzipCompressorInputStream}. + * + * <p> + * For example: + * </p> + * <pre>{@code + * TarFile s = TarFile.builder() + * .setPath(path) + * .setLenient(true) + * .setFileNameCharset(StandardCharsets.UTF_8) + * .get();} + * </pre> + * + * @see #get() + * @since 1.29.0 + */ + // @formatter:on + public static final class Builder extends AbstractTarBuilder<TarFile, Builder> { + + private SeekableByteChannel channel; + + /** + * Constructs a new instance. + */ + private Builder() { + // empty + } + + @Override + public TarFile get() throws IOException { + return new TarFile(this); + } + + /** + * Sets the SeekableByteChannel. + * + * @param channel the SeekableByteChannel. + * @return {@code this} instance. + */ + public Builder setSeekableByteChannel(final SeekableByteChannel channel) { + this.channel = channel; + return asThis(); + } + + } + + /** + * Creates a new builder. + * + * @return a new builder. + * @since 1.29.0 + */ + public static Builder builder() { + return new Builder(); + } + private final SeekableByteChannel archive; /** @@ -174,12 +233,24 @@ private int readSparse(final long pos, final ByteBuffer buf, final int numToRead private final Map<String, List<InputStream>> sparseInputStreams = new HashMap<>(); + private TarFile(final Builder builder) throws IOException { + this.archive = builder.channel != null ? builder.channel : Files.newByteChannel(builder.getPath()); + this.zipEncoding = ZipEncodingHelper.getZipEncoding(builder.getCharset()); + this.recordSize = builder.getRecordSize(); + this.recordBuffer = ByteBuffer.allocate(this.recordSize); + this.blockSize = builder.getBlockSize(); + this.lenient = builder.isLenient(); + forEach(entries::add); + } + /** * Constructor for TarFile. * * @param content the content to use. * @throws IOException when reading the tar archive fails. + * @deprecated Use {@link #builder()} and {@link Builder}. */ + @Deprecated public TarFile(final byte[] content) throws IOException { this(new SeekableInMemoryByteChannel(content)); } @@ -191,7 +262,9 @@ public TarFile(final byte[] content) throws IOException { * @param lenient when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to * {@link TarArchiveEntry#UNKNOWN}. When set to false such illegal fields cause an exception instead. * @throws IOException when reading the tar archive fails. + * @deprecated Use {@link #builder()} and {@link Builder}. */ + @Deprecated public TarFile(final byte[] content, final boolean lenient) throws IOException { this(new SeekableInMemoryByteChannel(content), TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, null, lenient); } @@ -202,7 +275,9 @@ public TarFile(final byte[] content, final boolean lenient) throws IOException { * @param content the content to use. * @param encoding the encoding to use. * @throws IOException when reading the tar archive fails. + * @deprecated Use {@link #builder()} and {@link Builder}. */ + @Deprecated public TarFile(final byte[] content, final String encoding) throws IOException { this(new SeekableInMemoryByteChannel(content), TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, encoding, false); } @@ -212,7 +287,9 @@ public TarFile(final byte[] content, final String encoding) throws IOException { * * @param archive the file of the archive to use. * @throws IOException when reading the tar archive fails. + * @deprecated Use {@link #builder()} and {@link Builder}. */ + @Deprecated public TarFile(final File archive) throws IOException { this(archive.toPath()); } @@ -224,7 +301,9 @@ public TarFile(final File archive) throws IOException { * @param lenient when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to * {@link TarArchiveEntry#UNKNOWN}. When set to false such illegal fields cause an exception instead. * @throws IOException when reading the tar archive fails. + * @deprecated Use {@link #builder()} and {@link Builder}. */ + @Deprecated public TarFile(final File archive, final boolean lenient) throws IOException { this(archive.toPath(), lenient); } @@ -235,7 +314,9 @@ public TarFile(final File archive, final boolean lenient) throws IOException { * @param archive the file of the archive to use. * @param encoding the encoding to use. * @throws IOException when reading the tar archive fails. + * @deprecated Use {@link #builder()} and {@link Builder}. */ + @Deprecated public TarFile(final File archive, final String encoding) throws IOException { this(archive.toPath(), encoding); } @@ -257,7 +338,9 @@ public TarFile(final Path archivePath) throws IOException { * @param lenient when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to * {@link TarArchiveEntry#UNKNOWN}. When set to false such illegal fields cause an exception instead. * @throws IOException when reading the tar archive fails. + * @deprecated Use {@link #builder()} and {@link Builder}. */ + @Deprecated public TarFile(final Path archivePath, final boolean lenient) throws IOException { this(Files.newByteChannel(archivePath), TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, null, lenient); } @@ -268,7 +351,9 @@ public TarFile(final Path archivePath, final boolean lenient) throws IOException * @param archivePath the path of the archive to use. * @param encoding the encoding to use. * @throws IOException when reading the tar archive fails. + * @deprecated Use {@link #builder()} and {@link Builder}. */ + @Deprecated public TarFile(final Path archivePath, final String encoding) throws IOException { this(Files.newByteChannel(archivePath), TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, encoding, false); } @@ -278,7 +363,9 @@ public TarFile(final Path archivePath, final String encoding) throws IOException * * @param content the content to use. * @throws IOException when reading the tar archive fails. + * @deprecated Use {@link #builder()} and {@link Builder}. */ + @Deprecated public TarFile(final SeekableByteChannel content) throws IOException { this(content, TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, null, false); } @@ -293,16 +380,12 @@ public TarFile(final SeekableByteChannel content) throws IOException { * @param lenient when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to * {@link TarArchiveEntry#UNKNOWN}. When set to false such illegal fields cause an exception instead. * @throws IOException when reading the tar archive fails. + * @deprecated Use {@link #builder()} and {@link Builder}. */ + @Deprecated public TarFile(final SeekableByteChannel archive, final int blockSize, final int recordSize, final String encoding, final boolean lenient) throws IOException { - this.archive = archive; - this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); - this.recordSize = recordSize; - this.recordBuffer = ByteBuffer.allocate(this.recordSize); - this.blockSize = blockSize; - this.lenient = lenient; - forEach(entries::add); + this(builder().setSeekableByteChannel(archive).setBlockSize(blockSize).setRecordSize(recordSize).setCharset(encoding).setLenient(lenient)); } /** diff --git a/src/test/java/org/apache/commons/compress/archivers/tar/TarFileTest.java b/src/test/java/org/apache/commons/compress/archivers/tar/TarFileTest.java index 38b0cced4..5b8b9a18f 100644 --- a/src/test/java/org/apache/commons/compress/archivers/tar/TarFileTest.java +++ b/src/test/java/org/apache/commons/compress/archivers/tar/TarFileTest.java @@ -77,6 +77,22 @@ void testArchiveWithTrailer() throws IOException { } } + @Test + void testBuilderSeekableByteChannel() throws IOException { + try (SeekableByteChannel channel = Files.newByteChannel(getPath("archive_with_trailer.tar")); + TarFile tarfile = TarFile.builder() + .setSeekableByteChannel(channel) + .setBlockSize(TarConstants.DEFAULT_BLKSIZE) + .setRecordSize(TarConstants.DEFAULT_RCDSIZE) + .setLenient(false) + .get()) { + final String tarAppendix = "Hello, world!\n"; + final ByteBuffer buffer = ByteBuffer.allocate(tarAppendix.length()); + channel.read(buffer); + assertEquals(tarAppendix, new String(buffer.array())); + } + } + @Test void testCompress197() throws IOException { try (TarFile tarFile = new TarFile(getPath("COMPRESS-197.tar"))) {