All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jsimpledb.kv.KVPairIterator Maven / Gradle / Ivy

The newest version!

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package org.jsimpledb.kv;

import com.google.common.base.Preconditions;

import java.util.Arrays;
import java.util.NoSuchElementException;

import org.jsimpledb.util.ByteUtil;
import org.jsimpledb.util.CloseableIterator;

/**
 * An {@link java.util.Iterator} that iterates over all key/value pairs in a {@link KVStore} within a range of keys,
 * without using the {@link KVStore#getRange KVStore.getRange()} method. Therefore, it can be used to implement
 * {@link KVStore#getRange KVStore.getRange()} in {@link KVStore} implementations that don't natively support iteration.
 * Instances support forward or reverse iteration and {@link #remove java.util.Iterator.remove()}.
 *
 * 

* The iteration is instead implemented using {@link KVStore#getAtLeast KVStore.getAtLeast()}, * {@link KVStore#getAtMost KVStore.getAtMost()}, and {@link KVStore#remove KVStore.remove()}. * *

Repositioning

* *

* Instances support arbitrary repositioning via {@link #setNextTarget setNextTarget()}. * *

Key Restrictions

* *

* Instances are configured with an (optional) {@link KeyRange} that restricts the iteration to the specified key range. * *

* Instances also support filtering visible values using a {@link KeyFilter}. * To appear in the iteration, keys must both be in the {@link KeyRange} and pass the {@link KeyFilter}, if any. * *

Concurrent Modification

* *

* Instances are thread safe, and always reflect the current state of the underlying {@link KVStore}, * even if it is mutated concurrently. */ public class KVPairIterator implements CloseableIterator { private final KVStore kv; private final boolean reverse; private final KeyRange keyRange; private final KeyFilter keyFilter; private KVPair currPair; // cached value to return from next() private byte[] nextKey; // next key lower/upper bound to go fetch, or null to start at the beginning private byte[] removeKey; // next key to remove if remove() invoked private boolean finished; // Constructors /** * Convenience constructor for forward iteration over a specified range. Equivalent to: *

* KVPairIterator(kv, keyRange, null, false) *
* * @param kv underlying {@link KVStore} * @param keyRange range restriction on visible keys, or null for none * @throws IllegalArgumentException if {@code kv} is null */ public KVPairIterator(KVStore kv, KeyRange keyRange) { this(kv, keyRange, null, false); } /** * Convenience constructor for forward iteration over all keys having a given prefix. Equivalent to: *
* KVPairIterator(kv, prefix, false) *
* * @param kv underlying {@link KVStore} * @param prefix range prefix * @throws IllegalArgumentException if any parameter is null */ public KVPairIterator(KVStore kv, byte[] prefix) { this(kv, prefix, false); } /** * Convenience constructor for iteration over all keys having a given prefix. Equivalent to: *
* KVPairIterator(kv, KeyRange.forPrefix(prefix), null, reverse) *
* * @param kv underlying {@link KVStore} * @param prefix range prefix * @param reverse true to iterate in a reverse direction, false to iterate in a forward direction * @throws IllegalArgumentException if any parameter is null */ public KVPairIterator(KVStore kv, byte[] prefix, boolean reverse) { this(kv, KeyRange.forPrefix(prefix), null, reverse); } /** * Primary constructor. * * @param kv underlying {@link KVStore} * @param keyRange range restriction on visible keys, or null for none * @param keyFilter filter restriction on visible keys, or null for none * @param reverse true to iterate in a reverse direction, false to iterate in a forward direction * @throws IllegalArgumentException if {@code kv} is null */ public KVPairIterator(KVStore kv, KeyRange keyRange, KeyFilter keyFilter, boolean reverse) { Preconditions.checkArgument(kv != null, "null kv"); this.kv = kv; this.keyRange = keyRange; this.keyFilter = keyFilter; this.reverse = reverse; this.setNextTarget(null); } // Methods /** * Get the {@link KVStore} associated with this instance. * * @return this instance's transaction */ public KVStore getKVStore() { return this.kv; } /** * Get the {@link KeyRange} instance used to restrict the range of visible keys, if any. * * @return {@link KeyRange} over which this iterator iterates, or null if it iterates over all keys */ public KeyRange getKeyRange() { return this.keyRange; } /** * Get the {@link KeyFilter} instance used to filter visible keys, if any. * * @return {@link KeyFilter} in which all keys returned by this iterator must be contained, or null if keys are not filtered */ public KeyFilter getKeyFilter() { return this.keyFilter; } /** * Determine if this instance is going forward or backward. * * @return true if this instance is reversed */ public boolean isReverse() { return this.reverse; } /** * Determine if the given key would be visible in this instance. Tests the key against * the configured {@link KeyRange} and/or {@link KeyFilter}, if any. * * @param key to test * @return true if key is both in range and not filtered out * @throws IllegalArgumentException if {@code key} is null */ public boolean isVisible(byte[] key) { Preconditions.checkArgument(kv != null, "null kv"); return (this.keyRange == null || this.keyRange.contains(key)) && (this.keyFilter == null || this.keyFilter.contains(key)); } /** * Reposition this instance by setting the next "target" key. * *

* The target key is the key we will use to find the next element via {@link KVStore#getAtLeast KVStore.getAtLeast()} * or {@link KVStore#getAtMost KVStore.getAtMost()} if this is a reverse iterator. In the forward case, the target key is an * inclusive lower bound on the next key, while in the reverse case it is an exclusive upper bound on the next key. * *

* This method may be used to reposition an interator during iteration or restart an iterator that has been exhausted. * Invoking this method does not affect the behavior of {@link #remove}, i.e., you can still {@link #remove} the previously * returned element even if you have invoked this method since invoking {@link #next}. * *

* A null {@code targetKey} means to reposition this instance at the beginning of the iteration. * *

* This instance's configured {@link KeyRange} and {@link KeyFilter}, if any, still apply: if {@code targetKey} is not * {@link #isVisible visible} to this instance, the next visible key after {@code targetKey} will be next in the iteration. * * @param targetKey next lower bound (exclusive) if going forward, or upper bound (exclusive) if going backward; * or null to restart this instance at the beginning of its iteration */ public void setNextTarget(byte[] targetKey) { // Clone target key to avoid mutation if (targetKey != null) targetKey = targetKey.clone(); // Ensure target is not prior to the beginning of the range if (this.keyRange != null) { if (this.reverse) { final byte[] maxKey = this.keyRange.getMax(); if (maxKey != null && (targetKey == null || ByteUtil.compare(targetKey, maxKey) > 0)) targetKey = maxKey; } else { final byte[] minKey = this.keyRange.getMin(); if (targetKey == null || ByteUtil.compare(targetKey, minKey) < 0) targetKey = minKey; } } // Update state synchronized (this) { this.nextKey = targetKey; this.finished = false; this.currPair = null; } } // Iterator @Override public synchronized boolean hasNext() { // Already have next element? if (this.currPair != null) return true; // No more elements? if (this.finished) return false; // Find next element that is not filtered out by KeyRange KVPair pair; while (true) { // Find next key/value pair if ((pair = this.reverse ? this.kv.getAtMost(this.nextKey, this.keyRange != null ? this.keyRange.getMin() : null) : this.kv.getAtLeast(this.nextKey, this.keyRange != null ? this.keyRange.getMax() : null)) == null) { this.finished = true; return false; } final byte[] key = pair.getKey(); // Check key range if (this.keyRange != null && !this.keyRange.contains(key)) { this.finished = true; return false; } // Check key filter; if going forward, avoid redundant call to seekHigher() if (this.keyFilter == null) break; if (!this.reverse) { final byte[] nextHigher = this.keyFilter.seekHigher(key); if (nextHigher != null && Arrays.equals(nextHigher, key)) break; this.nextKey = nextHigher; } else { if (this.keyFilter.contains(key)) break; this.nextKey = key.length > 0 ? this.keyFilter.seekLower(key) : null; } // We have skipped over the filtered-out key range, so try again if there is any left if (this.nextKey == null) { this.finished = true; return false; } } // Save it (pre-fetch) this.currPair = pair; return true; } @Override public synchronized KVPair next() { // Check there is a next element if (this.currPair == null && !this.hasNext()) throw new NoSuchElementException(); // Get next element final KVPair pair = this.currPair; final byte[] key = pair.getKey().clone(); this.removeKey = key; // Set up next advance this.nextKey = this.reverse ? key : ByteUtil.getNextKey(key); this.currPair = null; // Done return pair; } @Override public void remove() { final byte[] removeKeyCopy; synchronized (this) { if ((removeKeyCopy = this.removeKey) == null) throw new IllegalStateException(); this.removeKey = null; } this.kv.remove(removeKeyCopy); } // Closeable @Override public void close() { } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy