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

de.unkrig.commons.file.CompressUtil Maven / Gradle / Ivy

The newest version!

/*
 * de.unkrig.commons - A general-purpose Java class library
 *
 * Copyright (c) 2014, Arno Unkrig
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *       following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 *       following disclaimer in the documentation and/or other materials provided with the distribution.
 *    3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
 *       products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package de.unkrig.commons.file;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.zip.UnsupportedZipFeatureException;
import org.apache.commons.compress.compressors.CompressorInputStream;

import de.unkrig.commons.file.org.apache.commons.compress.archivers.ArchiveFormat;
import de.unkrig.commons.file.org.apache.commons.compress.archivers.ArchiveFormatFactory;
import de.unkrig.commons.file.org.apache.commons.compress.compressors.CompressionFormat;
import de.unkrig.commons.file.org.apache.commons.compress.compressors.CompressionFormatFactory;
import de.unkrig.commons.io.EventCounter;
import de.unkrig.commons.io.ExponentiallyLoggingEventCounter;
import de.unkrig.commons.io.InputStreams;
import de.unkrig.commons.io.MarkableFileInputStream;
import de.unkrig.commons.lang.AssertionUtil;
import de.unkrig.commons.lang.ExceptionUtil;
import de.unkrig.commons.lang.protocol.Predicate;
import de.unkrig.commons.nullanalysis.Nullable;

/** Utility class which implements functionality that is related to {@code org.apache.commons.compress}. */
public final
class CompressUtil {

    static { AssertionUtil.enableAssertionsForThisClass(); }

    private static final Logger LOGGER                         = Logger.getLogger(CompressUtil.class.getName());
    private static final Logger LOGGER_INPUT_STREAM_STATISTICS = Logger.getLogger(CompressUtil.class.getName() + ".inputStreamStatistics"); // SUPPRESS CHECKSTYLE LineLength

    private CompressUtil() {}

    /**
     * @param                                              {@link #handleArchive(ArchiveInputStream, ArchiveFormat)}
     *                                                        returns a value of this type, which is, in turn, returned
     *                                                        by the {@code processStream()} and {@code processValue()}
     *                                                        methods
     * @see #handleArchive(ArchiveInputStream, ArchiveFormat)
     */
    public
    interface ArchiveHandler {

        /**
         * May or may not read entries and data from the archiveInputStream, and may or may not close it.
         *
         * @see #processFile(File, Predicate, ArchiveHandler, Predicate, CompressorHandler, NormalContentsHandler)
         * @see #processStream(InputStream, Date, Predicate, ArchiveHandler, Predicate, CompressorHandler,
         *      NormalContentsHandler)
         */
        @Nullable T
        handleArchive(ArchiveInputStream archiveInputStream, ArchiveFormat archiveFormat) throws IOException;
    }

    /**
     * @param                                                        {@link #handleCompressor(CompressorInputStream,
     *                                                                  CompressionFormat)} returns a value of this
     *                                                                  type, which is, in turn, returned by the {@code
     *                                                                  processStream()} and {@code processValue()}
     *                                                                  methods
     * @see #handleCompressor(CompressorInputStream, CompressionFormat)
     */
    public
    interface CompressorHandler {

        /**
         * May or may not read from the compressorInputStream.
         *
         * @see #processFile(File, Predicate, ArchiveHandler, Predicate, CompressorHandler, NormalContentsHandler)
         * @see #processStream(InputStream, Date, Predicate, ArchiveHandler, Predicate, CompressorHandler,
         *      NormalContentsHandler)
         */
        @Nullable T
        handleCompressor(CompressorInputStream compressorInputStream, CompressionFormat compressionFormat)
        throws IOException;
    }

    /**
     * @param                                     {@link #handleNormalContents(InputStream, Date)} returns a value
     *                                               of this type, which is, in turn, returned by the {@code
     *                                               processStream()} and {@code processValue()} methods
     * @see #handleNormalContents(InputStream, Date)
     */
    public
    interface NormalContentsHandler {

        /**
         * May or may not read from the inputStream, and may or may not close it.
         *
         * @see #processFile(File, Predicate, ArchiveHandler, Predicate, CompressorHandler, NormalContentsHandler)
         * @see CompressUtil#processStream(InputStream, Date, Predicate, ArchiveHandler, Predicate, CompressorHandler,
         *      NormalContentsHandler)
         */
        @Nullable T
        handleNormalContents(InputStream inputStream, @Nullable Date lastModifiedDate) throws IOException;
    }

    /**
     * Invokes exactly one of archiveHandler, compressorHandler or
     * normalContentsHandler.
     * 

* An archive file is introspected iff lookIntoFormat evaluates to {@code true} for {@code * "archive-format-name:path"}. *

*

* A compressed file is introspected iff lookIntoFormat evaluates to {@code true} for {@code * "compression-format-name:path"}. *

* * @see ArchiveFormatFactory#allFormats() * @see CompressionFormatFactory#allFormats() */ @Nullable public static T processStream( String path, InputStream inputStream, @Nullable Date lastModifiedDate, Predicate lookIntoFormat, ArchiveHandler archiveHandler, CompressorHandler compressorHandler, NormalContentsHandler normalContentsHandler ) throws IOException { try { return CompressUtil.processStream( inputStream, // inputStream lastModifiedDate, // lastModifiedDate CompressUtil.lookIntoArchive(path, lookIntoFormat), // lookIntoArchive archiveHandler, // archiveHandler CompressUtil.lookIntoCompressed(path, lookIntoFormat), // lookIntoCompressed compressorHandler, // compressorHandler normalContentsHandler // normalContentsHandler ); } catch (UnsupportedZipFeatureException uzfe) { // Cannot use "ExceptionUtil.wrap(prefix, cause)" here, because this exception has none of the "usual" // constructors. throw new IOException(path, uzfe); } catch (IOException ioe) { throw ExceptionUtil.wrap(path, ioe); } catch (RuntimeException re) { throw ExceptionUtil.wrap(path, re); } catch (Error e) { // SUPPRESS CHECKSTYLE IllegalCatch throw ExceptionUtil.wrap(path, e); } } /** * Invokes exactly one of archiveHandler, compressorHandler or * normalContentsHandler. * * @param lookIntoArchive An archive stream is introspected iff lookIntoArchive evaluates to {@code * true} for the archive format * @param lookIntoCompressed A compressed stream is introspected iff lookIntoCompressed evaluates to * {@code true} for the compression format * @see CompressionFormatFactory#allFormats() */ @Nullable public static T processStream( InputStream inputStream, @Nullable Date lastModifiedDate, Predicate lookIntoArchive, ArchiveHandler archiveHandler, Predicate lookIntoCompressed, CompressorHandler compressorHandler, NormalContentsHandler normalContentsHandler ) throws IOException { if (!inputStream.markSupported()) inputStream = new BufferedInputStream(inputStream); ARCHIVE: { final ArchiveFormat archiveFormat = ArchiveFormatFactory.forContents(inputStream); if (archiveFormat == null) break ARCHIVE; if (!lookIntoArchive.evaluate(archiveFormat)) { return normalContentsHandler.handleNormalContents(inputStream, lastModifiedDate); } ArchiveInputStream ais; try { ais = archiveFormat.archiveInputStream(InputStreams.unclosable(inputStream)); } catch (ArchiveException ae) { throw new IOException(archiveFormat.getName(), ae); } return archiveHandler.handleArchive(ais, archiveFormat); } COMPRESSED: { final CompressionFormat compressionFormat = CompressionFormatFactory.forContents(inputStream); if (compressionFormat == null) break COMPRESSED; if (!lookIntoCompressed.evaluate(compressionFormat)) { return normalContentsHandler.handleNormalContents(inputStream, lastModifiedDate); } return compressorHandler.handleCompressor( compressionFormat.compressorInputStream(InputStreams.unclosable(inputStream)), compressionFormat ); } return normalContentsHandler.handleNormalContents(inputStream, lastModifiedDate); } /** * Invokes exactly one of archiveHandler, compressorHandler or * normalContentsHandler. *

* An archive file is introspected iff lookIntoFormat evaluates to {@code true} for * "{@code archive-format-name}{@code :}{@code path}". *

*

* A compressed file is introspected iff lookIntoFormat evaluates to {@code true} for "{@code * compression-format-name}{@code :}{@code path}". *

* * @param file Must be a "normal file" * @see ArchiveFormatFactory#allFormats() * @see CompressionFormatFactory#allFormats() * @see File#isFile() */ @Nullable public static T processFile( String path, File file, Predicate lookIntoFormat, ArchiveHandler archiveHandler, CompressorHandler compressorHandler, NormalContentsHandler normalContentsHandler ) throws IOException { return CompressUtil.processFile( file, CompressUtil.lookIntoArchive(path, lookIntoFormat), archiveHandler, CompressUtil.lookIntoCompressed(path, lookIntoFormat), compressorHandler, normalContentsHandler ); } /** * Invokes exactly one of archiveHandler, compressorHandler or * normalContentsHandler. * * @param file Must be a "normal file" * @param lookIntoArchive An archive file is introspected iff lookIntoArchive evaluates to {@code * true} for the archive format * @param lookIntoCompressed A compressed file is introspected iff lookIntoCompressed evaluates to * {@code true} for the compression format * @see CompressionFormatFactory#allFormats() */ @Nullable public static T processFile( final File file, Predicate lookIntoArchive, ArchiveHandler archiveHandler, Predicate lookIntoCompressed, CompressorHandler compressorHandler, NormalContentsHandler normalContentsHandler ) throws IOException { InputStream is = CompressUtil.markableFileInputStream(file); try { ArchiveFormat archiveFormat = ArchiveFormatFactory.forContents(is); if (archiveFormat != null) { if (!lookIntoArchive.evaluate(archiveFormat)) { long lastModified = file.lastModified(); Date lastModifiedDate = lastModified == 0 ? null : new Date(lastModified); return normalContentsHandler.handleNormalContents(is, lastModifiedDate); } is.close(); ArchiveInputStream ais = archiveFormat.open(file); is = ais; T result = archiveHandler.handleArchive(ais, archiveFormat); ais.close(); return result; } CompressionFormat compressionFormat = CompressionFormatFactory.forContents(is); if (compressionFormat != null) { if (!lookIntoCompressed.evaluate(compressionFormat)) { return normalContentsHandler.handleNormalContents(is, null); } CompressorInputStream cis = compressionFormat.compressorInputStream(new BufferedInputStream(is)); T result = compressorHandler.handleCompressor(cis, compressionFormat); cis.close(); return result; } CompressUtil.LOGGER.log(Level.FINER, "Processing normal file \"{0}\"", file); long lastModified = file.lastModified(); Date lastModifiedDate = lastModified == 0 ? null : new Date(lastModified); T result = normalContentsHandler.handleNormalContents(is, lastModifiedDate); is.close(); return result; } catch (UnsupportedZipFeatureException uzfe) { // Cannot use "ExceptionUtil.wrap(prefix, cause)" here, because this exception has none of the "usual" // constructors. throw new IOException( file + "!" + uzfe.getEntry().getName() + ": Unsupported ZIP feature \"" + uzfe.getFeature() + "\"", uzfe ); } catch (ArchiveException ae) { throw ExceptionUtil.wrap(file.toString(), ae, IOException.class); } catch (IOException ioe) { throw ExceptionUtil.wrap(file.toString(), ioe); } catch (RuntimeException re) { throw ExceptionUtil.wrap(file.toString(), re); } catch (Error e) { // SUPPRESS CHECKSTYLE IllegalCatch throw ExceptionUtil.wrap(file.toString(), e); } finally { try { is.close(); } catch (Exception e) {} } } private static Predicate lookIntoArchive(final String path, final Predicate lookIntoFormat) { return new Predicate() { @Override public boolean evaluate(ArchiveFormat af) { boolean result = lookIntoFormat.evaluate(af.getName() + ':' + path); CompressUtil.LOGGER.log( Level.FINER, "Look into archive \"{0}\"? => {1}", new Object[] { path, result } ); return result; } }; } private static Predicate lookIntoCompressed(final String path, final Predicate lookIntoFormat) { return new Predicate() { @Override public boolean evaluate(CompressionFormat cf) { boolean result = lookIntoFormat.evaluate(cf.getName() + ':' + path); CompressUtil.LOGGER.log( Level.FINER, "Look into compressed \"{0}\"? => {1}", new Object[] { path, result } ); return result; } }; } private static final EventCounter FILE_INPUT_STREAM_STATISTICS = new ExponentiallyLoggingEventCounter( "fileInputStream", CompressUtil.LOGGER_INPUT_STREAM_STATISTICS, Level.FINE ); private static InputStream markableFileInputStream(final File file) throws FileNotFoundException { return InputStreams.statisticsInputStream( new MarkableFileInputStream(file), CompressUtil.FILE_INPUT_STREAM_STATISTICS ); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy