org.apache.commons.compress.archivers.examples.Expander Maven / Gradle / Ivy
Show all versions of commons-compress Show documentation
/*
* 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
*
* http://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.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.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Enumeration;
import java.util.Iterator;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarFile;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.NullOutputStream;
/**
* Provides a high level API for expanding archives.
*
* @since 1.17
*/
public class Expander {
@FunctionalInterface
private interface ArchiveEntryBiConsumer {
void accept(T entry, OutputStream out) throws IOException;
}
@FunctionalInterface
private interface ArchiveEntrySupplier {
T get() throws IOException;
}
/**
* @param targetDirectory May be null to simulate output to dev/null on Linux and NUL on Windows.
*/
private void expand(final ArchiveEntrySupplier supplier, final ArchiveEntryBiConsumer writer, final Path targetDirectory)
throws IOException {
final boolean nullTarget = targetDirectory == null;
final Path targetDirPath = nullTarget ? null : targetDirectory.normalize();
T nextEntry = supplier.get();
while (nextEntry != null) {
final Path targetPath = nullTarget ? null : nextEntry.resolveIn(targetDirPath);
if (nextEntry.isDirectory()) {
if (!nullTarget && !Files.isDirectory(targetPath) && Files.createDirectories(targetPath) == null) {
throw new IOException("Failed to create directory " + targetPath);
}
} else {
final Path parent = nullTarget ? null : targetPath.getParent();
if (!nullTarget && !Files.isDirectory(parent) && Files.createDirectories(parent) == null) {
throw new IOException("Failed to create directory " + parent);
}
if (nullTarget) {
writer.accept(nextEntry, NullOutputStream.INSTANCE);
} else {
try (OutputStream outputStream = Files.newOutputStream(targetPath)) {
writer.accept(nextEntry, outputStream);
}
}
}
nextEntry = supplier.get();
}
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
* @param archive the file to expand
* @param targetDirectory the target directory, may be null to simulate output to dev/null on Linux and NUL on Windows.
* @throws IOException if an I/O error occurs
*/
public void expand(final ArchiveInputStream> archive, final File targetDirectory) throws IOException {
expand(archive, toPath(targetDirectory));
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
* @param archive the file to expand
* @param targetDirectory the target directory, may be null to simulate output to dev/null on Linux and NUL on Windows.
* @throws IOException if an I/O error occurs
* @since 1.22
*/
public void expand(final ArchiveInputStream> archive, final Path targetDirectory) throws IOException {
expand(() -> {
ArchiveEntry next = archive.getNextEntry();
while (next != null && !archive.canReadEntryData(next)) {
next = archive.getNextEntry();
}
return next;
}, (entry, out) -> IOUtils.copy(archive, out), targetDirectory);
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
*
* Tries to auto-detect the archive's format.
*
*
* @param archive the file to expand
* @param targetDirectory the target directory
* @throws IOException if an I/O error occurs
* @throws ArchiveException if the archive cannot be read for other reasons
*/
public void expand(final File archive, final File targetDirectory) throws IOException, ArchiveException {
expand(archive.toPath(), toPath(targetDirectory));
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
*
* Tries to auto-detect the archive's format.
*
*
*
* This method creates a wrapper around the archive stream which is never closed and thus leaks resources, please use
* {@link #expand(InputStream,File,CloseableConsumer)} instead.
*
*
* @param archive the file to expand
* @param targetDirectory the target directory
* @throws IOException if an I/O error occurs
* @throws ArchiveException if the archive cannot be read for other reasons
* @deprecated this method leaks resources
*/
@Deprecated
public void expand(final InputStream archive, final File targetDirectory) throws IOException, ArchiveException {
expand(archive, targetDirectory, CloseableConsumer.NULL_CONSUMER);
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
*
* Tries to auto-detect the archive's format.
*
*
*
* 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.
*
*
* @param archive the file to expand
* @param targetDirectory the target directory
* @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 read for other reasons
* @since 1.19
*/
public void expand(final InputStream archive, final File targetDirectory, final CloseableConsumer closeableConsumer) throws IOException, ArchiveException {
try (CloseableConsumerAdapter c = new CloseableConsumerAdapter(closeableConsumer)) {
expand(c.track(ArchiveStreamFactory.DEFAULT.createArchiveInputStream(archive)), targetDirectory);
}
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
*
* Tries to auto-detect the archive's format.
*
*
* @param archive the file to expand
* @param targetDirectory the target directory
* @throws IOException if an I/O error occurs
* @throws ArchiveException if the archive cannot be read for other reasons
* @since 1.22
*/
public void expand(final Path archive, final Path targetDirectory) throws IOException, ArchiveException {
try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(archive))) {
expand(ArchiveStreamFactory.detect(inputStream), archive, targetDirectory);
}
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
* @param archive the file to expand
* @param targetDirectory the target directory, may be null to simulate output to dev/null on Linux and NUL on Windows.
* @throws IOException if an I/O error occurs
*/
public void expand(final SevenZFile archive, final File targetDirectory) throws IOException {
expand(archive, toPath(targetDirectory));
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
* @param archive the file to expand
* @param targetDirectory the target directory, may be null to simulate output to dev/null on Linux and NUL on Windows.
* @throws IOException if an I/O error occurs
* @since 1.22
*/
public void expand(final SevenZFile archive, final Path targetDirectory) throws IOException {
expand(archive::getNextEntry, (entry, out) -> {
final byte[] buffer = new byte[8192];
int n;
while (-1 != (n = archive.read(buffer))) {
if (out != null) {
out.write(buffer, 0, n);
}
}
}, targetDirectory);
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
* @param archive the file to expand
* @param targetDirectory the target directory
* @param format the archive format. This uses the same format as accepted by {@link ArchiveStreamFactory}.
* @throws IOException if an I/O error occurs
* @throws ArchiveException if the archive cannot be read for other reasons
*/
public void expand(final String format, final File archive, final File targetDirectory) throws IOException, ArchiveException {
expand(format, archive.toPath(), toPath(targetDirectory));
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
*
* This method creates a wrapper around the archive stream which is never closed and thus leaks resources, please use
* {@link #expand(String,InputStream,File,CloseableConsumer)} instead.
*
*
* @param archive the file to expand
* @param targetDirectory the target directory
* @param format the archive format. This uses the same format as accepted by {@link ArchiveStreamFactory}.
* @throws IOException if an I/O error occurs
* @throws ArchiveException if the archive cannot be read for other reasons
* @deprecated this method leaks resources
*/
@Deprecated
public void expand(final String format, final InputStream archive, final File targetDirectory) throws IOException, ArchiveException {
expand(format, archive, targetDirectory, CloseableConsumer.NULL_CONSUMER);
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
*
* 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.
*
*
* @param archive the file to expand
* @param targetDirectory the target directory
* @param format the archive format. This uses the same format as accepted by {@link ArchiveStreamFactory}.
* @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 read for other reasons
* @since 1.19
*/
public void expand(final String format, final InputStream archive, final File targetDirectory, final CloseableConsumer closeableConsumer)
throws IOException, ArchiveException {
expand(format, archive, toPath(targetDirectory), closeableConsumer);
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
*
* 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.
*
*
* @param archive the file to expand
* @param targetDirectory the target directory
* @param format the archive format. This uses the same format as accepted by {@link ArchiveStreamFactory}.
* @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 read for other reasons
* @since 1.22
*/
public void expand(final String format, final InputStream archive, final Path targetDirectory, final CloseableConsumer closeableConsumer)
throws IOException, ArchiveException {
try (CloseableConsumerAdapter c = new CloseableConsumerAdapter(closeableConsumer)) {
final ArchiveInputStream> archiveInputStream = ArchiveStreamFactory.DEFAULT.createArchiveInputStream(format, archive);
expand(c.track(archiveInputStream), targetDirectory);
}
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
* @param archive the file to expand
* @param targetDirectory the target directory
* @param format the archive format. This uses the same format as accepted by {@link ArchiveStreamFactory}.
* @throws IOException if an I/O error occurs
* @throws ArchiveException if the archive cannot be read for other reasons
* @since 1.22
*/
public void expand(final String format, final Path archive, final Path targetDirectory) throws IOException, ArchiveException {
if (prefersSeekableByteChannel(format)) {
try (SeekableByteChannel channel = FileChannel.open(archive, StandardOpenOption.READ)) {
expand(format, channel, targetDirectory, CloseableConsumer.CLOSING_CONSUMER);
}
return;
}
try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(archive))) {
expand(format, inputStream, targetDirectory, CloseableConsumer.CLOSING_CONSUMER);
}
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
*
* This method creates a wrapper around the archive channel which is never closed and thus leaks resources, please use
* {@link #expand(String,SeekableByteChannel,File,CloseableConsumer)} instead.
*
*
* @param archive the file to expand
* @param targetDirectory the target directory
* @param format the archive format. This uses the same format as accepted by {@link ArchiveStreamFactory}.
* @throws IOException if an I/O error occurs
* @throws ArchiveException if the archive cannot be read for other reasons
* @deprecated this method leaks resources
*/
@Deprecated
public void expand(final String format, final SeekableByteChannel archive, final File targetDirectory) throws IOException, ArchiveException {
expand(format, archive, targetDirectory, CloseableConsumer.NULL_CONSUMER);
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
*
* 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.
*
*
* @param archive the file to expand
* @param targetDirectory the target directory
* @param format the archive format. This uses the same format as accepted by {@link ArchiveStreamFactory}.
* @param closeableConsumer is informed about the stream wrapped around the passed in channel
* @throws IOException if an I/O error occurs
* @throws ArchiveException if the archive cannot be read for other reasons
* @since 1.19
*/
public void expand(final String format, final SeekableByteChannel archive, final File targetDirectory, final CloseableConsumer closeableConsumer)
throws IOException, ArchiveException {
expand(format, archive, toPath(targetDirectory), closeableConsumer);
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
*
* 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.
*
*
* @param archive the file to expand
* @param targetDirectory the target directory
* @param format the archive format. This uses the same format as accepted by {@link ArchiveStreamFactory}.
* @param closeableConsumer is informed about the stream wrapped around the passed in channel
* @throws IOException if an I/O error occurs
* @throws ArchiveException if the archive cannot be read for other reasons
* @since 1.22
*/
public void expand(final String format, final SeekableByteChannel archive, final Path targetDirectory, final CloseableConsumer closeableConsumer)
throws IOException, ArchiveException {
try (CloseableConsumerAdapter c = new CloseableConsumerAdapter(closeableConsumer)) {
if (!prefersSeekableByteChannel(format)) {
expand(format, c.track(Channels.newInputStream(archive)), targetDirectory, CloseableConsumer.NULL_CONSUMER);
} else if (ArchiveStreamFactory.TAR.equalsIgnoreCase(format)) {
expand(c.track(new TarFile(archive)), targetDirectory);
} else if (ArchiveStreamFactory.ZIP.equalsIgnoreCase(format)) {
expand(c.track(ZipFile.builder().setSeekableByteChannel(archive).get()), targetDirectory);
} else if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(format)) {
expand(c.track(SevenZFile.builder().setSeekableByteChannel(archive).get()), targetDirectory);
} else {
// never reached as prefersSeekableByteChannel only returns true for TAR, ZIP and 7z
throw new ArchiveException("Don't know how to handle format " + format);
}
}
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
* @param archive the file to expand
* @param targetDirectory the target directory, may be null to simulate output to dev/null on Linux and NUL on Windows.
* @throws IOException if an I/O error occurs
* @since 1.21
*/
public void expand(final TarFile archive, final File targetDirectory) throws IOException {
expand(archive, toPath(targetDirectory));
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
* @param archive the file to expand
* @param targetDirectory the target directory, may be null to simulate output to dev/null on Linux and NUL on Windows.
* @throws IOException if an I/O error occurs
* @since 1.22
*/
public void expand(final TarFile archive, final Path targetDirectory) throws IOException {
final Iterator entryIterator = archive.getEntries().iterator();
expand(() -> entryIterator.hasNext() ? entryIterator.next() : null, (entry, out) -> {
try (InputStream in = archive.getInputStream(entry)) {
IOUtils.copy(in, out);
}
}, targetDirectory);
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
* @param archive the file to expand
* @param targetDirectory the target directory, may be null to simulate output to dev/null on Linux and NUL on Windows.
* @throws IOException if an I/O error occurs
*/
public void expand(final ZipFile archive, final File targetDirectory) throws IOException {
expand(archive, toPath(targetDirectory));
}
/**
* Expands {@code archive} into {@code targetDirectory}.
*
* @param archive the file to expand
* @param targetDirectory the target directory, may be null to simulate output to dev/null on Linux and NUL on Windows.
* @throws IOException if an I/O error occurs
* @since 1.22
*/
public void expand(final ZipFile archive, final Path targetDirectory) throws IOException {
final Enumeration entries = archive.getEntries();
expand(() -> {
ZipArchiveEntry next = entries.hasMoreElements() ? entries.nextElement() : null;
while (next != null && !archive.canReadEntryData(next)) {
next = entries.hasMoreElements() ? entries.nextElement() : null;
}
return next;
}, (entry, out) -> {
try (InputStream in = archive.getInputStream(entry)) {
IOUtils.copy(in, out);
}
}, targetDirectory);
}
private boolean prefersSeekableByteChannel(final String format) {
return ArchiveStreamFactory.TAR.equalsIgnoreCase(format) || ArchiveStreamFactory.ZIP.equalsIgnoreCase(format)
|| ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(format);
}
private Path toPath(final File targetDirectory) {
return targetDirectory != null ? targetDirectory.toPath() : null;
}
}