org.jsimpledb.kv.util.AbstractKVIterator Maven / Gradle / Ivy
Show all versions of jsimpledb-kv Show documentation
/*
* Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
*/
package org.jsimpledb.kv.util;
import com.google.common.base.Preconditions;
import org.jsimpledb.kv.KVPair;
import org.jsimpledb.kv.KVPairIterator;
import org.jsimpledb.kv.KVStore;
import org.jsimpledb.kv.KeyFilter;
import org.jsimpledb.kv.KeyRange;
import org.jsimpledb.util.ByteReader;
import org.jsimpledb.util.ByteUtil;
import org.jsimpledb.util.CloseableIterator;
import org.slf4j.LoggerFactory;
/**
* {@link java.util.Iterator} implementation whose values derive from key/value {@code byte[]} pairs in a {@link KVStore}.
* Instances support either forward or reverse iteration.
*
* Subclass Methods
*
*
* Subclasses must implement {@linkplain #decodePair decodePair()} to convert key/value pairs into iteration elements.
*
*
* This class provides a read-only implementation; for a mutable implementation, subclasses should also implement
* {@link #doRemove doRemove()}.
*
*
Prefix Mode
*
*
* Instances support "prefix mode" where the {@code byte[]} keys may have arbitrary trailing garbage, which is ignored,
* and so by definition no key can be a prefix of any other key. The length of the prefix is determined implicitly by the
* number of key bytes consumed by {@link #decodePair decodePair()}.
* When not in prefix mode, {@link #decodePair decodePair()} must consume the entire key to preserve correct semantics.
* If it fails to do so, an error is logged and, if assertions are enabled, an {@link AssertionError} is thrown.
*
*
Key Restrictions
*
*
* Instances are configured with an (optional) {@link KeyRange} that restricts the iteration to the specified range.
*
*
* Instances also support filtering visible values using a {@link KeyFilter}.
* To be visible in the iteration, keys must both be within the {@link KeyRange} and pass the {@link KeyFilter}.
*
*
Concurrent Modification
*
*
* Instances of this class are thread safe.
*
*
* Internally, when not in prefix mode and no {@link KeyFilter} is configured, this instance will rely on the
* iteration from {@link KVStore#getRange KVStore.getRange()}; otherwise, it will use a {@link KVPairIterator}.
* Therefore, in the former case, whether this iteration always reflects the current state of the underlying
* {@link KVStore} depends on the behavior of {@link KVStore#getRange KVStore.getRange()}.
*
* @see AbstractKVNavigableMap
* @see AbstractKVNavigableSet
* @param iteration element type
*/
public abstract class AbstractKVIterator implements CloseableIterator {
/**
* The underlying {@link KVStore}.
*/
protected final KVStore kv;
/**
* Whether we are in "prefix" mode.
*/
protected final boolean prefixMode;
/**
* Whether this instance is iterating in the reverse direction.
*/
protected final boolean reversed;
// Iteration state
private final CloseableIterator pairIterator;
private KVPair removePair;
private E removeValue;
// Constructors
/**
* Convenience constructor for when there are no range restrictions.
*
* @param kv underlying {@link KVStore}
* @param prefixMode whether to allow keys to have trailing garbage
* @param reversed whether to iterate in the reverse direction
*/
protected AbstractKVIterator(KVStore kv, boolean prefixMode, boolean reversed) {
this(kv, prefixMode, reversed, null, null);
}
/**
* Convenience constructor for when the range of visible {@link KVStore} keys is all keys sharing a given {@code byte[]} prefix.
*
* @param kv underlying {@link KVStore}
* @param prefixMode whether to allow keys to have trailing garbage
* @param reversed whether to iterate in the reverse direction
* @param prefix prefix defining minimum and maximum keys
* @throws IllegalArgumentException if {@code prefix} is null or empty
*/
protected AbstractKVIterator(KVStore kv, boolean prefixMode, boolean reversed, byte[] prefix) {
this(kv, prefixMode, reversed, KeyRange.forPrefix(prefix), null);
}
/**
* Primary constructor.
*
* @param kv underlying {@link KVStore}
* @param prefixMode whether to allow keys to have trailing garbage
* @param reversed whether to iterate in the reverse direction
* @param keyRange key range restriction, or null for none
* @param keyFilter key filter, or null for none
* @throws IllegalArgumentException if {@code kv} is null
*/
protected AbstractKVIterator(KVStore kv, boolean prefixMode, boolean reversed, KeyRange keyRange, KeyFilter keyFilter) {
Preconditions.checkArgument(kv != null, "null kv");
this.kv = kv;
this.prefixMode = prefixMode;
this.reversed = reversed;
// If possible use a straight KVStore iterator which is more efficient than a KVPairIterator
if (!this.prefixMode && keyFilter == null) {
final byte[] minKey = keyRange != null ? keyRange.getMin() : null;
final byte[] maxKey = keyRange != null ? keyRange.getMax() : null;
this.pairIterator = this.kv.getRange(minKey, maxKey, this.reversed);
} else
this.pairIterator = new KVPairIterator(this.kv, keyRange, keyFilter, this.reversed);
}
// Iterator
@Override
public boolean hasNext() {
return this.pairIterator.hasNext();
}
@Override
public synchronized E next() {
// Get next key/value pair
final KVPair pair = this.pairIterator.next();
// Decode key/value pair
final ByteReader keyReader = new ByteReader(pair.getKey());
final E value = this.decodePair(pair, keyReader);
if (!this.prefixMode && keyReader.remain() > 0) {
final String msg = this.getClass().getName() + "@"
+ Integer.toHexString(System.identityHashCode(this)) + ": " + keyReader.remain() + " undecoded bytes remain in key "
+ ByteUtil.toString(pair.getKey()) + ", value " + ByteUtil.toString(pair.getValue()) + " -> " + value;
LoggerFactory.getLogger(this.getClass()).error(msg);
assert false : msg;
}
// In prefix mode, skip over any additional keys having the same prefix as what we just decoded
if (this.prefixMode) {
final KVPairIterator kvPairIterator = (KVPairIterator)this.pairIterator;
final byte[] prefix = keyReader.getBytes(0, keyReader.getOffset());
kvPairIterator.setNextTarget(this.reversed ? prefix : ByteUtil.getKeyAfterPrefix(prefix));
}
// Done
this.removePair = pair;
this.removeValue = value;
return value;
}
@Override
public void remove() {
final E removeValueCopy;
final KVPair removePairCopy;
synchronized (this) {
if ((removePairCopy = this.removePair) == null)
throw new IllegalStateException();
this.removePair = null;
removeValueCopy = this.removeValue;
}
this.doRemove(removeValueCopy, removePairCopy);
}
// Closeable
@Override
public void close() {
this.pairIterator.close();
}
// Subclass methods
/**
* Decode an iteration element from a key/value pair.
*
*
* If not in prefix mode, all of {@code keyReader} must be consumed; otherwise, the consumed portion
* is the prefix and any following keys with the same prefix are ignored.
*
* @param pair key/value pair
* @param keyReader key input
* @return decoded iteration element
*/
protected abstract E decodePair(KVPair pair, ByteReader keyReader);
/**
* Remove the previously iterated value.
*
*
* The implementation in {@link AbstractKVIterator} always throws {@link UnsupportedOperationException}.
* Subclasses should override to make the iterator mutable.
*
* @param value most recent value returned by {@link #next}
* @param pair the key/value pair corresponding to {@code value}
*/
protected void doRemove(E value, KVPair pair) {
throw new UnsupportedOperationException();
}
}