blob: a6e0cd7c2ce42c15453f4a697c43416330cc4ab0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.io.tries;
import javax.annotation.concurrent.NotThreadSafe;
import org.apache.cassandra.io.util.Rebufferer;
import org.apache.cassandra.utils.bytecomparable.ByteComparable;
import org.apache.cassandra.utils.bytecomparable.ByteSource;
/**
* Thread-unsafe value iterator for on-disk tries. Uses the assumptions of {@link Walker}.
* <p>
* The main utility of this class is the {@link #nextPayloadedNode()} method, which lists all nodes that contain a
* payload within the requested bounds. The treatment of the bounds is non-standard (see
* {@link #ValueIterator(Rebufferer, long, ByteComparable, ByteComparable, boolean)}), necessary to properly walk
* tries of prefixes and separators.
*/
@NotThreadSafe
public class ValueIterator<CONCRETE extends ValueIterator<CONCRETE>> extends Walker<CONCRETE>
{
private final ByteSource limit;
private IterationPosition stack;
private long next;
static class IterationPosition
{
final long node;
final int limit;
final IterationPosition prev;
int childIndex;
IterationPosition(long node, int childIndex, int limit, IterationPosition prev)
{
super();
this.node = node;
this.childIndex = childIndex;
this.limit = limit;
this.prev = prev;
}
@Override
public String toString()
{
return String.format("[Node %d, child %d, limit %d]", node, childIndex, limit);
}
}
protected ValueIterator(Rebufferer source, long root)
{
super(source, root);
limit = null;
initializeNoLeftBound(root, 256);
}
/**
* Constrained iterator. The end position is always treated as inclusive, and we have two possible treatments for
* the start:
* <ul>
* <li> When {@code admitPrefix=false}, exact matches and any prefixes of the start are excluded.
* <li> When {@code admitPrefix=true}, the longest prefix of the start present in the trie is also included,
* provided that there is no entry in the trie between that prefix and the start. An exact match also
* satisfies this and is included.
* </ul>
* This behaviour is shared with the reverse counterpart {@link ReverseValueIterator}.
*/
protected ValueIterator(Rebufferer source, long root, ByteComparable start, ByteComparable end, boolean admitPrefix)
{
super(source, root);
limit = end != null ? end.asComparableBytes(BYTE_COMPARABLE_VERSION) : null;
if (start != null)
initializeWithLeftBound(root, start.asComparableBytes(BYTE_COMPARABLE_VERSION), admitPrefix, limit != null);
else
initializeNoLeftBound(root, limit != null ? limit.next() : 256);
}
private void initializeWithLeftBound(long root, ByteSource startStream, boolean admitPrefix, boolean atLimit)
{
IterationPosition prev = null;
int childIndex;
int limitByte;
long payloadedNode = -1;
try
{
// Follow start position while we still have a prefix, stacking path and saving prefixes.
go(root);
while (true)
{
int s = startStream.next();
childIndex = search(s);
// For a separator trie the latest payload met along the prefix is a potential match for start
if (admitPrefix)
{
if (childIndex == 0 || childIndex == -1)
{
if (payloadFlags() != 0)
payloadedNode = position;
}
else
{
payloadedNode = -1;
}
}
limitByte = 256;
if (atLimit)
{
limitByte = limit.next();
if (s < limitByte)
atLimit = false;
}
if (childIndex < 0)
break;
prev = new IterationPosition(position, childIndex, limitByte, prev);
go(transition(childIndex)); // child index is positive, transition must exist
}
childIndex = -1 - childIndex - 1;
stack = new IterationPosition(position, childIndex, limitByte, prev);
// Advancing now gives us first match if we didn't find one already.
if (payloadedNode != -1)
next = payloadedNode;
else
next = advanceNode();
}
catch (Throwable t)
{
super.close();
throw t;
}
}
private void initializeNoLeftBound(long root, int limitByte)
{
stack = new IterationPosition(root, -1, limitByte, null);
try
{
go(root);
if (payloadFlags() != 0)
next = root;
else
next = advanceNode();
}
catch (Throwable t)
{
super.close();
throw t;
}
}
/**
* Returns the position of the next node with payload contained in the iterated span.
*/
protected long nextPayloadedNode()
{
long toReturn = next;
if (next != -1)
next = advanceNode();
return toReturn;
}
private long advanceNode()
{
long child;
int transitionByte;
go(stack.node);
while (true)
{
int childIndex = stack.childIndex + 1;
transitionByte = transitionByte(childIndex);
if (transitionByte > stack.limit)
{
// ascend
stack = stack.prev;
if (stack == null) // exhausted whole trie
return -1;
go(stack.node);
continue;
}
child = transition(childIndex);
if (child != NONE)
{
assert child >= 0 : String.format("Expected value >= 0 but got %d - %s", child, this);
// descend
go(child);
int l = 256;
if (transitionByte == stack.limit)
l = limit.next();
stack.childIndex = childIndex;
stack = new IterationPosition(child, -1, l, stack);
if (payloadFlags() != 0)
return child;
}
else
{
stack.childIndex = childIndex;
}
}
}
}