| /* Copyright 2015 Google Inc. All Rights Reserved. |
| |
| Distributed under MIT license. |
| See file LICENSE for detail or copy at https://opensource.org/licenses/MIT |
| */ |
| |
| package org.brotli.dec; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| |
| /** |
| * {@link InputStream} decorator that decompresses brotli data. |
| * |
| * <p> Not thread-safe. |
| */ |
| public class BrotliInputStream extends InputStream { |
| |
| public static final int DEFAULT_INTERNAL_BUFFER_SIZE = 16384; |
| |
| /** |
| * Internal buffer used for efficient byte-by-byte reading. |
| */ |
| private byte[] buffer; |
| |
| /** |
| * Number of decoded but still unused bytes in internal buffer. |
| */ |
| private int remainingBufferBytes; |
| |
| /** |
| * Next unused byte offset. |
| */ |
| private int bufferOffset; |
| |
| /** |
| * Decoder state. |
| */ |
| private final State state = new State(); |
| |
| /** |
| * Creates a {@link InputStream} wrapper that decompresses brotli data. |
| * |
| * <p> For byte-by-byte reading ({@link #read()}) internal buffer with |
| * {@link #DEFAULT_INTERNAL_BUFFER_SIZE} size is allocated and used. |
| * |
| * <p> Will block the thread until first kilobyte of data of source is available. |
| * |
| * @param source underlying data source |
| * @throws IOException in case of corrupred data or source stream problems |
| */ |
| public BrotliInputStream(InputStream source) throws IOException { |
| this(source, DEFAULT_INTERNAL_BUFFER_SIZE, null); |
| } |
| |
| /** |
| * Creates a {@link InputStream} wrapper that decompresses brotli data. |
| * |
| * <p> For byte-by-byte reading ({@link #read()}) internal buffer of specified size is |
| * allocated and used. |
| * |
| * <p> Will block the thread until first kilobyte of data of source is available. |
| * |
| * @param source compressed data source |
| * @param byteReadBufferSize size of internal buffer used in case of |
| * byte-by-byte reading |
| * @throws IOException in case of corrupred data or source stream problems |
| */ |
| public BrotliInputStream(InputStream source, int byteReadBufferSize) throws IOException { |
| this(source, byteReadBufferSize, null); |
| } |
| |
| /** |
| * Creates a {@link InputStream} wrapper that decompresses brotli data. |
| * |
| * <p> For byte-by-byte reading ({@link #read()}) internal buffer of specified size is |
| * allocated and used. |
| * |
| * <p> Will block the thread until first kilobyte of data of source is available. |
| * |
| * @param source compressed data source |
| * @param byteReadBufferSize size of internal buffer used in case of |
| * byte-by-byte reading |
| * @param customDictionary custom dictionary data; {@code null} if not used |
| * @throws IOException in case of corrupred data or source stream problems |
| */ |
| public BrotliInputStream(InputStream source, int byteReadBufferSize, |
| byte[] customDictionary) throws IOException { |
| if (byteReadBufferSize <= 0) { |
| throw new IllegalArgumentException("Bad buffer size:" + byteReadBufferSize); |
| } else if (source == null) { |
| throw new IllegalArgumentException("source is null"); |
| } |
| this.buffer = new byte[byteReadBufferSize]; |
| this.remainingBufferBytes = 0; |
| this.bufferOffset = 0; |
| try { |
| State.setInput(state, source); |
| } catch (BrotliRuntimeException ex) { |
| throw new IOException(ex); |
| } |
| if (customDictionary != null) { |
| Decode.setCustomDictionary(state, customDictionary); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void close() throws IOException { |
| State.close(state); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public int read() throws IOException { |
| try { |
| if (bufferOffset >= remainingBufferBytes) { |
| remainingBufferBytes = read(buffer, 0, buffer.length); |
| bufferOffset = 0; |
| if (remainingBufferBytes == -1) { |
| return -1; |
| } |
| } |
| return buffer[bufferOffset++] & 0xFF; |
| } catch (BrotliRuntimeException ex) { |
| throw new IOException(ex); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public int read(byte[] destBuffer, int destOffset, int destLen) throws IOException { |
| if (destOffset < 0) { |
| throw new IllegalArgumentException("Bad offset: " + destOffset); |
| } else if (destLen < 0) { |
| throw new IllegalArgumentException("Bad length: " + destLen); |
| } else if (destOffset + destLen > destBuffer.length) { |
| throw new IllegalArgumentException( |
| "Buffer overflow: " + (destOffset + destLen) + " > " + destBuffer.length); |
| } else if (destLen == 0) { |
| return 0; |
| } |
| int copyLen = Math.max(remainingBufferBytes - bufferOffset, 0); |
| if (copyLen != 0) { |
| copyLen = Math.min(copyLen, destLen); |
| System.arraycopy(buffer, bufferOffset, destBuffer, destOffset, copyLen); |
| bufferOffset += copyLen; |
| destOffset += copyLen; |
| destLen -= copyLen; |
| if (destLen == 0) { |
| return copyLen; |
| } |
| } |
| try { |
| state.output = destBuffer; |
| state.outputOffset = destOffset; |
| state.outputLength = destLen; |
| state.outputUsed = 0; |
| Decode.decompress(state); |
| if (state.outputUsed == 0) { |
| return -1; |
| } |
| return state.outputUsed + copyLen; |
| } catch (BrotliRuntimeException ex) { |
| throw new IOException(ex); |
| } |
| } |
| } |