public final class

BitVector

extends Object
package org.andengine.util.adt.bit;

import org.andengine.util.adt.DataConstants;


/**
 * (c) Zynga 2011
 *
 * @author Nicolas Gramlich <ngramlich@zynga.com>
 * @since 15:56:32 - 30.10.2011
 */
public final class BitVector {
	// ===========================================================
	// Constants
	// ===========================================================

	// ===========================================================
	// Fields
	// ===========================================================

	private final int mCapacity;
	private final long[] mData;

	// ===========================================================
	// Constructors
	// ===========================================================

	private BitVector(final int pCapacity) {
		if(pCapacity <= 0) {
			throw new IllegalArgumentException("pCapacity must be > 0.");
		}
		this.mCapacity = pCapacity;

		/* Check if bytes perfectly fit into the data fields or if there are some overflow bytes that need special treatment. */
		final boolean perfectDataFit = pCapacity % DataConstants.BITS_PER_LONG == 0;

		final int dataCapacity;
		if(perfectDataFit) {
			dataCapacity = pCapacity / DataConstants.BITS_PER_LONG;
		} else {
			/* Extra field for overflow bytes. */
			dataCapacity = (pCapacity / DataConstants.BITS_PER_LONG) + 1;
		}
		this.mData = new long[dataCapacity];
	}

	public BitVector(final byte[] pBytes) {
		this(pBytes.length * DataConstants.BITS_PER_BYTE);

		final long[] data = this.mData;

		/* Check if bytes perfectly fit into the data fields or if there are some overflow bytes that need special treatment. */
		final boolean perfectDataFit = pBytes.length % DataConstants.BYTES_PER_LONG == 0;

		final int dataCapacity = data.length;
		final int lastCompleteDataIndex = (perfectDataFit) ? dataCapacity - 1 : dataCapacity - 2;

		for(int i = lastCompleteDataIndex; i >= 0; i--) {
			final int bytesOffset = i * DataConstants.BYTES_PER_LONG;

			data[i] = ((((long)pBytes[bytesOffset + 0]) << 56) & 0xFF00000000000000L)
					| ((((long)pBytes[bytesOffset + 1]) << 48) & 0xFF000000000000L)
					| ((((long)pBytes[bytesOffset + 2]) << 40) & 0xFF0000000000L)
					| ((((long)pBytes[bytesOffset + 3]) << 32) & 0xFF00000000L)
					| ((((long)pBytes[bytesOffset + 4]) << 24) & 0xFF000000L)
					| ((((long)pBytes[bytesOffset + 5]) << 16) & 0xFF0000L)
					| ((((long)pBytes[bytesOffset + 6]) <<  8) & 0xFF00L)
					| ((((long)pBytes[bytesOffset + 7]) <<  0) & 0xFFL);
		}

		/* Put overflow bytes into last data field. */
		if(!perfectDataFit) {
			long overflowData = 0;

			final int overflowDataIndex = dataCapacity - 1;
			final int overflowBytesOffset = overflowDataIndex * DataConstants.BYTES_PER_LONG;

			final int overflowByteCount = pBytes.length - overflowBytesOffset;
			switch(overflowByteCount) {
				case 7:
					overflowData = overflowData | ((((long)pBytes[overflowBytesOffset + 6]) <<  8) & 0xFF00L);
				case 6:
					overflowData = overflowData | ((((long)pBytes[overflowBytesOffset + 5]) << 16) & 0xFF0000L);
				case 5:
					overflowData = overflowData | ((((long)pBytes[overflowBytesOffset + 4]) << 24) & 0xFF000000L);
				case 4:
					overflowData = overflowData | ((((long)pBytes[overflowBytesOffset + 3]) << 32) & 0xFF00000000L);
				case 3:
					overflowData = overflowData | ((((long)pBytes[overflowBytesOffset + 2]) << 40) & 0xFF0000000000L);
				case 2:
					overflowData = overflowData | ((((long)pBytes[overflowBytesOffset + 1]) << 48) & 0xFF000000000000L);
				case 1:
					overflowData = overflowData | ((((long)pBytes[overflowBytesOffset + 0]) << 56) & 0xFF00000000000000L);
			}

			data[overflowDataIndex] = overflowData;
		}
	}

	// ===========================================================
	// Getter & Setter
	// ===========================================================

	public int getCapacity() {
		return this.mCapacity;
	}

	public boolean getBit(final int pPosition) {
		if(pPosition < 0) {
			throw new IllegalArgumentException("pPosition must be >= 0.");
		}
		if(pPosition >= this.mCapacity) {
			throw new IllegalArgumentException("pPosition must be < capacity.");
		}

		final int dataIndex = pPosition / DataConstants.BITS_PER_LONG;
		final int dataOffset = pPosition % DataConstants.BITS_PER_LONG;

		final long dataField = this.mData[dataIndex];

		final int rightShift = DataConstants.BITS_PER_LONG - dataOffset - 1;
		final long bit = (dataField >> rightShift) & 0x01;

		return bit != 0;
	}

	public byte getByte(final int pPosition) {
		return (byte) this.getBits(pPosition, DataConstants.BITS_PER_BYTE);
	}

	public short getShort(final int pPosition) {
		return (short) this.getBits(pPosition, DataConstants.BITS_PER_SHORT);
	}

	public int getInt(final int pPosition) {
		return (int) this.getBits(pPosition, DataConstants.BITS_PER_INT);
	}

	public long getLong(final int pPosition) {
		return this.getBits(pPosition, DataConstants.BITS_PER_LONG);
	}

	public long getBits(final int pPosition, final int pLength) {
		/* Sanity checks. */
		if(pPosition < 0) {
			throw new IllegalArgumentException("pPosition must be >= 0.");
		}
		if(pLength < 0) {
			throw new IllegalArgumentException("pLength must be >= 0.");
		}
		if(pPosition + pLength > this.mCapacity) {
			throw new IllegalArgumentException("pPosition + pLength must be <= capacity.");
		}

		/* Early exit. */
		if(pLength == 0) {
			return 0L;
		}

		final int dataIndex = pPosition / DataConstants.BITS_PER_LONG;
		final int offset = pPosition % DataConstants.BITS_PER_LONG;

		final long data;
		if(offset == 0) {
			data = this.mData[dataIndex];
		} else if(offset + pLength <= DataConstants.BITS_PER_LONG) {
			data = this.mData[dataIndex] << offset;
		} else {
			/* Join bits from adjacent data fields. */
			data = (this.mData[dataIndex] << offset) | (this.mData[dataIndex + 1] >>> (DataConstants.BITS_PER_LONG - offset));
		}

		if(pLength == DataConstants.BITS_PER_LONG) {
			return data;
		} else {
			final int rightShift = DataConstants.BITS_PER_LONG - pLength;
			final long mask = 0xFFFFFFFFFFFFFFFFL >>> rightShift;
			final long unmaskedBits = data >> rightShift;
			return unmaskedBits & mask;
		}
	}

	// ===========================================================
	// Methods for/from SuperClass/Interfaces
	// ===========================================================

	@Override
	public String toString() {
		final StringBuilder sb = new StringBuilder();
		sb.append('[');
		for(int i = 0; i < this.mCapacity; i++) {
			sb.append(this.getBit(i) ? '1' : '0');
			if(i % 8 == 7 && i < this.mCapacity -1) {
				sb.append(' ');
			}
		}
		sb.append(']');
		return sb.toString();
	}

	// ===========================================================
	// Methods
	// ===========================================================

	public byte[] toByteArray() {
		final int byteArrayLength;
		if(this.mCapacity % DataConstants.BITS_PER_BYTE == 0) {
			byteArrayLength = this.mCapacity / DataConstants.BITS_PER_BYTE;
		} else {
			byteArrayLength = (this.mCapacity / DataConstants.BITS_PER_BYTE) + 1;
		}

		final byte[] bytes = new byte[byteArrayLength];
		/* Check if bytes perfectly fit into the data fields or if there are some overflow bytes that need special treatment. */
		final boolean perfectDataFit = this.mCapacity % DataConstants.BITS_PER_LONG == 0;

		final long[] data = this.mData;
		final int dataCapacity = data.length;
		final int lastCompleteDataIndex = (perfectDataFit) ? dataCapacity - 1 : dataCapacity - 2;

		int bytesOffset = lastCompleteDataIndex * DataConstants.BYTES_PER_LONG + (DataConstants.BYTES_PER_LONG - 1);
		for(int i = lastCompleteDataIndex; i >= 0; i--) {
			final long dataField = data[i];

			bytes[bytesOffset--] = (byte) ((dataField >> 0) & 0xFF);
			bytes[bytesOffset--] = (byte) ((dataField >> 8) & 0xFF);
			bytes[bytesOffset--] = (byte) ((dataField >> 16) & 0xFF);
			bytes[bytesOffset--] = (byte) ((dataField >> 24) & 0xFF);
			bytes[bytesOffset--] = (byte) ((dataField >> 32) & 0xFF);
			bytes[bytesOffset--] = (byte) ((dataField >> 40) & 0xFF);
			bytes[bytesOffset--] = (byte) ((dataField >> 48) & 0xFF);
			bytes[bytesOffset--] = (byte) ((dataField >> 56) & 0xFF);
		}

		/* Put overflow bytes into last data field. */
		if(!perfectDataFit) {
			final int overflowDataIndex = dataCapacity - 1;
			final long overflowDataField = data[overflowDataIndex];

			final int overflowBytesOffset = overflowDataIndex * DataConstants.BYTES_PER_LONG;

			final int overflowByteCount = bytes.length % DataConstants.BYTES_PER_LONG;
			switch(overflowByteCount) {
				case 7:
					bytes[overflowBytesOffset + 6] = (byte) ((overflowDataField >> 8) & 0xFF);
				case 6:
					bytes[overflowBytesOffset + 5] = (byte) ((overflowDataField >> 16) & 0xFF);
				case 5:
					bytes[overflowBytesOffset + 4] = (byte) ((overflowDataField >> 24) & 0xFF);
				case 4:
					bytes[overflowBytesOffset + 3] = (byte) ((overflowDataField >> 32) & 0xFF);
				case 3:
					bytes[overflowBytesOffset + 2] = (byte) ((overflowDataField >> 40) & 0xFF);
				case 2:
					bytes[overflowBytesOffset + 1] = (byte) ((overflowDataField >> 48) & 0xFF);
				case 1:
					bytes[overflowBytesOffset + 0] = (byte) ((overflowDataField >> 56) & 0xFF);
			}
		}

		return bytes;
	}

	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================
}