package de.persosim.simulator.utils;
import java.util.Arrays;
import de.persosim.simulator.exception.BitFieldOutOfBoundsException;
/**
* This class implements a little endian bitfield providing several bitwise
* logical operations.
*
* @author mboonk
*
*/
public class BitField {
boolean[] storedBits;
/**
* Creates an empty (all zero bits) {@link BitField} of the given length.
* @param numberOfBits
*/
public BitField(int numberOfBits){
storedBits = new boolean[numberOfBits];
}
/**
* Creates a {@link BitField} of the given size and sets all additionally
* given bits to 1.
*
* @param numberOfBits
* @param setBits
* this contains the zero based indices of the bits to be set
* to 1
*/
public BitField(int numberOfBits, int ... setBits){
this(numberOfBits);
for (int bit : setBits){
if (bit > numberOfBits || bit < 0){
throw new IllegalArgumentException("The bits to be set must be inside the BitField");
}
storedBits[bit] = true;
}
}
/**
* Create a {@link BitField} from a big endian ordered byte array.
* @param numberOfBits
* @param bitsToStore
* @return
*/
public static BitField buildFromBigEndian(int numberOfBits, byte [] bitsToStore){
BitField result = new BitField(numberOfBits);
boolean [] sourceBits = new boolean [bitsToStore.length*8];
for (int i = 0; i < sourceBits.length; i++){
sourceBits[i] = ((bitsToStore[i/8] >>> 7-(i%8)) & 0b00000001) == 1;
}
int offset = bitsToStore.length*8 - numberOfBits;
for (int i = offset; i < sourceBits.length; i++){
result.setBit(numberOfBits - 1 - (i - offset), sourceBits[i]);
}
return result;
}
/**
* This constructor takes the given byte array and parses it beginning at
* element 0 and the LSB of this element up to the element in which the
* numberOfBits-1 bit is contained.
*
* @param numberOfBits
* to store in the field
* @param bitsToStore
* source data
*/
public BitField(int numberOfBits, byte[] bitsToStore) {
this(numberOfBits);
for (int i = 0; i < numberOfBits; i++) {
/*
* Access the next byte every 8 bits and mask away any potential
* sign. Then shift this byte such that the currently interesting
* bit is the LSB. Masking the other 7 bits away provides the value
* that is added to the internal representation of the bit field.
* This inverts bit order per byte and preserves byte order.
*/
setBit(i,
(((bitsToStore[i / 8] & 0xFF) >>> i % 8) & 0b00000001) == 1);
}
}
/**
* This constructor takes the given boolean array and parses it beginning at
* element 0 interpreted as LSB using the true/false values directly as bit
* values 1/0 respectively.
*
* @param bitsToStore
* source data
*/
public BitField(boolean[] bitsToStore) {
storedBits = Arrays.copyOf(bitsToStore, bitsToStore.length);
}
/**
* This methods concatenates the given {@link BitField} with this object.
*
* thisObject|field
*
* @param field
* to concatenate with
* @return the concatenation of this object with
*/
public BitField concatenate(BitField field) {
boolean[] result = new boolean[field.getNumberOfBits()
+ getNumberOfBits()];
System.arraycopy(storedBits, 0, result, 0, getNumberOfBits());
System.arraycopy(field.storedBits, 0, result, getNumberOfBits(), field.getNumberOfBits());
return new BitField(result);
}
/**
* Calculate an bitwise or over this and the given {@link BitField}.
*
* @param field
* @return a new {@link BitField} containing the result
*/
public BitField or(BitField field) {
boolean[] result = new boolean[Math.max(getNumberOfBits(), field.getNumberOfBits())];
for (int i = 0; i < result.length; i++) {
result[i] = getZeroPaddedBit(i) | field.getZeroPaddedBit(i);
}
return new BitField(result);
}
/**
* Calculate an bitwise and over this and the given {@link BitField}.
*
* @param field
* @return a new {@link BitField} containing the result
*/
public BitField and(BitField field) {
boolean[] result = new boolean[Math.max(getNumberOfBits(), field.getNumberOfBits())];
for (int i = 0; i < result.length; i++) {
result[i] = getZeroPaddedBit(i) & field.getZeroPaddedBit(i);
}
return new BitField(result);
}
private boolean getZeroPaddedBit(int index) {
if (index >= storedBits.length) {
return false;
}
return storedBits[index];
}
public int getNumberOfBits() {
return storedBits.length;
}
public boolean getBit(int index) {
if (0 <= index && index < storedBits.length) {
return storedBits[index];
} else {
throw new BitFieldOutOfBoundsException();
}
}
/**
* This method creates a new instance with one flipped bit.
* @param index the bit to flip
* @return
*/
public BitField flipBit(int index){
BitField result = new BitField(this.storedBits);
result.setBit(index, !result.getBit(index));
return result;
}
private void setBit(int index, boolean value) {
if (0 <= index && index < storedBits.length) {
storedBits[index] = value;
} else {
throw new BitFieldOutOfBoundsException();
}
}
@Override
public int hashCode() {
return Arrays.hashCode(storedBits);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BitField other = (BitField) obj;
if (!Arrays.equals(storedBits, other.storedBits))
return false;
return true;
}
/**
* This method creates a byte array representation of the bitfield. It
* contains the LSB of the field as the LSB of the element 0, bit 9 as LSB
* of element 1 etc.. After the internal bit field is exhausted, missing
* bits will be padded to 0.
*
* @return
*/
public byte[] getAsZeroPaddedByteArray() {
int length = getNumberOfBits() / 8;
if (getNumberOfBits() % 8 > 0) {
length++;
}
byte[] result = new byte[length];
for (int i = 0; i < getNumberOfBits(); i++) {
result[i / 8] |= (byte) (getBit(i) ? 1 : 0);
if (!(i == getNumberOfBits() - 1)) {
result[i / 8] = (byte) (result[i / 8] << 1);
}
}
return result;
}
/**
* This method creates a padded big endian byte array representation of this {@link BitField}
* @return a padded big endian byte array representation of this {@link BitField}
*/
public byte[] getAsZeroPaddedBigEndianByteArray() {
int length = getNumberOfBits() / 8;
if (getNumberOfBits() % 8 > 0) {
length++;
}
byte[] result = new byte[length];
for (int i = 0; i < getNumberOfBits(); i++) {
byte temp = (byte) ((byte) (getBit(i) ? 1 : 0) << (i % 8));
result[result.length - 1 - (i / 8)] |= temp;
}
return result;
}
@Override
public String toString() {
return getStringRepresentation(START_WITH_MOST_SIGNIFICANT_BIT);
}
public static boolean START_WITH_MOST_SIGNIFICANT_BIT = true;
public static boolean START_WITH_LEAST_SIGNIFICANT_BIT = false;
/**
* This method returns a String representation of this object consisting of
* {0, 1}. The String starts in reading direction either with the most or
* least significant bit. Either use
* {@link #START_WITH_MOST_SIGNIFICANT_BIT} or
* {@link #START_WITH_LEAST_SIGNIFICANT_BIT} as parameter.
*
* @param startWithMostSignificantBit
* start with most or least significant bit
* @return the String representation of this object
*/
public String getStringRepresentation(boolean startWithMostSignificantBit) {
StringBuilder sb = new StringBuilder(getNumberOfBits());
int noOfRemainingBits = getNumberOfBits();
int effectiveIndex, bitsToCount;
for(int i=0; i<getNumberOfBits(); i++) {
if(startWithMostSignificantBit) {
effectiveIndex = (getNumberOfBits() - 1) - i;
bitsToCount = noOfRemainingBits;
} else{
effectiveIndex = i;
bitsToCount = i;
}
if((i > 0) && ((bitsToCount % 8) == 0)) {
sb.append(" ");
}
if(getBit(effectiveIndex)) {
sb.append("1");
} else{
sb.append("0");
}
noOfRemainingBits--;
}
return sb.toString();
}
}