/*
* Copyright Gradiant (http://www.gradiant.org) 2014
*
* APACHE LICENSE v2.0
*
* Author: Dr. Luis Rodero-Merino (lrodero@gradiant.org)
*/
package org.streaminer.util;
import java.util.Arrays;
/**
* Table to store data in a byte array. A ByteBuffer could be used if we were sure that data items size will be
* an integer number of bytes.
*
* @author Luis Rodero-Merino
*
*/
public class ByteArrayTable {
private int bitsPerBucket;
private int buckets;
protected byte[] table = null;
public int size() {
return buckets;
}
public ByteArrayTable(int buckets, int bitsPerBucket) {
if(buckets <= 0)
throw new IllegalArgumentException("Cannot create a table with a non-positive number of buckets");
if(bitsPerBucket <= 0)
throw new IllegalArgumentException("Cannot create a table with a non-positive number of bits per bucket");
this.bitsPerBucket = bitsPerBucket;
this.buckets = buckets;
int tableSize = (int) Math.ceil(bitsPerBucket * buckets / 8.0D);
table = new byte[tableSize];
}
public boolean isItemInPos(byte[] item, int itemPos) {
if(item.length != bytesPerBucket())
throw new IllegalArgumentException("A data item must be an array of size " + bytesPerBucket() + " in bytes, to store the " + bitsPerBucket + " bits per bucket");
if(itemPos >= buckets)
throw new IllegalArgumentException("Cannot get item from position " + itemPos + ", valid range is [0," + (buckets-1) + "]");
if(itemPos < 0)
throw new IllegalArgumentException("Cannot get item from a negative position " + itemPos + ", valid range is [0," + (buckets-1) + "]");
byte[] data = get(itemPos);
for(int i = 0; i < data.length; i++)
if(data[i] != item[i])
return false;
return true;
}
public byte[] get(int itemPos) {
if(itemPos >= buckets)
throw new IllegalArgumentException("Cannot get item from position " + itemPos + ", valid range is [0," + (buckets-1) + "]");
if(itemPos < 0)
throw new IllegalArgumentException("Cannot get item from a negative position " + itemPos + ", valid range is [0," + (buckets-1) + "]");
// Locating affected bytes in table
int firstByteInd = itemPos * bitsPerBucket / 8;
int lastByteInd = ((itemPos + 1) * bitsPerBucket - 1) / 8;
byte[] item = new byte[lastByteInd-firstByteInd+1];
System.arraycopy(table, firstByteInd, item, 0, item.length);
// Shifting to the left to align the item array with the bytes in the table, new positions are also filled with 0's.
int firstBitInFirstByteInd = itemPos * bitsPerBucket % 8;
item = ByteUtil.shitfRightAndFill(item, firstBitInFirstByteInd);
// Removing leading byte if needed
if(item.length == (bytesPerBucket()+1))
item = Arrays.copyOfRange(item, 0, item.length-1);
// Just a small check...
if(item.length != bytesPerBucket())
throw new InternalError("Created an item with a number of bytes " + item.length + " that differes from the size of buckets " + bytesPerBucket());
// Removing leading bits that come from following item in table
if(bitsPerBucket % 8 != 0) {
byte mask = (byte)((0x01 << (bitsPerBucket % 8))-1);
item[item.length-1] &= mask;
}
return item;
}
public void insert(byte[] item, int itemPos) {
if(item.length != bytesPerBucket())
throw new IllegalArgumentException("A data item must be an array of size " + bytesPerBucket() + " in bytes, to store the " + bitsPerBucket + " bits per bucket");
if(itemPos >= buckets)
throw new IllegalArgumentException("Cannot insert item at position " + itemPos + ", valid range is [0," + (buckets-1) + "]");
if(itemPos < 0)
throw new IllegalArgumentException("Cannot insert item at a negative position " + itemPos + ", valid range is [0," + (buckets-1) + "]");
// Locating affected bytes in table
int firstByteInd = itemPos * bitsPerBucket / 8;
int lastByteInd = ((itemPos + 1) * bitsPerBucket - 1) / 8;
// We'll create a copy of the item array that will be combined with AND operation with the corresponding bytes in the table
byte[] itemCp = new byte[lastByteInd-firstByteInd+1];
System.arraycopy(item, 0, itemCp, 0, item.length);
// Just one small check, the amount of affected bytes must be equal to the item size or be one byte greater (should never happen otherwise but anyway)
if(item.length != itemCp.length && (item.length != (itemCp.length - 1)))
throw new InternalError("Affected bytes are in positions [" + firstByteInd + "," + lastByteInd + "], " + (itemCp.length+1) + " bytes affected in total, but item is " + item.length + " bytes large");
// Not all bits in the data item must be added to the table, only those that account for a bucket. The rest (which are the most significant ones, i.e. the ones at the left size),
// will be all replaced by 0's (it will be handy later on).
int lastByteMaskSize = item.length * 8 - bitsPerBucket; // E.g. we need three 0's in the mask
byte lastByteMask = (byte)((0xff >> (lastByteMaskSize))); // Building the mask 00011111
itemCp[item.length-1] = (byte)(itemCp[item.length-1] & lastByteMask); // Applying the mask in the last byte _coming from the original data item_
if(itemCp.length > item.length) // If an extra byte had to be created, filling it with 0's as well
itemCp[itemCp.length-1] = (byte)0x00;
// Now shifting to the left to align the item array with the bytes in the table, new positions are also filled with 0's.
int firstBitInFirstByteInd = itemPos * bitsPerBucket % 8;
itemCp = ByteUtil.shiftLeftAndFill(itemCp, firstBitInFirstByteInd);
// Setting to 0's all bits in table that are going to be replaced (i.e. the bits of the corresponding bucket)
delete(itemPos);
// Finally, combining the affected bytes in the table with the item array
for(int i = 0; i < itemCp.length; i++)
itemCp[i] = (byte)(itemCp[i] | table[i+firstByteInd]);
System.arraycopy(itemCp, 0, table, firstByteInd, itemCp.length);
}
public void delete(int itemPos) {
if(itemPos >= buckets)
throw new IllegalArgumentException("Cannot delete item in position " + itemPos + ", valid range is [0," + (buckets-1) + "]");
if(itemPos < 0)
throw new IllegalArgumentException("Cannot delete item in a negative position " + itemPos + ", valid range is [0," + (buckets-1) + "]");
for(int i = itemPos*bitsPerBucket; i < (itemPos+1)*bitsPerBucket; i++)
ByteUtil.insertZeroIn(table, i);
}
private int bytesPerBucket() {
return (int) Math.ceil(bitsPerBucket / 8.0D);
}
public int getTableLength() {
return table.length;
}
public byte[] getTable() {
return table;
}
}