package org.andengine.util.adt.bit; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.Arrays; import org.andengine.util.adt.data.constants.DataConstants; import org.andengine.util.exception.MethodNotYetImplementedException; /** * (c) 2013 Nicolas Gramlich * * @author Nicolas Gramlich * @since Nov 20, 2012 */ public class ByteBackedBitVector extends BitVector { // =========================================================== // Constants // =========================================================== // =========================================================== // Fields // =========================================================== private final int mSize; private final byte[] mData; // =========================================================== // Constructors // =========================================================== public ByteBackedBitVector(final int pSize) throws IllegalArgumentException { this(pSize, new byte[BitVector.calculateByteSize(pSize)]); } public ByteBackedBitVector(final byte[] pData) throws IllegalArgumentException, NullPointerException { this(pData.length * Byte.SIZE, pData); } public ByteBackedBitVector(final int pSize, final byte[] pData) throws IllegalArgumentException, NullPointerException { if (pData == null) { throw new IllegalArgumentException("pData must not be null"); } if (pSize > (pData.length * Byte.SIZE)) { throw new IllegalArgumentException("pData is too short."); } if (BitVector.calculateByteSize(pSize) < pData.length) { throw new IllegalArgumentException("pData is too long."); } this.mSize = pSize; this.mData = pData; } public ByteBackedBitVector(final DataInputStream pDataInputStream) throws IOException { this.mSize = pDataInputStream.readInt(); this.mData = new byte[BitVector.calculateByteSize(this.mSize)]; pDataInputStream.readFully(this.mData); } public static ByteBackedBitVector load(final DataInputStream pDataInputStream) throws IOException { return new ByteBackedBitVector(pDataInputStream); } // =========================================================== // Getter & Setter // =========================================================== @Override public int getSize() { return this.mSize; } public int getByteSize() { return this.mData.length; } @Override public int getBit(final int pIndex) throws IllegalArgumentException { if ((pIndex < 0) || (pIndex >= this.mSize)) { throw new IllegalArgumentException("pIndex out of bounds: " + pIndex); } final int byteIndex = BitVector.getByteIndex(pIndex); final int indexInByte = BitVector.getIndexInByte(pIndex); return BitVector.getBitInByte(this.mData[byteIndex], indexInByte); } @Override public boolean getBitAsBoolean(final int pIndex) throws IllegalArgumentException { return this.getBit(pIndex) == BitVector.TRUE; } @Override public void setBit(final int pIndex) throws IllegalArgumentException { this.setBit(pIndex, true); } @Override public void clearBit(final int pIndex) throws IllegalArgumentException { this.setBit(pIndex, false); } @Override public void setBit(final int pIndex, final boolean pTrue) throws IllegalArgumentException { if ((pIndex < 0) || (pIndex >= this.mSize)) { throw new IllegalArgumentException("pIndex out of bounds: " + pIndex); } final int byteIndex = BitVector.getByteIndex(pIndex); final int indexInByte = BitVector.getIndexInByte(pIndex); final byte oldByte = this.mData[byteIndex]; final byte newByte = BitVector.setBitInByte(oldByte, indexInByte, pTrue); this.mData[byteIndex] = newByte; } @Override public int getBits(final int pIndex, final int pCount) throws IllegalArgumentException { if ((pIndex < 0) || (pIndex + pCount > this.mSize)) { throw new IllegalArgumentException("pIndex out of bounds: " + pIndex); } int bits = 0; int bitsLeft = pCount; int index = pIndex; while (bitsLeft >= Byte.SIZE) { bits = bits << Byte.SIZE; bits |= (this.getByte(index)) & 0xFF; index += Byte.SIZE; bitsLeft -= Byte.SIZE; } for (int i = 0; i < bitsLeft; i++) { bits = bits << 1; if(this.getBit(index) == BitVector.TRUE) { bits |= 0x01; } index++; } return bits; } @Override public long getLongBits(final int pIndex, final int pLength) throws IllegalArgumentException { throw new MethodNotYetImplementedException(); } @Override public void setBits(final int pIndex, final byte pBits, final int pBitIndex, final int pBitCount) throws IllegalArgumentException { if ((pIndex < 0) || ((pIndex + pBitCount) > this.mSize)) { throw new IllegalArgumentException("pIndex out of bounds: " + pIndex); } if ((pBitIndex < 0) || ((pBitIndex + pBitCount) > Byte.SIZE)) { throw new IllegalArgumentException("pIndex out of bounds: " + pBitIndex); } final int indexInByte = BitVector.getIndexInByte(pIndex); final int byteIndex = BitVector.getByteIndex(pIndex); if (indexInByte == 0) { /* Perfect match, easy get. */ final byte newByte = BitVector.setBitsInByte(this.mData[byteIndex], 0, pBits, pBitIndex, pBitCount); this.mData[byteIndex] = newByte; } else { final byte highByte = this.mData[byteIndex]; final int highByteBitCount = Math.min(pBitCount, Byte.SIZE - indexInByte); final byte newHighByte = BitVector.setBitsInByte(highByte, indexInByte, pBits, pBitIndex, highByteBitCount); this.mData[byteIndex] = newHighByte; if (highByteBitCount < pBitCount) { final byte lowByte = this.mData[byteIndex + 1]; final int lowByteBitIndex = pBitIndex + highByteBitCount; final int lowByteBitCount = pBitCount - highByteBitCount; final byte newLowByte = BitVector.setBitsInByte(lowByte, 0, pBits, lowByteBitIndex, lowByteBitCount); this.mData[byteIndex + 1] = newLowByte; } } } @Override public void setBits(final int pIndex, final short pBits, final int pBitIndex, final int pBitCount) throws IllegalArgumentException { if ((pIndex < 0) || ((pIndex + pBitCount) > this.mSize)) { throw new IllegalArgumentException("pIndex out of bounds: " + pIndex); } if ((pBitIndex < 0) || ((pBitIndex + pBitCount) > Short.SIZE)) { throw new IllegalArgumentException("pBitIndex out of bounds: " + pBitIndex); } final int highByteBitCount = Math.min(pBitCount, Math.max(0, Byte.SIZE - pBitIndex)); if (highByteBitCount != 0) { final byte highByte = (byte)((pBits >> (1 * Byte.SIZE)) & 0xFF); this.setBits(pIndex, highByte, pBitIndex, highByteBitCount); } if (pBitCount > highByteBitCount) { final byte lowByte = (byte)((pBits >> (0 * Byte.SIZE)) & 0xFF); final int lowByteBitCount = pBitCount - highByteBitCount; if(highByteBitCount == 0) { this.setBits(pIndex, lowByte, (pBitIndex - Byte.SIZE) % Byte.SIZE, lowByteBitCount); } else { this.setBits(pIndex + highByteBitCount, lowByte, 0, lowByteBitCount); } } } @Override public void setBits(final int pIndex, final int pBits, final int pBitIndex, final int pBitCount) throws IllegalArgumentException { if ((pIndex < 0) || ((pIndex + pBitCount) > this.mSize)) { throw new IllegalArgumentException("pIndex out of bounds: " + pIndex); } if ((pBitIndex < 0) || ((pBitIndex + pBitCount) > Integer.SIZE)) { throw new IllegalArgumentException("pBitIndex out of bounds: " + pBitIndex); } final int highShortBitCount = Math.min(pBitCount, Math.max(0, Short.SIZE - pBitIndex)); if (highShortBitCount != 0) { final short highShort = (short)((pBits >> (1 * Short.SIZE)) & 0xFFFF); this.setBits(pIndex, highShort, pBitIndex, highShortBitCount); } if (pBitCount > highShortBitCount) { final short lowShort = (short)((pBits >> (0 * Short.SIZE)) & 0xFFFF); final int lowShortBitCount = pBitCount - highShortBitCount; if(highShortBitCount == 0) { this.setBits(pIndex, lowShort, (pBitIndex - Short.SIZE) % Short.SIZE, lowShortBitCount); } else { this.setBits(pIndex + highShortBitCount, lowShort, 0, lowShortBitCount); } } } @Override public byte getByte(final int pIndex) throws IllegalArgumentException { if ((pIndex < 0) || ((pIndex + Byte.SIZE) > this.mSize)) { throw new IllegalArgumentException("pIndex out of bounds: " + pIndex); } final int indexInByte = BitVector.getIndexInByte(pIndex); final int byteIndex = BitVector.getByteIndex(pIndex); if (indexInByte == 0) { /* Perfect match, easy get. */ return this.mData[byteIndex]; } else { final byte highByte = this.mData[byteIndex]; final byte lowByte = this.mData[byteIndex + 1]; final int highBits = BitVector.getBitsInByte(highByte, indexInByte, Byte.SIZE - indexInByte); final int lowBits = BitVector.getBitsInByte(lowByte, 0, indexInByte); final int result = (highBits << indexInByte) + lowBits; return (byte)result; } } @Override public final void setByte(final int pIndex, final byte pByte) throws IllegalArgumentException { if ((pIndex < 0) || ((pIndex + Byte.SIZE) > this.mSize)) { throw new IllegalArgumentException("pIndex out of bounds: " + pIndex); } final int indexInByte = BitVector.getIndexInByte(pIndex); final int byteIndex = BitVector.getByteIndex(pIndex); if (indexInByte == 0) { /* Perfect match, easy set. */ this.mData[byteIndex] = pByte; } else { final byte highByte = this.mData[byteIndex]; final byte lowByte = this.mData[byteIndex + 1]; this.mData[byteIndex] = BitVector.setBitsInByte(highByte, indexInByte, pByte, 0, Byte.SIZE - indexInByte); this.mData[byteIndex + 1] = BitVector.setBitsInByte(lowByte, 0, pByte, Byte.SIZE - indexInByte, indexInByte); } } @Override public short getShort(final int pIndex) throws IllegalArgumentException { if ((pIndex < 0) || ((pIndex + Short.SIZE) > this.mSize)) { throw new IllegalArgumentException("pIndex out of bounds: " + pIndex); } final int highByte = this.getByte(pIndex) & 0xFF; final int lowByte = this.getByte(pIndex + Byte.SIZE) & 0xFF; final short result = (short)((highByte << Byte.SIZE) | lowByte); return result; } @Override public final void setShort(final int pIndex, final short pShort) throws IllegalArgumentException { if ((pIndex < 0) || ((pIndex + Short.SIZE) > this.mSize)) { throw new IllegalArgumentException("pIndex out of bounds: " + pIndex); } final byte highByte = (byte)((pShort >> Byte.SIZE) & 0xFF); final byte lowByte = (byte)(pShort & 0xFF); this.setByte(pIndex, highByte); this.setByte(pIndex + Byte.SIZE, lowByte); } @Override public int getInt(final int pIndex) throws IllegalArgumentException { if ((pIndex < 0) || ((pIndex + Integer.SIZE) > this.mSize)) { throw new IllegalArgumentException("pIndex out of bounds: " + pIndex); } final int highestByte = this.getByte(pIndex + (0 * Byte.SIZE)) & 0xFF; final int highByte = this.getByte(pIndex + (1 * Byte.SIZE)) & 0xFF; final int lowByte = this.getByte(pIndex + (2 * Byte.SIZE)) & 0xFF; final int lowestByte = this.getByte(pIndex + (3 * Byte.SIZE)) & 0xFF; final int result = (highestByte << (3 * Byte.SIZE)) | (highByte << (2 * Byte.SIZE)) | (lowByte << (1 * Byte.SIZE)) | lowestByte; return result; } @Override public void setInt(final int pIndex, final int pInt) throws IllegalArgumentException { if ((pIndex < 0) || ((pIndex + Integer.SIZE) > this.mSize)) { throw new IllegalArgumentException("pIndex out of bounds: " + pIndex); } this.setByte(pIndex + (0 * Byte.SIZE), (byte)((pInt >> (3 * Byte.SIZE)) & 0xFF)); this.setByte(pIndex + (1 * Byte.SIZE), (byte)((pInt >> (2 * Byte.SIZE)) & 0xFF)); this.setByte(pIndex + (2 * Byte.SIZE), (byte)((pInt >> (1 * Byte.SIZE)) & 0xFF)); this.setByte(pIndex + (3 * Byte.SIZE), (byte)((pInt >> (0 * Byte.SIZE)) & 0xFF)); } @Override public long getLong(final int pIndex) throws IllegalArgumentException { return this.getLongBits(pIndex, DataConstants.BITS_PER_LONG); } @Override public void setLong(final int pIndex, final long pLong) throws IllegalArgumentException { throw new MethodNotYetImplementedException(); } @Override public void clear() { this.fill((byte)0x00); } @Override public void fill(final byte pByte) { Arrays.fill(this.mData, pByte); } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Override public void save(final DataOutputStream pDataOutputStream) throws IOException { pDataOutputStream.writeInt(this.mSize); pDataOutputStream.write(this.mData); } @Override public byte[] toByteArray() { final byte[] bytes = new byte[this.mData.length]; System.arraycopy(this.mData, 0, bytes, 0, this.mData.length); return bytes; } @Override public String toString() { final StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append('['); for (int i = 0; i < this.mSize; i++) { if (this.getBit(i) == BitVector.TRUE) { stringBuilder.append('1'); } else { stringBuilder.append('0'); } if (((i % Byte.SIZE) == (Byte.SIZE - 1)) && (i < (this.mSize - 1))) { stringBuilder.append(',').append(' '); } } stringBuilder.append(']'); return stringBuilder.toString(); } // =========================================================== // Methods // =========================================================== // =========================================================== // Inner and Anonymous Classes // =========================================================== }