org.apache.cassandra.io.tries.TrieNode Maven / Gradle / Ivy
Show all versions of cassandra-all Show documentation
/*
* 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 java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.io.util.SizedInts;
/**
* Trie node types and manipulation mechanisms. The main purpose of this is to allow for handling tries directly as
* they are on disk without any serialization, and to enable the creation of such files.
*
* The serialization methods take as argument a generic {@code SerializationNode} and provide a method {@code typeFor}
* for choosing a suitable type to represent it, which can then be used to calculate size and write the node.
*
* To read a file containing trie nodes, one would use {@code at} to identify the node type and then the various
* read methods to retrieve the data. They all take a buffer (usually memory-mapped) containing the data, and a position
* in it that identifies the node.
*
* These node types do not specify any treatment of payloads. They are only concerned with providing 4 bits of
* space for {@code payloadFlags}, and a way of calculating the position after the node. Users of this class by convention
* use non-zero payloadFlags to indicate a payload exists, write it (possibly in flag-dependent format) at serialization
* time after the node itself is written, and read it using the {@code payloadPosition} value.
*
* To improve efficiency, multiple node types depending on the number of transitions are provided:
* -- payload only, which has no outgoing transitions
* -- single outgoing transition
* -- sparse, which provides a list of transition bytes with corresponding targets
* -- dense, where the transitions span a range of values and having the list (and the search in it) can be avoided
*
* For each of the transition-carrying types we also have "in-page" versions where transition targets are the 4, 8 or 12
* lowest bits of the position within the same page. To save one further byte, the single in-page versions using 4 or 12
* bits cannot carry a payload.
*
* This class is effectively an enumeration; abstract class permits instances to extend each other and reuse code.
*
* See {@code org/apache/cassandra/io/sstable/format/bti/BtiFormat.md} for a description of the mechanisms of writing
* and reading an on-disk trie.
*/
@SuppressWarnings({ "SameParameterValue" })
public abstract class TrieNode
{
/** Value used to indicate a branch (e.g. for transition and lastTransition) does not exist. */
public static final int NONE = -1;
// Consumption (read) methods
/**
* Returns the type of node stored at this position. It can then be used to call the methods below.
*/
public static TrieNode at(ByteBuffer src, int position)
{
return Types.values[(src.get(position) >> 4) & 0xF];
}
/**
* Returns the 4 payload flag bits. Node types that cannot carry a payload return 0.
*/
public int payloadFlags(ByteBuffer src, int position)
{
return src.get(position) & 0x0F;
}
/**
* Return the position just after the node, where the payload is usually stored.
*/
abstract public int payloadPosition(ByteBuffer src, int position);
/**
* Returns search index for the given byte in the node. If exact match is present, this is >= 0, otherwise as in
* binary search.
*/
abstract public int search(ByteBuffer src, int position, int transitionByte); // returns as binarySearch
/**
* Returns the upper childIndex limit. Calling transition with values 0...transitionRange - 1 is valid.
*/
abstract public int transitionRange(ByteBuffer src, int position);
/**
* Returns the byte value for this child index, or Integer.MAX_VALUE if there are no transitions with this index or
* higher to permit listing the children without needing to call transitionRange.
*
* @param childIndex must be >= 0, though it is allowed to pass a value greater than {@code transitionRange - 1}
*/
abstract public int transitionByte(ByteBuffer src, int position, int childIndex);
/**
* Returns the delta between the position of this node and the position of the target of the specified transition.
* This is always a negative number. Dense nodes use 0 to specify "no transition".
*
* @param childIndex must be >= 0 and < {@link #transitionRange(ByteBuffer, int)} - note that this is not validated
* and behaviour of this method is undefined for values outside of that range
*/
abstract long transitionDelta(ByteBuffer src, int position, int childIndex);
/**
* Returns position of node to transition to for the given search index. Argument must be positive. May return NONE
* if a transition with that index does not exist (DENSE nodes).
* Position is the offset of the node within the ByteBuffer. positionLong is its global placement, which is the
* base for any offset calculations.
*
* @param positionLong although it seems to be obvious, this argument must be "real", that is, each child must have
* the calculated absolute position >= 0, otherwise the behaviour of this method is undefined
* @param childIndex must be >= 0 and < {@link #transitionRange(ByteBuffer, int)} - note that this is not validated
* and behaviour of this method is undefined for values outside of that range
*/
public long transition(ByteBuffer src, int position, long positionLong, int childIndex)
{
// note: this is not valid for dense nodes
return positionLong + transitionDelta(src, position, childIndex);
}
/**
* Returns the highest transition for this node, or NONE if none exist (PAYLOAD_ONLY nodes).
*/
public long lastTransition(ByteBuffer src, int position, long positionLong)
{
return transition(src, position, positionLong, transitionRange(src, position) - 1);
}
/**
* Returns a transition that is higher than the index returned by {@code search}. This may not exist (if the
* argument was higher than the last transition byte), in which case this returns the given {@code defaultValue}.
*/
abstract public long greaterTransition(ByteBuffer src, int position, long positionLong, int searchIndex, long defaultValue);
/**
* Returns a transition that is lower than the index returned by {@code search}. Returns {@code defaultValue} for
* {@code searchIndex} equals 0 or -1 as lesser transition for those indexes does not exist.
*/
abstract public long lesserTransition(ByteBuffer src, int position, long positionLong, int searchIndex, long defaultValue);
// Construction (serialization) methods
/**
* Returns a node type that is suitable to store the node.
*/
public static TrieNode typeFor(SerializationNode> node, long nodePosition)
{
int c = node.childCount();
if (c == 0)
return Types.PAYLOAD_ONLY;
int bitsPerPointerIndex = 0;
long delta = node.maxPositionDelta(nodePosition);
assert delta < 0;
while (!Types.singles[bitsPerPointerIndex].fits(-delta))
++bitsPerPointerIndex;
if (c == 1)
{
if (node.payload() != null && Types.singles[bitsPerPointerIndex].bytesPerPointer == FRACTIONAL_BYTES)
++bitsPerPointerIndex; // next index will permit payload
return Types.singles[bitsPerPointerIndex];
}
TrieNode sparse = Types.sparses[bitsPerPointerIndex];
TrieNode dense = Types.denses[bitsPerPointerIndex];
return (sparse.sizeofNode(node) < dense.sizeofNode(node)) ? sparse : dense;
}
/**
* Returns the size needed to serialize this node.
*/
abstract public int sizeofNode(SerializationNode> node);
/**
* Serializes the node. All transition target positions must already have been defined. {@code payloadBits} must
* be four bits.
*/
abstract public void serialize(DataOutputPlus out, SerializationNode> node, int payloadBits, long nodePosition) throws IOException;
// Implementations
final int bytesPerPointer;
static final int FRACTIONAL_BYTES = 0;
TrieNode(int ordinal, int bytesPerPointer)
{
this.ordinal = ordinal;
this.bytesPerPointer = bytesPerPointer;
}
final int ordinal;
static private class PayloadOnly extends TrieNode
{
// byte flags
// var payload
PayloadOnly(int ordinal)
{
super(ordinal, FRACTIONAL_BYTES);
}
@Override
public int payloadPosition(ByteBuffer src, int position)
{
return position + 1;
}
@Override
public int search(ByteBuffer src, int position, int transitionByte)
{
return -1;
}
@Override
public long transitionDelta(ByteBuffer src, int position, int childIndex)
{
return 0;
}
@Override
public long transition(ByteBuffer src, int position, long positionLong, int childIndex)
{
return NONE;
}
@Override
public long lastTransition(ByteBuffer src, int position, long positionLong)
{
return NONE;
}
@Override
public long greaterTransition(ByteBuffer src, int position, long positionLong, int searchIndex, long defaultValue)
{
return defaultValue;
}
@Override
public long lesserTransition(ByteBuffer src, int position, long positionLong, int searchIndex, long defaultValue)
{
return defaultValue;
}
@Override
public int transitionByte(ByteBuffer src, int position, int childIndex)
{
return Integer.MAX_VALUE;
}
@Override
public int transitionRange(ByteBuffer src, int position)
{
return 0;
}
public int sizeofNode(SerializationNode> node)
{
return 1;
}
@Override
public void serialize(DataOutputPlus dest, SerializationNode> node, int payloadBits, long nodePosition) throws IOException
{
dest.writeByte((ordinal << 4) + (payloadBits & 0x0F));
}
}
static private class Single extends TrieNode
{
// byte flags
// byte transition
// bytesPerPointer bytes transition target
// var payload
Single(int ordinal, int bytesPerPointer)
{
super(ordinal, bytesPerPointer);
}
@Override
public int payloadPosition(ByteBuffer src, int position)
{
return position + 2 + bytesPerPointer;
}
@Override
public int search(ByteBuffer src, int position, int transitionByte)
{
int c = src.get(position + 1) & 0xFF;
if (transitionByte == c)
return 0;
return transitionByte < c ? -1 : -2;
}
public long transitionDelta(ByteBuffer src, int position, int childIndex)
{
return -readBytes(src, position + 2);
}
@Override
public long lastTransition(ByteBuffer src, int position, long positionLong)
{
return transition(src, position, positionLong, 0);
}
@Override
public long greaterTransition(ByteBuffer src, int position, long positionLong, int searchIndex, long defaultValue)
{
return (searchIndex == -1) ? transition(src, position, positionLong, 0) : defaultValue;
}
@Override
public long lesserTransition(ByteBuffer src, int position, long positionLong, int searchIndex, long defaultValue)
{
return searchIndex == 0 || searchIndex == -1 ? defaultValue : transition(src, position, positionLong, 0);
}
@Override
public int transitionByte(ByteBuffer src, int position, int childIndex)
{
return childIndex == 0 ? src.get(position + 1) & 0xFF : Integer.MAX_VALUE;
}
@Override
public int transitionRange(ByteBuffer src, int position)
{
return 1;
}
public int sizeofNode(SerializationNode> node)
{
return 2 + bytesPerPointer;
}
@Override
public void serialize(DataOutputPlus dest, SerializationNode> node, int payloadBits, long nodePosition) throws IOException
{
int childCount = node.childCount();
assert childCount == 1;
dest.writeByte((ordinal << 4) + (payloadBits & 0x0F));
dest.writeByte(node.transition(0));
writeBytes(dest, -node.serializedPositionDelta(0, nodePosition));
}
}
static private class SingleNoPayload4 extends Single
{
// 4-bit type ordinal
// 4-bit target delta
// byte transition
// no payload!
SingleNoPayload4(int ordinal)
{
super(ordinal, FRACTIONAL_BYTES);
}
@Override
public int payloadFlags(ByteBuffer src, int position)
{
return 0;
}
// Although we don't have a payload position, provide one for calculating the size of the node.
@Override
public int payloadPosition(ByteBuffer src, int position)
{
return position + 2;
}
@Override
public long transitionDelta(ByteBuffer src, int position, int childIndex)
{
return -(src.get(position) & 0xF);
}
@Override
boolean fits(long delta)
{
return 0 <= delta && delta <= 0xF;
}
@Override
public void serialize(DataOutputPlus dest, SerializationNode> node, int payloadBits, long nodePosition) throws IOException
{
assert payloadBits == 0;
int childCount = node.childCount();
assert childCount == 1;
long pd = -node.serializedPositionDelta(0, nodePosition);
assert pd > 0 && pd < 0x10;
dest.writeByte((ordinal << 4) + (int) (pd & 0x0F));
dest.writeByte(node.transition(0));
}
@Override
public int sizeofNode(SerializationNode> node)
{
return 2;
}
}
static private class SingleNoPayload12 extends Single
{
// 4-bit type ordinal
// 12-bit target delta
// byte transition
// no payload!
SingleNoPayload12(int ordinal)
{
super(ordinal, FRACTIONAL_BYTES);
}
@Override
public int payloadFlags(ByteBuffer src, int position)
{
return 0;
}
// Although we don't have a payload position, provide one for calculating the size of the node.
@Override
public int payloadPosition(ByteBuffer src, int position)
{
return position + 3;
}
@Override
public int search(ByteBuffer src, int position, int transitionByte)
{
int c = src.get(position + 2) & 0xFF;
if (transitionByte == c)
return 0;
return transitionByte < c ? -1 : -2;
}
@Override
public long transitionDelta(ByteBuffer src, int position, int childIndex)
{
return -(src.getShort(position) & 0xFFF);
}
@Override
public int transitionByte(ByteBuffer src, int position, int childIndex)
{
return childIndex == 0 ? src.get(position + 2) & 0xFF : Integer.MAX_VALUE;
}
@Override
boolean fits(long delta)
{
return 0 <= delta && delta <= 0xFFF;
}
@Override
public void serialize(DataOutputPlus dest, SerializationNode> node, int payloadBits, long nodePosition) throws IOException
{
assert payloadBits == 0;
int childCount = node.childCount();
assert childCount == 1;
long pd = -node.serializedPositionDelta(0, nodePosition);
assert pd > 0 && pd < 0x1000;
dest.writeByte((ordinal << 4) + (int) ((pd >> 8) & 0x0F));
dest.writeByte((byte) pd);
dest.writeByte(node.transition(0));
}
@Override
public int sizeofNode(SerializationNode> node)
{
return 3;
}
}
static private class Sparse extends TrieNode
{
// byte flags
// byte count (<= 255)
// count bytes transitions
// count ints transition targets
// var payload
Sparse(int ordinal, int bytesPerPointer)
{
super(ordinal, bytesPerPointer);
}
@Override
public int transitionRange(ByteBuffer src, int position)
{
return src.get(position + 1) & 0xFF;
}
@Override
public int payloadPosition(ByteBuffer src, int position)
{
int count = transitionRange(src, position);
return position + 2 + (bytesPerPointer + 1) * count;
}
@Override
public int search(ByteBuffer src, int position, int key)
{
int l = -1; // known < key
int r = transitionRange(src, position); // known > key
position += 2;
while (l + 1 < r)
{
int m = (l + r + 1) / 2;
int childTransition = src.get(position + m) & 0xFF;
int cmp = Integer.compare(key, childTransition);
if (cmp < 0)
r = m;
else if (cmp > 0)
l = m;
else
return m;
}
return -r - 1;
}
@Override
public long transitionDelta(ByteBuffer src, int position, int childIndex)
{
assert childIndex >= 0;
int range = transitionRange(src, position);
assert childIndex < range;
return -readBytes(src, position + 2 + range + bytesPerPointer * childIndex);
}
@Override
public long greaterTransition(ByteBuffer src, int position, long positionLong, int searchIndex, long defaultValue)
{
if (searchIndex < 0)
searchIndex = -1 - searchIndex;
else
++searchIndex;
if (searchIndex >= transitionRange(src, position))
return defaultValue;
return transition(src, position, positionLong, searchIndex);
}
public long lesserTransition(ByteBuffer src, int position, long positionLong, int searchIndex, long defaultValue)
{
if (searchIndex == 0 || searchIndex == -1)
return defaultValue;
if (searchIndex < 0)
searchIndex = -2 - searchIndex;
else
--searchIndex;
return transition(src, position, positionLong, searchIndex);
}
@Override
public int transitionByte(ByteBuffer src, int position, int childIndex)
{
return childIndex < transitionRange(src, position) ? src.get(position + 2 + childIndex) & 0xFF : Integer.MAX_VALUE;
}
@Override
public int sizeofNode(SerializationNode> node)
{
return 2 + node.childCount() * (1 + bytesPerPointer);
}
@Override
public void serialize(DataOutputPlus dest, SerializationNode> node, int payloadBits, long nodePosition) throws IOException
{
int childCount = node.childCount();
assert childCount > 0;
assert childCount < 256;
dest.writeByte((ordinal << 4) + (payloadBits & 0x0F));
dest.writeByte(childCount);
for (int i = 0; i < childCount; ++i)
dest.writeByte(node.transition(i));
for (int i = 0; i < childCount; ++i)
writeBytes(dest, -node.serializedPositionDelta(i, nodePosition));
}
}
static private class Sparse12 extends Sparse
{
// byte flags
// byte count (<= 255)
// count bytes transitions
// count 12-bits transition targets
// var payload
Sparse12(int ordinal)
{
super(ordinal, FRACTIONAL_BYTES);
}
@Override
public int payloadPosition(ByteBuffer src, int position)
{
int count = transitionRange(src, position);
return position + 2 + (5 * count + 1) / 2;
}
@Override
public long transitionDelta(ByteBuffer src, int position, int childIndex)
{
return -read12Bits(src, position + 2 + transitionRange(src, position), childIndex);
}
@Override
public int sizeofNode(SerializationNode> node)
{
return 2 + (node.childCount() * 5 + 1) / 2;
}
@Override
public void serialize(DataOutputPlus dest, SerializationNode> node, int payloadBits, long nodePosition) throws IOException
{
int childCount = node.childCount();
assert childCount < 256;
dest.writeByte((ordinal << 4) + (payloadBits & 0x0F));
dest.writeByte(childCount);
for (int i = 0; i < childCount; ++i)
dest.writeByte(node.transition(i));
int i;
for (i = 0; i + 2 <= childCount; i += 2)
{
int p0 = (int) -node.serializedPositionDelta(i, nodePosition);
int p1 = (int) -node.serializedPositionDelta(i + 1, nodePosition);
assert p0 > 0 && p0 < (1 << 12);
assert p1 > 0 && p1 < (1 << 12);
dest.writeByte(p0 >> 4);
dest.writeByte((p0 << 4) | (p1 >> 8));
dest.writeByte(p1);
}
if (i < childCount)
{
long pd = -node.serializedPositionDelta(i, nodePosition);
assert pd > 0 && pd < (1 << 12);
dest.writeShort((short) (pd << 4));
}
}
@Override
boolean fits(long delta)
{
return 0 <= delta && delta <= 0xFFF;
}
}
static private class Dense extends TrieNode
{
// byte flags
// byte start
// byte length-1
// length ints transition targets (-1 for not present)
// var payload
static final int NULL_VALUE = 0;
Dense(int ordinal, int bytesPerPointer)
{
super(ordinal, bytesPerPointer);
}
@Override
public int transitionRange(ByteBuffer src, int position)
{
return 1 + (src.get(position + 2) & 0xFF);
}
@Override
public int payloadPosition(ByteBuffer src, int position)
{
return position + 3 + transitionRange(src, position) * bytesPerPointer;
}
@Override
public int search(ByteBuffer src, int position, int transitionByte)
{
int l = src.get(position + 1) & 0xFF;
int i = transitionByte - l;
if (i < 0)
return -1;
int len = transitionRange(src, position);
if (i >= len)
return -len - 1;
long t = transition(src, position, 0L, i);
return t != -1 ? i : -i - 1;
}
@Override
public long transitionDelta(ByteBuffer src, int position, int childIndex)
{
return -readBytes(src, position + 3 + childIndex * bytesPerPointer);
}
@Override
public long transition(ByteBuffer src, int position, long positionLong, int childIndex)
{
long v = transitionDelta(src, position, childIndex);
return v != NULL_VALUE ? v + positionLong : NONE;
}
@Override
public long greaterTransition(ByteBuffer src, int position, long positionLong, int searchIndex, long defaultValue)
{
if (searchIndex < 0)
searchIndex = -1 - searchIndex;
else
++searchIndex;
int len = transitionRange(src, position);
for (; searchIndex < len; ++searchIndex)
{
long t = transition(src, position, positionLong, searchIndex);
if (t != NONE)
return t;
}
return defaultValue;
}
@Override
public long lesserTransition(ByteBuffer src, int position, long positionLong, int searchIndex, long defaultValue)
{
if (searchIndex == 0 || searchIndex == -1)
return defaultValue;
if (searchIndex < 0)
searchIndex = -2 - searchIndex;
else
--searchIndex;
for (; searchIndex >= 0; --searchIndex)
{
long t = transition(src, position, positionLong, searchIndex);
if (t != -1)
return t;
}
assert false : "transition must always exist at 0, and we should not be called for less of that";
return defaultValue;
}
@Override
public int transitionByte(ByteBuffer src, int position, int childIndex)
{
if (childIndex >= transitionRange(src, position))
return Integer.MAX_VALUE;
int l = src.get(position + 1) & 0xFF;
return l + childIndex;
}
@Override
public int sizeofNode(SerializationNode> node)
{
int l = node.transition(0);
int r = node.transition(node.childCount() - 1);
return 3 + (r - l + 1) * bytesPerPointer;
}
@Override
public void serialize(DataOutputPlus dest, SerializationNode> node, int payloadBits, long nodePosition) throws IOException
{
int childCount = node.childCount();
dest.writeByte((ordinal << 4) + (payloadBits & 0x0F));
int l = node.transition(0);
int r = node.transition(childCount - 1);
assert 0 <= l && l <= r && r <= 255;
dest.writeByte(l);
dest.writeByte(r - l); // r is included, i.e. this is len - 1
for (int i = 0; i < childCount; ++i)
{
int next = node.transition(i);
while (l < next)
{
writeBytes(dest, NULL_VALUE);
++l;
}
writeBytes(dest, -node.serializedPositionDelta(i, nodePosition));
++l;
}
}
}
static private class Dense12 extends Dense
{
// byte flags
// byte start
// byte length-1
// length 12-bits transition targets (-1 for not present)
// var payload
Dense12(int ordinal)
{
super(ordinal, FRACTIONAL_BYTES);
}
@Override
public int payloadPosition(ByteBuffer src, int position)
{
return position + 3 + (transitionRange(src, position) * 3 + 1) / 2;
}
@Override
public long transitionDelta(ByteBuffer src, int position, int childIndex)
{
return -read12Bits(src, position + 3, childIndex);
}
@Override
public int sizeofNode(SerializationNode> node)
{
int l = node.transition(0);
int r = node.transition(node.childCount() - 1);
return 3 + ((r - l + 1) * 3 + 1) / 2;
}
@Override
public void serialize(DataOutputPlus dest, SerializationNode> node, int payloadBits, long nodePosition) throws IOException
{
int childCount = node.childCount();
dest.writeByte((ordinal << 4) + (payloadBits & 0x0F));
int l = node.transition(0);
int r = node.transition(childCount - 1);
assert 0 <= l && l <= r && r <= 255;
dest.writeByte(l);
dest.writeByte(r - l); // r is included, i.e. this is len - 1
int carry = 0;
int start = l;
for (int i = 0; i < childCount; ++i)
{
int next = node.transition(i);
while (l < next)
{
carry = write12Bits(dest, NULL_VALUE, l - start, carry);
++l;
}
long pd = node.serializedPositionDelta(i, nodePosition);
carry = write12Bits(dest, (int) -pd, l - start, carry);
++l;
}
if (((l - start) & 1) == 1)
dest.writeByte(carry);
}
@Override
boolean fits(long delta)
{
return 0 <= delta && delta <= 0xFFF;
}
}
static private class LongDense extends Dense
{
// byte flags
// byte start
// byte length-1
// length long transition targets (-1 for not present)
// var payload
LongDense(int ordinal)
{
super(ordinal, 8);
}
@Override
public long transitionDelta(ByteBuffer src, int position, int childIndex)
{
return -src.getLong(position + 3 + childIndex * 8);
}
@Override
public void writeBytes(DataOutputPlus dest, long ofs) throws IOException
{
dest.writeLong(ofs);
}
@Override
boolean fits(long delta)
{
return true;
}
}
static int read12Bits(ByteBuffer src, int base, int searchIndex)
{
int word = src.getShort(base + (3 * searchIndex) / 2);
if ((searchIndex & 1) == 0)
word = (word >> 4);
return word & 0xFFF;
}
static int write12Bits(DataOutput dest, int value, int index, int carry) throws IOException
{
assert 0 <= value && value <= 0xFFF;
if ((index & 1) == 0)
{
dest.writeByte(value >> 4);
return value << 4;
}
else
{
dest.writeByte(carry | (value >> 8));
dest.writeByte(value);
return 0;
}
}
long readBytes(ByteBuffer src, int position)
{
return SizedInts.readUnsigned(src, position, bytesPerPointer);
}
void writeBytes(DataOutputPlus dest, long ofs) throws IOException
{
assert fits(ofs);
SizedInts.write(dest, ofs, bytesPerPointer);
}
boolean fits(long delta)
{
return 0 <= delta && delta < (1L << (bytesPerPointer * 8));
}
@Override
public String toString()
{
String res = getClass().getSimpleName();
if (bytesPerPointer >= 1)
res += (bytesPerPointer * 8);
return res;
}
static class Types
{
static final TrieNode PAYLOAD_ONLY = new PayloadOnly(0);
static final TrieNode SINGLE_NOPAYLOAD_4 = new SingleNoPayload4(1);
static final TrieNode SINGLE_8 = new Single(2, 1);
static final TrieNode SINGLE_NOPAYLOAD_12 = new SingleNoPayload12(3);
static final TrieNode SINGLE_16 = new Single(4, 2);
static final TrieNode SPARSE_8 = new Sparse(5, 1);
static final TrieNode SPARSE_12 = new Sparse12(6);
static final TrieNode SPARSE_16 = new Sparse(7, 2);
static final TrieNode SPARSE_24 = new Sparse(8, 3);
static final TrieNode SPARSE_40 = new Sparse(9, 5);
static final TrieNode DENSE_12 = new Dense12(10);
static final TrieNode DENSE_16 = new Dense(11, 2);
static final TrieNode DENSE_24 = new Dense(12, 3);
static final TrieNode DENSE_32 = new Dense(13, 4);
static final TrieNode DENSE_40 = new Dense(14, 5);
static final TrieNode LONG_DENSE = new LongDense(15);
// The position of each type in this list must match its ordinal value. Checked by the static block below.
static final TrieNode[] values = new TrieNode[]{ PAYLOAD_ONLY,
SINGLE_NOPAYLOAD_4, SINGLE_8, SINGLE_NOPAYLOAD_12, SINGLE_16,
SPARSE_8, SPARSE_12, SPARSE_16, SPARSE_24, SPARSE_40,
DENSE_12, DENSE_16, DENSE_24, DENSE_32, DENSE_40,
LONG_DENSE }; // Catch-all
// We can't fit all types * all sizes in 4 bits, so we use a selection. When we don't have a matching instance
// we just use something more general that can do its job.
// The arrays below must have corresponding types for all sizes specified by the singles row.
// Note: 12 bit sizes are important, because that size will fit any pointer within a page-packed branch.
static final TrieNode[] singles = new TrieNode[]{ SINGLE_NOPAYLOAD_4, SINGLE_8, SINGLE_NOPAYLOAD_12, SINGLE_16, DENSE_24, DENSE_32, DENSE_40, LONG_DENSE };
static final TrieNode[] sparses = new TrieNode[]{ SPARSE_8, SPARSE_8, SPARSE_12, SPARSE_16, SPARSE_24, SPARSE_40, SPARSE_40, LONG_DENSE };
static final TrieNode[] denses = new TrieNode[]{ DENSE_12, DENSE_12, DENSE_12, DENSE_16, DENSE_24, DENSE_32, DENSE_40, LONG_DENSE };
static
{
//noinspection ConstantConditions
assert sparses.length == singles.length && denses.length == singles.length && values.length <= 16;
for (int i = 0; i < values.length; ++i)
assert values[i].ordinal == i;
}
}
}