package iamrescue.communication; import java.util.Arrays; import java.util.List; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.Validate; public class BitStream { private static final int BITS_PER_BYTE = 8; private static final int TRAILING_BITS_ENCODING = 3; private boolean[] bits; private int size = 0; private int currentPointer = 0; private int savedPointer = -1; private int startPointer; private int endPointer; public BitStream(boolean[] array) { this.bits = array; size = bits.length; } public void markStart() { startPointer = currentPointer; } public void moveStart(int bits) { startPointer += bits; } public void moveEnd(int bits) { endPointer += bits; } public void markEnd() { endPointer = currentPointer; } public BitStream extractStartToEnd() { if (endPointer == -1 || startPointer == -1 || startPointer > endPointer) { throw new IllegalStateException( "Pointers are inconsistent. Start: " + startPointer + ", end:" + endPointer); } boolean[] newBits = new boolean[endPointer - startPointer]; System.arraycopy(bits, startPointer, newBits, 0, newBits.length); return new BitStream(newBits); } public BitStream(byte[] array) { bits = new boolean[array.length * BITS_PER_BYTE]; for (int i = 0; i < array.length; i++) append(array[i]); int trailingBits = array[0] & 7; // remove bits that encode number of trailing bits and trailing bits // themselves bits = ArrayUtils.subarray(bits, 3, bits.length - trailingBits); size -= 3 + trailingBits; } public BitStream() { bits = new boolean[32]; size = 0; } public BitStream(List<Boolean> bits) { this.bits = new boolean[bits.size()]; for (Boolean b : bits) { append(b); } } public void append(boolean bit) { if (size == bits.length) { // double the bit array boolean[] newBits = new boolean[bits.length * 2]; System.arraycopy(bits, 0, newBits, 0, bits.length); bits = newBits; } bits[size++] = bit; } public void append(boolean... bits) { for (int i = 0; i < bits.length; i++) { append(bits[i]); } } public byte[] toByteArray() { // three extra bits to encode the number of bits in the final byte // (trailing bits) int bitCount = size + TRAILING_BITS_ENCODING; int byteCount = (bitCount - 1) / BITS_PER_BYTE + 1; byte[] result = new byte[byteCount]; int bitsInLastByte = bitCount % BITS_PER_BYTE; int trailingBitCount = (BITS_PER_BYTE - bitsInLastByte) % BITS_PER_BYTE; // encode how many bits there are in the final byte // encode the number of trailing bits in the first three bits result[0] = setBit(result[0], 0, (trailingBitCount & 1) != 0); result[0] = setBit(result[0], 1, (trailingBitCount & 2) != 0); result[0] = setBit(result[0], 2, (trailingBitCount & 4) != 0); // from 3 to end, encode the actual bit array for (int i = TRAILING_BITS_ENCODING; i < size + TRAILING_BITS_ENCODING; i++) { result[i / BITS_PER_BYTE] = setBit(result[i / BITS_PER_BYTE], i % BITS_PER_BYTE, bits[i - TRAILING_BITS_ENCODING]); } // TODO debugging check: remove for competition // Validate.isTrue(new BitStream(result).equals(this)); return result; } private byte setBit(byte b, int i, boolean c) { if (c) { return setBit(b, i); } return b; } public int getTrailingZeros() { int count = 0; int i = size - 1; while (i >= 0 && !bits[i--]) { count++; } return count; } @Override public String toString() { return Arrays.toString(getBits()); } private byte setBit(byte b, int bit) { return (byte) (b | 1 << bit); } public BitStream concatenate(BitStream encode) { boolean[] concatenated = new boolean[size() + encode.size()]; System.arraycopy(bits, 0, concatenated, 0, size()); System.arraycopy(encode.bits, 0, concatenated, size(), encode.size()); return new BitStream(concatenated); } public void mark() { Validate.isTrue(savedPointer == -1); savedPointer = currentPointer; } public void reset() { currentPointer = savedPointer; savedPointer = -1; } /** * Reads bs.length bytes to bs * * @param bs * @return */ public void read(byte[] bs) { for (int i = 0; i < bs.length; i++) { bs[i] = readByte(); } } public boolean readBit() { if (currentPointer > size - 1) { throw new IllegalArgumentException("End of stream reached"); } return bits[currentPointer++]; } public byte readByte() { byte b = 0; for (int i = 0; i < BITS_PER_BYTE; i++) { if (readBit()) b = setBit(b, i); } return b; } /** * Number of bits still available * * @return */ public int available() { return size() - currentPointer; } public int size() { return size; } public void append(byte[] encodeByte) { for (byte b : encodeByte) { append(b); } } public void append(byte b) { for (int j = 0; j < BITS_PER_BYTE; j++) { append((b & (1 << j)) != 0); } } @Override public boolean equals(Object obj) { if (obj instanceof BitStream) { BitStream other = (BitStream) obj; return Arrays.equals(other.getBits(), getBits()); } return false; } private boolean[] getBits() { return ArrayUtils.subarray(bits, 0, size); } }