    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 *   https://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 */
    019
    020package org.apache.commons.compress.compressors.zstandard;
    021
    022import java.io.IOException;
    023import java.io.InputStream;
    024
    025import org.apache.commons.compress.compressors.CompressorInputStream;
    026import org.apache.commons.compress.utils.InputStreamStatistics;
    027import org.apache.commons.io.input.BoundedInputStream;
    028
    029import com.github.luben.zstd.BufferPool;
    030import com.github.luben.zstd.ZstdInputStream;
    031
    032/**
    033 * {@link CompressorInputStream} implementation to decode Zstandard encoded stream.
    034 *
    035 * <p>
    036 * This class avoids making the underlying {@code zstd} classes part of the public or protected API. The underlying implementation is provided through the
    037 * <a href="https://github.com/luben/zstd-jni/">Zstandard JNI</a> library which is based on <a href="https://github.com/facebook/zstd/">zstd</a>.
    038 * </p>
    039 *
    040 * @see <a href="https://github.com/luben/zstd-jni/">Zstandard JNI</a>
    041 * @see <a href="https://github.com/facebook/zstd/">zstd</a>
    042 * @since 1.16
    043 */
    044public class ZstdCompressorInputStream extends CompressorInputStream implements InputStreamStatistics {
    045
    046    private final BoundedInputStream countingStream;
    047    private final ZstdInputStream decIS;
    048
    049    /**
    050     * Constructs a new input stream that decompresses zstd-compressed data from the specific input stream.
    051     *
    052     * @param in         the input stream of compressed data.
    053     * @throws IOException if an I/O error occurs.
    054     */
    055    public ZstdCompressorInputStream(final InputStream in) throws IOException {
    056        this.decIS = new ZstdInputStream(countingStream = BoundedInputStream.builder().setInputStream(in).get());
    057    }
    058
    059    /**
    060     * Constructs a new input stream that decompresses zstd-compressed data from the specific input stream.
    061     *
    062     * @param in         the input stream of compressed data.
    063     * @param bufferPool a configuration of zstd-jni that allows users to customize how buffers are recycled. Either a {@link com.github.luben.zstd.NoPool} or a
    064     *                   {@link com.github.luben.zstd.RecyclingBufferPool} is allowed here.
    065     * @throws IOException if an I/O error occurs.
    066     */
    067    public ZstdCompressorInputStream(final InputStream in, final BufferPool bufferPool) throws IOException {
    068        this.decIS = new ZstdInputStream(countingStream = BoundedInputStream.builder().setInputStream(in).get(), bufferPool);
    069    }
    070
    071    @Override
    072    public int available() throws IOException {
    073        return decIS.available();
    074    }
    075
    076    @Override
    077    public void close() throws IOException {
    078        decIS.close();
    079    }
    080
    081    /**
    082     *
    083     * {@inheritDoc}
    084     *
    085     * @since 1.17
    086     */
    087    @Override
    088    public long getCompressedCount() {
    089        return countingStream.getCount();
    090    }
    091
    092    @Override
    093    public synchronized void mark(final int readLimit) {
    094        decIS.mark(readLimit);
    095    }
    096
    097    @Override
    098    public boolean markSupported() {
    099        return decIS.markSupported();
    100    }
    101
    102    @Override
    103    public int read() throws IOException {
    104        final int ret = decIS.read();
    105        count(ret == -1 ? 0 : 1);
    106        return ret;
    107    }
    108
    109    @Override
    110    public int read(final byte[] b) throws IOException {
    111        return read(b, 0, b.length);
    112    }
    113
    114    @Override
    115    public int read(final byte[] buf, final int off, final int len) throws IOException {
    116        if (len == 0) {
    117            return 0;
    118        }
    119        final int ret = decIS.read(buf, off, len);
    120        count(ret);
    121        return ret;
    122    }
    123
    124    @Override
    125    public synchronized void reset() throws IOException {
    126        decIS.reset();
    127    }
    128
    129    @Override
    130    public long skip(final long n) throws IOException {
    131        return org.apache.commons.io.IOUtils.skip(decIS, n);
    132    }
    133
    134    @Override
    135    public String toString() {
    136        return decIS.toString();
    137    }
    138}