aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/apache/commons/compress/utils/BitInputStream.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/apache/commons/compress/utils/BitInputStream.java')
-rw-r--r--src/main/java/org/apache/commons/compress/utils/BitInputStream.java203
1 files changed, 203 insertions, 0 deletions
diff --git a/src/main/java/org/apache/commons/compress/utils/BitInputStream.java b/src/main/java/org/apache/commons/compress/utils/BitInputStream.java
new file mode 100644
index 000000000..8abff5f78
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/utils/BitInputStream.java
@@ -0,0 +1,203 @@
+/*
+ * 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 CountingInputStream in;
+ private final ByteOrder byteOrder;
+ private long bitsCached = 0;
+ private int bitsCachedSize = 0;
+
+ /**
+ * 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 = new CountingInputStream(in);
+ this.byteOrder = byteOrder;
+ }
+
+ @Override
+ public void close() throws IOException {
+ in.close();
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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 IllegalArgumentException("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);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ * Drops bits until the next bits will be read from a byte boundary.
+ * @since 1.16
+ */
+ public void alignWithByteBoundary() {
+ int toSkip = bitsCachedSize % Byte.SIZE;
+ if (toSkip > 0) {
+ readCachedBits(toSkip);
+ }
+ }
+
+ /**
+ * Returns the number of bytes read from the underlying stream.
+ *
+ * <p>This includes the bytes read to fill the current cache and
+ * not read as bits so far.</p>
+ * @return the number of bytes read from the underlying stream
+ * @since 1.17
+ */
+ public long getBytesRead() {
+ return in.getBytesRead();
+ }
+
+ private long processBitsGreater57(final int count) throws IOException {
+ final long bitsOut;
+ int overflowBits = 0;
+ long overflow = 0L;
+
+ // bitsCachedSize >= 57 and left-shifting it 8 bits would cause an overflow
+ int bitsToAddCount = count - bitsCachedSize;
+ overflowBits = Byte.SIZE - bitsToAddCount;
+ final long nextByte = in.read();
+ if (nextByte < 0) {
+ return nextByte;
+ }
+ if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
+ long bitsToAdd = nextByte & MASKS[bitsToAddCount];
+ bitsCached |= (bitsToAdd << bitsCachedSize);
+ overflow = (nextByte >>> bitsToAddCount) & MASKS[overflowBits];
+ } else {
+ bitsCached <<= bitsToAddCount;
+ long bitsToAdd = (nextByte >>> (overflowBits)) & MASKS[bitsToAddCount];
+ bitsCached |= bitsToAdd;
+ overflow = nextByte & MASKS[overflowBits];
+ }
+ bitsOut = bitsCached & MASKS[count];
+ bitsCached = overflow;
+ bitsCachedSize = overflowBits;
+ return bitsOut;
+ }
+
+ private long readCachedBits(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;
+ }
+
+ /**
+ * 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;
+ }
+
+}