/*
* 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.map.Factory;
import com.github.geophile.erdo.map.LazyRecord;
import com.github.geophile.erdo.map.MapCursor;
import com.github.geophile.erdo.map.diskmap.DBStructure;
import com.github.geophile.erdo.map.diskmap.IndexRecord;
import com.github.geophile.erdo.map.mergescan.AbstractMultiRecord;
import com.github.geophile.erdo.map.mergescan.MultiRecordKey;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
public class WriteableTree extends Tree
{
// WriteableTree interface
public int append(LazyRecord record) throws IOException, InterruptedException
{
int recordCount;
if (record instanceof AbstractMultiRecord) {
assert record instanceof LevelOneMultiRecord : record;
recordCount = appendMultiRecord((LevelOneMultiRecord) record);
} else {
recordCount = appendSingleRecord(record);
}
return recordCount;
}
public Tree close() throws IOException, InterruptedException
{
finalizeRightEdge();
return this;
}
public WriteableTreeLevel level(int levelNumber)
{
return (WriteableTreeLevel) super.level(levelNumber);
}
public WriteableTree(Factory factory, DBStructure dbStructure, long treeId)
{
super(factory, dbStructure, treeId);
}
// For use by this class
private int appendSingleRecord(LazyRecord record) throws IOException, InterruptedException
{
WriteableTreeLevel leafLevel = level(0);
if (previousAppendWasMultiRecord) {
// Can't append to shared segment so start a new one.
IndexRecord indexRecord = leafLevel.closeCurrentLeafSegment();
assert indexRecord == null : indexRecord;
leafLevel.startNewLeafSegment();
}
IndexRecord indexRecord = leafLevel.append(record);
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "{0}: append single record {1}", new Object[]{this, record});
}
if (indexRecord != null) {
propagateUp(1, indexRecord);
}
previousAppendWasMultiRecord = false;
return 1;
}
private int appendMultiRecord(LevelOneMultiRecord multiRecord) throws IOException, InterruptedException
{
if (LOG.isLoggable(Level.INFO)) {
LOG.log(Level.INFO, "{0}: append multirecord {1}", new Object[]{this, multiRecord});
}
WriteableTreeLevel leafLevel = level(0);
// A linked segment is its own segment, so ensure that the current one is closed.
IndexRecord indexRecord = null;
if (!previousAppendWasMultiRecord) {
indexRecord = leafLevel.closeCurrentLeafSegment();
}
if (indexRecord != null) {
propagateUp(1, indexRecord);
}
int linkedSegmentPageAddress = leafLevel.linkIn(multiRecord);
// Append index records pointing to the pages of the linked leaf file. First, ensure that level 1 exists.
if (levels.size() == 1) {
levels.add(WriteableTreeLevel.create(this, 1));
}
MapCursor levelOneScan = multiRecord.levelOneScan();
LazyRecord lazyIndexRecord;
while ((lazyIndexRecord = levelOneScan.next()) != null) {
indexRecord = (IndexRecord) lazyIndexRecord.materializeRecord();
assert
indexRecord.key().compareTo(((MultiRecordKey)multiRecord.key()).hi()) <= 0
: String.format("multiRecord: %s, indexRecord: %s", multiRecord, indexRecord);
indexRecord.childPageAddress(linkedSegmentPageAddress++);
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE,
"{0}: Propagating index record {1} from linked file page of {2}",
new Object[]{this, indexRecord, multiRecord});
}
propagateUp(1, indexRecord);
}
previousAppendWasMultiRecord = true;
int recordsCovered = multiRecord.leafSegment().leafRecords();
return recordsCovered;
}
private void propagateUp(int indexLevel, IndexRecord indexRecord)
throws IOException, InterruptedException
{
assert indexLevel >= 1 : indexLevel;
if (indexLevel == levels.size()) {
// A page was just written to the root level. It could be the first page on that level, in which
// case there's nothing to do. Or it could be the second page on that level, in which case we need
// to create a new root level. It couldn't be the 3rd+ page on that level, since a root level is
// no longer root after it gets its second page.
TreeLevel rootLevel = levels.get(indexLevel - 1);
assert rootLevel.segments() == 1 : rootLevel;
TreeSegment rootSegment = rootLevel.segment(0);
int pagesOnCurrentRootLevel = rootSegment.pages();
if (pagesOnCurrentRootLevel == 1) {
// Nothing to do
} else if (pagesOnCurrentRootLevel == 2) {
createNewRootLevel(indexRecord);
} else {
assert false : pagesOnCurrentRootLevel;
}
} else {
IndexRecord newIndexRecord = level(indexLevel).append(indexRecord);
if (newIndexRecord != null) {
propagateUp(indexLevel + 1, newIndexRecord);
}
}
}
private void createNewRootLevel(IndexRecord indexRecord) throws IOException, InterruptedException
{
// Create new root level
int indexLevel = levels.size();
WriteableTreeLevel newRootLevel = WriteableTreeLevel.create(this, indexLevel);
levels.add(newRootLevel);
newRootLevel.append(indexRecord);
}
private void finalizeRightEdge() throws IOException, InterruptedException
{
if (!levels.isEmpty()) {
List<IndexRecord> promoted = finalizeRightEdge(levels.size() - 1);
TreeLevel rootLevel = levels.get(levels.size() - 1);
assert rootLevel.segments() == 1 : rootLevel;
TreeSegment rootSegment = rootLevel.segment(0);
if (rootSegment.pages() == 1) {
// There should be at most one promoted IndexRecord, resulting from the writing of
// the root page.
assert promoted.size() <= 1 : promoted;
} else if (rootSegment.pages() > 1) {
// Need to create a new root level
createNewRootLevel(promoted.get(0));
WriteableTreeLevel newRootLevel = level(levels.size() - 1);
IndexRecord promotedFromNewRoot;
for (int i = 1; i < promoted.size(); i++) {
promotedFromNewRoot = newRootLevel.append(promoted.get(i));
assert promotedFromNewRoot == null : promotedFromNewRoot;
}
newRootLevel.finalizeRightEdge();
}
}
}
private List<IndexRecord> finalizeRightEdge(int level) throws IOException, InterruptedException
{
List<IndexRecord> outgoing = new ArrayList<IndexRecord>(2);
IndexRecord promoted;
WriteableTreeLevel treeLevel = level(level);
if (level > 0) {
List<IndexRecord> incoming = finalizeRightEdge(level - 1);
for (IndexRecord indexRecord : incoming) {
promoted = treeLevel.append(indexRecord);
if (promoted != null) {
outgoing.add(promoted);
}
}
}
promoted = treeLevel.finalizeRightEdge();
if (promoted != null) {
outgoing.add(promoted);
}
return outgoing;
}
// Object state
private boolean previousAppendWasMultiRecord = false;
}