/* * 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; import com.github.geophile.erdo.util.Transferrable; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; /* * For all DiskPageSections: * SIZE: 2 byte section size (including header). * COUNT: 2 byte count of DATA records, (counts the calls to append). * METADATA_SIZE: 2 byte size of METADATA. * METADATA: fixed-size area containing metadata. * RECORD_SIZE: 2-byte size of each record (used only by DiskPageSectionFixedLengthRecords) * * For all DiskPageSections * DATA: COUNT records with no separators. * * For DiskPageSectionVariableLengthRecords only: * END: Array of 2-byte deltas into DATA area, (relative to start of DATA). * * Due to the use of 2-byte integers, a section size is limited to 64k. */ abstract class DiskPageSection { // DiskPageSection interface public final int metadataUByte(int position) { assert state == State.CLOSED : this; return buffer.get(metadataPosition + position) & 0xff; } public final int metadataUShort(int position) { assert state == State.CLOSED : this; return buffer.getChar(metadataPosition + position) & 0xffff; } public final long metadataUInt(int position) { assert state == State.CLOSED : this; return ((long) buffer.getInt(metadataPosition + position)) & MAX_UINT; } public final long metadataULong(int position) { assert state == State.CLOSED : this; return buffer.getLong(metadataPosition + position); } public final void metadataAppendUByte(int x) { assert state == State.METADATA : this; assert x >= 0; assert x <= MAX_UBYTE : this; assert buffer.position() >= metadataPosition : this; buffer.put((byte) x); assert buffer.position() <= dataPosition : this; } public final void metadataAppendUShort(int x) { assert state == State.METADATA : this; assert x >= 0; assert x <= MAX_USHORT : this; assert buffer.position() >= metadataPosition : this; buffer.putChar((char) x); assert buffer.position() <= dataPosition : this; } public final void metadataAppendUInt(long x) { assert state == State.METADATA : this; assert x >= 0; assert x <= MAX_UINT : this; assert buffer.position() >= metadataPosition : this; buffer.putInt((int) x); assert buffer.position() <= dataPosition : this; } public final void metadataAppendULong(long x) { assert state == State.METADATA : this; assert x >= 0; assert buffer.position() >= metadataPosition : this; buffer.putLong(x); assert buffer.position() <= dataPosition : this; } public final void metadataClose() { state = State.DATA; buffer.position(dataPosition); } public final void append(Transferrable data) throws BufferOverflowException { assert state == State.DATA : this; assert buffer.position() >= dataPosition : this; assert data != null; buffer.mark(); try { int recordStart = buffer.position(); data.writeTo(buffer); int recordEnd = buffer.position(); recordEndsAt(recordEnd); size += recordEnd - recordStart; count += data.recordCount(); } catch (BufferOverflowException e) { buffer.reset(); throw e; } } public final void append(ByteBuffer data) throws BufferOverflowException { assert state == State.DATA : this; assert buffer.position() >= dataPosition : this; assert data != null; int recordStart = buffer.position(); buffer.put(data); // If insufficient space, leaves buffer alone and throws BufferOverflowException int recordEnd = buffer.position(); recordEndsAt(recordEnd); size += recordEnd - recordStart; count++; // Because append(ByteBuffer) is only used for single records } public final void mark() { assert state == State.DATA || state == State.METADATA : this; markCount = count; markPosition = buffer.position(); } public void resetToMark() { assert state == State.DATA || state == State.METADATA : this; assert markCount >= 0 : this; assert markPosition >= 0 : this; count = markCount; buffer.position(markPosition); markCount = -1; markPosition = -1; // Subclass resets size and its own state } public int size() { return size; } public void close() { if (state != State.CLOSED) { // Write size and count assert size == buffer.position() - start; buffer.putChar(start, (char) size); buffer.putChar(countPosition, (char) count); state = State.CLOSED; } } public final int count() { return count; } public final ByteBuffer buffer() { return buffer; } public final ByteBuffer accessBuffer() { return buffer.duplicate(); } public void recordSize(int recordSize) { throw new UnsupportedOperationException(); } // For use by subclasses // Used when creating a new page protected DiskPageSection(int metadataSize, ByteBuffer buffer) { this.buffer = buffer; this.metadataSize = metadataSize; this.start = 0; this.countPosition = this.start + OFFSET_SIZE; this.metadataSizePosition = this.countPosition + OFFSET_SIZE; this.metadataPosition = this.metadataSizePosition + OFFSET_SIZE; this.recordSizePosition = this.metadataPosition + metadataSize; this.dataPosition = this.recordSizePosition + OFFSET_SIZE; // Write metadataSize writeUShort(this.metadataSizePosition, metadataSize); // Prepare to write metadata, or if there is none, then data. if (metadataSize == 0) { this.state = State.DATA; this.buffer.position(this.dataPosition); } else { this.state = State.METADATA; this.buffer.position(this.metadataPosition); } this.size = this.dataPosition - this.start; this.count = 0; } // Used when reading a page protected DiskPageSection(ByteBuffer pageBuffer) { this.buffer = pageBuffer.duplicate(); this.start = this.buffer.position(); this.countPosition = this.start + OFFSET_SIZE; this.metadataSizePosition = this.countPosition + OFFSET_SIZE; this.metadataPosition = this.metadataSizePosition + OFFSET_SIZE; this.metadataSize = readUShort(this.metadataSizePosition); this.recordSizePosition = this.metadataPosition + this.metadataSize; this.dataPosition = this.recordSizePosition + OFFSET_SIZE; this.size = readUShort(this.start); this.count = readUShort(this.countPosition); this.state = State.CLOSED; this.buffer.limit(this.buffer.position() + this.size); // Subclass sets pageBuffer.position to end of what's been read } // For use by this package abstract void recordEndsAt(int recordEnd); abstract void setBoundariesInBuffer(int position, ByteBuffer dataBuffer); final int readUShort(int position) { return buffer.getChar(position); } final void writeUShort(int position, int value) { buffer.putChar(position, (char) value); } // Class state private static final long MAX_UBYTE = 0xffL; private static final long MAX_USHORT = 0xffffL; private static final long MAX_UINT = 0xffffffffL; protected static final int OFFSET_SIZE = 2; // Object state protected final ByteBuffer buffer; protected final int metadataSize; protected final int start; // position of size protected final int countPosition; protected final int metadataSizePosition; protected final int metadataPosition; protected final int recordSizePosition; protected final int dataPosition; protected State state; protected int count; protected int size; private int markCount = -1; private int markPosition = -1; // Inner classes protected enum State { METADATA, DATA, CLOSED } }