/* Copyright (c) 2011 Danish Maritime Authority.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dk.dma.ais.binary;
import java.util.Arrays;
/**
* Class to represent a binary array with utility methods to add and extract values
*/
public class BinArray {
/** Precompiled list of int to six bit mappings. */
private static final int[] INT_TO_SIX_BIT;
private boolean[] bitSet = new boolean[1024];
private int length;
private int readPtr;
static {
int[] toSixbit = new int[256 * 256]; // we actually only use 256, but we parse chars around instead of bytes
for (int chr = 0; chr < toSixbit.length; chr++) {
if (chr < 48 || chr > 119 || chr > 87 && chr < 96) {
toSixbit[chr] = -1;
} else if (chr < 0x60) {
toSixbit[chr] = chr - 48 & 0x3F;
} else {
toSixbit[chr] = chr - 56 & 0x3F;
}
}
INT_TO_SIX_BIT = toSixbit;
}
/**
* Ensures that the BinArray can hold enough bits.
*
* @param bitsRequired
* the minimum acceptable number of bits.
*/
private void ensureCapacity(int bitsRequired) {
if (bitSet.length < bitsRequired) {
// Allocate larger of doubled size or required size
int request = Math.max(2 * bitSet.length, bitsRequired);
bitSet = Arrays.copyOf(bitSet, request);
}
}
/**
* Append bits from a sixbit encoded string
*
* @param str
* @param padBits
* @throws SixbitException
*/
public void appendSixbit(String str, int padBits) throws SixbitException {
if (str.length() == 0) {
return;
}
int len = str.length() * 6 - padBits;
int length = this.length;
ensureCapacity(length + len);
boolean[] bitSet = this.bitSet; // store it in a local variable
int slen = str.length() - 1;
for (int i = 0; i < slen; i++) {
char chr = str.charAt(i);
int binVal = INT_TO_SIX_BIT[chr];
if (binVal == -1) {
throw new SixbitException("Illegal sixbit ascii char: " + chr);
}
bitSet[length] = (binVal & 32) > 0;
bitSet[length + 1] = (binVal & 16) > 0;
bitSet[length + 2] = (binVal & 8) > 0;
bitSet[length + 3] = (binVal & 4) > 0;
bitSet[length + 4] = (binVal & 2) > 0;
bitSet[length + 5] = (binVal & 1) > 0;
length += 6;
}
// Process the last char which might be padded
char chr = str.charAt(slen);
int binVal = INT_TO_SIX_BIT[chr];
if (binVal == -1) {
throw new SixbitException("Illegal sixbit ascii char: " + chr);
}
int bits = 6 - padBits;
switch (bits) {
case 6:
bitSet[length + 5] = (binVal & 1) > 0;
case 5:
bitSet[length + 4] = (binVal & 2) > 0;
case 4:
bitSet[length + 3] = (binVal & 4) > 0;
case 3:
bitSet[length + 2] = (binVal & 8) > 0;
case 2:
bitSet[length + 1] = (binVal & 16) > 0;
case 1:
bitSet[length] = (binVal & 32) > 0;
}
this.length = length + bits;
}
/**
* Append another binary array
*
* @param binArray
*/
public void append(BinArray binArray) {
int len = binArray.length;
ensureCapacity(length + len);
System.arraycopy(binArray.bitSet, 0, bitSet, length, len);
length += len;
}
/**
* Append value with number of bits bits
*
* @param val
* @param bits
*/
public void append(long val, int bits) {
long powMask = 1;
ensureCapacity(length + bits);
for (int i = length + bits - 1; i >= length; i--) {
bitSet[i] = (val & powMask) > 0;
powMask <<= 1;
}
length += bits;
}
/**
* Get six bit string representation of the next len six bit characters and move read ptr
*
* @param len
* @return string
* @throws SixbitException
*/
public String getString(int len) throws SixbitException {
char[] resStr = new char[len];
for (int i = 0; i < len; i++) {
resStr[i] = (char) intToascii((char) getVal(6));
}
return new String(resStr);
}
/**
* Get value from the next bits number of bits and move read pointer
*
* @param bits
* @return
* @throws SixbitException
*/
public long getVal(int bits) throws SixbitException {
int to = readPtr + bits - 1;
long res = getVal(readPtr, to);
readPtr = to + 1;
return res;
}
/**
* Get value from bit position from and to
*
* @param from
* @param to
* @return
* @throws SixbitException
*/
public long getVal(int from, int to) throws SixbitException {
if (to >= length) {
throw new SixbitException(length + " is not enough bits. At least " + to + " expected.");
}
long val = 0;
long powMask = 1;
for (int i = to; i >= from; i--) {
if (bitSet[i]) {
val += powMask;
}
powMask <<= 1;
}
return val;
}
/**
* Get bit length
*
* @return
*/
public int getLength() {
return length;
}
/**
* Get the position of the read ptr within the array
*
* @return
*/
public int getReadPtr() {
return readPtr;
}
public int size() {
return bitSet.length;
}
/**
* Returns true if there are more bits to read
*
* @return
*/
public boolean hasMoreBits() {
return readPtr < length - 1;
}
/**
* Convert six bit int value to character
*
* @param val
* @return
* @throws SixbitException
*/
public static int intToascii(int val) throws SixbitException {
if (val > 63) {
throw new SixbitException("Char value " + val + " not allowed");
}
if (val < 32) {
return val + 64;
} else {
return val;
}
}
/**
* Convert a int value to a sixbit ascii value
*
* @param val
* @return
* @throws SixbitException
*/
public static int intToSixbit(int val) throws SixbitException {
if (val > 63) {
throw new SixbitException("Char value " + val + " not allowed");
}
if (val < 40) {
return val + 48;
} else {
return val + 56;
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("BinArray [TODO");
builder.append("]");
return builder.toString();
}
public void doneReading() {
readPtr = 0;
}
}