org.apache.cassandra.index.sasi.utils.trie.PatriciaTrie Maven / Gradle / Ivy
Show all versions of cassandra-all Show documentation
/*
* Copyright 2005-2010 Roger Kapsi, Sam Berlin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.index.sasi.utils.trie;
import java.io.Serializable;
import java.util.*;
/**
* This class is taken from https://github.com/rkapsi/patricia-trie (v0.6), and slightly modified
* to correspond to Cassandra code style, as the only Patricia Trie implementation,
* which supports pluggable key comparators (e.g. commons-collections PatriciaTrie (which is based
* on rkapsi/patricia-trie project) only supports String keys)
* but unfortunately is not deployed to the maven central as a downloadable artifact.
*/
/**
* PATRICIA {@link Trie}
*
* Practical Algorithm to Retrieve Information Coded in Alphanumeric
*
* A PATRICIA {@link Trie} is a compressed {@link Trie}. Instead of storing
* all data at the edges of the {@link Trie} (and having empty internal nodes),
* PATRICIA stores data in every node. This allows for very efficient traversal,
* insert, delete, predecessor, successor, prefix, range, and {@link #select(Object)}
* operations. All operations are performed at worst in O(K) time, where K
* is the number of bits in the largest item in the tree. In practice,
* operations actually take O(A(K)) time, where A(K) is the average number of
* bits of all items in the tree.
*
*
Most importantly, PATRICIA requires very few comparisons to keys while
* doing any operation. While performing a lookup, each comparison (at most
* K of them, described above) will perform a single bit comparison against
* the given key, instead of comparing the entire key to another key.
*
*
The {@link Trie} can return operations in lexicographical order using the
* {@link #traverse(Cursor)}, 'prefix', 'submap', or 'iterator' methods. The
* {@link Trie} can also scan for items that are 'bitwise' (using an XOR
* metric) by the 'select' method. Bitwise closeness is determined by the
* {@link KeyAnalyzer} returning true or false for a bit being set or not in
* a given key.
*
*
Any methods here that take an {@link Object} argument may throw a
* {@link ClassCastException} if the method is expecting an instance of K
* and it isn't K.
*
* @see Radix Tree
* @see PATRICIA
* @see Crit-Bit Tree
*
* @author Roger Kapsi
* @author Sam Berlin
*/
public class PatriciaTrie extends AbstractPatriciaTrie implements Serializable
{
private static final long serialVersionUID = -2246014692353432660L;
public PatriciaTrie(KeyAnalyzer super K> keyAnalyzer)
{
super(keyAnalyzer);
}
public PatriciaTrie(KeyAnalyzer super K> keyAnalyzer, Map extends K, ? extends V> m)
{
super(keyAnalyzer, m);
}
@Override
public Comparator super K> comparator()
{
return keyAnalyzer;
}
@Override
public SortedMap prefixMap(K prefix)
{
return lengthInBits(prefix) == 0 ? this : new PrefixRangeMap(prefix);
}
@Override
public K firstKey()
{
return firstEntry().getKey();
}
@Override
public K lastKey()
{
TrieEntry entry = lastEntry();
return entry != null ? entry.getKey() : null;
}
@Override
public SortedMap headMap(K toKey)
{
return new RangeEntryMap(null, toKey);
}
@Override
public SortedMap subMap(K fromKey, K toKey)
{
return new RangeEntryMap(fromKey, toKey);
}
@Override
public SortedMap tailMap(K fromKey)
{
return new RangeEntryMap(fromKey, null);
}
/**
* Returns an entry strictly higher than the given key,
* or null if no such entry exists.
*/
private TrieEntry higherEntry(K key)
{
// TODO: Cleanup so that we don't actually have to add/remove from the
// tree. (We do it here because there are other well-defined
// functions to perform the search.)
int lengthInBits = lengthInBits(key);
if (lengthInBits == 0)
{
if (!root.isEmpty())
{
// If data in root, and more after -- return it.
return size() > 1 ? nextEntry(root) : null;
}
else
{
// Root is empty & we want something after empty, return first.
return firstEntry();
}
}
TrieEntry found = getNearestEntryForKey(key);
if (compareKeys(key, found.key))
return nextEntry(found);
int bitIndex = bitIndex(key, found.key);
if (Tries.isValidBitIndex(bitIndex))
{
return replaceCeil(key, bitIndex);
}
else if (Tries.isNullBitKey(bitIndex))
{
if (!root.isEmpty())
{
return firstEntry();
}
else if (size() > 1)
{
return nextEntry(firstEntry());
}
else
{
return null;
}
}
else if (Tries.isEqualBitKey(bitIndex))
{
return nextEntry(found);
}
// we should have exited above.
throw new IllegalStateException("invalid lookup: " + key);
}
/**
* Returns a key-value mapping associated with the least key greater
* than or equal to the given key, or null if there is no such key.
*/
TrieEntry ceilingEntry(K key)
{
// Basically:
// Follow the steps of adding an entry, but instead...
//
// - If we ever encounter a situation where we found an equal
// key, we return it immediately.
//
// - If we hit an empty root, return the first iterable item.
//
// - If we have to add a new item, we temporarily add it,
// find the successor to it, then remove the added item.
//
// These steps ensure that the returned value is either the
// entry for the key itself, or the first entry directly after
// the key.
// TODO: Cleanup so that we don't actually have to add/remove from the
// tree. (We do it here because there are other well-defined
// functions to perform the search.)
int lengthInBits = lengthInBits(key);
if (lengthInBits == 0)
{
if (!root.isEmpty())
{
return root;
}
else
{
return firstEntry();
}
}
TrieEntry found = getNearestEntryForKey(key);
if (compareKeys(key, found.key))
return found;
int bitIndex = bitIndex(key, found.key);
if (Tries.isValidBitIndex(bitIndex))
{
return replaceCeil(key, bitIndex);
}
else if (Tries.isNullBitKey(bitIndex))
{
if (!root.isEmpty())
{
return root;
}
else
{
return firstEntry();
}
}
else if (Tries.isEqualBitKey(bitIndex))
{
return found;
}
// we should have exited above.
throw new IllegalStateException("invalid lookup: " + key);
}
private TrieEntry replaceCeil(K key, int bitIndex)
{
TrieEntry added = new TrieEntry<>(key, null, bitIndex);
addEntry(added);
incrementSize(); // must increment because remove will decrement
TrieEntry ceil = nextEntry(added);
removeEntry(added);
modCount -= 2; // we didn't really modify it.
return ceil;
}
private TrieEntry replaceLower(K key, int bitIndex)
{
TrieEntry added = new TrieEntry<>(key, null, bitIndex);
addEntry(added);
incrementSize(); // must increment because remove will decrement
TrieEntry prior = previousEntry(added);
removeEntry(added);
modCount -= 2; // we didn't really modify it.
return prior;
}
/**
* Returns a key-value mapping associated with the greatest key
* strictly less than the given key, or null if there is no such key.
*/
TrieEntry lowerEntry(K key)
{
// Basically:
// Follow the steps of adding an entry, but instead...
//
// - If we ever encounter a situation where we found an equal
// key, we return it's previousEntry immediately.
//
// - If we hit root (empty or not), return null.
//
// - If we have to add a new item, we temporarily add it,
// find the previousEntry to it, then remove the added item.
//
// These steps ensure that the returned value is always just before
// the key or null (if there was nothing before it).
// TODO: Cleanup so that we don't actually have to add/remove from the
// tree. (We do it here because there are other well-defined
// functions to perform the search.)
int lengthInBits = lengthInBits(key);
if (lengthInBits == 0)
return null; // there can never be anything before root.
TrieEntry found = getNearestEntryForKey(key);
if (compareKeys(key, found.key))
return previousEntry(found);
int bitIndex = bitIndex(key, found.key);
if (Tries.isValidBitIndex(bitIndex))
{
return replaceLower(key, bitIndex);
}
else if (Tries.isNullBitKey(bitIndex))
{
return null;
}
else if (Tries.isEqualBitKey(bitIndex))
{
return previousEntry(found);
}
// we should have exited above.
throw new IllegalStateException("invalid lookup: " + key);
}
/**
* Returns a key-value mapping associated with the greatest key
* less than or equal to the given key, or null if there is no such key.
*/
TrieEntry floorEntry(K key) {
// TODO: Cleanup so that we don't actually have to add/remove from the
// tree. (We do it here because there are other well-defined
// functions to perform the search.)
int lengthInBits = lengthInBits(key);
if (lengthInBits == 0)
{
return !root.isEmpty() ? root : null;
}
TrieEntry found = getNearestEntryForKey(key);
if (compareKeys(key, found.key))
return found;
int bitIndex = bitIndex(key, found.key);
if (Tries.isValidBitIndex(bitIndex))
{
return replaceLower(key, bitIndex);
}
else if (Tries.isNullBitKey(bitIndex))
{
if (!root.isEmpty())
{
return root;
}
else
{
return null;
}
}
else if (Tries.isEqualBitKey(bitIndex))
{
return found;
}
// we should have exited above.
throw new IllegalStateException("invalid lookup: " + key);
}
/**
* Finds the subtree that contains the prefix.
*
* This is very similar to getR but with the difference that
* we stop the lookup if h.bitIndex > lengthInBits.
*/
private TrieEntry subtree(K prefix)
{
int lengthInBits = lengthInBits(prefix);
TrieEntry current = root.left;
TrieEntry path = root;
while(true)
{
if (current.bitIndex <= path.bitIndex || lengthInBits < current.bitIndex)
break;
path = current;
current = !isBitSet(prefix, current.bitIndex)
? current.left : current.right;
}
// Make sure the entry is valid for a subtree.
TrieEntry entry = current.isEmpty() ? path : current;
// If entry is root, it can't be empty.
if (entry.isEmpty())
return null;
// if root && length of root is less than length of lookup,
// there's nothing.
// (this prevents returning the whole subtree if root has an empty
// string and we want to lookup things with "\0")
if (entry == root && lengthInBits(entry.getKey()) < lengthInBits)
return null;
// Found key's length-th bit differs from our key
// which means it cannot be the prefix...
if (isBitSet(prefix, lengthInBits) != isBitSet(entry.key, lengthInBits))
return null;
// ... or there are less than 'length' equal bits
int bitIndex = bitIndex(prefix, entry.key);
return (bitIndex >= 0 && bitIndex < lengthInBits) ? null : entry;
}
/**
* Returns the last entry the {@link Trie} is storing.
*
* This is implemented by going always to the right until
* we encounter a valid uplink. That uplink is the last key.
*/
private TrieEntry lastEntry()
{
return followRight(root.left);
}
/**
* Traverses down the right path until it finds an uplink.
*/
private TrieEntry followRight(TrieEntry node)
{
// if Trie is empty, no last entry.
if (node.right == null)
return null;
// Go as far right as possible, until we encounter an uplink.
while (node.right.bitIndex > node.bitIndex)
{
node = node.right;
}
return node.right;
}
/**
* Returns the node lexicographically before the given node (or null if none).
*
* This follows four simple branches:
* - If the uplink that returned us was a right uplink:
* - If predecessor's left is a valid uplink from predecessor, return it.
* - Else, follow the right path from the predecessor's left.
* - If the uplink that returned us was a left uplink:
* - Loop back through parents until we encounter a node where
* node != node.parent.left.
* - If node.parent.left is uplink from node.parent:
* - If node.parent.left is not root, return it.
* - If it is root & root isEmpty, return null.
* - If it is root & root !isEmpty, return root.
* - If node.parent.left is not uplink from node.parent:
* - Follow right path for first right child from node.parent.left
*
* @param start the start entry
*/
private TrieEntry previousEntry(TrieEntry start)
{
if (start.predecessor == null)
throw new IllegalArgumentException("must have come from somewhere!");
if (start.predecessor.right == start)
{
return isValidUplink(start.predecessor.left, start.predecessor)
? start.predecessor.left
: followRight(start.predecessor.left);
}
TrieEntry node = start.predecessor;
while (node.parent != null && node == node.parent.left)
{
node = node.parent;
}
if (node.parent == null) // can be null if we're looking up root.
return null;
if (isValidUplink(node.parent.left, node.parent))
{
if (node.parent.left == root)
{
return root.isEmpty() ? null : root;
}
else
{
return node.parent.left;
}
}
else
{
return followRight(node.parent.left);
}
}
/**
* Returns the entry lexicographically after the given entry.
* If the given entry is null, returns the first node.
*
* This will traverse only within the subtree. If the given node
* is not within the subtree, this will have undefined results.
*/
private TrieEntry nextEntryInSubtree(TrieEntry node, TrieEntry parentOfSubtree)
{
return (node == null) ? firstEntry() : nextEntryImpl(node.predecessor, node, parentOfSubtree);
}
private boolean isPrefix(K key, K prefix)
{
return keyAnalyzer.isPrefix(key, prefix);
}
/**
* A range view of the {@link Trie}
*/
private abstract class RangeMap extends AbstractMap implements SortedMap
{
/**
* The {@link #entrySet()} view
*/
private transient volatile Set> entrySet;
/**
* Creates and returns an {@link #entrySet()}
* view of the {@link RangeMap}
*/
protected abstract Set> createEntrySet();
/**
* Returns the FROM Key
*/
protected abstract K getFromKey();
/**
* Whether or not the {@link #getFromKey()} is in the range
*/
protected abstract boolean isFromInclusive();
/**
* Returns the TO Key
*/
protected abstract K getToKey();
/**
* Whether or not the {@link #getToKey()} is in the range
*/
protected abstract boolean isToInclusive();
@Override
public Comparator super K> comparator()
{
return PatriciaTrie.this.comparator();
}
@Override
public boolean containsKey(Object key)
{
return inRange(Tries.cast(key)) && PatriciaTrie.this.containsKey(key);
}
@Override
public V remove(Object key)
{
return (!inRange(Tries.cast(key))) ? null : PatriciaTrie.this.remove(key);
}
@Override
public V get(Object key)
{
return (!inRange(Tries.cast(key))) ? null : PatriciaTrie.this.get(key);
}
@Override
public V put(K key, V value)
{
if (!inRange(key))
throw new IllegalArgumentException("Key is out of range: " + key);
return PatriciaTrie.this.put(key, value);
}
@Override
public Set> entrySet()
{
if (entrySet == null)
entrySet = createEntrySet();
return entrySet;
}
@Override
public SortedMap subMap(K fromKey, K toKey)
{
if (!inRange2(fromKey))
throw new IllegalArgumentException("FromKey is out of range: " + fromKey);
if (!inRange2(toKey))
throw new IllegalArgumentException("ToKey is out of range: " + toKey);
return createRangeMap(fromKey, isFromInclusive(), toKey, isToInclusive());
}
@Override
public SortedMap headMap(K toKey)
{
if (!inRange2(toKey))
throw new IllegalArgumentException("ToKey is out of range: " + toKey);
return createRangeMap(getFromKey(), isFromInclusive(), toKey, isToInclusive());
}
@Override
public SortedMap tailMap(K fromKey)
{
if (!inRange2(fromKey))
throw new IllegalArgumentException("FromKey is out of range: " + fromKey);
return createRangeMap(fromKey, isFromInclusive(), getToKey(), isToInclusive());
}
/**
* Returns true if the provided key is greater than TO and
* less than FROM
*/
protected boolean inRange(K key)
{
K fromKey = getFromKey();
K toKey = getToKey();
return (fromKey == null || inFromRange(key, false))
&& (toKey == null || inToRange(key, false));
}
/**
* This form allows the high endpoint (as well as all legit keys)
*/
protected boolean inRange2(K key)
{
K fromKey = getFromKey();
K toKey = getToKey();
return (fromKey == null || inFromRange(key, false))
&& (toKey == null || inToRange(key, true));
}
/**
* Returns true if the provided key is in the FROM range
* of the {@link RangeMap}
*/
protected boolean inFromRange(K key, boolean forceInclusive)
{
K fromKey = getFromKey();
boolean fromInclusive = isFromInclusive();
int ret = keyAnalyzer.compare(key, fromKey);
return (fromInclusive || forceInclusive) ? ret >= 0 : ret > 0;
}
/**
* Returns true if the provided key is in the TO range
* of the {@link RangeMap}
*/
protected boolean inToRange(K key, boolean forceInclusive)
{
K toKey = getToKey();
boolean toInclusive = isToInclusive();
int ret = keyAnalyzer.compare(key, toKey);
return (toInclusive || forceInclusive) ? ret <= 0 : ret < 0;
}
/**
* Creates and returns a sub-range view of the current {@link RangeMap}
*/
protected abstract SortedMap createRangeMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive);
}
/**
* A {@link RangeMap} that deals with {@link Entry}s
*/
private class RangeEntryMap extends RangeMap
{
/**
* The key to start from, null if the beginning.
*/
protected final K fromKey;
/**
* The key to end at, null if till the end.
*/
protected final K toKey;
/**
* Whether or not the 'from' is inclusive.
*/
protected final boolean fromInclusive;
/**
* Whether or not the 'to' is inclusive.
*/
protected final boolean toInclusive;
/**
* Creates a {@link RangeEntryMap} with the fromKey included and
* the toKey excluded from the range
*/
protected RangeEntryMap(K fromKey, K toKey)
{
this(fromKey, true, toKey, false);
}
/**
* Creates a {@link RangeEntryMap}
*/
protected RangeEntryMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)
{
if (fromKey == null && toKey == null)
throw new IllegalArgumentException("must have a from or to!");
if (fromKey != null && toKey != null && keyAnalyzer.compare(fromKey, toKey) > 0)
throw new IllegalArgumentException("fromKey > toKey");
this.fromKey = fromKey;
this.fromInclusive = fromInclusive;
this.toKey = toKey;
this.toInclusive = toInclusive;
}
@Override
public K firstKey()
{
Map.Entry e = fromKey == null
? firstEntry()
: fromInclusive ? ceilingEntry(fromKey) : higherEntry(fromKey);
K first = e != null ? e.getKey() : null;
if (e == null || toKey != null && !inToRange(first, false))
throw new NoSuchElementException();
return first;
}
@Override
public K lastKey()
{
Map.Entry e = toKey == null
? lastEntry()
: toInclusive ? floorEntry(toKey) : lowerEntry(toKey);
K last = e != null ? e.getKey() : null;
if (e == null || fromKey != null && !inFromRange(last, false))
throw new NoSuchElementException();
return last;
}
@Override
protected Set> createEntrySet()
{
return new RangeEntrySet(this);
}
@Override
public K getFromKey()
{
return fromKey;
}
@Override
public K getToKey()
{
return toKey;
}
@Override
public boolean isFromInclusive()
{
return fromInclusive;
}
@Override
public boolean isToInclusive()
{
return toInclusive;
}
@Override
protected SortedMap createRangeMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)
{
return new RangeEntryMap(fromKey, fromInclusive, toKey, toInclusive);
}
}
/**
* A {@link Set} view of a {@link RangeMap}
*/
private class RangeEntrySet extends AbstractSet>
{
private final RangeMap delegate;
private int size = -1;
private int expectedModCount = -1;
/**
* Creates a {@link RangeEntrySet}
*/
public RangeEntrySet(RangeMap delegate)
{
if (delegate == null)
throw new NullPointerException("delegate");
this.delegate = delegate;
}
@Override
public Iterator> iterator()
{
K fromKey = delegate.getFromKey();
K toKey = delegate.getToKey();
TrieEntry first = fromKey == null ? firstEntry() : ceilingEntry(fromKey);
TrieEntry last = null;
if (toKey != null)
last = ceilingEntry(toKey);
return new EntryIterator(first, last);
}
@Override
public int size()
{
if (size == -1 || expectedModCount != PatriciaTrie.this.modCount)
{
size = 0;
for (Iterator> it = iterator(); it.hasNext(); it.next())
{
++size;
}
expectedModCount = PatriciaTrie.this.modCount;
}
return size;
}
@Override
public boolean isEmpty()
{
return !iterator().hasNext();
}
@Override
public boolean contains(Object o)
{
if (!(o instanceof Map.Entry, ?>))
return false;
@SuppressWarnings("unchecked")
Map.Entry entry = (Map.Entry) o;
K key = entry.getKey();
if (!delegate.inRange(key))
return false;
TrieEntry node = getEntry(key);
return node != null && Tries.areEqual(node.getValue(), entry.getValue());
}
@Override
public boolean remove(Object o)
{
if (!(o instanceof Map.Entry, ?>))
return false;
@SuppressWarnings("unchecked")
Map.Entry entry = (Map.Entry) o;
K key = entry.getKey();
if (!delegate.inRange(key))
return false;
TrieEntry node = getEntry(key);
if (node != null && Tries.areEqual(node.getValue(), entry.getValue()))
{
removeEntry(node);
return true;
}
return false;
}
/**
* An {@link Iterator} for {@link RangeEntrySet}s.
*/
private final class EntryIterator extends TrieIterator>
{
private final K excludedKey;
/**
* Creates a {@link EntryIterator}
*/
private EntryIterator(TrieEntry first, TrieEntry last)
{
super(first);
this.excludedKey = (last != null ? last.getKey() : null);
}
@Override
public boolean hasNext()
{
return next != null && !Tries.areEqual(next.key, excludedKey);
}
@Override
public Map.Entry next()
{
if (next == null || Tries.areEqual(next.key, excludedKey))
throw new NoSuchElementException();
return nextEntry();
}
}
}
/**
* A submap used for prefix views over the {@link Trie}.
*/
private class PrefixRangeMap extends RangeMap
{
private final K prefix;
private K fromKey = null;
private K toKey = null;
private int expectedModCount = -1;
private int size = -1;
/**
* Creates a {@link PrefixRangeMap}
*/
private PrefixRangeMap(K prefix)
{
this.prefix = prefix;
}
/**
* This method does two things. It determinates the FROM
* and TO range of the {@link PrefixRangeMap} and the number
* of elements in the range. This method must be called every
* time the {@link Trie} has changed.
*/
private int fixup()
{
// The trie has changed since we last
// found our toKey / fromKey
if (size == - 1 || PatriciaTrie.this.modCount != expectedModCount)
{
Iterator> it = entrySet().iterator();
size = 0;
Map.Entry entry = null;
if (it.hasNext())
{
entry = it.next();
size = 1;
}
fromKey = entry == null ? null : entry.getKey();
if (fromKey != null)
{
TrieEntry prior = previousEntry((TrieEntry)entry);
fromKey = prior == null ? null : prior.getKey();
}
toKey = fromKey;
while (it.hasNext())
{
++size;
entry = it.next();
}
toKey = entry == null ? null : entry.getKey();
if (toKey != null)
{
entry = nextEntry((TrieEntry)entry);
toKey = entry == null ? null : entry.getKey();
}
expectedModCount = PatriciaTrie.this.modCount;
}
return size;
}
@Override
public K firstKey()
{
fixup();
Map.Entry e = fromKey == null ? firstEntry() : higherEntry(fromKey);
K first = e != null ? e.getKey() : null;
if (e == null || !isPrefix(first, prefix))
throw new NoSuchElementException();
return first;
}
@Override
public K lastKey()
{
fixup();
Map.Entry e = toKey == null ? lastEntry() : lowerEntry(toKey);
K last = e != null ? e.getKey() : null;
if (e == null || !isPrefix(last, prefix))
throw new NoSuchElementException();
return last;
}
/**
* Returns true if this {@link PrefixRangeMap}'s key is a prefix
* of the provided key.
*/
@Override
protected boolean inRange(K key)
{
return isPrefix(key, prefix);
}
/**
* Same as {@link #inRange(Object)}
*/
@Override
protected boolean inRange2(K key)
{
return inRange(key);
}
/**
* Returns true if the provided Key is in the FROM range
* of the {@link PrefixRangeMap}
*/
@Override
protected boolean inFromRange(K key, boolean forceInclusive)
{
return isPrefix(key, prefix);
}
/**
* Returns true if the provided Key is in the TO range
* of the {@link PrefixRangeMap}
*/
@Override
protected boolean inToRange(K key, boolean forceInclusive)
{
return isPrefix(key, prefix);
}
@Override
protected Set> createEntrySet()
{
return new PrefixRangeEntrySet(this);
}
@Override
public K getFromKey()
{
return fromKey;
}
@Override
public K getToKey()
{
return toKey;
}
@Override
public boolean isFromInclusive()
{
return false;
}
@Override
public boolean isToInclusive()
{
return false;
}
@Override
protected SortedMap createRangeMap(K fromKey, boolean fromInclusive,
K toKey, boolean toInclusive)
{
return new RangeEntryMap(fromKey, fromInclusive, toKey, toInclusive);
}
}
/**
* A prefix {@link RangeEntrySet} view of the {@link Trie}
*/
private final class PrefixRangeEntrySet extends RangeEntrySet
{
private final PrefixRangeMap delegate;
private TrieEntry prefixStart;
private int expectedModCount = -1;
/**
* Creates a {@link PrefixRangeEntrySet}
*/
public PrefixRangeEntrySet(PrefixRangeMap delegate)
{
super(delegate);
this.delegate = delegate;
}
@Override
public int size()
{
return delegate.fixup();
}
@Override
public Iterator> iterator()
{
if (PatriciaTrie.this.modCount != expectedModCount)
{
prefixStart = subtree(delegate.prefix);
expectedModCount = PatriciaTrie.this.modCount;
}
if (prefixStart == null)
{
Set> empty = Collections.emptySet();
return empty.iterator();
}
else if (lengthInBits(delegate.prefix) >= prefixStart.bitIndex)
{
return new SingletonIterator(prefixStart);
}
else
{
return new EntryIterator(prefixStart, delegate.prefix);
}
}
/**
* An {@link Iterator} that holds a single {@link TrieEntry}.
*/
private final class SingletonIterator implements Iterator>
{
private final TrieEntry entry;
private int hit = 0;
public SingletonIterator(TrieEntry entry)
{
this.entry = entry;
}
@Override
public boolean hasNext()
{
return hit == 0;
}
@Override
public Map.Entry next()
{
if (hit != 0)
throw new NoSuchElementException();
++hit;
return entry;
}
@Override
public void remove()
{
if (hit != 1)
throw new IllegalStateException();
++hit;
PatriciaTrie.this.removeEntry(entry);
}
}
/**
* An {@link Iterator} for iterating over a prefix search.
*/
private final class EntryIterator extends TrieIterator>
{
// values to reset the subtree if we remove it.
protected final K prefix;
protected boolean lastOne;
protected TrieEntry subtree; // the subtree to search within
/**
* Starts iteration at the given entry & search only
* within the given subtree.
*/
EntryIterator(TrieEntry startScan, K prefix)
{
subtree = startScan;
next = PatriciaTrie.this.followLeft(startScan);
this.prefix = prefix;
}
@Override
public Map.Entry next()
{
Map.Entry entry = nextEntry();
if (lastOne)
next = null;
return entry;
}
@Override
protected TrieEntry findNext(TrieEntry prior)
{
return PatriciaTrie.this.nextEntryInSubtree(prior, subtree);
}
@Override
public void remove()
{
// If the current entry we're removing is the subtree
// then we need to find a new subtree parent.
boolean needsFixing = false;
int bitIdx = subtree.bitIndex;
if (current == subtree)
needsFixing = true;
super.remove();
// If the subtree changed its bitIndex or we
// removed the old subtree, get a new one.
if (bitIdx != subtree.bitIndex || needsFixing)
subtree = subtree(prefix);
// If the subtree's bitIndex is less than the
// length of our prefix, it's the last item
// in the prefix tree.
if (lengthInBits(prefix) >= subtree.bitIndex)
lastOne = true;
}
}
}
}