/*
* *****************************************************************************
* Copyright (C) 2014-2026 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
* Note: the super class bitset behind this class may have a size larger * that the size parameter specified. * * @param size of constructed bitset */ 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(); this.mCRC = toCopyFrom.mCRC; this.mSize = toCopyFrom.mSize; } 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. * * @return number of corrected bits in this message * @see CorrectedBinaryMessage subclass that supports setting this value */ 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; } /** * @return a (new) copy of this binary message */ 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; } /** * Current size of the message indicating the quantity of bits that have been added to this message. This value * is less than or equal to the size() value. * @return current size in bits. */ public int currentSize() { return mPointer; } 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 + "/" + size() + "] bits"); } } /** * Adds bit1 and bit2 to this bitset. * * Note: this method does not provide overfill protection. * * @param bit1 to add. * @param bit2 to add. */ public void add(boolean bit1, boolean bit2) { set(mPointer++, bit1); set(mPointer++, bit2); } public String toString() { return toHexString(); } /** * Binary representation of the message * @return message in binary 0/1 format */ public String toBinaryString() { StringBuilder sb = new StringBuilder(); for(int x = 0; x < mSize; x++) { sb.append((this.get(x) ? "1" : "0")); } return sb.toString(); } /** * Hex string representation of the message * @return message in hex */ public String toHexString() { StringBuilder sb = new StringBuilder(); for(int x = 0; x < size(); x += 4) { sb.append(getNibbleAsHex(x)); } return sb.toString(); } /** * Overrides the parent method which returns a BitSet so that we can return a BinaryMessage * @param from inclusive * @param to exclusive * @return */ @Override public BinaryMessage get(int from, int to) { return new BinaryMessage(super.get(from, to), (to - from)); } /** * 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; } /** * Returns the integer value of the message field described by the field argument. * @param intField with start and end indices (inclusive). * @return integer field value. */ public int getInt(IntField intField) { int value = 0; for(int index = intField.start(); index <= intField.end(); index++) { value = Integer.rotateLeft(value, 1); if(get(index)) { value++; } } return value; } /** * Returns the integer value of the message field described by the field argument. * @param fragmentedField with an array of message indices * @return integer field value. */ public int getInt(FragmentedIntField fragmentedField) { int value = 0; for(int index: fragmentedField.indices()) { value = Integer.rotateLeft(value, 1); if(get(index)) { value++; } } return value; } /** * Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices. * @param intField to inspect * @return true if any of the bits are set, indicating a non-zero value. */ public boolean hasInt(IntField intField) { return nextSetBit(intField.start()) <= intField.end(); } /** * Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices. * @param fragmentedField to inspect * @return true if any of the bits are set, indicating a non-zero value. */ public boolean hasInt(FragmentedIntField fragmentedField) { for(int index: fragmentedField.indices()) { if(get(index)) { return true; } } return false; } /** * Returns the integer value of the message field described by the field argument where the message start index is * offset within this binary message. * @param intField with start and end indices (inclusive). * @param offset to the start of the first message index. * @return integer field value. */ public int getInt(IntField intField, int offset) { int value = 0; for(int index = intField.start() + offset; index <= intField.end() + offset; index++) { value = Integer.rotateLeft(value, 1); if(get(index)) { value++; } } return value; } /** * Returns the integer value of the message field described by the field argument where the message start index is * offset within this binary message. * @param fragmentedField with an array of field indices * @param offset to the start of the first message index. * @return integer field value. */ public int getInt(FragmentedIntField fragmentedField, int offset) { int value = 0; for(int index: fragmentedField.indices()) { value = Integer.rotateLeft(value, 1); if(get(index + offset)) { value++; } } return value; } /** * Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices. * @param intField to inspect * @param offset to the first bit of the message. * @return true if any of the bits are set, indicating a non-zero value. */ public boolean hasInt(IntField intField, int offset) { return nextSetBit(intField.start() + offset) <= (intField.end() + offset); } /** * Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices. * @param intField to inspect * @param offset to the first bit of the message. * @return true if any of the bits are set, indicating a non-zero value. */ public boolean hasInt(FragmentedIntField fragmentedField, int offset) { for(int index: fragmentedField.indices()) { if(get(index + offset)) { return true; } } return false; } /** * Returns the long value of the message field described by the field argument. * @param intField with start and end indices (inclusive). * @return integer field value. */ public long getLong(LongField intField) { long value = 0; for(int index = intField.start(); index <= intField.end(); index++) { value = Long.rotateLeft(value, 1); if(get(index)) { value++; } } return value; } /** * Returns the long value of the message field described by the field argument where the message start index is * offset within this binary message. * @param intField with start and end indices (inclusive). * @param offset to the start of the first message index. * @return field value. */ public long getLong(LongField intField, int offset) { long value = 0; for(int index = intField.start() + offset; index <= intField.end() + offset; index++) { value = Long.rotateLeft(value, 1); if(get(index)) { value++; } } return value; } /** * 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 * @param offset to apply to each of the bit position in the bits array. * @return - integer value of the bit array */ public int getInt(int[] bits, int offset) { 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 + offset)) { 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]); } } } /** * Sets the integer value to the field described by the argument. * @param value to set * @param intField describing the indices of the field. */ public void setInt(int value, IntField intField) { for(int x = 0; x < intField.width(); x++) { int mask = 1 << (intField.width() - x - 1); if((value & mask) == mask) { set(intField.start() + x); } else { clear(intField.start() + 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 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 * @param offset to apply to each of the bit positions * @return - byte value of the bit array */ public byte getByte(int[] bits, int offset) { 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 + offset)) { value++; } } return (byte)(value & 0xFF); } /** * Returns the byte value contained between index and index + 7 bit positions. If the length of this message is * shorter than index + 7, then the least significant bits are set to zero in the returned value. * * @param startIndex specifying the start of the byte value * @return byte value contained at index <> index + 7 bit positions */ public byte getByte(int startIndex) { int value = 0; for(int x = 0; x < 8; x++) { value <<= 1; int index = startIndex + x; if(index <= size() && get(index)) { value++; } } return (byte)value; } /** * Converts the message to a byte array. * * Note: BitSet.toByteArray() outputs the bytes in big-endian (inverted) order. This method retains the correct * endianness where bit 0 is the most significant bit of the first byte. */ public byte[] getBytes() { byte[] bytes = new byte[(int)FastMath.ceil((double)size() / 8.0)]; for(int x = 0; x < bytes.length; x++) { bytes[x] = getByte(x * 8); } return bytes; } /** * Returns the 4-bit nibble value contained between index and index + 3 bit positions. If the length of this * message is shorter than index + 3, then the least significant bits are set to zero in the returned value. * * @param startIndex specifying the start of the byte value * @return nibble value contained at index <> index + 3 bit positions */ public int getNibble(int startIndex) { int value = 0; for(int x = 0; x < 4; x++) { value <<= 1; int index = startIndex + x; if(index <= size() && get(index)) { value++; } } return 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; } /** * 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 * @param offset to apply to each of the bits indices * @return - integer value of the bit array */ public long getLong(int[] bits, int offset) { 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 + offset)) { value++; } } return value; } /** * Returns the bit values between start and end (inclusive) bit indices. If the overall length of the bit sequence * is not a multiple of 8 bits, the value is zero padded with least significant bits to make it a multiple of 8. * @param start index * @param end index * @return hexadecimal representation of the bit sequence */ public String getHex(int start, int end) { StringBuilder sb = new StringBuilder(); for(int x = start; x <= end; x += 4) { sb.append(getNibbleAsHex(x)); } return sb.toString(); } /** * Returns the integer field formatted as a hex value using zero prefixes to pad the hex character count to fully * represent the size (width) of the field. * @param field to parse as hex * @return hex value. */ public String getHex(IntField field) { int width = Math.ceilDiv(field.width(), 4); return String.format("%0" + width + "X", getInt(field)); } /** * Returns the long field formatted as a hex value using zero prefixes to pad the hex character count to fully * represent the size (width) of the field. * @param field to parse as hex * @return hex value. */ public String getHex(LongField field) { int width = Math.ceilDiv(field.width(), 4); return String.format("%0" + width + "X", getLong(field)); } /** * Format the byte value that starts at the specified index as hexadecimal. If the length of the message is less * than the start index plus 7 bits, then the value represents those bits as high-order bits with zero padding in * the least significant bits to make up the 8 bit value. * @param startIndex of the byte. * @return hexadecimal representation of the byte value */ public String getNibbleAsHex(int startIndex) { int value = getNibble(startIndex); return Integer.toHexString(value).toUpperCase(); } /** * 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"); } } /** * 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(FastMath.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 twos-complement value of the bits between start and end, inclusive * @param start bit position * @param end bit position * @return twos complement value */ public int getTwosComplement(int start, int end) { if(get(start)) { //Negative value - flip and add one BinaryMessage fragment = getSubMessage(start, end + 1); fragment.flip(0, fragment.size()); return -(fragment.getInt(0, fragment.size() - 1) + 1); } else { //Positive value - return the contents. return getInt(start, end); } } /** * 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(FastMath.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); } } } /** * Loads the binary message into this message starting at the offset. Note: this method * does not do any length checking for the message being loaded and if that message is longer * than this message, it would be loaded outside of the size (length) boundary of this message, but will not * produce an error. * * @param offset into this message to load the binary message * @param binaryMessage to load */ public void load(int offset, BinaryMessage binaryMessage) { for(int x = 0; x < binaryMessage.size(); x++) { if(binaryMessage.get(x)) { set(x + offset); } else { clear(x + offset); } } } /** * 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 binary message loaded with the byte array * @param bytes to load * @return loaded binary message */ public static BinaryMessage from(byte[] bytes) { BinaryMessage message = new BinaryMessage(bytes.length * 8); for(int x = 0; x < bytes.length; x++) { message.setByte(x * 8, bytes[x]); } return message; } /** * 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; } /** * Loads the hexadecimal text into a binary message * @param hex text to load from * @return loaded binary message */ public static BinaryMessage loadHex(String hex) { if(!hex.matches("[0-9A-F]*")) { throw new IllegalArgumentException("Message must contain only 0-9 and A-F hexadecimal characters"); } BinaryMessage message = new BinaryMessage(hex.length() * 4); for(int x = 0; x < hex.length(); x++) { String rawHex = hex.substring(x, x + 1); message.setInt(Integer.parseInt(rawHex, 16), IntField.length4(x * 4)); } return message; } /** * 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); } /** * Parse ISO-7 bit characters from the message starting at the offset. * @param offset where to start parsing. * @param characterCount number of characters to parse * @return parsed message. */ public String parseISO7(int offset, int characterCount) { byte[] bytes = new byte[characterCount]; int index; for(int x = 0; x < characterCount; x++) { index = x * 7 + offset; if(index == 0) { bytes[x] = getByte(CHARACTER_8_BIT, index); bytes[x] = (byte)((bytes[x] >> 1) & 0x7F); } else { index--; bytes[x] = getByte(CHARACTER_8_BIT, index); bytes[x] &= (byte)0x7F; //mask off the high bit since we have to parse it as an 8-bit byte. } } return new String(bytes); } /** * Parse ISO-8 bit characters from the message starting at the offset. * @param offset where to start parsing. * @param characterCount number of characters to parse * @return parsed message. */ public String parseISO8(int offset, int characterCount) { byte[] bytes = new byte[characterCount]; for(int x = 0; x < characterCount; x++) { bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset); } return new String(bytes); } /** * Parse UTF-8 characters from the message starting at the offset. * @param offset where to start parsing. * @param characterCount number of characters to parse * @return parsed message. * @throws UnsupportedEncodingException if UTF-8 encoding is not supported. */ public String parseUTF8(int offset, int characterCount) throws UnsupportedEncodingException { byte[] bytes = new byte[characterCount]; for(int x = 0; x < characterCount; x++) { bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset); } return new String(bytes, UTF_8); } /** * Parse GB2312 Simplified Chinese Characters from the message starting at the offset. * @param offset where to start parsing. * @param characterCount number of characters to parse * @return parsed message. * @throws UnsupportedEncodingException if GB2312 encoding is not supported. */ public String parseGB2312(int offset, int characterCount) throws UnsupportedEncodingException { int length = characterCount * 2; byte[] bytes = new byte[length]; for(int x = 0; x < length; x++) { bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset); } return new String(bytes, GB2312); } /** * Parse 16-bit unicode characters from the message starting at the offset. * @param offset where to start parsing. * @param characterCount number of characters to parse * @return parsed message. */ public String parseUnicode(int offset, int characterCount) { int length = characterCount * 2; byte[] bytes = new byte[length]; for(int x = 0; x < length; x++) { bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset); } return new String(bytes, Charset.forName("UTF-16")); } /** * Parse 4-bit Binary Coded Decimal (BCD) characters from the message starting at the offset. * @param offset where to start parsing. * @param characterCount number of characters to parse * @return parsed message. */ public String parseBCD4(int offset, int characterCount) { StringBuilder sb = new StringBuilder(); int count = 0; while(count < characterCount) { int nibble = getNibble(offset); //This will parse digits 0-9 and anything higher will parse as hexadecimal A-F sb.append(Integer.toHexString(nibble).toUpperCase()); offset += 4; count++; } return sb.toString(); } }