/*
* 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.tree;
import com.github.geophile.erdo.Configuration;
import com.github.geophile.erdo.map.LazyRecord;
import com.github.geophile.erdo.map.diskmap.DiskPage;
import com.github.geophile.erdo.map.diskmap.IndexRecord;
import com.github.geophile.erdo.map.diskmap.PageId;
import com.github.geophile.erdo.segmentfilemanager.AbstractSegmentFileManager;
import com.github.geophile.erdo.util.AbstractPool;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicInteger;
class WriteableTreeSegment extends TreeSegment
{
public IndexRecord append(LazyRecord record)
throws IOException, InterruptedException
{
IndexRecord indexRecord = null;
boolean appended = page.append(record);
if (!appended) {
closePage();
indexRecord = new IndexRecord(page.firstKey(), page.pageAddress());
if (lastPageOfSegment()) {
closeSegment(); // Drops page
// Can't start a new page to hold the record being appended because the segment is full.
// The caller will detect this and put the record in a new segment.
} else {
// Start a new page and put the record being appended into it.
createNewPage();
appended = page.append(record);
assert appended : record;
}
}
if (appended && summary != null) {
summary.append(record.key());
}
return indexRecord;
}
public IndexRecord finalizeRightEdge() throws IOException, InterruptedException
{
IndexRecord indexRecord = null;
if (closePage()) {
indexRecord = new IndexRecord(page.firstKey(), page.pageAddress());
}
closeSegment(); // Drops page
return indexRecord;
}
@Override
public void checkClosed()
{
assert segmentBuffer == null : this;
assert erdoIdsBuffer == null : this;
assert timestampsBuffer == null : this;
assert keysBuffer == null : this;
assert recordsBuffer == null : this;
assert zeros == null : this;
}
public WriteableTreeSegment(TreeLevel level, int segmentNumber, long segmentId)
{
super(level, segmentNumber, segmentId);
treeCreationBuffers = buffers();
Configuration configuration = level.tree().factory().configuration();
pageSizeBytes = configuration.diskPageSizeBytes();
segmentBuffer = treeCreationBuffers.segmentBuffer();
erdoIdsBuffer = treeCreationBuffers.erdoIdsBuffer();
timestampsBuffer = treeCreationBuffers.timestampsBuffer();
keysBuffer = treeCreationBuffers.keysBuffer();
recordsBuffer = treeCreationBuffers.recordsBuffer();
zeros = treeCreationBuffers.zeros();
segmentBuffer.clear();
createNewPage();
}
boolean isLastPage(int pageAddress)
{
return level.tree().pageNumber(pageAddress) == maxPagesInSegment - 1;
}
// For use by this class
private void createNewPage()
{
erdoIdsBuffer.clear();
if (timestampsBuffer != null) {
timestampsBuffer.clear();
}
keysBuffer.clear();
recordsBuffer.clear();
Tree tree = level.tree();
page = new DiskPage(tree.factory(),
new PageId(segmentId, pageCount),
level.levelNumber(),
tree.pageAddress(segmentNumber, pageCount),
erdoIdsBuffer,
level.isLeaf() ? timestampsBuffer : null,
keysBuffer,
recordsBuffer);
pageCount++;
}
private boolean closePage()
{
boolean empty = true;
if (page != null) {
if (page.nRecords() > 0) {
page.close();
empty = false;
}
// flip buffers
erdoIdsBuffer.flip();
if (timestampsBuffer != null) {
timestampsBuffer.flip();
}
keysBuffer.flip();
recordsBuffer.flip();
// compute occupied
int occupied = erdoIdsBuffer.limit() + keysBuffer.limit() + recordsBuffer.limit();
if (timestampsBuffer != null) {
occupied += timestampsBuffer.limit();
}
assert occupied <= pageSizeBytes : occupied;
// copy to segment buffer
segmentBuffer.put(erdoIdsBuffer);
if (timestampsBuffer != null) {
segmentBuffer.put(timestampsBuffer);
}
segmentBuffer.put(keysBuffer);
segmentBuffer.put(recordsBuffer);
segmentBuffer.put(zeros, 0, pageSizeBytes - occupied);
}
return !empty;
}
private void closeSegment() throws IOException, InterruptedException
{
if (!empty()) {
writeSegmentFile();
if (level.isLeaf()) {
summary.write();
}
}
BUFFERS_POOL.get().returnResource(treeCreationBuffers);
page = null;
treeCreationBuffers = null;
segmentBuffer = null;
recordsBuffer = null;
keysBuffer = null;
erdoIdsBuffer = null;
timestampsBuffer = null;
zeros = null;
}
private boolean lastPageOfSegment()
{
return pageCount == maxPagesInSegment;
}
private boolean empty()
{
return pageCount == 1 && page != null && page.nRecords() == 0;
}
private void writeSegmentFile() throws IOException, InterruptedException
{
segmentBuffer.flip();
if (segmentBuffer.limit() > 0) {
Tree tree = level.tree();
AbstractSegmentFileManager segmentFileManager = tree.factory().segmentFileManager();
File file = tree.dbStructure().segmentFile(segmentId);
segmentFileManager.create(file, tree.treeId(), segmentId);
segmentFileManager.write(file, 0, segmentBuffer);
}
}
private TreeCreationBuffers buffers()
{
TreeCreationBuffersPool buffersPool = BUFFERS_POOL.get();
if (buffersPool == null) {
buffersPool = new TreeCreationBuffersPool(treeLevel().tree().factory().configuration());
BUFFERS_POOL.set(buffersPool);
}
return buffersPool.takeResource();
}
// Class state
// A pool of tree creation buffers is needed per thread. While a thread writes only one tree at a time, a
// tree may have multiple levels, and a set of buffers is needed for each level.
private static final ThreadLocal<TreeCreationBuffersPool> BUFFERS_POOL = new ThreadLocal<>();
private static final AtomicInteger BUFFERS_COUNTER = new AtomicInteger(0);
// Object state
private DiskPage page;
private final int pageSizeBytes;
// Buffers: The segmentBuffer accumulates pages as they are closed, so that the whole segment can be written
// in one operation. timestampsBuffer, keysBuffer and recordsBuffer are used by each DiskPage, and then when
// the page is closed, copied to the segmentBuffer. zeros is used to supply zeros when filling out the unused part
// of a page.
private TreeCreationBuffers treeCreationBuffers;
private ByteBuffer segmentBuffer;
private ByteBuffer erdoIdsBuffer;
private ByteBuffer timestampsBuffer;
private ByteBuffer keysBuffer;
private ByteBuffer recordsBuffer;
private byte[] zeros;
// Inner classes
private class TreeCreationBuffersPool extends AbstractPool<TreeCreationBuffers>
{
@Override
public TreeCreationBuffers newResource()
{
return new TreeCreationBuffers(configuration, BUFFERS_COUNTER.getAndIncrement());
}
@Override
public void activate(TreeCreationBuffers buffers)
{
buffers.markInUse();
}
@Override
public void deactivate(TreeCreationBuffers buffers)
{
buffers.markFree();
}
TreeCreationBuffersPool(Configuration configuration)
{
this.configuration = configuration;
}
private final Configuration configuration;
}
}