org.apache.commons.compress.utils.BitInputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of commons-compress Show documentation
Show all versions of commons-compress Show documentation
Apache Commons Compress software defines an API for working with
compression and archive formats. These include: bzip2, gzip, pack200,
lzma, xz, Snappy, traditional Unix Compress, DEFLATE, DEFLATE64, LZ4,
Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.commons.compress.utils;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;
/**
* Reads bits from an InputStream.
*
* @since 1.10
* @NotThreadSafe
*/
public class BitInputStream implements Closeable {
private static final int MAXIMUM_CACHE_SIZE = 63; // bits in long minus sign bit
private static final long[] MASKS = new long[MAXIMUM_CACHE_SIZE + 1];
static {
for (int i = 1; i <= MAXIMUM_CACHE_SIZE; i++) {
MASKS[i] = (MASKS[i - 1] << 1) + 1;
}
}
private final org.apache.commons.io.input.BoundedInputStream in;
private final ByteOrder byteOrder;
private long bitsCached;
private int bitsCachedSize;
/**
* Constructor taking an InputStream and its bit arrangement.
*
* @param in the InputStream
* @param byteOrder the bit arrangement across byte boundaries, either BIG_ENDIAN (aaaaabbb bb000000) or LITTLE_ENDIAN (bbbaaaaa 000000bb)
*/
public BitInputStream(final InputStream in, final ByteOrder byteOrder) {
this.in = org.apache.commons.io.input.BoundedInputStream.builder().setInputStream(in).asSupplier().get();
this.byteOrder = byteOrder;
}
/**
* Drops bits until the next bits will be read from a byte boundary.
*
* @since 1.16
*/
public void alignWithByteBoundary() {
final int toSkip = bitsCachedSize % Byte.SIZE;
if (toSkip > 0) {
readCachedBits(toSkip);
}
}
/**
* Returns an estimate of the number of bits that can be read from this input stream without blocking by the next invocation of a method for this input
* stream.
*
* @throws IOException if the underlying stream throws one when calling available
* @return estimate of the number of bits that can be read without blocking
* @since 1.16
*/
public long bitsAvailable() throws IOException {
return bitsCachedSize + (long) Byte.SIZE * in.available();
}
/**
* Returns the number of bits that can be read from this input stream without reading from the underlying input stream at all.
*
* @return estimate of the number of bits that can be read without reading from the underlying stream
* @since 1.16
*/
public int bitsCached() {
return bitsCachedSize;
}
/**
* Clears the cache of bits that have been read from the underlying stream but not yet provided via {@link #readBits}.
*/
public void clearBitCache() {
bitsCached = 0;
bitsCachedSize = 0;
}
@Override
public void close() throws IOException {
in.close();
}
/**
* Fills the cache up to 56 bits
*
* @param count
* @return return true, when EOF
* @throws IOException
*/
private boolean ensureCache(final int count) throws IOException {
while (bitsCachedSize < count && bitsCachedSize < 57) {
final long nextByte = in.read();
if (nextByte < 0) {
return true;
}
if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
bitsCached |= nextByte << bitsCachedSize;
} else {
bitsCached <<= Byte.SIZE;
bitsCached |= nextByte;
}
bitsCachedSize += Byte.SIZE;
}
return false;
}
/**
* Returns the number of bytes read from the underlying stream.
*
* This includes the bytes read to fill the current cache and not read as bits so far.
*
*
* @return the number of bytes read from the underlying stream
* @since 1.17
*/
public long getBytesRead() {
return in.getCount();
}
private long processBitsGreater57(final int count) throws IOException {
final long bitsOut;
final int overflowBits;
long overflow = 0L;
// bitsCachedSize >= 57 and left-shifting it 8 bits would cause an overflow
final int bitsToAddCount = count - bitsCachedSize;
overflowBits = Byte.SIZE - bitsToAddCount;
final long nextByte = in.read();
if (nextByte < 0) {
return nextByte;
}
if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
final long bitsToAdd = nextByte & MASKS[bitsToAddCount];
bitsCached |= bitsToAdd << bitsCachedSize;
overflow = nextByte >>> bitsToAddCount & MASKS[overflowBits];
} else {
bitsCached <<= bitsToAddCount;
final long bitsToAdd = nextByte >>> overflowBits & MASKS[bitsToAddCount];
bitsCached |= bitsToAdd;
overflow = nextByte & MASKS[overflowBits];
}
bitsOut = bitsCached & MASKS[count];
bitsCached = overflow;
bitsCachedSize = overflowBits;
return bitsOut;
}
/**
* Returns at most 63 bits read from the underlying stream.
*
* @param count the number of bits to read, must be a positive number not bigger than 63.
* @return the bits concatenated as a long using the stream's byte order. -1 if the end of the underlying stream has been reached before reading the
* requested number of bits
* @throws IOException on error
*/
public long readBits(final int count) throws IOException {
if (count < 0 || count > MAXIMUM_CACHE_SIZE) {
throw new IOException("count must not be negative or greater than " + MAXIMUM_CACHE_SIZE);
}
if (ensureCache(count)) {
return -1;
}
if (bitsCachedSize < count) {
return processBitsGreater57(count);
}
return readCachedBits(count);
}
private long readCachedBits(final int count) {
final long bitsOut;
if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
bitsOut = bitsCached & MASKS[count];
bitsCached >>>= count;
} else {
bitsOut = bitsCached >> bitsCachedSize - count & MASKS[count];
}
bitsCachedSize -= count;
return bitsOut;
}
}