/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.github.geophile.erdo.map.diskmap;
// An array of longs >= 0. Keeps track of min value present, and the number of bytes required to represent deltas
// from this minimum, (0, 1, 2 or 4). This is necessary so that, as the page is filled, we know how much data
// is required to store this array. When closed, the deltas are stored, in an array of 1, 2, 4, or 8-byte integers.
// (Or if all the timestamps are identical, no array is needed.)
import com.github.geophile.erdo.util.Transferrable;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
public class CompressibleLongArray implements Transferrable
{
// Transferrable interface
public void writeTo(ByteBuffer buffer) throws BufferOverflowException
{
assert closed();
// User is expected to know the number of array elements
switch (deltaBytes) {
case 0:
break;
case 1:
for (int i = 0; i < size; i++) {
buffer.put(deltas1Byte[i]);
}
break;
case 2:
for (int i = 0; i < size; i++) {
buffer.putShort(deltas2Byte[i]);
}
break;
case 4:
for (int i = 0; i < size; i++) {
buffer.putInt(deltas4Byte[i]);
}
break;
case 8:
for (int i = 0; i < size; i++) {
buffer.putLong(deltas8Byte[i]);
}
break;
default:
assert false;
break;
}
}
public void readFrom(ByteBuffer buffer)
{
// size and deltaBytes must be set before reading
assert size != -1;
assert deltaBytes != -1;
switch (deltaBytes) {
case 0:
break;
case 1:
deltas1Byte = new byte[size];
for (int i = 0; i < size; i++) {
deltas1Byte[i] = buffer.get();
}
break;
case 2:
deltas2Byte = new short[size];
for (int i = 0; i < size; i++) {
deltas2Byte[i] = buffer.getShort();
}
break;
case 4:
deltas4Byte = new int[size];
for (int i = 0; i < size; i++) {
deltas4Byte[i] = buffer.getInt();
}
break;
case 8:
deltas8Byte = new long[size];
for (int i = 0; i < size; i++) {
deltas8Byte[i] = buffer.getLong();
}
break;
default:
assert false;
break;
}
}
public int recordCount()
{
return size;
}
// CompressibleLongArray interface
public void close()
{
if (!closed()) {
// Convert to deltas
switch (deltaBytes) {
case 0:
break;
case 1:
deltas1Byte = new byte[size];
for (int i = 0; i < size; i++) {
deltas1Byte[i] = (byte) ((array[i] - min) & 0xff);
}
break;
case 2:
deltas2Byte = new short[size];
for (int i = 0; i < size; i++) {
deltas2Byte[i] = (short) ((array[i] - min) & 0xffff);
}
break;
case 4:
deltas4Byte = new int[size];
for (int i = 0; i < size; i++) {
deltas4Byte[i] = (int) (array[i] - min);
}
break;
case 8:
deltas8Byte = array;
for (int i = 0; i < size; i++) {
deltas8Byte[i] -= min;
}
}
array = null;
}
}
public long at(int position)
{
assert closed();
switch (deltaBytes) {
case 0: return min;
case 1: return min + (deltas1Byte[position] & 0xffL);
case 2: return min + (deltas2Byte[position] & 0xffffL);
case 4: return min + (deltas4Byte[position] & 0xffffffffL);
case 8: return min + deltas8Byte[position];
default:
assert false;
return -1;
}
}
public long min()
{
return min;
}
public int deltaBytes()
{
return deltaBytes == -1 ? 0 : deltaBytes;
}
public byte[] deltas1Byte()
{
assert deltas1Byte != null;
return deltas1Byte;
}
public short[] deltas2Byte()
{
assert deltas2Byte != null;
return deltas2Byte;
}
public int[] deltas4Byte()
{
assert deltas4Byte != null;
return deltas4Byte;
}
public long[] deltas8Byte()
{
assert deltas8Byte != null;
return deltas8Byte;
}
public int size()
{
return size;
}
public void append(long x)
{
// Store the new value
if (size == array.length) {
long[] newArray = new long[size * 2];
System.arraycopy(array, 0, newArray, 0, size);
array = newArray;
}
array[size++] = x;
// Adjust min, max, deltaBytes
if (size == 1) {
min = x;
max = x;
deltaBytes = 0;
} else if (x < min) {
min = x;
adjustDeltaBytes();
} else if (x > max) {
max = x;
adjustDeltaBytes();
}
// else: min, max, deltaBytes don't change
}
public void removeLast()
{
assert !closed();
size--;
// Recompute min, max, deltaBytes
min = array[0];
max = min;
for (int i = 1; i < size; i++) {
min = Math.min(min, array[i]);
max = Math.max(max, array[i]);
}
deltaBytes = 0;
adjustDeltaBytes();
}
public CompressibleLongArray(int capacity)
{
size = 0;
array = new long[Math.max(capacity, MIN_INITIAL_SIZE)];
}
// Setting up for deserialization
public CompressibleLongArray(long min, int size, int deltaBytes)
{
this.min = min;
this.size = size;
this.deltaBytes = deltaBytes;
}
// For use by this class
private void adjustDeltaBytes()
{
long delta = max - min;
int newDeltaBytes = 0;
if ((delta & MASK_4_BYTES) != 0) {
newDeltaBytes = 8;
} else if ((delta & MASK_2_BYTES) != 0) {
newDeltaBytes = 4;
} else if ((delta & MASK_1_BYTE) != 0) {
newDeltaBytes = 2;
} else if (delta != 0) {
newDeltaBytes = 1;
}
deltaBytes = Math.max(deltaBytes, newDeltaBytes);
}
private boolean closed()
{
return array == null;
}
// Class state
private static final int MIN_INITIAL_SIZE = 10;
private static final long MASK_1_BYTE = ~0xffL;
private static final long MASK_2_BYTES = ~0xffffL;
private static final long MASK_4_BYTES = ~0xffffffffL;
// Object state
private int size = -1;
private long[] array;
private long min = -1L;
private long max = -1L;
private int deltaBytes = -1;
private byte[] deltas1Byte;
private short[] deltas2Byte;
private int[] deltas4Byte;
private long[] deltas8Byte;
}