/*
* 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.util;
import com.github.geophile.erdo.memorymonitor.MemoryTracker;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
public class PackedArray implements MemoryTracker.Trackable<PackedArray>
{
// MemoryTracker.Trackable interface
public long sizeBytes()
{
return buffers.size() * BUFFER_CAPACITY + bounds.size();
}
// PackedArray interface
public void at(int position, Transferrable transferrable)
{
int end = bounds.at(position);
int endBuffer = end >>> 16;
int endBufferOffset = end & 0xffff;
int startBuffer = 0;
int startBufferOffset = 0;
if (position > 0) {
int start = bounds.at(position - 1);
startBuffer = start >>> 16;
startBufferOffset = start & 0xffff;
}
assert startBuffer == endBuffer || startBuffer == endBuffer - 1;
ByteBuffer buffer = buffers.get(endBuffer);
if (startBuffer < endBuffer) {
startBufferOffset = 0;
}
ByteBuffer element = buffer.duplicate();
element.limit(endBufferOffset);
element.position(startBufferOffset);
transferrable.readFrom(element);
}
public void append(Transferrable transferrable)
{
// Copy transferrable into a ByteBuffer.
ensureBuffer();
try {
currentBuffer.mark();
transferrable.writeTo(currentBuffer);
} catch (BufferOverflowException firstException) {
currentBuffer = null;
ensureBuffer();
try {
transferrable.writeTo(currentBuffer);
} catch (BufferOverflowException secondException) {
// Restore state to where we were before the first attempt to append transferrable.
removeLastBuffer();
currentBuffer.mark();
throw new ElementTooLargeException(transferrable);
}
}
// Record the end of the element just copied in.
assert currentBuffer.position() <= BUFFER_CAPACITY : currentBuffer;
bounds.append(((buffers.size() - 1) << 16) | currentBuffer.position());
}
public int size()
{
return bounds.size();
}
public PackedArray(MemoryTracker<PackedArray> memoryTracker)
{
this.memoryTracker = memoryTracker;
}
// For use by this class
private void ensureBuffer()
{
if (currentBuffer == null) {
currentBuffer = ByteBuffer.allocate(BUFFER_CAPACITY);
buffers.add(currentBuffer);
memoryTracker.track(sizeBytes());
}
}
private void removeLastBuffer()
{
buffers.remove(buffers.size() - 1);
currentBuffer = buffers.get(buffers.size() - 1);
memoryTracker.track(sizeBytes() - BUFFER_CAPACITY);
}
// Class state
// BUFFER_CAPACITY must be < 1<<16 because bounds element stores buffer offset in 16 bits.
private final static int BUFFER_CAPACITY = 65000;
// Object state
// PackedArray elements are packed into ByteBuffers. A single element is completely contained
// within one ByteBuffer. A bound value stores an index into buffers in the high 16 bits. The
// low 16 bits point into the indicated buffer.
// In general, a PackedArray element is located by finding the bounds at either end, and using
// these to construct a ByteBuffer. There are two subtleties. 1) the 0th PackedArray element
// begins at 0, and this bound is not stored. 2) For the last element in a ByteBuffer, the end
// of the element is described by the ByteBuffer's limit.
private final List<ByteBuffer> buffers = new ArrayList<ByteBuffer>();
private ByteBuffer currentBuffer; // Last element of buffers, and the buffer currently being loaded.
private final IntArray bounds = new IntArray(null);
// Memory tracking is not precise because this PackedArray and the bounds IntArray grow
// independently of one another. Should be close enough.
private final MemoryTracker<PackedArray> memoryTracker;
public class ElementTooLargeException extends RuntimeException
{
public ElementTooLargeException(Transferrable transferrable)
{
this.transferrable = transferrable;
}
public Transferrable transferrable()
{
return transferrable;
}
private Transferrable transferrable;
}
}