package mil.nga.giat.geowave.core.store.flatten;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import mil.nga.giat.geowave.core.index.ByteArrayId;
import mil.nga.giat.geowave.core.store.adapter.DataAdapter;
import mil.nga.giat.geowave.core.store.dimension.NumericDimensionField;
import mil.nga.giat.geowave.core.store.index.CommonIndexModel;
import mil.nga.giat.geowave.core.store.index.CommonIndexValue;
/**
* Utility methods when dealing with bitmasks in Accumulo
*
* @since 0.9.1
*/
public class BitmaskUtils
{
public static byte[] generateANDBitmask(
final byte[] bitmask1,
final byte[] bitmask2 ) {
final byte[] result = new byte[Math.min(
bitmask1.length,
bitmask2.length)];
for (int i = 0; i < result.length; i++) {
result[i] = bitmask1[i];
result[i] &= bitmask2[i];
}
return result;
}
public static boolean isAnyBitSet(
final byte[] array ) {
for (final byte b : array) {
if (b != 0) {
return true;
}
}
return false;
}
/**
* Generates a composite bitmask given a list of field positions. The
* composite bitmask represents a true bit for every positive field position
*
* For example, given field 0, field 1, and field 2 this method will return
* 00000111
*
* @param fieldPositions
* a list of field positions
* @return a composite bitmask
*/
public static byte[] generateCompositeBitmask(
final SortedSet<Integer> fieldPositions ) {
final byte[] retVal = new byte[(fieldPositions.last() / 8) + 1];
for (final Integer fieldPosition : fieldPositions) {
final int bytePosition = fieldPosition / 8;
final int bitPosition = fieldPosition % 8;
retVal[bytePosition] |= (1 << bitPosition);
}
return retVal;
}
/**
* Generates a composite bitmask given a single field position. The
* composite bitmask represents a true bit for this field position
*
* For example, given field 2 this method will return 00000100
*
* @param fieldPosition
* a field position
* @return a composite bitmask
*/
public static byte[] generateCompositeBitmask(
final Integer fieldPosition ) {
return generateCompositeBitmask(new TreeSet<Integer>(
Collections.singleton(fieldPosition)));
}
/**
* Iterates the set (true) bits within the given composite bitmask and
* generates a list of field positions.
*
* @param compositeBitmask
* the composite bitmask
* @return a list of field positions
*/
public static List<Integer> getFieldPositions(
final byte[] bitmask ) {
final List<Integer> fieldPositions = new ArrayList<>();
int currentByte = 0;
for (final byte singleByteBitMask : bitmask) {
for (int bit = 0; bit < 8; ++bit) {
if (((singleByteBitMask >>> bit) & 0x1) == 1) {
fieldPositions.add((currentByte * 8) + bit);
}
}
currentByte++;
}
return fieldPositions;
}
/**
* Generates a field subset bitmask for the given index, adapter, and fields
*
* @param indexModel
* the index's CommonIndexModel
* @param fieldIds
* the fields to include in the subset, as Strings
* @param adapterAssociatedWithFieldIds
* the adapter for the type whose fields are being subsetted
* @return the field subset bitmask
*/
public static byte[] generateFieldSubsetBitmask(
final CommonIndexModel indexModel,
final List<String> fieldIds,
final DataAdapter<?> adapterAssociatedWithFieldIds ) {
final SortedSet<Integer> fieldPositions = new TreeSet<Integer>();
// dimension fields must also be included
for (final NumericDimensionField<? extends CommonIndexValue> dimension : indexModel.getDimensions()) {
fieldPositions.add(adapterAssociatedWithFieldIds.getPositionOfOrderedField(
indexModel,
dimension.getFieldId()));
}
for (final String fieldId : fieldIds) {
fieldPositions.add(adapterAssociatedWithFieldIds.getPositionOfOrderedField(
indexModel,
new ByteArrayId(
fieldId)));
}
return generateCompositeBitmask(fieldPositions);
}
/**
* Generates a new value byte array representing a subset of fields of the
* given value
*
* @param value
* the original column value
* @param originalBitmask
* the bitmask from the column qualifier
* @param newBitmask
* the field subset bitmask
* @return the subsetted value as a byte[]
*/
public static byte[] constructNewValue(
final byte[] value,
final byte[] originalBitmask,
final byte[] newBitmask ) {
final ByteBuffer originalBytes = ByteBuffer.wrap(value);
final List<byte[]> valsToKeep = new ArrayList<>();
int totalSize = 0;
final List<Integer> originalPositions = getFieldPositions(originalBitmask);
// convert list to set for quick contains()
final Set<Integer> newPositions = new HashSet<Integer>(
getFieldPositions(newBitmask));
if (originalPositions.size() > 1) {
for (final Integer originalPosition : originalPositions) {
final int len = originalBytes.getInt();
final byte[] val = new byte[len];
originalBytes.get(val);
if (newPositions.contains(originalPosition)) {
valsToKeep.add(val);
totalSize += len;
}
}
}
else if (!newPositions.isEmpty()) {
// this shouldn't happen because we should already catch the case
// where the bitmask is unchanged
return value;
}
else {
// and this shouldn't happen because we should already catch the
// case where the resultant bitmask is empty
return null;
}
if (valsToKeep.size() == 1) {
final ByteBuffer retVal = ByteBuffer.allocate(totalSize);
retVal.put(valsToKeep.get(0));
return retVal.array();
}
final ByteBuffer retVal = ByteBuffer.allocate((valsToKeep.size() * 4) + totalSize);
for (final byte[] val : valsToKeep) {
retVal.putInt(val.length);
retVal.put(val);
}
return retVal.array();
}
}