package mil.nga.giat.geowave.datastore.accumulo.query;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.accumulo.core.data.ByteSequence;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Range;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.iterators.IteratorEnvironment;
import org.apache.accumulo.core.iterators.SkippingIterator;
import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
import org.apache.accumulo.core.iterators.system.InterruptibleIterator;
import org.apache.hadoop.io.Text;
import mil.nga.giat.geowave.core.index.ByteArrayUtils;
/**
* This class is an Accumulo Iterator that can support skipping by a fixed
* cardinality on a Space Filling Curve (skipping by incrementing a fixed bit
* position of the row ID).
*
*/
public class FixedCardinalitySkippingIterator extends
SkippingIterator implements
InterruptibleIterator
{
protected static final String CARDINALITY_SKIPPING_ITERATOR_NAME = "CARDINALITY_SKIPPING_ITERATOR";
protected static final int CARDINALITY_SKIPPING_ITERATOR_PRIORITY = 35;
protected static final String CARDINALITY_SKIP_INTERVAL = "cardinality";
protected Text nextRow;
protected Integer bitPosition;
protected Collection<ByteSequence> columnFamilies;
private boolean reachedEnd = false;
protected boolean inclusive = false;
protected Range range;
public FixedCardinalitySkippingIterator() {
super();
}
public FixedCardinalitySkippingIterator(
final SortedKeyValueIterator<Key, Value> source ) {
setSource(source);
}
protected FixedCardinalitySkippingIterator(
final SortedKeyValueIterator<Key, Value> source,
final Integer bitPosition,
final Collection<ByteSequence> columnFamilies,
final boolean inclusive ) {
this(
source);
this.columnFamilies = columnFamilies;
this.bitPosition = bitPosition;
this.inclusive = inclusive;
}
@Override
public void init(
final SortedKeyValueIterator<Key, Value> source,
final Map<String, String> options,
final IteratorEnvironment env )
throws IOException {
final String bitPositionStr = options.get(CARDINALITY_SKIP_INTERVAL);
if (bitPositionStr == null) {
throw new IllegalArgumentException(
"'precision' must be set for " + FixedCardinalitySkippingIterator.class.getName());
}
try {
bitPosition = Integer.parseInt(bitPositionStr);
}
catch (final Exception e) {
throw new IllegalArgumentException(
"Unable to parse value",
e);
}
super.init(
source,
options,
env);
}
@Override
public void next()
throws IOException {
final byte[] nextRowBytes = incrementBit(getTopKey().getRow().getBytes());
if (nextRowBytes == null) {
reachedEnd = true;
}
else {
nextRow = new Text(
nextRowBytes);
}
super.next();
}
@Override
public Key getTopKey() {
if (reachedEnd) {
return null;
}
return super.getTopKey();
}
@Override
public Value getTopValue() {
if (reachedEnd) {
return null;
}
return super.getTopValue();
}
@Override
public boolean hasTop() {
if (reachedEnd) {
return false;
}
return super.hasTop();
}
private byte[] incrementBit(
final byte[] row ) {
final int cardinality = bitPosition + 1;
final byte[] rowCopy = new byte[(int) Math.ceil(cardinality / 8.0)];
System.arraycopy(
row,
0,
rowCopy,
0,
rowCopy.length);
// number of bits not used in the last byte
int remainder = (8 - (cardinality % 8));
if (remainder == 8) {
remainder = 0;
}
final int numIncrements = (int) Math.pow(
2,
remainder);
if (remainder > 0) {
for (int i = 0; i < remainder; i++) {
rowCopy[rowCopy.length - 1] |= (1 << (i));
}
}
for (int i = 0; i < numIncrements; i++) {
if (!ByteArrayUtils.increment(rowCopy)) {
return null;
}
}
return rowCopy;
}
@Override
protected void consume()
throws IOException {
while (getSource().hasTop() && ((nextRow != null) && (getSource().getTopKey().getRow().compareTo(
nextRow) < 0))) {
// seek to the next column family in the sorted list of
// column families
reseek(new Key(
nextRow));
}
}
private void reseek(
final Key key )
throws IOException {
if (range.afterEndKey(key)) {
range = new Range(
range.getEndKey(),
true,
range.getEndKey(),
range.isEndKeyInclusive());
getSource().seek(
range,
columnFamilies,
inclusive);
}
else {
range = new Range(
key,
true,
range.getEndKey(),
range.isEndKeyInclusive());
getSource().seek(
range,
columnFamilies,
inclusive);
}
}
@Override
public SortedKeyValueIterator<Key, Value> deepCopy(
final IteratorEnvironment env ) {
return new FixedCardinalitySkippingIterator(
getSource().deepCopy(
env),
bitPosition,
columnFamilies,
inclusive);
}
@Override
public void seek(
final Range range,
final Collection<ByteSequence> columnFamilies,
final boolean inclusive )
throws IOException {
this.range = range;
this.columnFamilies = columnFamilies;
this.inclusive = inclusive;
reachedEnd = false;
super.seek(
range,
columnFamilies,
inclusive);
}
@Override
public void setInterruptFlag(
final AtomicBoolean flag ) {
((InterruptibleIterator) getSource()).setInterruptFlag(flag);
}
}