/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package bits;
import java.util.Arrays;
import java.util.BitSet;
import java.util.logging.LogManager;
import edac.CRC;
import org.apache.commons.lang3.Validate;
public class BinaryMessage extends BitSet
{
private static final long serialVersionUID = 1L;
/**
* Logical (ie constructed) size of this bitset, despite the actual size of
* the super bitset that this class is based on
*/
private int mSize = 0;
/**
* Pointer to the next fill index location, when adding bits to this bitset
* one at a time.
*/
private int mPointer = 0;
/**
* Bitset that buffers bits added one at a time, up to the size of the this
* bitset.
*
* Note: the super class bitset behind this class may have a size larger
* that the size parameter specified.
* @param size
*/
/**
* Used for temporary storage of CRC check results when we're passing this
* message to an EDAC function.
*/
private CRC mCRC;
public BinaryMessage( int size )
{
super( size );
mSize = size;
}
/**
* Constructs a bitset buffer and preloads it with the bits contained in
* the bitsToPreload parameter. If the bitsToPreload are longer than the
* size of the bitset, only those bits that fit will be preloaded
*
* @param size
* @param bitsToPreload
*/
public BinaryMessage( int size, boolean[] bitsToPreload )
{
this( size );
int pointer = 0;
while( !this.isFull() && pointer < bitsToPreload.length )
{
try
{
this.add( bitsToPreload[ pointer ] );
}
catch( BitSetFullException e )
{
e.printStackTrace();
}
pointer++;
}
}
/**
* Constructs a new BitSetBuffer from an existing one
*/
private BinaryMessage( BinaryMessage toCopyFrom )
{
this( toCopyFrom.size() );
this.or( toCopyFrom );
this.mPointer = toCopyFrom.pointer();
}
public BinaryMessage( BitSet bitset, int size )
{
this( size );
this.or( bitset );
this.mPointer = size - 1;
}
public BinaryMessage(byte[] data)
{
this(BitSet.valueOf(data), data.length * 8);
}
/**
* Returns a mew binary message containing the bits from (inclusive) to
* end (exclusive).
*
* @param start
* @param end
* @return
*/
public BinaryMessage getSubMessage( int start, int end )
{
BitSet subset = this.get( start, end );
return new BinaryMessage( subset, end - start );
}
public CRC getCRC()
{
return mCRC;
}
public void setCRC( CRC crc )
{
mCRC = crc;
}
/**
* Current pointer index
*/
public int pointer()
{
return mPointer;
}
/**
* Sets the pointer to a specific value
* @param index
*/
public void setPointer( int index )
{
mPointer = index;
}
/**
* Moves the current pointer position left (negative adjustment) or
* right (positive adjustment)
*/
public void adjustPointer( int adjustment )
{
mPointer += adjustment;
}
/**
* Static method to construct a new BitSetBuffer, preloaded with the bits
* from the preload parameter, and then filled with the bits from the
* second bitsetbuffer parameter.
*
* @param preloadBits - boolean array of bits to be prepended to the new
* bitset
* @param bitsetToAppend - full bitset to be appended to the residual bits array
* @return - new Bitset preloaded with residual bits and new bitset
*/
public static BinaryMessage merge( boolean[] preloadBits, BinaryMessage bitsetToAppend )
{
BinaryMessage returnValue = new BinaryMessage( preloadBits.length + bitsetToAppend.size(), preloadBits );
int pointer = 0;
while( pointer < bitsetToAppend.size() && !returnValue.isFull() )
{
try
{
returnValue.add( bitsetToAppend.get( pointer ) );
}
catch( BitSetFullException e )
{
e.printStackTrace();
}
pointer++;
}
return returnValue;
}
/**
* Returns a (new) copy of this bitsetbuffer
* @return
*/
public BinaryMessage copy()
{
return new BinaryMessage( this );
}
public boolean isFull()
{
return mPointer >= mSize;
}
/**
* Overrides the in-build size() method of the bitset and returns the value
* specified at instantiation. The actual bitset size may be larger than
* this value, and that size is managed by the super class.
*/
@Override
public int size()
{
return mSize;
}
public void setSize( int size )
{
mSize = size;
}
/**
* Clears (sets to false or 0) the bits in this bitset and resets the
* pointer to zero.
*/
@Override
public void clear()
{
this.clear( 0, mSize );
mPointer = 0;
}
/**
* Adds a the bit parameters to this bitset, placing it in the index
* specified by mPointer, and incrementing mPointer to prepare for the next
* call to this method
* @param value
* @throws BitSetFullException - if the size specified at construction is
* exceeded. Invoke full() to determine if the bitset is full either before
* adding a new bit, or after adding a bit.
*/
public void add( boolean value ) throws BitSetFullException
{
if( !isFull() )
{
this.set( mPointer++, value );
}
else
{
throw new BitSetFullException( "bitset is full -- contains " +
( mPointer + 1 ) + " bits" );
}
}
public String toString()
{
StringBuilder sb = new StringBuilder();
for( int x = 0; x < mSize; x++ )
{
sb.append( ( this.get( x ) ? "1" : "0" ) );
}
return sb.toString();
}
/**
* Returns this bitset as an array of integer ones and zeros
*/
public int[] toIntegerArray()
{
int[] values = new int[ mSize ];
for (int i = nextSetBit( 0 ); i >= 0 && i < mSize; i = nextSetBit( i+1 ) )
{
values[ i ] = 1;
}
return values;
}
/**
* Returns this message as a little endian byte array. Extra 0 bits will be padded to the end to make the overall
* length a multiple of 8.
* @return little endian message byte array
*/
public byte[] toByteArray()
{
int length = size() / 8;
if(length * 8 < size())
{
length ++;
}
byte[] bytes = new byte[length];
int pointer = 0;
for(int x = 0; x < length; x++)
{
bytes[x] = getByte(pointer);
pointer += 8;
}
return bytes;
}
/**
* Returns this bitset as a reversed bit order array of integer ones and zeros
* from the specified index range
*/
public int[] toReverseIntegerArray( int start, int end )
{
int[] values = new int[ end - start + 1 ];
for (int i = nextSetBit( start ); i >= start && i <= end; i = nextSetBit( i+1 ) )
{
values[ end - i ] = 1;
}
return values;
}
/**
* Returns a boolean array from startIndex to end of the bitset
*/
public boolean[] getBits( int startIndex )
{
return getBits( startIndex, mSize - 1 );
}
/**
* Returns a boolean array of the right-most bitCount number of bits
*/
public boolean[] right( int bitCount )
{
return getBits( mSize - bitCount - 1 );
}
/**
* Returns a boolean array representing the bits located from startIndex
* through endIndex
*/
public boolean[] getBits( int startIndex, int endIndex )
{
boolean[] returnValue = null;
if( startIndex >= 0 &&
startIndex < endIndex &&
endIndex < mSize )
{
returnValue = new boolean[ endIndex - startIndex + 1 ];
int bitsetPointer = startIndex;
int returnPointer = 0;
while( bitsetPointer <= endIndex )
{
returnValue[ returnPointer ] = this.get( bitsetPointer );
bitsetPointer++;
returnPointer++;
}
}
return returnValue;
}
/**
* Returns the integer value represented by the bit array
* @param bits - an array of bit positions that will be treated as if they
* were contiguous bits, with index 0 being the MSB and index
* length - 1 being the LSB
* @return - integer value of the bit array
*/
public int getInt( int[] bits )
{
if( bits.length > 32 )
{
throw new IllegalArgumentException( "Overflow - must be 32 bits "
+ "or less to fit into a primitive integer value" );
}
int value = 0;
for( int index: bits )
{
value = Integer.rotateLeft( value, 1 );
if( get( index ) )
{
value++;
}
}
return value;
}
public void setInt(int value, int[] indices)
{
for(int x = 0; x < indices.length; x++)
{
int mask = 1<<(indices.length - x - 1);
if((value & mask) == mask)
{
set(indices[x]);
}
else
{
clear(indices[x]);
}
}
}
/**
* Returns the byte value represented by the bit array
* @param bits - an array of bit positions that will be treated as if they
* were contiguous bits, with index 0 being the MSB and index
* length - 1 being the LSB
* @return - byte value of the bit array
*/
public byte getByte( int[] bits )
{
if( bits.length != 8 )
{
throw new IllegalArgumentException( "Invalid - there must be 8"
+ "indexes to form a proper byte" );
}
int value = 0;
for( int index: bits )
{
value = Integer.rotateLeft( value, 1 );
if( get( index ) )
{
value++;
}
}
return (byte)( value & 0xFF );
}
/**
* Returns the byte value contained between index and index + 7 bit positions
* @param index specifying the start of the byte value
* @return byte value contained at index <> index + 7 bit positions
*/
public byte getByte(int index)
{
Validate.isTrue((index + 7) <= size());
int value = 0;
for(int x = 0; x < 8; x++)
{
value = value << 1;
if(get(index + x))
{
value++;
}
}
return (byte)value;
}
/**
* Sets the byte value at index position through index + 7 position.
*
* @param index bit position where to write the MSB of the byte value
* @param value to write at the index through index + 7 bit positions
*/
public void setByte(int index, byte value)
{
Validate.isTrue((index + 8) <= size());
int mask = 0x80;
for(int x = 0; x < 8; x++)
{
if((mask & value) == mask)
{
set(index + x);
}
else
{
clear(index + x);
}
mask = mask >> 1;
}
}
/**
* Returns the long value represented by the bit array
* @param bits - an array of bit positions that will be treated as if they
* were contiguous bits, with index 0 being the MSB and index
* length - 1 being the LSB
* @return - integer value of the bit array
*/
public long getLong( int[] bits )
{
if( bits.length > 64 )
{
throw new IllegalArgumentException( "Overflow - must be 64 bits "
+ "or less to fit into a primitive long value" );
}
long value = 0;
for( int index: bits )
{
value = Long.rotateLeft( value, 1 );
if( get( index ) )
{
value++;
}
}
return value;
}
/**
* Converts up to 63 bits from the bit array into an integer and then
* formats the value into hexadecimal, prefixing the value with zeros to
* provide a total length of digitDisplayCount;
*
* @param bits
* @param digitDisplayCount
* @return
*/
public String getHex( int[] bits, int digitDisplayCount )
{
if( bits.length <= 32 )
{
int value = getInt( bits );
return String.format( "%0" + digitDisplayCount + "X", value );
}
else if( bits.length <= 64 )
{
long value = getLong( bits );
return String.format( "%0" + digitDisplayCount + "X", value );
}
else
{
throw new IllegalArgumentException( "BitSetBuffer.getHex() "
+ "maximum array length is 63 bits" );
}
}
public String getHex( int msb, int lsb, int digitDisplayCount )
{
int length = lsb - msb;
if( length <= 32 )
{
int value = getInt( msb, lsb );
return String.format( "%0" + digitDisplayCount + "X", value );
}
else if( length <= 64 )
{
long value = getLong( msb, lsb );
return String.format( "%0" + digitDisplayCount + "X", value );
}
else
{
throw new IllegalArgumentException( "BitSetBuffer.getHex() "
+ "maximum array length is 64 bits" );
}
}
/**
* Returns the int value represented by the bit range. This method will
* parse the bits in big endian or little endian format. The start value
* represents the MSB and the end value represents the LSB of the value.
*
* start < end: little endian interpretation
* end < start: big endian interpretation
*
* @param start - MSB of the value
* @param end - LSB of the value
*
* @return - int value of the bit range
*/
public int getInt( int start, int end )
{
if( Math.abs( end - start ) > 32 )
{
throw new IllegalArgumentException( "Overflow - must be 32 bits "
+ "or less to fit into a primitive integer value" );
}
int value = 0;
if( start < end )
{
for( int x = start; x <= end; x++ )
{
value = Integer.rotateLeft( value, 1 );
if( get( x ) )
{
value++;;
}
}
}
else
{
for( int x = end; x >= start; x-- )
{
value = Integer.rotateLeft( value, 1 );
if( get( x ) )
{
value++;;
}
}
}
return value;
}
/**
* Returns the long value represented by the bit range. This method will
* parse the bits in big endian or little endian format. The start value
* represents the MSB and the end value represents the LSB of the value.
*
* start < end: little endian interpretation
* end < start: big endian interpretation
*
* @param start - MSB of the value
* @param end - LSB of the value
*
* @return - long value of the bit range
*/
public long getLong( int start, int end )
{
if( Math.abs( end - start ) > 64 )
{
throw new IllegalArgumentException( "Overflow - must be 64 bits "
+ "or less to fit into a primitive long value" );
}
long value = 0;
if( start < end )
{
for( int x = start; x <= end; x++ )
{
value = Long.rotateLeft( value, 1 );
if( get( x ) )
{
value++;
}
}
}
else
{
for( int x = end; x >= start; x-- )
{
value = Long.rotateLeft( value, 1 );
if( get( x ) )
{
value++;
}
}
}
return value;
}
/**
* Creates a buffer of size=width and fills the buffer with the fill value
* @param width - size of the buffer
* @param fill - initial fill value
* @return - filled buffer
*/
public static BinaryMessage getBuffer( int width, long fill )
{
BinaryMessage buffer = new BinaryMessage( width );
buffer.load( 0, width, fill );
return buffer;
}
/**
* Loads the value into the buffer starting at the offset index, and
* assuming that the value represents (width) number of bits. The MSB of
* the value will be located at the offset and the LSB of the value will
* be located at ( offset + width ).
*
* @param offset - starting bit index for the MSB of the value
* @param width - representative bit width of the value
* @param value - value to be loaded into the buffer
*/
public void load( int offset, int width, long value )
{
for( int x = 0; x < width; x++ )
{
long mask = Long.rotateLeft( 1, width - x - 1 );
if( ( mask & value ) == mask )
{
set( offset + x );
}
else
{
clear( offset + x );
}
}
}
/**
* Generates an array of message bit position indexes to support accessing
* a contiguous field value
*
* @param start - starting bit position of the field
* @param length - field length
* @return - array of field indexes
*/
public static int[] getFieldIndexes( int start, int length, boolean bigEndian )
{
int[] checksumIndexes = new int[ length ];
for( int x = 0; x < length; x++ )
{
if( bigEndian )
{
checksumIndexes[ length - x - 1 ] = start + x;
}
else
{
checksumIndexes[ x ] = start + x;
}
}
return checksumIndexes;
}
/**
* Creates a bitsetbuffer loaded from a string of zeros and ones
*
* @param message - string containing only zeros and ones
* @return - loaded buffer
*/
public static BinaryMessage load( String message )
{
if( !message.matches( "[01]*" ) )
{
throw new IllegalArgumentException(
"Message must contain only zeros and ones" );
}
BinaryMessage buffer = new BinaryMessage( message.length() );
for( int x = 0; x < message.length(); x++ )
{
if( message.substring( x, x + 1 ).contentEquals( "1" ) )
{
buffer.set( x );
}
}
return buffer;
}
/**
* Left rotates the bits between start and end indices, number of places.
*/
public void rotateLeft( int places, int startIndex, int endIndex )
{
for( int x = 0; x < places; x++ )
{
rotateLeft( startIndex, endIndex );
}
}
/**
* Left rotates the bits between start and end and wraps the left-most
* bit around to the end.
*/
public void rotateLeft( int startIndex, int endIndex )
{
boolean wrapBit = get( startIndex );
for( int x = startIndex; x < endIndex; x++ )
{
if( get( x + 1 ) )
{
set( x );
}
else
{
clear( x );
}
}
if( wrapBit )
{
set( endIndex );
}
else
{
clear( endIndex );
}
}
/**
* Right rotates the bits between start and end indices, number of places.
*/
public void rotateRight( int places, int startIndex, int endIndex )
{
for( int x = 0; x < places; x++ )
{
rotateRight( startIndex, endIndex );
}
}
/**
* Right rotates the bits between start and end and wraps the right-most
* bit around to the start.
*/
public void rotateRight( int startIndex, int endIndex )
{
boolean wrapBit = get( endIndex );
for( int x = endIndex - 1; x >= startIndex; x-- )
{
if( get( x ) )
{
set( x + 1 );
}
else
{
clear( x + 1 );
}
}
if( wrapBit )
{
set( startIndex );
}
else
{
clear( startIndex );
}
}
/**
* Performs exclusive or of the value against this bitset starting at the
* offset position using width bits from the value.
*/
public void xor( int offset, int width, int value )
{
BinaryMessage mask = new BinaryMessage( this.size() );
mask.load( offset, width, value );
this.xor( mask );
}
public static void main(String[] args)
{
BinaryMessage b = new BinaryMessage(32);
int[] indices = {2,3,7,8};
b.setInt(0xF, indices);
System.out.println(b.toString());
}
}