mirror of
https://github.com/google/brotli.git
synced 2025-01-19 04:20:08 +00:00
164 lines
4.7 KiB
Java
Executable File
164 lines
4.7 KiB
Java
Executable File
/* 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
|
|
*/
|
|
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
|
|
*/
|
|
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
|
|
*/
|
|
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 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);
|
|
}
|
|
}
|
|
}
|