BitReader.java
/*
* 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.tika.parser.microsoft.onenote.fsshttpb.util;
import java.io.IOException;
import java.util.Arrays;
import java.util.BitSet;
import java.util.UUID;
/**
* A class is used to extract values across byte boundaries with arbitrary bit positions.
*/
public class BitReader {
/**
* A start position which will be not changed in the process of reading.
* This value will be used for recording the start position and will be used by the function reset.
*/
private final long startPosition;
/**
* The length of the byte Array which contains the byte need to be read.
*/
private final long length;
/**
* A byte array which contains the bytes need to be read.
*/
private byte[] byteArray;
/**
* An offset which is used to keep trace for the current read position in bit.
*/
private long offset;
/**
* Initializes a new instance of the BitReader class with specified bytes buffer and start position in byte.
*
* @param array Specify the byte array which contains the bytes need to be read.
* @param index Specify the start position in byte.
*/
public BitReader(byte[] array, int index) {
this.offset = ((long) index * 8) - 1;
this.startPosition = this.offset;
this.length = (long) array.length * 8;
this.byteArray = array;
}
private static String toBinaryString(BitSet bs, int nbits) {
final StringBuilder buffer = new StringBuilder(bs.size());
for (int i = nbits - 1; i >= 0; --i) {
if (i < bs.size()) {
buffer.append(bs.get(i) ? "1" : "0");
} else {
buffer.append("0");
}
}
return buffer.toString();
}
public boolean getCurrent() {
return (byteArray[(int) offset >> 3] & (1 << ((int) offset & 7))) != 0;
}
/**
* Read specified bit length content as an UInt64 type and increase the bit offset.
*
* @param readingLength Specify the reading bit length.
* @return Return the UInt64 type value.
*/
public long readUInt64(int readingLength) throws IOException {
byte[] uint64Bytes = this.getBytes(readingLength, 8);
return LittleEndianBitConverter.toUInt64(uint64Bytes, 0);
}
/**
* Read specified bit length content as an UInt32 type and increase the bit offset with the specified length.
*
* @param readingLength Specify the reading bit length.
* @return Return the UInt32 type value.
*/
public int readUInt32(int readingLength) throws IOException {
byte[] uint32Bytes = this.getBytes(readingLength, 4);
return LittleEndianBitConverter.toUInt32(uint32Bytes, 0);
}
public int readUInt16(int readingLength) throws IOException {
byte[] uint16Bytes = this.getBytes(readingLength, 2);
return LittleEndianBitConverter.ToUInt16(uint16Bytes, 0);
}
/**
* Reading the bytes specified by the byte length.
*
* @param readingLength Specify the reading byte length.
* @return Return the read bytes array.
*/
public byte[] readBytes(int readingLength) throws IOException {
return this.getBytes(readingLength * 8, readingLength);
}
/**
* Read specified bit length content as an UInt16 type and increase the bit offset with the specified length.
*
* @param readingLength Specify the reading bit length.
* @return Return the UInt16 value.
*/
public short readInt16(int readingLength) throws IOException {
byte[] uint16Bytes = this.getBytes(readingLength, 2);
return LittleEndianBitConverter.toInt16(uint16Bytes, 0);
}
/**
* Read specified bit length content as an Int32 type and increase the bit offset with the specified length.
*
* @param readingLength Specify the reading bit length.
* @return Return the Int32 type value.
*/
public int readInt32(int readingLength) throws IOException {
byte[] uint32Bytes = this.getBytes(readingLength, 4);
return LittleEndianBitConverter.toInt32(uint32Bytes, 0);
}
/**
* Read as a GUID from the current offset position and increate the bit offset with 128 bit.
*
* @return Return the GUID value.
*/
public UUID readGuid() throws IOException {
return UUID.nameUUIDFromBytes(this.getBytes(128, 16));
}
/**
* Advances the enumerator to the next bit of the byte array.
*
* @return true if the enumerator was successfully advanced to the next bit; false if the enumerator
* has passed the end of the byte array.
*/
public boolean moveNext() {
return ++this.offset < this.length;
}
/**
* Assign the internal read buffer to null.
*/
public void dispose() {
this.byteArray = null;
}
/**
* Sets the enumerator to its initial position, which is before the first bit in the byte array.
*/
public void reset() {
this.offset = this.startPosition;
}
/**
* Construct a byte array with specified bit length and the specified the byte array size.
*
* @param needReadlength Specify the need read bit length.
* @param size Specify the byte array size.
* @return Returns the constructed byte array.
*/
private byte[] getBytes(int needReadlength, int size) throws IOException {
BitSet retSet = new BitSet(size);
int i = 0;
while (i < needReadlength) {
if (!this.moveNext()) {
throw new IOException("Unexpected to meet the byte array end.");
}
if (getCurrent()) {
retSet.set(i);
} else {
retSet.clear(i);
}
i++;
}
byte[] result = new byte[size];
Arrays.fill(result, (byte) 0);
byte[] retSetBa = retSet.toByteArray();
for (i = 0; i < retSetBa.length; ++i) {
result[i] = retSetBa[i];
}
return result;
}
}