DeltaContext.java
package zserio.runtime.array;
import java.io.IOException;
import java.math.BigInteger;
import zserio.runtime.array.ArrayElement.IntegralArrayElement;
import zserio.runtime.array.ArrayTraits.IntegralArrayTraits;
import zserio.runtime.io.BitStreamReader;
import zserio.runtime.io.BitStreamWriter;
/**
* Context for delta packing created for each packable field.
*
* Contexts are always newly created for each array operation (bitSizeOfPacked, initializeOffsetsPacked,
* readPacked, writePacked). They must be initialized at first via calling the init method for each packable
* element present in the array. After the full initialization, only a single method (bitSizeOf, read, write)
* can be repeatedly called for exactly the same sequence of packable elements.
*/
public final class DeltaContext extends PackingContext
{
/**
* Calls the initialization step for a single element.
*
* @param arrayTraits Standard array traits.
* @param element Current element.
*/
public void init(IntegralArrayTraits arrayTraits, IntegralArrayElement element)
{
numElements++;
unpackedBitSize += bitSizeOfUnpacked(arrayTraits, element);
if (previousElement == null)
{
previousElement = element.toBigInteger();
firstElementBitSize = (byte)unpackedBitSize;
}
else
{
if (maxBitNumber <= MAX_BIT_NUMBER_LIMIT)
{
isPacked = true;
final BigInteger bigElement = element.toBigInteger();
final BigInteger delta = bigElement.subtract(previousElement);
final byte maxBitNumber = bitLength(delta);
if (maxBitNumber > this.maxBitNumber)
{
this.maxBitNumber = maxBitNumber;
if (maxBitNumber > MAX_BIT_NUMBER_LIMIT)
isPacked = false;
}
previousElement = bigElement;
}
}
}
/**
* Returns length of the packed element stored in the bit stream in bits.
*
* @param arrayTraits Standard array traits.
* @param element Value of the current element.
*
* @return Length of the packed element stored in the bit stream in bits.
*/
public int bitSizeOf(IntegralArrayTraits arrayTraits, IntegralArrayElement element)
{
if (!processingStarted)
{
processingStarted = true;
finishInit();
return bitSizeOfDescriptor() + bitSizeOfUnpacked(arrayTraits, element);
}
else if (!isPacked)
{
return bitSizeOfUnpacked(arrayTraits, element);
}
else
{
return maxBitNumber + (maxBitNumber > 0 ? 1 : 0);
}
}
/**
* Reads a packed element from the bit stream.
*
* @param arrayTraits Standard array traits.
* @param reader Bit stream reader.
*
* @return Packed element.
*
* @throws IOException Failure during bit stream manipulation.
*/
public IntegralArrayElement read(IntegralArrayTraits arrayTraits, BitStreamReader reader) throws IOException
{
if (!processingStarted)
{
processingStarted = true;
readDescriptor(reader);
return readUnpacked(arrayTraits, reader);
}
else if (!isPacked)
{
return readUnpacked(arrayTraits, reader);
}
else
{
if (maxBitNumber > 0)
{
final BigInteger delta = BigInteger.valueOf(reader.readSignedBits(maxBitNumber + 1));
previousElement = previousElement.add(delta);
}
return arrayTraits.fromBigInteger(previousElement);
}
}
/**
* Writes the packed element to the bit stream.
*
* @param arrayTraits Standard array traits.
* @param writer Bit stream writer.
* @param element Current element.
*
* @throws IOException Failure during bit stream manipulation.
*/
public void write(IntegralArrayTraits arrayTraits, BitStreamWriter writer, IntegralArrayElement element)
throws IOException
{
if (!processingStarted)
{
processingStarted = true;
finishInit();
writeDescriptor(writer);
writeUnpacked(arrayTraits, writer, element);
}
else if (!isPacked)
{
writeUnpacked(arrayTraits, writer, element);
}
else
{
if (maxBitNumber > 0)
{
final BigInteger bigElement = element.toBigInteger();
final BigInteger delta = bigElement.subtract(previousElement);
writer.writeSignedBits(delta.longValue(), maxBitNumber + 1);
previousElement = bigElement;
}
}
}
private void finishInit()
{
if (isPacked)
{
final int deltaBitSize = maxBitNumber + (maxBitNumber > 0 ? 1 : 0);
final int packedBitSizeWithDescriptor = 1 + MAX_BIT_NUMBER_BITS + // descriptor
firstElementBitSize + (numElements - 1) * deltaBitSize;
final int unpackedBitSizeWithDescriptor = 1 + unpackedBitSize;
if (packedBitSizeWithDescriptor >= unpackedBitSizeWithDescriptor)
isPacked = false;
}
}
private int bitSizeOfDescriptor()
{
return isPacked ? 1 + MAX_BIT_NUMBER_BITS : 1;
}
private static int bitSizeOfUnpacked(IntegralArrayTraits arrayTraits, IntegralArrayElement element)
{
return arrayTraits.bitSizeOf(element);
}
private void readDescriptor(BitStreamReader reader) throws IOException
{
isPacked = reader.readBool();
if (isPacked)
maxBitNumber = (byte)reader.readBits(MAX_BIT_NUMBER_BITS);
}
private IntegralArrayElement readUnpacked(IntegralArrayTraits arrayTraits, BitStreamReader reader)
throws IOException
{
final IntegralArrayElement element = arrayTraits.read(reader);
previousElement = element.toBigInteger();
return element;
}
private void writeDescriptor(BitStreamWriter writer) throws IOException
{
writer.writeBool(isPacked);
if (isPacked)
writer.writeBits(maxBitNumber, MAX_BIT_NUMBER_BITS);
}
private void writeUnpacked(IntegralArrayTraits arrayTraits, BitStreamWriter writer,
IntegralArrayElement element) throws IOException
{
previousElement = element.toBigInteger();
arrayTraits.write(writer, element);
}
private static byte bitLength(BigInteger element)
{
// need to call abs() first to get the same behavior as in Python and C++
return (byte)element.abs().bitLength();
}
private static final byte MAX_BIT_NUMBER_BITS = 6;
private static final byte MAX_BIT_NUMBER_LIMIT = 62;
private BigInteger previousElement; // BigInteger covers all integral array element values
private byte maxBitNumber = 0;
private boolean isPacked = false;
private boolean processingStarted = false;
private byte firstElementBitSize = 0;
private int numElements = 0;
private int unpackedBitSize = 0;
}