    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 * http://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019package org.apache.commons.compress.compressors.xz;
    020
    021import java.io.IOException;
    022import java.io.InputStream;
    023import org.tukaani.xz.XZ;
    024import org.tukaani.xz.SingleXZInputStream;
    025import org.tukaani.xz.XZInputStream;
    026
    027import org.apache.commons.compress.compressors.CompressorInputStream;
    028
    029/**
    030 * XZ decompressor.
    031 * @since 1.4
    032 */
    033public class XZCompressorInputStream extends CompressorInputStream {
    034    private final InputStream in;
    035
    036    /**
    037     * Checks if the signature matches what is expected for a .xz file.
    038     *
    039     * @param   signature     the bytes to check
    040     * @param   length        the number of bytes to check
    041     * @return  true if signature matches the .xz magic bytes, false otherwise
    042     */
    043    public static boolean matches(final byte[] signature, final int length) {
    044        if (length < XZ.HEADER_MAGIC.length) {
    045            return false;
    046        }
    047
    048        for (int i = 0; i < XZ.HEADER_MAGIC.length; ++i) {
    049            if (signature[i] != XZ.HEADER_MAGIC[i]) {
    050                return false;
    051            }
    052        }
    053
    054        return true;
    055    }
    056
    057    /**
    058     * Creates a new input stream that decompresses XZ-compressed data
    059     * from the specified input stream. This doesn't support
    060     * concatenated .xz files.
    061     *
    062     * @param       inputStream where to read the compressed data
    063     *
    064     * @throws      IOException if the input is not in the .xz format,
    065     *                          the input is corrupt or truncated, the .xz
    066     *                          headers specify options that are not supported
    067     *                          by this implementation, or the underlying
    068     *                          <code>inputStream</code> throws an exception
    069     */
    070    public XZCompressorInputStream(final InputStream inputStream)
    071            throws IOException {
    072        this(inputStream, false);
    073    }
    074
    075    /**
    076     * Creates a new input stream that decompresses XZ-compressed data
    077     * from the specified input stream.
    078     *
    079     * @param       inputStream where to read the compressed data
    080     * @param       decompressConcatenated
    081     *                          if true, decompress until the end of the
    082     *                          input; if false, stop after the first .xz
    083     *                          stream and leave the input position to point
    084     *                          to the next byte after the .xz stream
    085     *
    086     * @throws      IOException if the input is not in the .xz format,
    087     *                          the input is corrupt or truncated, the .xz
    088     *                          headers specify options that are not supported
    089     *                          by this implementation, or the underlying
    090     *                          <code>inputStream</code> throws an exception
    091     */
    092    public XZCompressorInputStream(final InputStream inputStream,
    093                                   final boolean decompressConcatenated)
    094            throws IOException {
    095        if (decompressConcatenated) {
    096            in = new XZInputStream(inputStream);
    097        } else {
    098            in = new SingleXZInputStream(inputStream);
    099        }
    100    }
    101
    102    @Override
    103    public int read() throws IOException {
    104        final int ret = in.read();
    105        count(ret == -1 ? -1 : 1);
    106        return ret;
    107    }
    108
    109    @Override
    110    public int read(final byte[] buf, final int off, final int len) throws IOException {
    111        final int ret = in.read(buf, off, len);
    112        count(ret);
    113        return ret;
    114    }
    115
    116    @Override
    117    public long skip(final long n) throws IOException {
    118        return in.skip(n);
    119    }
    120
    121    @Override
    122    public int available() throws IOException {
    123        return in.available();
    124    }
    125
    126    @Override
    127    public void close() throws IOException {
    128        in.close();
    129    }
    130}