All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.compress.archivers.examples.Expander Maven / Gradle / Ivy

Go to download

Apache Commons Compress defines an API for working with compression and archive formats. These include bzip2, gzip, pack200, LZMA, XZ, Snappy, traditional Unix Compress, DEFLATE, DEFLATE64, LZ4, Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.

There is a newer version: 1.27.1
Show newest version
/*
 * 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; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy