package net.contrapunctus.rngzip.util;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
/**
* This class is used to read individual bits from an input stream.
* It is compatible with BitOutputStream.
*
* <p class='license'>This is free software; you may modify and/or
* redistribute it under the terms of the GNU General Public License,
* but it comes with <b>absolutely no warranty.</b>
*
* @author Christopher League
* @see BitOutputStream
*/
public final class BitInputStream implements Closeable
{
/* To be compatible with BitOutputStream, we must do the opposite:
* we fill the buffer from MOST significant to LEAST. The ‘mask’
* has exactly one bit set, which is the next one to load. When
* ‘mask’ becomes zero, that means the buffer is empty and we need
* to read a new byte from the underlying stream.
*/
private byte buf;
private int mask;
private InputStream in;
private boolean close_p;
/* We set ‘in’ to null when the stream is closed. */
private void check()
{
if(in == null)
throw new IllegalStateException("Stream already closed.");
}
/**
* Construct a BitInputStream on top of the provided InputStream.
*
* @param in the underlying binary input stream.
* @param close_p determines whether closing this stream also
* closes the underlying stream.
* @throws IllegalArgumentException if ‘in’ is null.
*/
public BitInputStream(InputStream in, boolean close_p)
{
if(in == null)
throw new IllegalArgumentException("Input stream was null");
this.in = in;
this.close_p = close_p;
this.buf = 0;
this.mask = 0; // start with empty buffer
}
/**
* Convenience constructor for the common case where the underlying
* stream <i>should</i> be closed when this stream is closed.
* Equivalent to {@code BitInputStream(in, true)}.
*
* @param in the underlying binary input stream.
* @throws IllegalArgumentException if ‘in’ is null.
*/
public BitInputStream(InputStream in)
{
this(in, true);
}
/**
* Read a single bit from the input stream.
*
* @return the bit read, as a boolean value.
* @throws IOException if there is a failure reading the underlying
* stream.
* @throws EOFException if there is an end-of-file condition on the
* underlying stream.
* @throws IllegalStateException if the stream is already closed.
*/
public boolean readBit() throws IOException
{
check();
if(mask == 0) {
/* Buffer is empty */
int r = in.read();
if(r < 0) throw new EOFException();
assert r >= 0 && r <= 255 : r;
buf = (byte) r;
mask = 0x80;
}
int r = buf & mask;
mask >>>= 1;
return r != 0;
}
/**
* Read ‘n’ bits from the input stream, and return them in the
* least-significant bits of a long integer value.
*
* @param n the number of bits to read.
* @return a long integer value where the ‘n’ least-significant
* bits come from the stream, and any other bits are zero.
* @throws IllegalArgumentException if ‘n’ is negative or >64.
* @throws IOException if there is a failure reading the underlying
* stream.
* @throws EOFException if there is an end-of-file condition on the
* underlying stream.
* @throws IllegalStateException if the stream is already closed.
*/
public long readBits(int n) throws IOException
{
if(n < 0 || n > 64)
throw new IllegalArgumentException("n must be 0..64");
long r = 0;
/* We keep this simple by just calling readBit() repeatedly. */
for(int i = 0; i < n; i++) {
r <<= 1;
if(readBit()) r |= 1;
}
return r;
}
/**
* It is not strictly necessary to close a BitInputStream (unlike a
* BitOutputStream), but if ‘close_p’ was set to true, this will
* close the underlying stream.
*
* @throws IllegalStateException if the stream is already closed.
* @throws IOException if the underlying stream complains during
* closing.
*/
public void close() throws IOException
{
check();
if(close_p) {
in.close();
}
in = null;
}
}