Java tutorial
/******************************************************************************* * 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 io.github.dsheirer.bits; import io.github.dsheirer.edac.CRC; import org.apache.commons.lang3.Validate; import java.util.BitSet; 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; } /** * Indicates the number of bit errors that were corrected in this message. * * @see CorrectedBinaryMessage subclass that supports setting this value * @return number of corrected bits in this message */ public int getCorrectedBitCount() { return 0; } /** * 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()); } }